Web2.0时代中,C++也照样演绎着它经久不衰的生命力。很多人认为C++做网络通信开发等等太傻,虽然我不得不承认它的WebServices通信程序开发速度不如C#,C#中很多忽略的细节,在C++中全部需要自己进行定义,但是它也不是不可以做,下面跟随笔者来一起学习使用C++调用OpenAPI的方法吧。
1、 你需要的东西
其实系统中早就已经存在了SOAP的接口,XP下的是1.0版本,组件位置在C:/Program Files/Common Files/MSSoap/Binaries/MSSOAP1.dll,C是系统盘符。其实调用OpenAPI,系统里面自带的Soap1.0组件就已经足够了,微软现在官方提供3.0的Soap SDK,对我们意义不大,接口名称后面都加了”30”,最值得一提的是3.0的Soap SDK中提供的Trace Utility,这个工具可以方便我们调试。
你还需要XML解析器的支持,也就是系统里面的MSXML组件,这个想必大家都知道吧。只需要import进来,然后using namespace 就可以使用其中的接口了。
2、 了解对方的OpenAPI声明
我们这里以CSDN的OpenAPI做范例进行讲解。他们的一个讲解页面是http://community.csdn.net/openapi/openapiexplain.htm,是用C#作为主要语言,没关系,我们打开http://forum.csdn.net/OpenApi/forumapi.asmx,能看到ForumAPI提供了7个API。我们先看GetUserProfile,获取用户信息的这个API,我们能看到发送的XML的格式为如下:
POST /OpenApi/forumapi.asmx HTTP/1.1
Host: forum.csdn.net
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://www.csdn.net/GetUserProfile"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUserProfile xmlns="http://www.csdn.net/">
<identity>
<username>string</username>
<password>string</password>
</identity>
<username>string</username>
</GetUserProfile>
</soap:Body>
</soap:Envelope>
报头部分只需要注意一个地方,SOAPAction的值即可
Xsi、xsd和soap则可以理解成Envelope 的3个属性。<soap:Body>里面的是body,是我们需要发送的内容,<GetUserProfile xmlns="http://www.csdn.net/">和</GetUserProfile>之间就是数据了,GetUserProfile是函数名,http://www.csdn.net/是名称空间(Namespace)。函数名也能算是一个Element(元素),而XML中,元素之间嵌套元素,也就是Element里面还有Element。你认为<identity>也是一个元素吗?对,是的。不过,它里面又有个两个元素username和password,而identity元素和下面那个username是平级的,我们C++伪代码是不是可以这么写?
struct GetUserProfile
{
struct identity
{
string username;
string password;
}
string username;
}
我们现在已经摸清了发送的XML格式了,该怎么工作呢?
首先
#import <msxml3.dll>
using namespace MSXML2;
#import "C:/Program Files/Common Files/MSSoap/Binaries/MSSOAP1.dll" /
exclude("IStream", "ISequentialStream", "_LARGE_INTEGER", /
"_ULARGE_INTEGER", "tagSTATSTG", "_FILETIME")
using namespace MSSOAPLib;
将两个关键的组件包含进来。
接下来
CoInitialize(NULL); //初始化COM对象
//定义如下三个接口对象
ISoapSerializerPtr Serializer;
ISoapReaderPtr Reader;
ISoapConnectorPtr Connector;
//创建Connector对象实例
Connector.CreateInstance(__uuidof(HttpConnector));
Connector->Property["EndPointURL"] = "http://forum.csdn.net/OpenApi/forumapi.asmx";
//这个是写提供服务的URL,EndPointURL这个属性
Connector->Connect();//连接
// 开始消息
Connector->Property["SoapAction"] = "http://www.csdn.net/GetUserProfile";
Connector->BeginMessage();
// 创建SoapSerializer对象实例
Serializer.CreateInstance(__uuidof(SoapSerializer));
// 将serializer连接到connector的输入字符串
Serializer->Init(_variant_t((IUnknown*)Connector->InputStream));
//下面个属性设置好,前面讲过的
Serializer->startEnvelope("SOAP","http://schemas.xmlsoap.org/soap/envelope/","");
//开始Envelope
Serializer->SoapAttribute("xsi", "", "http://www.w3.org/2001/XMLSchema-instance", "xmlns");
Serializer->SoapAttribute("xsd", "", "http://www.w3.org/2001/XMLSchema", "xmlns");
Serializer->startBody(""); //<soap:Body>
//开始GetUserProfile元素
Serializer->startElement("GetUserProfile","http://www.csdn.net/","NONE",""); //<GetUserProfile xmlns="http://www.csdn.net/">
Serializer->startElement("identity","http://www.csdn.net/","NONE","");//<identity>
Serializer->startElement("username","http://www.csdn.net/","NONE","");
Serializer->writeString("tr0j4n"); //笔者的CSDN用户名
Serializer->endElement();
Serializer->startElement("password","http://www.csdn.net/","NONE",""); Serializer->writeString("1Z3azg5W78dsq"); //笔者的CSDN密码
Serializer->endElement();
Serializer->endElement(); //</identity>
Serializer->startElement("username","http://www.csdn.net/","NONE","");
Serializer->writeString("tr0j4n"); //需要查询的CSDN ID的用户信息
Serializer->endElement();
Serializer->endElement();//</GetUserProfile>
//结束GetUserProfile元素
Serializer->endBody();//</soap:Body>
Serializer->endEnvelope();//结束Envelope
Connector->EndMessage();//结束消息
上面的startElement和endElement是开始元素和结束标志的时候使用的,你看出了上面的代码中各个元素之间的嵌套和并列关系了么?我已经在后面注释中和对应的XML进行了对应,您还可以参考前面的那段伪代码。WriteString是向所在的元素中写入值,注意在startElement的时候不要忘了加上Namespace的字符串。
经过上面的代码,您就把那段标准格式的请求发出去了,如果您的程序出现问题,在Connector->EndMessage();这一句程序抛出异常,请检查你前面的startElement和endElement函数的使用情况,是不是有配对没有弄好,切记,这是很多人提出来的问题。
接下来我们需要接受服务器返回的信息,先看下服务器回显的XML格式如下:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUserProfileResponse xmlns="http://www.csdn.net/">
<GetUserProfileResult>boolean</GetUserProfileResult>
<profile>
<point>int</point>
<techExpertPoint>int</techExpertPoint>
<topForums>
<TopForum>
<forumId>guid</forumId>
<expertPoint>int</expertPoint>
<rank>string</rank>
</TopForum>
<TopForum>
<forumId>guid</forumId>
<expertPoint>int</expertPoint>
<rank>string</rank>
</TopForum>
</topForums>
<nonTechExpertPoint>int</nonTechExpertPoint>
<nickName>string</nickName>
<username>string</username>
</profile>
<error>
<errId>int</errId>
<errInfo>string</errInfo>
<description>string</description>
</error>
</GetUserProfileResponse>
</soap:Body>
</soap:Envelope>
我们来写获得服务器返回XML信息的代码:
// 创建Reader实例
Reader.CreateInstance(__uuidof(SoapReader));
// 将reader联接到connector的输出字符串
Reader->Load(_variant_t((IUnknown*)Connector->OutputStream), "http://www.csdn.net/GetUserProfile");
我们看下ISoapReaderPtr接口的获得返回值的两个方法,一个是RPCResult,MSDN的解释是This property returns the first child element of the first entry in the <Body> element of a SOAP message.还有个是RPCStruct,This property returns the first entry in the <Body> element of a SOAP message.
我们看上面的返回的XML的内容,看到<GetUserProfileResult>boolean</GetUserProfileResult>了没?那是不是FirstChild呢?first entry in the <Body> element of a SOAP message。所以我们只需要Reader->RPCResult->text不就能获得GetUserProfileResult这个值了吗,如果其他的元素您不关心的话,你完全可以将其转换成char *输出成功与否即可。这就是你为什么在Google上能搜索出一堆C++调用WebServices的文章其中的代码都几乎一样,有printf("Return Message is : %s/n",(const char*)Reader->RPCResult->text);这句话的原因,但是那些文章都很不负责任,它就没有想过我们需要其他的元素的值了吗?