zoukankan      html  css  js  c++  java
  • vc(VS2008,UNICODE)下用gsoap调用WCF服务

    首先声明:文章中的内容大都来源于网络,做的过程遇到了不少问题,所以想写下此文,记录一个完整的过程,以飨后人。

    gsoap为何物,相信能看到这篇文章的人一定是有所了解了。不过还是啰嗦一下,保持文章的完整性。

    gSOAP是一个夸平台的,用于开发Web Service服务端和客户端的工具,在WindowsLinuxMAC OSUNIX下使用CC++语言编码,集合了SSL功能。

    下载地址:http://sourceforge.net/projects/gsoap2

    官方网站:http://genivia.com/Products/gsoap/index.html

    1、配置gSOAP
    下载gSOAP,解压后在gsoap\bin\win32里找到wsdl2h.exe和soapcpp2.exe程序。通过这两个程序可以生成客户端需要的C/C++文件。这两个程序的使用方法:
    wsdl2h.exe: 编译wsdl文件生成c/c++头文件
    -o 文件名,指定输出头文件 
    -n 名空间前缀 代替默认的ns 
    -c 产生纯C代码,否则是C++代码 
    -s 不要使用STL代码 
    -t 文件名,指定type map文件,默认为typemap.dat 
    -e 禁止为enum成员加上名空间前缀

    soapcpp2.exe: gSOAP编译器,编译头文件生成服务器和客户端都需要的c/c++文件
    (如果使用STL,需要从压缩包里找到stlvector.h放到soapcpp2.exe目录下,否则运行失败)
    -C 仅生成客户端代码 
    -S 仅生成服务器端代码 
    -L 不要产生soapClientLib.c和soapServerLib.c文件 
    -c 产生纯C代码,否则是C++代码(与头文件有关) 
    -I 指定import路径(见上文) 
    -x 不要产生XML示例文件 
    -i 生成C++包装,客户端为xxxxProxy.h(.cpp),服务器端为xxxxService.h(.cpp)

    写个批处理:
    wsdl2h -o xxxService.h "WSDL文件URL"
    soapcpp2 -C xxxService.h
    如果是asmx,可以在URL后加 "?WSDL" 来获取WSDL文件,它是一个XML,用来描述接口,它是与语言无关的,类似COM的IDL文件。

    2、创建VC工程,把生成的文件拷到工程目录
    生成的文件有:
    soapStub.h   从输入 Header 文件生成的经过修改且带标注的 Header 文件 
    soapH.h        主 Header 文件,所有客户机和服务源代码都要将其包括在内 
    soapC.c        指定数据结构的序列化器和反序列化器 
    soapClient.c 远程操作的客户机存根例程 
    stdsoap2.h stdsoap2.cpp 运行时库的 Header 文件 
    stdsoap2.cpp 运行时 C++ 库,带 XML 解析器和运行时支持例程 

    为 soapC.c、soapClient.c 和 stdsoap2.cpp 选择 Not using precompiled headers,因为它们并不依赖于 stdafx.h。(生成.c或者.cpp由编译选项决定,.c是纯C代码,.cpp是c++的)

    在vs2008中,如图所示,先选中文件,项目——属性,选择“不使用预编译头”。

    3、调用Web服务:

    调用web服务有两种方式,可以直接调用也可以使用代理

    3.1直接调用

    _ns1__Add add ; // 调用接口
    _ns1__AddResponse addResponse ; // 用来接收返回值
    
    double dx = 1, dy = 2, dResult = 0;
    
    struct soap soap; 
    soap_init(&soap);
    getContext.x = &dx ;
    getContext.y = &dy;
    
    double result = soap_call___ns1__Add(&soap,NULL,NULL,&add,&addResponse);
    if(result == SOAP_OK) 
    {
    dResult = *addResponse.AddResult;
    }
    else
    soap_print_fault(&soap,stderr);

     

    3.2使用代理

         double dx = 1, dy = 2, dResult = 0;
    	_ns1__Add add;
    	_ns1__AddResponse addResponse ;
    	add.x = &dx;
    	add.y = &dy;
    	BasicHttpBinding_USCORECalculatorService q;       //代理类
    	double result = q.__ns1__Add(&add,&addResponse);
    	if(result == SOAP_OK)   
    	{
    		dResult = *addResponse.AddResult;
    	}
    	else
    		soap_print_fault(q.soap,stderr);
    

    个人认为,使用代理会稍微方便一些,如果直接调用,上面的代码还不完整,需要调用 soap_destroy(&clientSOAP)、soap_end(soap)清除环境变量、soap_done(&clientSOAP),soap定义一次可以重复使用。至于soap初始化的其他方法,可以参照gsoap的文档,在文章最后我会给出gsoap中文文档的下载地址。

    至此,简单的demo调用应该是可以了,不过总会有意想不到的事情发生

    4、解决415错误:

     这个是由于服务端客户端soap协议不统一造成的,我使用的gsoap是2.8.8版本,编译出来的xxx.nsmap中soap协议是1.2版本,连接的wcf服务是1.1版本。

    目前这个问题有两个方法可以解决,1,手工修改nsmap文件中的协议,2,使用gsoap编译器“-1”编译选项(此方法未测试,据说有些时候也无效)

    //手动修改namespace, 生成soap1.1.
      struct Namespace namespaces[] =
     {
      {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", NULL, NULL},
      {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", NULL, NULL},
      {"xsi", "http://www.w3.org/2001/XMLSchema-instance", NULL, NULL},
      {"xsd", "http://www.w3.org/2001/XMLSchema", NULL, NULL},
     
      
      //{"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://www.w3.org/2003/05/soap-envelope", NULL},
      //{"SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://www.w3.org/2003/05/soap-encoding", NULL},
      //{"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL},
      //{"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL},
      
      {"ns4", "http://schemas.datacontract.org/2004/07/SnailGame.Toolkits.MessageSendService", NULL, NULL},
      {"ns3", "http://schemas.microsoft.com/2003/10/Serialization/", NULL, NULL},
      {"ns1", "http://tempuri.org/", NULL, NULL},
      {NULL, NULL, NULL, NULL}
     };
    

    5、解决中午乱码的问题:

    通过上面的步骤,已经可以成功连接了,但是在遇到中文字符的时候,问题就出现了。

    5.1中文字符返回值

    这个解决的办法主要就是要把字符转换成utf-8

    1>设置GSOAP的编码模式 soap_set_mode(soap, SOAP_C_UTFSTRING);

    2>将传输回来的字符流进行字符转换

      //ns5__SimpleNetObject是WCF中定义的结构体,返回值是此类型
      //mapServiceResponse用来接收返回值,参看前面的addResponse就明白了   ns5__SimpleNetObject d = *mapServiceResponse.PublishMapServiceResult;   std::wstring str;   std::string stt = d.Message;   //转换为utf-8编码   bool fl = WFromUTF8(reinterpret_cast<const char* >(stt.c_str()), str);   CString s = str.c_str();

    3>字符转换函数

    #if !defined(TEXTCONVERT_H)
    #define TEXTCONVERT_H
    //多字节字符串转换为宽字节字符串
    // CodePage:[in]编码
    // lpcwszText:[in]多字节字符串
    // lppszVal:[out]宽字节字符串
    //返回值:TRUE表示成功,其它表示失败
    inline BOOL WcharFromChar(UINT CodePage,const char *lpcszText,std::wstring &wstrVal)
    {
    	WCHAR *lpwszVal( NULL ); 
    	int nLength(0);size_t dwSize(0);
    	nLength = ::MultiByteToWideChar( CodePage, 0, lpcszText, -1 , NULL, 0); 
    	if( nLength <= 0 ) return FALSE;dwSize = nLength * sizeof(WCHAR);
    	lpwszVal = (WCHAR *)malloc( dwSize );
    	if( NULL == lpwszVal ) return FALSE;
    	memset(lpwszVal,0x0,dwSize);
    	nLength = ::MultiByteToWideChar( CodePage, 0, lpcszText, -1 , lpwszVal, nLength );  
    	if( nLength <= 0 )
    	{
    		free(lpwszVal);
    		lpwszVal = NULL; 
    		return FALSE;
    	}
    	wstrVal.assign( lpwszVal );
    	free(lpwszVal);
    	lpwszVal = NULL;
    	return TRUE;
    }
    
    //UTF-8字符串转换为宽字节字符串
    // lpcwszText:[in]UTF-8字符串
    // lppszVal:[out]宽字节字符串
    //返回值:TRUE表示成功,其它表示失败
    inline BOOL WFromUTF8(const char *lpcszText,std::wstring &wstrVal )
    {
    	return WcharFromChar(CP_UTF8,lpcszText,wstrVal);
    }
    #endif
    

    5.2中文字符参数

    中文字符返回值的问题解决了,新的问题又出现了,如果按照上面的设置,如果是有中文字符的参数,这个参数就会乱码了,真是一波刚平一波又起啊。

    有人提出了用setloacal的方法来解决,不过这个办法我使用的时候无效。

    返回值乱码,我们是将其从转换成utf-8的编码方式,那么,我们传人的参数有问题,在传人参数之前,先将其转换成utf-8是不是就可以呢。(当前是unicode,大概等价于utf-16,未查证)

    如果是linux系统,貌似有直接的装换函数,可惜我是在windows平台,那就自己写吧,有了上面的转换方法,照猫画虎,也就是那样。

    //字符编码转换
    std::string Convert(std::wstring str,int targetCodepage)
    { 
    	BYTE* pTargetData = NULL;  
    	size_t souceLen=WideCharToMultiByte(targetCodepage,0,(LPWSTR)str.c_str(),-1,(char *)pTargetData,0,NULL,NULL);  
    	pTargetData=new BYTE[souceLen+1];  
    	memset(pTargetData,0,souceLen+1);  
    	WideCharToMultiByte(targetCodepage,0,(LPWSTR)str.c_str(),-1,  
    		(char*)pTargetData,souceLen,NULL,NULL); 
    
    	std::string rt((char*)pTargetData);  
    	delete [] pTargetData; 
    	return rt;  
    }
    

    调用方法

    CString str = _T("测试中文");

    std::wstring ss = str;

    std::string  var  =Convert(ss,CP_UTF8);

    行文至此,我所解决的问题也就是这么多了,目前也可以运行起来了,不过还是有几个小问题困扰着我

    遇到的问题

    1>在我的机子上wsdl工具无法生成头文件,gsoap工具正常,同样的工具,在我同事的机子上可以正常运行

    2>WCF选择http协议没有问题,如果是TCP协议,就不能正常连接,不知是不是不支持

    解决的问题

    本文解决的问题,主要是C++语言对wcf服务的访问

    c++访问wcf的3种方式

    1,托管c++,缺点猜想,部署项目的时候需要包含.net库,讨厌这种拖泥带水的。

    2,用c#访问WCF,重新包装一个接口,生成dll, 供c++使用;缺点,还是需要公共语言运行库支持,也即托管c++

    3, 用gsoap

    综合考虑,还是用gsoap比较好,而且我的程序是C/S结构的,客户端用c++编写,如果加入clr,部署起来会比较麻烦。

    最后奉上两个下载链接,为了防止外链失效,我给一个博客园的下载地址

    gSoap 2.8.8版本下载

    gSoap 中文文档下载

    很可惜,试了一下,我的上传权限单个文件只有1M,无法上传,有需要的留个邮箱吧,我看到了就发给你们。其实这些自己搜索也很容易找的。

     

    参考文章:http://www.cppblog.com/qiujian5628/archive/2008/06/19/54019.html

    http://hi.baidu.com/lbird/blog/item/0222bb6eb2eb12d480cb4a50.html

    http://blog.csdn.net/zozoiiiiiiii/article/details/7418339

    http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/1ff173ee-c371-45b1-986b-fbb67ea94de8/

    http://blog.sina.com.cn/s/blog_6a22547f0100v0wn.html

    http://blog.csdn.net/spyy26224574/article/details/3340913

     

    两天做的过程中,查看了很多人的文章,由于参看内容繁杂,在此未能一一列举,深表歉意。

  • 相关阅读:
    初级安全入门——安全漏洞的检测与利用
    Gazebo11的安装与启动
    ROS入门(四)——Gazebo的基本使用
    ROS入门(二)——服务和全局参数
    ROS入门(一)——基本概念和话题
    数据结构与算法(三+)——列表的Java实现
    Java EE入门(二十)——Maven基础
    Java EE入门(十九)——Redis基础
    Java EE入门(十八)——Ajax&JSON基础
    npm中如何更新自己已经发布的1.0.0的模块包?
  • 原文地址:https://www.cnblogs.com/junyuz/p/2501933.html
Copyright © 2011-2022 走看看