C语言通过IXMLHttpRequest以get或post方式发送http请求获取服务器文本或xml数据

news2024/9/28 14:06:49

做过网页设计的人应该都知道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 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.csdn.net/ZLK1214/article/details/131748124icon-default.png?t=N7T8https://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 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 <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, &params, &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, &params, &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, &params, &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++;
		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函数的第二个参数发给服务器的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络——网络层(1)

计算机网络——网络层(1&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 网络层&#xff1a;数据平面网络层概述核心功能协议总结 路由器工作原理路由器的工作步骤总结 网际协议IPv4主要特点不足IPv6主要特点现状 通用转发和SDN通用转发SDN&#xff08;软件…

《WebKit技术内幕》学习之十三(3):移动WebKit

3 其他机制 3.1 新渲染机制 为了移动领域更好的用户体验&#xff0c;渲染机制所做的改进主要是提升渲染性能来增加响应的速度&#xff0c;甚至不惜牺牲一些跟规范定义的行为不一致的地方。在这一小节中主要介绍三个方面的技术&#xff0c;其一是Tiled Backing Store&#x…

【LLM】FuseLLM:大模型融合trick-知识融合LLMs

前言 传统的模型融合方法分为集成的方法和权重合并的方法&#xff0c;这两种方法在以往的NLP的比赛中非常常见&#xff0c;是一种提分手段。然而&#xff0c;上述两种方法都需要预训练或者微调相应的模型。在大模型场景下&#xff0c;对每个源模型都进行初始化成本太高&#x…

是用原生js创建红包雨效果

需求: 创建红包雨 答案: // 红包雨代码 开始 // 添加遮罩层 const addOverlay () > {const overlay document.createElement(div);overlay.className overlay;overlay.style.position fixed;overlay.style.top 0;overlay.style.left 0;overlay.style.width 100%;o…

华为机考入门python3--(0)测试题1-句子平均重量

分类&#xff1a;字符串 知识点&#xff1a; 获取输入 input().strip().split(" ") 拼接列表 " ".join(list) 输出指定位数的浮点数 print("%.2f" % value) len() 函数对于很多内置的数据类型都适用&#xff0c;它返回对象的元素个数或长度。…

Qt6入门教程 11:父子对象关系

在上一篇中的纯手写部分&#xff0c;不管是创建菜单、工具栏还是状态栏&#xff0c;我们new完之后都未显式的调用delete进行销毁&#xff0c;这样难道不会有内存泄漏么&#xff1f; QMenuBar *menuBar new QMenuBar(this); QToolBar *toolBar new QToolBar(this); QStatusBa…

yolov8上使用gpu教程

yolov8上使用gpu教程 安装Cuda和Cudnnyolov8上使用gpu 安装Cuda和Cudnn 1.查看支持的cuda版本&#xff0c;并去官网下载。 nvidia-smi2.网址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 3.安装细节 安装的前提基础是&#xff0c;有vs的C环境。我电脑有…

GBASE南大通用Connection 构造函数

GBASE南大通用分享  重载列表 1) 初始化一个新的 GBaseConnection 类实例。 GBaseConnection() 2) 当给定连接字符串的时候初始化一个新的 GBaseConnection 类实例。 GBaseConnection(string)  注释 当创建一个新的 GBaseConnection 实例的时候&#xff0c;其属性设…

DataStream API(源算子)

目录 源算子 1&#xff0c;从集合中读取数据 2&#xff0c;从文件读取数据 3&#xff0c;从 Socket 读取数据 4&#xff0c;从 Kafka 读取数据 5&#xff0c;自定义源算子 6&#xff0c;Flink 支持的数据类型 6.1 Flink 支持多种数据类型&#xff0c;包括但不限于&…

动态SQL:MyBatis强大的特性之一

一般来说&#xff0c;一个程序的服务器可以部署多个&#xff0c;但是数据库却只能有一个。这么多服务器&#xff0c;如果每天都要给数据库海量的操作数据&#xff0c;数据库的压力就会非常大。 所以为了减轻数据库的压力&#xff0c;我们可以把一些查询数据库的语句简化&#…

在Rust中编写自定义Error

前言 之前我们聊过&#xff0c;Result<T, E> 类型可以方便地用于错误传导&#xff0c;Result<T, E>是模板类型&#xff0c;实例化后可以是各种类型&#xff0c;但 Rust 要求传导的 Result 中的 E 是相同类型的&#xff0c;或者能够自动转化为相同类型。比如&#…

单例模式-C#实现

该实例基于WPF实现&#xff0c;直接上代码&#xff0c;下面为三层架构的代码。 一 Model using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 设计模式练习.Model.单例模式 {//单例模式的实现in…

el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)

elementplus 的 el-checkbox 官方代码中的多选框组实例如下&#xff1a; 上方代码中选中哪个选项就会往 checkList 数组中加入选项的 label 值&#xff0c;如果需要实现展示的值与选中的值不一样要怎么实现呢&#xff1f; 解决方法 el-checkbox组件中存在插槽&#xff0c;只需…

【Linux】 开始使用 gcc 吧!!!

Linux 1 认识gcc2 背景知识3 gcc 怎样完成 &#xff1f;3.1 预处理预处理^条件编译 3.2 编译3.3 汇编3.4 链接 4 函数库5 gcc 基本选项Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读下一篇文章见&#xff01;&#xff01;&#xff01; 1 认识gcc 我们在windows环…

02.领域驱动设计:了解领域、子域、核心域、通用域、支撑域、通用语言和限界上下文

目录 概要 1、领域 2、子领域 建立领域模型步骤&#xff1a; 3、核心域 4、通用域 5、支撑域 6、思考题 7、通用语言 8、限界上下文 限界上下文和微服务的关系 9、总结 限界上下文在微服务设计中的作用和意义是什么 概要 领域驱动设计&#xff08;DDD&#xff09;…

Web09--jQuery基础

1、jQuery概述 1.1 什么是jQuery jQuery是一款优秀的JavaScript的轻量级框架之一&#xff0c;封装了DOM操作、事件绑定、ajax等功能。特别值得一提的是基于jQuery平台的插件非常丰富&#xff0c;大多数前端业务场景都有其封装好的工具可直接使用。 jQuery下载和版本介绍 官…

qml中访问控件内部的子项

如何访问Repeater类型内部的子项、Row等布局类型内部的子项以及ListView内部的子项等。。。 1、测试代码 import QtQuick 2.0 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.3 import QtQml 2.12Window {id: windowobjectName: "m…

彩色图像处理之彩色图像分割的python实现——数字图像处理

原理 彩色图像分割是图像处理领域的一个重要技术&#xff0c;它旨在将一幅彩色图像划分为多个区域或对象。其基本原理包括以下几个方面&#xff1a; 像素特征的提取&#xff1a;彩色图像分割首先涉及到像素级的特征提取。在彩色图像中&#xff0c;常用的特征包括颜色、纹理和…

Javadoc的讲解使用

概述&#xff1a;JavaDoc 是用于生成 Java 代码文档的工具。通过编写 JavaDoc 注释&#xff0c;可以为代码中的类、接口、方法、字段等元素添加文档注释&#xff0c;这些注释将被 JavaDoc 工具解析并生成相应的 HTML 文档。 目录 讲解 使用 结果 讲解 下面是一些关于 Java…

常用通信总线学习——RS232与RS485

RS232概述 RS-232标准接口&#xff08;又称EIA RS-232&#xff09;是常用的串行通信接口标准之一&#xff0c;它是由美国电子工业协会(Electronic Industry Association&#xff0c;EIA)联合贝尔系统公司、调制解调器厂家及计算机终端生产厂家于1970年共同制定&#xff0c;其全…