做过网页设计的人应该都知道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_a