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:
<?php header('Content-type: text/html; charset=utf-8'); ?>
当前时间为<?=date("Y年n月j日 H:i:s")?>。
json_test.php:
<?php
header('Content-type: application/json');
header('Pragma: no-cache');
header('Cache-control: no-cache');
header('Expires: 0');$arr = array();
$arr["date"] = date("Y-n-j H:i:s");
$arr["time"] = time();
$arr["desc"] = "abcd简体中文";
echo json_encode($arr);
?>
xml_test.php:
<?php
header('Content-type: text/xml');session_start();
header('Pragma: no-cache');
header('Cache-control: no-cache');
header('Expires: 0');echo '<?xml version="1.0" encoding="utf-8"?>';
$timestr = strftime("%Y%m%d%H%M%S");
echo "<test timestr=\"$timestr\" example=\"简体中文\">";
foreach ($_COOKIE as $name => $value) {$_name = htmlspecialchars($name);$_value = htmlspecialchars($value);echo "<cookie name=\"$_name\">$_value</cookie>";
}
foreach ($_GET as $name => $value) {$_name = htmlspecialchars($name);$_value = htmlspecialchars($value);echo "<param method=\"get\" name=\"$_name\">$_value</param>";
}
foreach ($_POST as $name => $value) {$_name = htmlspecialchars($name);$_value = htmlspecialchars($value);echo "<param method=\"post\" name=\"$_name\">$_value</param>";
}
echo '</test>';
?>
第二节 以同步方式发送Ajax请求
下面我们来看看C语言如何像网页里面那样用XMLHttpRequest发送ajax请求。
请注意IXMLHttpRequest和IXMLHTTPRequest是两个名字不同但内容完全相同的接口,可以互换使用。
/* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */
#define COBJMACROS
#include <stdio.h>
#include <MsXml6.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';
}// 示例: 读取纯文本内容 (同步方式)
// 请注意有两个名称不同但内容完全一样的接口: 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 codehr = 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转成elementbstr = 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");elsemethod_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);}elseprintf("IDispatch_QueryInterface() failed\n");IDispatch_Release(disp);}elseprintf("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.csdn.net/ZLK1214/article/details/131748124
https://blog.csdn.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: <?xml version="1.0"?>
<test timestr="20240124203740" example="简体中文"><param method="get" name="txt"
>xp系统下载</param><param method="get" name="yaya">喝完药感觉清爽了许多</param><
/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: <?xml version="1.0"?>
<test timestr="20240124203740" example="简体中文"><cookie name="PHPSESSID">ktvth
t5ebhkssu0k5srknmpic3</cookie><param method="get" name="txt">xp系统下载</param><
param method="post" name="hello">测试环境后台管理系统</param><param method="post
" name="yaya">雾雨 连阴天 羽羽 浅浅健健康康 黛衍 音音锦鲤附</param></test>
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 <stdio.h>
#include <MsXml6.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';
}// 示例: 读取纯文本内容 (异步方式)
// 请注意有两个名称不同但内容完全一样的接口: 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 codehr = 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 <stdio.h>
#include <MsXml6.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;
}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 <assert.h>
#include <stdio.h>
#include <time.h>
#include <Windows.h>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++;elselen += 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 | 引用计数加1 |
| Release | 引用计数减1 |
| GetTypeInfoCount | (本例程用不到这个函数) |
| 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 <stdio.h>
#include <MsXml6.h>
#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 codehr = 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 oncetypedef void (*xhr_callback)(IXMLHttpRequest *xhr);IDispatch *create_xhr_callback(IXMLHttpRequest *xhr, xhr_callback callback);
xhr_callback.c:
/* 这个程序只能在C编译器下编译成功, 请确保源文件的扩展名为c */
#define COBJMACROS
#include <stdio.h>
#include <MsXml6.h>
#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);}elseprintf("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回应内容并显示。
相关文章:
C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据
做过网页设计的人应该都知道ajax。 Ajax即Asynchronous Javascript And XML(异步的JavaScript和XML)。使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发…...
QtRVSim(二)一个 RISC-V 程序的解码流程
继上一篇文章简单代码分析后,本文主要调研如何实现对指令的解析运行。 调试配置 使用 gdb 工具跟踪调试运行。 c_cpp_properties.json 项目配置: {"name": "QtRvSim","includePath": ["${workspaceFolder}/**&quo…...
x-cmd pkg | httpx - 为 Python 设计的下一代 HTTP 客户端库
目录 简介首次用户功能特点进一步探索 简介 HTTPX 是一个为 Python 设计的下一代 HTTP 客户端库,由 Tom Christie 创建。它提供了同步和异步的 API,并支持 HTTP/1.1 和 HTTP/2 协议。与 Requests 库类似,但增加了对异步请求的支持和 HTTP/2 …...
代码随想录算法训练营第四十二天(动态规划篇)|62. 不同路径
62. 不同路径 题目链接:62. 不同路径 - 力扣(LeetCode) 思路 dp[i][j]: 从0到位置[i, j]共有dp[i][j]条路径。 dp[i][j] dp[i-1][j] dp[i][j-1] 到位置[i,j],可以从它的上面或者左边来,所以路径和为这两个方向的路…...
YOLO 全面回顾:从最初的YOLOv1到最新的YOLOv8、YOLO-NAS,以及整合了 Transformers 的 YOLO
YOLO 全面回顾 综述评估指标YOLO v1YOLO v2YOLO v3YOLO v4YOLOv5 与 Scaled-YOLOv4 YOLORYOLOXYOLOv6YOLOv7DAMO-YOLOYOLOv8PP-YOLO, PP-YOLOv2, and PP-YOLOEYOLO-NASYOLO with Transformers 综述 论文:https://arxiv.org/pdf/2304.00501.pdf 代码:gi…...
Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心,Kotlin(2)
Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心,Kotlin(2) 在 Android ScaleGestureDetector检测双指缩放Bitmap基于Matrix动画移动到双指捏合中心点ImageView区域中心,Kotlin-CSDN博客 …...
同为科技(TOWE)自动控制循环定时插座
随着科技的发展,智能化家居已成为我们生活的重要组成部分。作为国内领先的智能家居品牌,同为科技(TOWE)推出的自动控制循环定时插座,无疑将科技与生活完美地结合在一起。 1.外观设计 同为科技(TOWE&#x…...
游戏设计模式
单列模式 概念 单例模式是一种创建型设计模式,可以保证一个类只有一个实例,并提供一个访问该实例的全局节点。 优点 可以派生:在单例类的实例构造函数中可以设置以允许子类派生。受控访问:因为单例类封装他的唯一实例…...
CUBEMX与FreeRTOS在Arm Compiler 6下的配置方法
在嵌入式开发中,STM32是一种广泛使用的微控制器。为了提高开发效率,我们通常会利用ST公司提供的STM32CubeMX工具来配置硬件,并结合FreeRTOS这一实时操作系统来进行多任务处理。本文将深入探讨如何在这一框架下,使用Arm Compiler 6…...
Android Studio 提示Use app:drawableStartCompat instead of android:drawableStart
每次提交代码时,AS这个老妈子总爱唠叨一堆warning,这些Warning都在讲什么? 1.Use app:drawableStartCompat instead of android:drawableStart 在Android开发中,android:drawableStart和app:drawableStartCompat是两个用于设置…...
C# wpf 实现任意控件(包括窗口)更多调整大小功能
WPF拖动改变大小系列 第一节 Grid内控件拖动调整大小 第二节 Canvas内控件拖动调整大小 第三节 窗口拖动调整大小 第四节 附加属性实现拖动调整大小 第五章 拓展更多调整大小功能(本章) 文章目录 WPF拖动改变大小系列前言一、添加的功能1、任意控件Drag…...
Vue+OpenLayers7入门到实战:快速搭建Vue+OpenLayers7地图脚手架项目。从零开始构建Vue项目并整合OpenLayers7.5.2
返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7 前言 本章针对Vue初学者,对Vue不熟悉,甚至还不会Vue的入门学生读者。 本章会详细讲解从NodeJS环境到npm环境的各个步骤,再到使用vue-cli脚手架快速生成项目,以及添加OpenLayers7地图库依赖,编写简单的xyz高德地图显示…...
mysql-线上常用运维sql
1.表备份 INSERT INTO table1 SELECT * FROM table2; 2.用一个表中的字段更新另一张表中的字段 UPDATE table2 JOIN table1 ON table2.id table1.id SET table2.column2 table1.column1; 3.在MySQL中,查询一个表的列字段值是否包含另一个表的字段,…...
Linux之进程间通信(system V 共享内存)
目录 一、共享内存 1、基本原理 2、共享内存的创建 3、共享内存的释放 4、共享内存的关联 5、共享内存的去关联 6、查看IPC资源 二、完整通信代码 三、共享内存的特点 四、信号量 1、相关概念 2、信号量概念 进程间通信的本质就是让不同的进程看到同一个资源。而前…...
数据库 sql select *from account where name=‘张三‘ 执行过程
select *from account where name张三分析上面语句的执行过程 用到了索引 由于是根据 1.name字段进行查询,所以先根据name张三’到name字段的二级索引中进行匹配查 找。但是在二级索引中只能查找到 Arm 对应的主键值 10。 2.由于查询返回的数据是*,…...
力扣日记1.27-【回溯算法篇】131. 分割回文串
力扣日记:【回溯算法篇】131. 分割回文串 日期:2023.1.27 参考:代码随想录、力扣 131. 分割回文串 题目描述 难度:中等 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可…...
如何用web界面打开华为防火墙
目录 1.创建一个虚拟网卡 2.cloud操作 3.防火墙上操作 4. 登录 1.创建一个虚拟网卡 2.cloud操作 3.防火墙上操作 4. 登录...
力扣20、有效的括号(简单)
1 题目描述 图1 题目描述 2 题目解读 给定的字符串只包含括号,判断这个字符串中的括号是否按照正确顺序出现,即这个字符串是否有效。 3 解法一:栈 C的STL中的stack,在解题时非常好用。 3.1 解题思路 使用栈stk,并枚举…...
Android 系统启动过程
当按下电源时,引导芯片代码会从预定义的地方(固化在ROM) 开始执行,加载引导程序BootLoader到RAM,然后执行。 启动内核的第一个进程idle(pid0),idle进程是Linux系统第一个进程,是init进程和kthreadd进程的父进程。 idle的主要作用 初始化进程以及内存管…...
基于STM32的智能手环设计与实现
需要原理图工程,源码,PCB工程的朋友收藏,这篇文章关注我,私我吧!!! 基于STM32的智能手环设计与实现 摘要一、研究背景及意义二、实现功能三、系统方案设计系统方案设计框图3.1 单片机芯片选择3…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
