帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据
发布时间:2024-03-06 02:13:10   分类:帮助文档
C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据 做过网页设计的人应该都知道ajax。 Ajax即Asynchronous Javascript And XML(异步的JavaScript和XML)。使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 在IE浏览器中,Ajax技术就是基于JavaScript里面的XMLHttpRequest。AJAX通过XMLHttpRequest对象发出HTTP 请求,得到服务器返回的数据后,再进行处理。现在,服务器返回的都是JSON格式的数据,XML格式已经过时了,但是AJAX这个名字已经成了一个通用名词,字面含义已经消失了。 XMLHttpRequest对象是AJAX的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有XML和Http,它实际上可以使用多种协议(比如file或ftp),发送任何格式的数据(包括字符串和二进制)。 其实,不仅在网页上能用JavaScript语言调用XMLHttpRequest组件,在桌面窗口程序里面也可以用C语言或C++调用XMLHttpRequest组件。 XMLHttpRequest是微软msxml6.0里面的组件。msxml6.0可直接解析服务器返回的xml文档,但json数据需要在网上找cJSON库来解析。 第一节 准备Web服务器页面 首先我们要在自己的服务器上准备好处理ajax请求的页面,本文准备了三个示例页面:str_test.php、json_test.php和xml_test.php,分别用来产生文本回应、json回应和xml回应。xml_test.php页面支持get和post两种ajax请求方式。 PHP文件用UTF-8编码保存,但C文件要用GB2312编码保存。 这是因为在微软的简体中文Windows系统里面,以A结尾的API函数(如CreateWindowA)采用的是GB2312编码的char *字符串,以W结尾的API函数(如CreateWindowW)采用的是UTF-16编码的wchar_t *字符串,COM组件使用的BSTR字符串是在wchar_t *字符串的基础上增加了4字节的字符串长度前缀。 IXMLHttpRequest组件(一种COM组件)工作时使用的是UTF-16编码的BSTR字符串。 我们在收到IXMLHttpRequest组件提供的UTF-16编码的BSTR字符串后,如果想要用printf在控制台上打印出来,就需要用convert_bstr_to_string函数将BSTR转换为GB2312编码的char *字符串。想要直接打印BSTR字符串不能用printf函数,要用WriteConsoleW函数才行。如果是显示在窗口的文本框或者窗口的标题栏上那就简单了,直接用SetWindowTextW或SetDlgItemTextW函数,把BSTR字符串传进去就行了。 同样,在给IXMLHttpRequest组件传递字符串参数时,需要先用convert_string_to_bstr函数将GB2312编码的char *字符串转换为UTF-16编码的带长度前缀的BSTR字符串。 如果是写入txt文本文件的话,我们就可以采用UTF-8编码,用MultiByteToWideChar函数(CP_UTF8)将BSTR转换成UTF-8编码的char *字符串,再用fprintf或fwrite写入txt文件。 str_test.php: 当前时间为。  json_test.php: xml_test.php: '; $timestr = strftime("%Y%m%d%H%M%S"); echo ""; foreach ($_COOKIE as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "$_value"; } foreach ($_GET as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "$_value"; } foreach ($_POST as $name => $value) { $_name = htmlspecialchars($name); $_value = htmlspecialchars($value); echo "$_value"; } echo ''; ?> 第二节 以同步方式发送Ajax请求 下面我们来看看C语言如何像网页里面那样用XMLHttpRequest发送ajax请求。 请注意IXMLHttpRequest和IXMLHTTPRequest是两个名字不同但内容完全相同的接口,可以互换使用。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include #include #pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } // 示例: 读取纯文本内容 (同步方式) // 请注意有两个名称不同但内容完全一样的接口: IXMLHttpRequest和IXMLHTTPRequest, 随便用哪个都可以 // 但class id只有一个: CLSID_XMLHTTPRequest void str_test(const char *url) { char *response; long status; BSTR method_bstr, url_bstr, response_bstr; HRESULT hr; IXMLHttpRequest *xhr; VARIANT async; // VARIANT代表一个弱类型的变量 VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 // get参数内容是放到url字符串里面的 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_FALSE; // 选择非异步方式 (也就是同步方式) null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); // 最后两个参数是用户名和密码, 不用填 SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); // 发送请求 // 由于我们选择的是同步方式, 所以send函数要等到所有数据都接收完了才返回 hr = IXMLHttpRequest_send(xhr, null); // 第二个参数是post数据内容, 不用管 if (SUCCEEDED(hr)) printf("send() succeeded\n"); // 获取请求状态 // 0: open函数还没调用 // 1: send函数还没调用 // 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response // 3: 已收到了部分数据, 可以读responseBody和responseText // 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 hr = IXMLHttpRequest_get_readyState(xhr, &status); if (SUCCEEDED(hr)) printf("ready state: %d\n", status); // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response: %s\n", response); free(response); SysFreeString(response_bstr); } IXMLHttpRequest_Release(xhr); } printf("\n"); } // 直接显示xml代码 void display_xmlstr(IXMLDOMDocument *xmldoc) { char *str; BSTR bstr; HRESULT hr; hr = IXMLDOMDocument_get_xml(xmldoc, &bstr); if (SUCCEEDED(hr)) { str = convert_bstr_to_string(bstr); remove_last_crlf(str); printf("xmlstr: %s\n", str); free(str); SysFreeString(bstr); } } // 显示xml属性值 void display_attr(IXMLDOMElement *elem, const char *name) { char *value; BSTR bstr; // 表示一个带长度前缀的utf16编码的字符串 VARIANT variant; // 表示一个弱类型的变量 bstr = convert_string_to_bstr(name); IXMLDOMElement_getAttribute(elem, bstr, &variant); SysFreeString(bstr); // 使用完字符串后必须释放 if (variant.vt == VT_BSTR) { value = convert_bstr_to_string(variant.bstrVal); printf("%s: %s\n", name, value); free(value); } else if (variant.vt == VT_NULL) printf("%s: (null)\n", name); VariantClear(&variant); } // 检查xml节点名称是否为指定名称 int check_node_name(IXMLDOMNode *node, const char *expected_name) { char *name; int ret; BSTR bstr; IXMLDOMNode_get_nodeName(node, &bstr); name = convert_bstr_to_string(bstr); SysFreeString(bstr); ret = (strcmp(name, expected_name) == 0); free(name); return ret; } // 检查xml属性值是否为指定值 int check_node_attr(IXMLDOMNode *node, const char *attr, const char *expected_value) { char *value; int ret = 0; BSTR bstr; HRESULT hr; IXMLDOMElement *elem; VARIANT variant; IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, &elem); // 先把node转成element bstr = convert_string_to_bstr(attr); hr = IXMLDOMElement_getAttribute(elem, bstr, &variant); // 再通过element获取属性值 SysFreeString(bstr); if (SUCCEEDED(hr)) { if (variant.vt == VT_BSTR) { value = convert_bstr_to_string(variant.bstrVal); ret = (strcmp(value, expected_value) == 0); free(value); } else if (variant.vt == VT_NULL) ret = (expected_value == NULL || expected_value[0] == '\0'); VariantClear(&variant); } IXMLDOMElement_Release(elem); return ret; } void display_xml(IXMLDOMDocument *xmldoc) { char *text; long i, len; BSTR text_bstr; HRESULT hr; IXMLDOMElement *root; IXMLDOMNode *node; IXMLDOMNodeList *list; IXMLDOMDocument_get_documentElement(xmldoc, &root); display_attr(root, "timestr"); display_attr(root, "example"); display_attr(root, "title"); hr = IXMLDOMElement_get_childNodes(root, &list); if (SUCCEEDED(hr)) { IXMLDOMNodeList_get_length(list, &len); for (i = 0; i < len; i++) { IXMLDOMNodeList_get_item(list, i, &node); if (check_node_name(node, "param") && check_node_attr(node, "method", "get") && check_node_attr(node, "name", "txt")) { IXMLDOMNode_get_text(node, &text_bstr); text = convert_bstr_to_string(text_bstr); printf("txt: %s\n", text); free(text); SysFreeString(text_bstr); } else if (check_node_name(node, "param") && check_node_attr(node, "method", "post") && check_node_attr(node, "name", "hello")) { IXMLDOMNode_get_text(node, &text_bstr); text = convert_bstr_to_string(text_bstr); printf("hello: %s\n", text); free(text); SysFreeString(text_bstr); } IXMLDOMNode_Release(node); } IXMLDOMNodeList_Release(list); } IXMLDOMElement_Release(root); } // 示例: 读取xml内容 (同步方式) void xml_test(const char *url, const char *post_data) { long status; BSTR method_bstr, url_bstr, header_bstr, value_bstr; HRESULT hr; IDispatch *disp; IXMLDOMDocument *xmldoc; IXMLHttpRequest *xhr; VARIANT async; VARIANT body; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { if (post_data == NULL) method_bstr = convert_string_to_bstr("GET"); else method_bstr = convert_string_to_bstr("POST"); printf("method: %ls\n", method_bstr); // 不带中文的BSTR可直接用%ls输出, 带中文就不行了 url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_FALSE; null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); if (post_data == NULL) body = null; else { header_bstr = convert_string_to_bstr("Content-Type"); value_bstr = convert_string_to_bstr("application/x-www-form-urlencoded"); IXMLHttpRequest_setRequestHeader(xhr, header_bstr, value_bstr); SysFreeString(header_bstr); SysFreeString(value_bstr); body.vt = VT_BSTR; body.bstrVal = convert_string_to_bstr(post_data); } hr = IXMLHttpRequest_send(xhr, body); if (SUCCEEDED(hr)) printf("send() succeeded\n"); VariantClear(&body); hr = IXMLHttpRequest_get_readyState(xhr, &status); if (SUCCEEDED(hr)) printf("ready state: %d\n", status); hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); hr = IXMLHttpRequest_get_responseXML(xhr, &disp); if (SUCCEEDED(hr)) { printf("get_responseXML() succeeded\n"); hr = IDispatch_QueryInterface(disp, &IID_IXMLDOMDocument, &xmldoc); if (SUCCEEDED(hr)) { printf("IDispatch_QueryInterface() succeeded\n"); display_xmlstr(xmldoc); display_xml(xmldoc); IXMLDOMDocument_Release(xmldoc); } else printf("IDispatch_QueryInterface() failed\n"); IDispatch_Release(disp); } else printf("get_responseXML() failed"); IXMLHttpRequest_Release(xhr); } printf("\n"); } int main() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); // 初始化COM组件对象模型 str_test("http://adv.purasbar.com/mcu/test/str_test.php"); str_test("http://adv.purasbar.com/mcu/test/json_test.php"); xml_test("http://adv.purasbar.com/mcu/test/xml_test.php?txt=xp%E7%B3%BB%E7%BB%9F%E4%B8%8B%E8%BD%BD&yaya=%E5%96%9D%E5%AE%8C%E8%8D%AF%E6%84%9F%E8%A7%89%E6%B8%85%E7%88%BD%E4%BA%86%E8%AE%B8%E5%A4%9A", NULL); // get方式 xml_test("http://adv.purasbar.com/mcu/test/xml_test.php?txt=xp%E7%B3%BB%E7%BB%9F%E4%B8%8B%E8%BD%BD", "hello=%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F&yaya=%E9%9B%BE%E9%9B%A8%20%E8%BF%9E%E9%98%B4%E5%A4%A9%20%E7%BE%BD%E7%BE%BD%20%E6%B5%85%E6%B5%85%E5%81%A5%E5%81%A5%E5%BA%B7%E5%BA%B7%20%E9%BB%9B%E8%A1%8D%20%E9%9F%B3%E9%9F%B3%E9%94%A6%E9%B2%A4%E9%99%84"); // post方式 CoUninitialize(); return 0; } get参数的提交方法: 将参数直接附加到URL网址的后面。如http://XXX//xml_test.php?A=B&C=D&E=F。汉字和符号必须用urlencode函数编码成%XX的形式。 post数据的提交方法: 通过VARIANT弱类型变量传入IXMLHttpRequest_send函数的第二个参数。没有post数据时要传入VT_NULL值。 汉字和符号同样也必须用urlencode函数编码成%XX的形式。只是最前面不需要以问号开头。 关于urlencode/urldecode、htmlspecialchars、trim和str_replace函数的C语言实现,请参考这篇文章: https://blog..net/ZLK1214/article/details/131748124https://blog..net/ZLK1214/article/details/131748124 程序运行结果: open() succeeded send() succeeded ready state: 4 http status code: 200 response: 当前时间为2024年1月24日 20:37:39。 open() succeeded send() succeeded ready state: 4 http status code: 200 response: {"date":"2024-1-24 20:37:40","time":1706099860,"desc":"abcd\u7b80\u4f5 3\u4e2d\u6587"} method: GET open() succeeded send() succeeded ready state: 4 http status code: 200 get_responseXML() succeeded IDispatch_QueryInterface() succeeded xmlstr: xp系统下载喝完药感觉清爽了许多< /test> timestr: 20240124203740 example: 简体中文 title: (null) txt: xp系统下载 method: POST open() succeeded send() succeeded ready state: 4 http status code: 200 get_responseXML() succeeded IDispatch_QueryInterface() succeeded xmlstr: ktvth t5ebhkssu0k5srknmpic3xp系统下载< param method="post" name="hello">测试环境后台管理系统雾雨 连阴天 羽羽 浅浅健健康康 黛衍 音音锦鲤附 timestr: 20240124203740 example: 简体中文 title: (null) txt: xp系统下载 hello: 测试环境后台管理系统 请按任意键继续. . . 第三节 以异步方式发送Ajax请求 IXMLHttpRequest_open函数的六个参数分别是XMLHttpRequest对象、提交方式、URL网址、是否为异步方式、用户名和密码。 其中第四个参数若为VARIANT_FALSE,则是同步方式;若为VARIANT_TRUE,则是异步方式。 如果选择同步方式,则IXMLHttpRequest_send会阻塞,直到收到完整的服务器回应后,函数才返回。 如果选择异步方式,则IXMLHttpRequest_send不会阻塞,会立即返回。后面程序需要用一个while循环轮询IXMLHttpRequest_get_readyState,当ready_state=4时说明收到了完整的数据内容。 IXMLHttpRequest_get_readyState的值定义如下: 0: open函数还没调用 1: send函数还没调用 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response 3: 已收到了部分数据, 可以读responseBody和responseText 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 IXMLHttpRequest_get_status函数返回的是HTTP的回应码,通常为200。如果找不到指定网页,就是400。如果遇到服务器端程序错误,就是500。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include #include #pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } // 示例: 读取纯文本内容 (异步方式) // 请注意有两个名称不同但内容完全一样的接口: IXMLHttpRequest和IXMLHTTPRequest, 随便用哪个都可以 // 但class id只有一个: CLSID_XMLHTTPRequest void str_test(const char *url) { char *response; long ready_state, status; BSTR method_bstr, url_bstr, response_bstr; HRESULT hr; IXMLHttpRequest *xhr; VARIANT async; // VARIANT代表一个弱类型的变量 VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 // get参数内容是放到url字符串里面的 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 (选择这种方式后, main里面CoInitializeEx必须用COINIT_MULTITHREADED) null.vt = VT_NULL; hr = IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); // 最后两个参数是用户名和密码, 不用填 SysFreeString(method_bstr); SysFreeString(url_bstr); if (SUCCEEDED(hr)) printf("open() succeeded\n"); // 发送请求 // 由于我们选择的是异步方式, 所以send函数会直接返回 hr = IXMLHttpRequest_send(xhr, null); // 第二个参数是post数据内容, 不用管 if (SUCCEEDED(hr)) printf("send() succeeded\n"); while (1) { // 获取请求状态 // 0: open函数还没调用 // 1: send函数还没调用 // 2: send函数调用了, get_status()函数已经可以使用了, 也可以读http headers了, 但是还读不了response // 3: 已收到了部分数据, 可以读responseBody和responseText // 4: 所有数据都已经接收完了, responseBody和responseText已经是完整数据了 hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready state: %d\n", ready_state); if (ready_state == 4) break; } } // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("http status code: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response: %s\n", response); free(response); SysFreeString(response_bstr); } IXMLHttpRequest_Release(xhr); } printf("\n"); } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 str_test("http://adv.purasbar.com/mcu/test/str_test.php"); CoUninitialize(); return 0; } 程序运行后,会不断地打印ready state=1,直到收到完整数据时ready state=4才退出循环,打印出收到的回应内容。 请注意,使用异步模式的前提是main函数里面CoInitializeEx的参数为COINIT_MULTITHREADED,不能是COINIT_APARTMENTTHREADED,否则readyState将一直为1,程序陷入死循环。 还有就是,COINIT_MULTITHREADED这个选项是不能在UI(图形界面)线程里面使用的,UI线程里面只能用COINIT_APARTMENTTHREADED。一般在主线程里面处理UI,单独新开一个线程处理网络通信。 第四节 直接通过IDispatch接口读取XML数据 IXMLHttpRequest_get_responseXML函数返回的是IDispatch接口,可以用IDispatch_QueryInterface函数将IDispatch转换成IXMLDOMDocument接口,然后读取XML数据。 IXMLDOMDocument其实是IDispatch的子类。实际上我们直接用IDispatch接口也可以读写xml数据。只不过要用wchar_t *字符串指定要调用的函数名称,用IDispatch_GetIDsOfNames函数查出函数名称对应的函数ID,用VARIANT数组(VARIANTARG是VARIANT的别名)指定函数参数后,再用IDispatch_Invoke函数通过函数ID和函数参数调用函数,并得到函数的返回值。 /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include #include #pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } void read_attribute_from_disp(IDispatch *disp, const char *attr) { char *s; wchar_t *func_name; DISPID func_id; DISPPARAMS params = {0}; HRESULT hr; VARIANT result; VARIANTARG arg; // 调用getAttribute方法 (带1个参数) func_name = L"getAttribute"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { arg.vt = VT_BSTR; arg.bstrVal = convert_string_to_bstr(attr); params.rgvarg = &arg; params.cArgs = 1; hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &result, NULL, NULL); VariantClear(&arg); if (SUCCEEDED(hr)) { if (result.vt == VT_BSTR) { s = convert_bstr_to_string(result.bstrVal); printf("%ls: %s\n", func_name, s); free(s); } VariantClear(&result); } } } void read_xml_from_disp(IDispatch *disp) { wchar_t *func_name; DISPID func_id; DISPPARAMS params = {0}; HRESULT hr; VARIANT result; // 调用hasChildNodes方法 func_name = L"hasChildNodes"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &result, NULL, NULL); // DISPATCH_METHOD指明是调用方法 if (SUCCEEDED(hr)) { if (result.vt == VT_BOOL) { if (result.boolVal == VARIANT_TRUE) printf("%ls: true\n", func_name); else if (result.boolVal == VARIANT_FALSE) printf("%ls: false\n", func_name); } VariantClear(&result); // VARIANT类型的变量统一用这个函数释放 } } // 读取documentElement属性 func_name = L"documentElement"; hr = IDispatch_GetIDsOfNames(disp, &IID_NULL, &func_name, 1, LOCALE_SYSTEM_DEFAULT, &func_id); if (SUCCEEDED(hr)) { hr = IDispatch_Invoke(disp, func_id, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &result, NULL, NULL); // DISPATCH_PROPERTYGET指明是读取属性 if (SUCCEEDED(hr)) { if (result.vt == VT_DISPATCH) { read_attribute_from_disp(result.pdispVal, "timestr"); read_attribute_from_disp(result.pdispVal, "example"); } VariantClear(&result); } } } // 示例: 用IDispatch接口直接读取XML数据 (异步方式) void xml_test(const char *url) { long ready_state; BSTR method_bstr, url_bstr; HRESULT hr; IDispatch *disp; IXMLHttpRequest *xhr; VARIANT async; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 null.vt = VT_NULL; IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); IXMLHttpRequest_send(xhr, null); while (1) { hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready state: %d\n", ready_state); if (ready_state == 4) break; } } hr = IXMLHttpRequest_get_responseXML(xhr, &disp); if (SUCCEEDED(hr)) { read_xml_from_disp(disp); IDispatch_Release(disp); } IXMLHttpRequest_Release(xhr); } } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 xml_test("http://adv.purasbar.com/mcu/test/xml_test.php"); CoUninitialize(); return 0; } 在上面的程序中,我们直接用IDispatch_Invoke函数调用了hasChildNode函数判断了XML根节点下是否有子节点,获取了documentElement属性,然后还执行了documentElement.getAttribute()函数读取XML根节点上的两个属性值,属性名通过arg变量传入,属性值通过result变量传出,VARIANT弱类型变量使用完成后用VariantClear函数释放。 第五节 通过urlencode函数打包GET和POST数据 GET数据和POST数据采用的是键值对的形式,其中的汉字和特殊符号要用%XX编码,其中XX是UTF-8编码对应的十六进制数,通常一个汉字占三个字节。 当然也可以采用其他编码,如GB2312,但是我们的PHP服务器上用的是UTF-8编码。这个跟PHP代码有关。 C语言里面char *字符串是GB2312编码,先用MultiByteToWideChar转成UTF-16编码,再用WideCharToMultiByte转成UTF-8编码,最后用urlencode函数编码成%XX的形式。 键值对最终要写成A=B&C=D&E=F的形式,其中A、C、E是键名,B、D、F是它们对应的值,我们用create_param_string函数来完成这项工作。 #include #include #include #include struct param { char *name; char *value; char *reserved[4]; }; int urlencode_buf(char *buf, int *bufsize) { char x; int i, j, len; if (buf == NULL) { *bufsize = 1; return -1; } len = 0; for (i = 0; buf[i] != '\0'; i++) { if (isalnum((unsigned char)buf[i]) || strchr(" -_.", buf[i]) != NULL) len++; else len += 3; } if (*bufsize < len + 1) { if (len >= i) *bufsize = len + 1; else *bufsize = i + 1; return -1; } j = len - 1; for (i--; i >= 0; i--) { if (isalnum((unsigned char)buf[i]) || strchr("-_.", buf[i]) != NULL) { buf[j] = buf[i]; j--; } else if (buf[i] == ' ') { buf[j] = '+'; j--; } else { x = buf[i] & 15; buf[j] = (x < 10) ? (x + '0') : (x - 10 + 'A'); x = (buf[i] >> 4) & 15; buf[j - 1] = (x < 10) ? (x + '0') : (x - 10 + 'A'); buf[j - 2] = '%'; j -= 3; } } buf[len] = '\0'; return len; } char *urlencode(const char *s) { char *p; int size = 0; urlencode_buf((char *)s, &size); p = malloc(size); if (p != NULL) { if (s != NULL) strcpy_s(p, size, s); else *p = '\0'; urlencode_buf(p, &size); } return p; } char *gb2312_to_utf8(const char *s) { char *us = NULL; int n; wchar_t *ws; if (s == NULL) s = ""; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); us = malloc(n); if (us != NULL) WideCharToMultiByte(CP_UTF8, 0, ws, -1, us, n, NULL, NULL); free(ws); } return us; } char *create_param_string(const char *url, struct param params[], int count) { char *s = NULL; int i, j, n; int len = 0; int total = 0; // 计算字符串总长度 if (url != NULL) total = (int)strlen(url) + 1; // +'?' for (i = 0; i < count; i++) { if (i != 0) total++; // +'&' params[i].reserved[0] = gb2312_to_utf8(params[i].name); if (params[i].reserved[0] == NULL) goto cleanup; params[i].reserved[1] = urlencode(params[i].reserved[0]); if (params[i].reserved[1] == NULL) goto cleanup; total += (int)strlen(params[i].reserved[1]); if (params[i].value != NULL) { params[i].reserved[2] = gb2312_to_utf8(params[i].value); if (params[i].reserved[2] == NULL) goto cleanup; params[i].reserved[3] = urlencode(params[i].reserved[2]); if (params[i].reserved[3] == NULL) goto cleanup; total += 1 + (int)strlen(params[i].reserved[3]); // +'=' } else { params[i].reserved[2] = NULL; params[i].reserved[3] = NULL; } } // 分配内存 s = malloc(total + 1); if (s == NULL) goto cleanup; // 合成字符串 if (url != NULL) { n = (int)strlen(url); memcpy(s, url, n); len = n; if (count > 0) { s[n] = '?'; len++; } } for (i = 0; i < count; i++) { if (i != 0) { s[len] = '&'; len++; } n = (int)strlen(params[i].reserved[1]); memcpy(s + len, params[i].reserved[1], n); len += n; if (params[i].reserved[3] != NULL) { s[len] = '='; len++; n = (int)strlen(params[i].reserved[3]); memcpy(s + len, params[i].reserved[3], n); len += n; } } s[len] = '\0'; assert(len == total); cleanup: for (i = 0; i < count; i++) { for (j = 0; j < _countof(params[i].reserved); j++) { if (params[i].reserved[j] != NULL) { free(params[i].reserved[j]); params[i].reserved[j] = NULL; } } } return s; } int main() { char *str; char timestr[50]; struct param params[4]; struct tm tm; time_t t; params[0].name = "hello"; params[0].value = "world"; params[1].name = "yaya"; params[1].value = "喝完药感觉清爽了许多"; params[2].name = "test"; params[2].value = NULL; params[3].name = "msg"; params[3].value = timestr; time(&t); localtime_s(&tm, &t); strftime(timestr, sizeof(timestr), "当前时间: %Y-%m-%d %H:%M:%S", &tm); str = create_param_string("http://www.example.com/hello.php", params, _countof(params)); if (str != NULL) { printf("GET参数示例: %s\n", str); free(str); } str = create_param_string(NULL, params, _countof(params)); if (str != NULL) { printf("POST参数示例: %s\n", str); free(str); } return 0; } 程序运行结果如下。GET参数是附加到URL网址后面的,用问号隔开。POST数据是通过IXMLHttpRequest_send函数的第二个参数发给服务器的。 第六节 使用IXMLHttpRequest_put_onreadystatechange回调函数处理Ajax异步请求 当IXMLHttpRequest设置成异步模式后,数据还没有接收完,IXMLHttpRequest_send函数就会提前返回。这样我们就需要用一个while循环来轮询IXMLHttpRequest_get_readyState的值,当ready_state=4时才表明数据接收完了,这时才能读取response回应内容。 在刚才的程序运行截图里面可以看得出来,“ready state: 1”打印了很长一串,这期间CPU一直在空转,这说明while循环的执行时间还是很长的。 为了解决这个问题,我们可以使用IXMLHttpRequest_put_onreadystatechange函数设置一个回调函数,每当ready_state的值发生变化时IXMLHttpRequest自动帮我们调用这个回调函数,我们在这个回调函数里面去读取response回应。 IXMLHttpRequest_put_onreadystatechange函数有两个参数,第一个参数是IXMLHttpRequest对象,第二个参数是我们自己定义的包含回调函数的IDispatch对象。也就是说我们要自己实现一个IDispatch对象。 刚才在第四节里面已经解释了IDispatch到底是个什么东西。IDispatch是一个根据函数名字符串调用函数的工具。IXMLHttpRequest_get_responseXML函数是IXMLHttpRequest给我们提供IDispatch对象,而现在我们使用IXMLHttpRequest_put_onreadystatechange函数,就要自己实现一个IDispatch对象传递给IXMLHttpRequest。 C语言实现COM接口的方法是建立一个自定义的结构体,这个结构体的第一个成员变量是要实现的接口的函数指针表的指针,其他成员可随便自定义。 函数指针表的类型名称是在接口名称后面加上Vtbl。我们要实现的是IDispatch接口,所以函数指针表的类型名为IDispatchVtbl。 struct xxx {     IDispatchVtbl *pvtbl;          …… (其他自定义成员) }; pvtbl成员变量是一个指针,一定要指向一个变量,通常把指向的这个变量也放到结构体里面,或者单独malloc出来也行。 (方法1) struct xxx {     IDispatchVtbl *pvtbl;          IDispatchVtbl vtbl;     …… (其他自定义成员) }; struct xxx x; x.pvtbl = &x.vtbl; (方法2) struct xxx {     IDispatchVtbl *pvtbl;          …… (其他自定义成员) }; struct xxx x; x.pvtbl = malloc(sizeof(IDispatchVtbl)); IDispatchVtbl也是一个结构体,里面一共有7个成员变量,全部为函数指针,这就是我们要实现的7个函数,如下表所示。 函数名作用QueryInterface切换父类或子类AddRef引用计数加1Release引用计数减1GetTypeInfoCount(本例程用不到这个函数)GetTypeInfo(本例程用不到这个函数)GetIDsOfNames获取函数名对应的函数ID (本例程用不到这个函数)Invoke执行指定ID号的函数 IDispatch接口继承IUnknown接口,QueryInterface、AddRef和Release这三个函数是父类IUnknown里面的函数,分别用于切换父子类、增加和减少引用计数。 通常我们定义的struct xxx结构体变量是由malloc动态分配出来的,引用计数的值什么时候为0决定了什么时候释放结构体变量所占用的内存。 struct xxx *p = malloc(sizeof(struct xxx)); 然后就可以转换成IDispatch指针,并使用系统提供的IDispatch_XXX系列函数操作IDispatch对象。 IDispatch *disp = (IDispatch *)p; IDispatch_XXX(disp, ...); 当onreadystatechange事件发生时,IXMLHttpRequest会去调用我们的IDispatch对象的Invoke函数,传入的函数ID(dispIdMember)为0,参数个数(pDispParams->cArgs)也为0,函数也不需要返回值(pVarResult=NULL)。 我们来看一下例程。 main.c: /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include #include #include "xhr_callback.h" #pragma comment(lib, "msxml6.lib") // char *转BSTR // 用完后调用SysFreeString释放 BSTR convert_string_to_bstr(const char *s) { int n; wchar_t *ws; BSTR bstr = NULL; n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); ws = malloc(n * sizeof(wchar_t)); if (ws != NULL) { MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n); bstr = SysAllocString(ws); free(ws); } return bstr; } // BSTR转char * // 用完后调用free释放 char *convert_bstr_to_string(BSTR bstr) { char *s; int n; n = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL); s = malloc(n); if (s != NULL) WideCharToMultiByte(CP_ACP, 0, bstr, -1, s, n, NULL, NULL); return s; } // 去掉字符串末尾的\r\n void remove_last_crlf(char *str) { int len; len = (int)strlen(str); if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n') str[len - 2] = '\0'; } void test_callback(IXMLHttpRequest *xhr) { char *response; long ready_state, status; BSTR response_bstr; HRESULT hr; // 获取请求状态 hr = IXMLHttpRequest_get_readyState(xhr, &ready_state); if (SUCCEEDED(hr)) { printf("ready_state: %d\n", ready_state); if (ready_state == 4) { // 获取http返回的response code hr = IXMLHttpRequest_get_status(xhr, &status); if (SUCCEEDED(hr)) printf("status: %d\n", status); // 读取并显示文本内容 hr = IXMLHttpRequest_get_responseText(xhr, &response_bstr); if (SUCCEEDED(hr)) { // response_bstr是utf16编码, 转换后的response是gb2312编码 // 把函数里面的CP_ACP改成CP_UTF8就可以转成utf8编码, 但是utf8就没办法用printf打印, 只能用fwrite写入txt文件再用记事本打开查看 response = convert_bstr_to_string(response_bstr); remove_last_crlf(response); printf("response text: %s\n", response); free(response); SysFreeString(response_bstr); } } } } void test(const char *url) { BSTR method_bstr, url_bstr; HRESULT hr; IDispatch *xhr_callback_disp; IXMLHttpRequest *xhr; VARIANT async; VARIANT null; hr = CoCreateInstance(&CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLHttpRequest, &xhr); if (SUCCEEDED(hr)) { // 打开连接 method_bstr = convert_string_to_bstr("GET"); url_bstr = convert_string_to_bstr(url); async.vt = VT_BOOL; async.boolVal = VARIANT_TRUE; // 选择异步方式 null.vt = VT_NULL; IXMLHttpRequest_open(xhr, method_bstr, url_bstr, async, null, null); SysFreeString(method_bstr); SysFreeString(url_bstr); // 设置回调函数 xhr_callback_disp = create_xhr_callback(xhr, test_callback); if (xhr_callback_disp != NULL) { IXMLHttpRequest_put_onreadystatechange(xhr, xhr_callback_disp); IDispatch_Release(xhr_callback_disp); } // 发送请求 IXMLHttpRequest_send(xhr, null); IXMLHttpRequest_Release(xhr); } } int main() { CoInitializeEx(NULL, COINIT_MULTITHREADED); // 初始化COM组件对象模型 test("http://adv.purasbar.com/mcu/test/str_test.php"); Sleep(5000); CoUninitialize(); return 0; } xhr_callback.h: #pragma once typedef void (*xhr_callback)(IXMLHttpRequest *xhr); IDispatch *create_xhr_callback(IXMLHttpRequest *xhr, xhr_callback callback); xhr_callback.c: /* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */ #define COBJMACROS #include #include #include "xhr_callback.h" // 实现IDispatch接口 // IDispatch接口继承IUnknown接口 struct xhr_callback { IDispatchVtbl *pvtbl; IDispatchVtbl vtbl; ULONG refcnt; xhr_callback callback; IXMLHttpRequest *xhr; }; static HRESULT STDMETHODCALLTYPE xhr_callback_query_interface(IDispatch *This, REFIID riid, void ppvObject) { wchar_t *ws; HRESULT hr; hr = StringFromIID(riid, &ws); if (SUCCEEDED(hr)) { printf("xhr_callback_query_interface: rrid=%ls\n", ws); CoTaskMemFree(ws); } else printf("xhr_callback_query_interface\n"); if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDispatch)) { IDispatch_AddRef(This); *ppvObject = This; return S_OK; } else { *ppvObject = NULL; return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE xhr_callback_addref(IDispatch *This) { struct xhr_callback *p = (struct xhr_callback *)This; p->refcnt++; printf("xhr_callback_addref: %u\n", p->refcnt); return p->refcnt; } static ULONG STDMETHODCALLTYPE xhr_callback_release(IDispatch *This) { struct xhr_callback *p = (struct xhr_callback *)This; p->refcnt--; printf("xhr_callback_release: %u\n", p->refcnt); if (p->refcnt == 0) { IXMLHttpRequest_Release(p->xhr); memset(p, 0, sizeof(struct xhr_callback)); free(p); return 0; } return p->refcnt; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_type_info_count(IDispatch *This, UINT *pctinfo) { printf("xhr_callback_get_type_info_count\n"); *pctinfo = 0; // 不提供类型信息 return S_OK; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_type_info(IDispatch *This, UINT iTInfo, LCID lcid, ITypeInfo ppTInfo) { printf("xhr_callback_get_type_info\n"); *ppTInfo = NULL; return S_OK; } static HRESULT STDMETHODCALLTYPE xhr_callback_get_ids_of_names(IDispatch *This, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { UINT i; printf("xhr_callback_get_ids_of_names\n"); for (i = 0; i < cNames; i++) rgDispId[i] = DISPID_UNKNOWN; return DISP_E_UNKNOWNNAME; } static HRESULT STDMETHODCALLTYPE xhr_callback_invoke(IDispatch *This, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { struct xhr_callback *p = (struct xhr_callback *)This; printf("xhr_callback_invoke: dispIdMember=%d, cArgs=%u, pVarResult=0x%p\n", dispIdMember, pDispParams->cArgs, pVarResult); if (dispIdMember == 0 && pDispParams->cArgs == 0) { if (p->callback != NULL) p->callback(p->xhr); } if (pVarResult != NULL) VariantInit(pVarResult); return S_OK; } IDispatch *create_xhr_callback(IXMLHttpRequest *xhr, xhr_callback callback) { struct xhr_callback *p; IDispatch *disp; p = malloc(sizeof(struct xhr_callback)); if (p == NULL) return NULL; memset(p, 0, sizeof(struct xhr_callback)); p->pvtbl = &p->vtbl; p->pvtbl->QueryInterface = xhr_callback_query_interface; p->pvtbl->AddRef = xhr_callback_addref; p->pvtbl->Release = xhr_callback_release; p->pvtbl->GetTypeInfoCount = xhr_callback_get_type_info_count; p->pvtbl->GetTypeInfo = xhr_callback_get_type_info; p->pvtbl->GetIDsOfNames = xhr_callback_get_ids_of_names; p->pvtbl->Invoke = xhr_callback_invoke; p->callback = callback; IXMLHttpRequest_AddRef(xhr); p->xhr = xhr; disp = (IDispatch *)p; IDispatch_AddRef(disp); return disp; } 程序运行结果如下。 可以看到,只有当ready_state的值发生了变化,回调函数才会执行。回调函数总共就执行了四次,效率一下子提升了许多。在第四次执行回调函数的时候,ready_state=4,我们在test_callback回调函数中读取了IXMLHttpRequest_get_responseText回应内容并显示。 
香港云服务器租用推荐
服务器租用资讯
·租用美国服务器配置
·怎样使用美国服务器(新的服务器怎样使用)
·怎么联系美国服务器(本服务器在美国受到法律)
·云服务器美国电影(美国高防云服务器)
·源服务器在美国(美国服务器ip)
·邮箱搭建美国服务器(群晖搭建邮箱服务器)
·微信美国服务器(微信小程序要服务器吗)
·受美国服务器保护(此服务器受美国保护)
·手机vpn美国服务器
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价