C#访问gsoap的服务

C++开发一个webservice,然后C#开发客户端,这样就需要C#的客户端访问gsoap的服务端。(大家都知道gsoap是C/C++开发webservice的最佳利器) 为什么不考虑直接用wcf开发webservice?那是考虑到以后有可能要移植到Linux平台。

gsoap实现C#(wpf)客户端调用C++服务器端

首先建立C++服务器端,这里需要使用到gsoap
第一步:自己在桌面建立一个文件夹,命名为add,打开add文件夹,新建txt文档,命名为add.txt,拷贝下面的代码到add.txt,点击保存,然后修改add.txt后缀名为add.h

//gsoap ns service name: add
//gsoap ns service namespace: http://localhost/add.wsdl
//gsoap ns service location: http://localhost
//gsoap ns service executable: add.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:add
int ns__add( int num1, int num2, int* sum );

第二步:从gsoap文件里面拷贝soapcpp2.exe到add文件夹里面,然后打开cmd命令行,输入:soapcpp2.exe add.h会生成许多文件,如图

这里写图片描述
这里写图片描述
第三步:新建Win32控制台应用程序,命名为:gsoapApplication_1_S;
添加wsock32.lib引用;
从add文件夹中拷贝如下图所示文件到该项目中,其中黑圈里面的两个项目来自gsoap拷贝:
这里写图片描述
第四步:修改gsoapApplication_1_S.cpp,其代码如下:
// gsoapApplication_1_S.cpp : 定义控制台应用程序的入口点。

//
#include "add.h"
#include "add.nsmap"
int main(int argc, char* argv[])
{  
    int m, s;
    /* master and slave sockets */    
    struct soap add_soap;    
    soap_init(&add_soap);    
    //soap_set_namespaces(&add_soap, add_namespaces);    
    m = soap_bind(&add_soap, NULL, 4567, 100);        
    if (m < 0)        
    {            
        soap_print_fault(&add_soap, stderr);            
        exit(-1);        
    }                
    fprintf(stderr, "Socket connection successful: master socket = %d/n", m);        
    for ( ; ; )        
    {            
        s = soap_accept(&add_soap);            
        if (s < 0)            
        {                
        soap_print_fault(&add_soap, stderr);                
        exit(-1);            
        }            
        fprintf(stderr, "Socket connection successful: slave socket = %d/n", s);                        
        soap_serve(&add_soap);//该句说明该server的服务            
        soap_end(&add_soap);         }
    return 0; }
//server端的实现函数与addmethod.h中声明的函数相同,但是多了一个当前的soap连接的参数
int ns__add(struct soap *add_soap, int num1, int num2, int *sum)
{    
    *sum = num1 + num2;    
    return SOAP_OK;
}

第五步:编译成功,打开cmd命令行,输入gsoapApplication_1_S.exe 4567打开主服务,如图
这里写图片描述
第六步:打开IE浏览器,输入:http://localhost:4567 回车,如图所示,服务器端已经配置成功
这里写图片描述

配置C#(wpf)客户端,使用到wsdl.exe,在本地搜索一下,没有的话需要在网上下载一个,我是在网上下载的,下载很简单
第一步:拷贝wsdl.exe到add文件夹,打开vs自带的黑屏命令行工具,输入:wsdl /out add.wsdl回车,会生成.cs文件,文件里面包含多线程、代理等信息,如下图
这里写图片描述

第二步:新建WPF应用程序,命名为gsoapApplication_1_C_WPF,拷贝add.cs,add.wsdl两个文件到该工程中,(主要针对用别人的服务没法修改,亦可这样操作:右键点击C#窗口项目的“引用”选择“添加服务引用”,就会出现一个填写wsdl所在地址的对话框,可以选择是远端webservice服务地址下的wsdl,当然也可以是本地的。我选择的是本地地址,也就是C:\add.wsdl,点击“前往”。就会找到服务。这时下面的名字空间最好写的和当初gsoap建立时指定的命名空间一样。其实不一样也可以,但是在项目中使用的时候用的就是这个新的名字了。)修改add.cs文件代码的”http://localhost/add.cgi“为”http://localhost:4567/add.cgi“,如图所示:
这里写图片描述

第三步:MainWindow.xaml界面代码如下:

<Window x:Class="gsoapApplication_1_C_WPF.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
Title="MainWindow" Height="350" Width="525">    
<Grid>        
<GroupBox Header="Add" HorizontalAlignment="Left" Margin="52,43,0,0" VerticalAlignment="Top" Height="236" Width="412">            
<Grid>                
<Grid.RowDefinitions>                    
<RowDefinition/>                    
<RowDefinition/>                    
<RowDefinition/>                    
<RowDefinition/>                
</Grid.RowDefinitions>                
<Grid.ColumnDefinitions>                    
<ColumnDefinition/>                    
<ColumnDefinition/>                
</Grid.ColumnDefinitions>                
<Grid Grid.Row="0" Grid.Column="0">                    
<Label Content="number 1:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
<Grid Grid.Row="0" Grid.Column="1">                    
<TextBox Name="num1" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
</Grid>                
<Grid Grid.Row="1" Grid.Column="0">                    
<Label Content="number 2:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
<Grid Grid.Row="1" Grid.Column="1">                    
<TextBox Name="num2" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
</Grid>                
<Grid Grid.Row="2" Grid.Column="0">                    
<Label Content="result----:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
<Grid Grid.Row="2" Grid.Column="1">                    
<TextBox Name="result" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
</Grid>                
<Grid Grid.Row="3" Grid.Column="1">                    
<Button Name="submitAdd" Content="计算" Click="submitAdd_Click" Width="80" Height="30"/> </Grid>            
</Grid>                    
</GroupBox>
</Grid>
</Window>

第四步:MainWindow.xaml.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace gsoapApplication_1_C_WPF
{    
    /// <summary>    
    /// Interaction logic for MainWindow.xaml    
    /// </summary>    
    public partial class MainWindow : Window    
    {        
        public MainWindow()        
        {            
            InitializeComponent();                    
        }
        private void submitAdd_Click(object sender, RoutedEventArgs e)        
        {            
            add myadd = new add();            
            int num1 = Convert.ToInt32(this.num1.Text);            
            int num2 = Convert.ToInt32(this.num2.Text);            
            int? sum = 0;            
            bool sumSpecified = false;                         
            myadd.Calladd(num1, num2,out sum, out sumSpecified);            
            this.result.Text = sum.ToString();        
        }    
    }
}

第五步:编译,生成,结果如图所示(不要忘了打开主服务哦~):
这里写图片描述

发现新的问题

对于接口.h文件中用自定义的结构体,会出现无法辨识的问题。
例如:

//gsoap ns service name: add
//gsoap ns service namespace: http://localhost/add.wsdl
//gsoap ns service location: http://localhost
//gsoap ns service executable: add.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:add
int ns__add( int num1, int num2, int* sum );
typedef struct ns__point
{
    int x;
    int y;
};
int ns__addPt(struct ns__point num1, struct ns__point num2, struct ns__point* result );

这里写图片描述

1、C#提供的WebService的URL一般形如:http://localhost/WebService.asmx,但是,C++能提供的只能是:http://localhost/。C++做客户端的时候调用没有问题,但是当C#做客户端的时候,引用C++提供的RUL时,会提示没用执行方法(HTTP GET method not implemented)。做C#开发的大部分会认为C++方提供的不是WebService,或者说提供的WebService根本就不全,都不带.asmx文件。做C++开发的会认为他传输的数据符合soap协议,靠http传输数据,他就是WebService。
2、当我们解决了第一步后,紧接着会发现另外一个问题。当我们需要传输自定义数据类型时(在C++中称结构体,在C#中称实体),从C++返回的信息中,C#无法构建出实体类。

3、当传输的信息中带有中文字符时,乱码满天飞。

为了解决这些问题,我们先简单了解一下WebService。
Web Service互操作协议栈:
、服务发现 (UDDI)
、服务描述(WSDL)
、服务调用(SOAP)
、消息编码 (XML)
、传输网络层(HTTP, TCP/IP)
其中WSDL描述WebService都有什么方法、方法有什么参数,什么返回值等。SOAP(简单对象访问协议(Simple Object Access Protocol)是一种轻量的、简单的、基于XML的协议。传输的数据就需要遵循这个协议。我比较简单得认为传输的数据需要遵循这种格式。

借用微软的这个图描述下WebService的调用过程:
这里写图片描述

C++、C写的WebService相互调用martin新浪博客 - mylovejsj - 宋孝先的博客

开始解决问题。作为.NET开发人员,我们根本就接触不到底层的东西,全被封装了。

C++做的确实是WebService,只是他们需要给提供一个描述文档,即.WSDL文件。使用.NET提供的wsdl.exe工具,使用命令:wsdl /o: c:\webservice.cs c:\webservice.wsdl。通过webservice.wsdl文档,生成代理类,将代理类写入webservice.cs文件中。我们拷贝这个cs文件到项目中,将URL指向http://localhost/,就能像以往那样使用WebService了。

当出现无法传递复杂类型数据时,是因为使用gsoap生成的wsdl文件与.Net中生成的wsdl文件不一样。具体代码如下:

<!-- operation response element -->
<element >
<complexType>
<sequence>
<element type="xsd:int" minOccurs="1" maxOccurs="1"/>
<element type="xsd:int" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>

以上为gsoap生成的。返回实体result,实体有两个属性:a,b。

<s:element >
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" type="tns:result" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType >
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" type="s:int" />
<s:element minOccurs="1" maxOccurs="1" type="s:int" />
</s:sequence>
</s:complexType>

以上是.NET生成的。
在下面的文件中,多出

<s:element >
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" type="tns:result" />
</s:sequence>
</s:complexType>
</s:element>

这个便是.NET中用来构造实体的。当我们出现情况4.2时,gsoap中尽量使用.NET生成的wsdl文档,生成.h文件,以避免C++中的结构无法在C#中转换成实体。

第三个问题,我们是通过将中文转换成16进制后传输过来,然后再转换成中文。下面提供C#转换的代码:

/// <summary>
/// 从16进制转换成汉字
/// </summary>
/// <param ></param>
/// <returns></returns>
public static string GetChsFromHex(string hex)
{
    if (hex == null)
    throw new ArgumentNullException("hex");
    if (hex.Length % 2 != 0)
    {
        hex += "20";//空格
        //throw new ArgumentException("hex is not a valid number!", "hex");
    }
    // 需要将 hex 转换成 byte 数组。
    byte[] bytes = new byte[hex.Length / 2];

    for (int i = 0; i < bytes.Length; i++)
    {
        try
        {
            // 每两个字符是一个 byte。
            bytes[i] = byte.Parse(hex.Substring(i * 2, 2),
            System.Globalization.NumberStyles.HexNumber);
        }
        catch
        {
            // Rethrow an exception with custom message.
            throw new ArgumentException("hex is not a valid hex number!", "hex");
        }
    }

    // 获得 GB2312,Chinese Simplified。
    System.Text.Encoding chs = System.Text.Encoding.GetEncoding("gb2312");


    return chs.GetString(bytes);
}

/// <summary>
/// 从汉字转换到16进制
/// </summary>
/// <param ></param>
/// <returns></returns>
public static string GetHexFromChs(string s)
{
    if ((s.Length % 2) != 0)
    {
        s += " ";//空格
        //throw new ArgumentException("s is not valid chinese string!");
    }

    System.Text.Encoding chs = System.Text.Encoding.GetEncoding("gb2312");

    byte[] bytes = chs.GetBytes(s);

    string str = "";

    for (int i = 0; i < bytes.Length; i++)
    {
        str += string.Format("{0:X}", bytes[i]);
    }

    return str;
}

注:以上来转换代码源于网络,C++中转换的代码也可以在网上找到。

例子解决代码

//gsoap ns service name: calc
//gsoap ns service style: rpc
//gsoap ns service encoding: encoded
//gsoap ns service namespace: http://127.0.0.1:8089/calc.wsdl
//gsoap ns service location: http://127.0.0.1:8089/cal.cgi
//gsoap ns schema  namespace:    urn:calc
typedef struct ns__point
{
    int x;
    int y;
};

struct ns__pointResponse
{
    int ret;
    struct ns__point pt;
};

int ns__add(int num1, int num2, int* result );
int ns__addPt(struct ns__point num1, struct ns__point num2, struct ns__pointResponse* result );

这里写图片描述
图中会写下calc.cs,就说明成功了。
至于为什么这么写就解决问题了,那就要扯到如何生成接口头文件了。

由wsdl文件生成头文件

开发客户端程序
(1)客户端服务端通信必不可少的一个东西:接口定义。
gSoap使用中,需要此接口文件(也就是以后常提到的头文件)此文件可以两种方式得到:
1: 由现有的wsdl文件转换得到(本文使用这种方式)
2: 自己编写
(2)两个工具的用途
wsdl2h.exe——–名字就能看出来,完成 wsdl文件(.wsdl)到(2)头文件(.h)的转换,即由写好的wsdl格式的接口定义文件转换为C/C++格式的头文件
soapcpp2.exe——根据接口头文件生成应用程序客户端和服务端的框架文件(soapClient.cpp、soapServer.cpp等).

结合例子说明过程
示例:引用向导中的例子–计算器
由wsdl文件生成接口头文件
wsdl文件URL地址:http://www.cs.fsu.edu/~engelen/calc.wsdl
操作流程
a) 程序->运行 输入cmd 启动命令行;
b) 打开wsdl2h.exe所在文件路径(不打开的话下面输入时要记着输入全路径,不然找不着了);
c) 输入:wsdl2h.exe -o cal.h http://www.cs.fsu.edu/~engelen/calc.wsdl,
其中
“wsdl2h.exe”,因为第二步中打了wsdl2h文件所在路径,所以此处可以直接用了,若没打开要输入所在完整路径
“cal.h”为生成的头文件名,此文件的保存位置在wsdl2h.exe同一文件目录下(要修改的话要请参照命令行语法规则,此处不多说了,有点脱离主题)
       “-o”是生成选项,表示输出,也可以增加其它选项(向导中出现 “-s -o”、”-c -o”等),具体选项说明请参照向导中介绍。
http://www.cs.fsu.edu/~engelen/calc.wsdl“是wsdl文件的位置,此处是在网络地址(这个地址是可以用的),如果wsdl文件在本地的也可以,路径设置好就OK了
注意:以上四项中每项间都有空格分开 不要手误啊!
d) 回车 运行吧,看提示信息,没有提示故障的话,查找cal.h吧(cal.h的保存路径要记好啊,自己设置的);
e) 得到cal.h后,打开可以看到接口定义;
f) 在命令行中 输入:soapcpp2.exe cal.h
其中
“soapcpp2.exe”,此处把soapcpp2.exe和wsdl2h.exe放在同一目录下了,且前面已打开此路径;
“cal.h”即由wsdl生成的头文件,此文件保存位置与soapcpp2.exe相同,故此处可这样输入,若不同还须设置文件路径;
同样两项之间也有空格!
可以增加控制选项如”soapcpp2.exe -C cal.h”则只生成客户端所需文件, “soapcpp2.exe -S calc.h”则只生成服务端所需文件
g) 回车 运行。
如果提示找不到文件 stlvector.h,因为cal.h文件中引用了文件stlvector.h;解决方法:开发包\gsoap\import目下有此文件,复制一份到cal.h所在目录。
注意:此过程中要检查生成的编译信息,确保没有警告信息出现(警告信息可能是gSoap不支持的内容,如果不去除,程序运行故障很难找到根本原因),另不能只看编译结果成功就认为是正确的,存在警告信息也显示为编译成功!!
h) 编译成功,生成的框架文件保存位置与cal.h在同一目录下,包括:calc.nsmap、soapC.cpp、soapcalcObject.h、soapcalcProxy.h、soapClient.cpp、soapClient.lib
、soapH.h、soapServer.cpp、soapServer.Lib、soapStub.h和接口的请求应答xml文件。
    其中
      calc.nsmap 为命名空间定义文件;
      soapClient.cpp 客户端文件
soapServer.cpp 服务端文件
soapcalcProxy.h 代理用到
soapStub.h 此文件中为程序中要用到的接口的声明(将cal.h中的接口经过处理后,在程序中供调用的接口)
xml文件 为接口的xml格式,可以据此帮助调试等。。。
其它文件作用还没完全搞明白,向导中有说明,此处不说了以免误导^_^
i) 编译过程完成。下面开始程序(客户端)
VC6中新建空的win32控制台工程,新建源文件calcClient.cpp;
复制编译生成的文件(包括cal.h和stlvector)到程序目录中;
在工程中添加文件calc.nsmap、soapC.cpp、soapClient.cpp、soapH.h、soapStub.h(使用代理添加soapcalcProxy.h);
编译,提示无法找到文件stdsoap2.h、stdsoap2.cpp文件,这两个文件为gSoap的源码文件,在\gsoap文件夹中找到这两个文件,复制到程序目录下并添加到工程中;
在calcClient.cpp文件中 添加语句#include “soapH.h”, #include “calc.nsmap”
///////////////////////////calcClient.cpp源码//

#include <stdio.h>
#include "soapH.h"
#include "calc.nsmap"
const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";
int main(int argc, char **argv)
{
 struct soap soap;
 double a, b, result;
 if (argc < 4)
 { 
     fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");
     exit(0);
 }
 soap_init(&soap);
 a = strtod(argv[2], NULL);
 b = strtod(argv[3], NULL);
 switch (*argv[1])
 { case 'a':
   soap_call_ns2__add(&soap, server, "", a, b, result);
   break;
 case 's':
   soap_call_ns2__sub(&soap, server, "", a, b, result);
   break;
 case 'm':
   soap_call_ns2__mul(&soap, server, "", a, b, result);
   break;
 case 'd':
   soap_call_ns2__div(&soap, server, "", a, b, result);
   break;
 case 'p':
   soap_call_ns2__pow(&soap, server, "", a, b, result);
   break;
 default:
   fprintf(stderr, "Unknown command\n");
   exit(0);
 }
 if (soap.error)
 { 
     soap_print_fault(&soap, stderr);
     exit(1);
 }
 else
 printf("result = %g\n", result);
 soap_destroy(&soap);
 soap_end(&soap);
 soap_done(&soap);
 return 0;
}

其中soap_call_ns2__add、soap_call_ns2__sub、soap_call_ns2__mul、soap_call_ns2__div、soap_call_ns2__pow为接口函数,在soapStub.h中可以找到函数声明。

自定义接口头文件

接口头文件的格式在向导中没有看到明确的说明性的内容,但通过看开发包中示例程序中头文件定义和通过wsdl生成的头文件的内容,可以发现,头文件中都会出现以下几行信息
//gsoap ns service name: calc Simple calculator service
//gsoap ns service style: rpc
//gsoap ns service encoding: encoded
//gsoap ns service namespace: http://websrv.cs.fsu.edu/~engelen/calc.wsdl
//gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
//gsoap ns service method-style: add rpc
//gsoap ns service method-encoding: add http://schemas.xmlsoap.org/soap/encoding/
//gsoap ns service method-action: add “”
这些都是头文件中的说明性信息,编译时会用到这里的信息,这些信息有命名空间的和服务名称的名字,有些信息为默认参数信息,如service location 如果在客户端调用接口方法时,没有输入服务端地址,方法执行时将自动调用此处的地址做为服务器地址。后三行是对add方法的说明,在写头文件时可以修改一下这些说明信息如下:
//gsoap ns service name: calc Simple calculator service
//gsoap ns service style: rpc
//gsoap ns service namespace: urn:calc
//gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
这些信息是很重要的,如果是已定义好的接口,一定要注意命名空间名字统一。
下面开始主要工作:接口定义
gSoap对“”和“_”(下划线、双下划线)有特殊用法,接口定义时函数名前要加上命名空间名和双下划线
举例:加法运算
int add(int num1, int num2, int &num3);
在接口头文件定义时要写成如下格式
int ns__add(int num1, int nmu2, int &num3);
其中
“ns”是命名空间名称,此名称可以自定义成其它名字 但其后”urn:calc”不可随意更改,这个是要与服务端统一的
“__”是编译时识别符号,如果没有的话xml文件是编译不出来的

“_”(单下划线)定义名称中要慎用,自己定义的变量或函数名称中使用下划线的地方要在下划线后加上”USCORE”;
例 定义 ns__add_sum(int num_1, int num2, int &num_3);
要写成如下形式
ns__add_USCOREsum(int num_USCORE1, int num2, int &num_USCORE3);
“add”是接口函数名 这个是客户端与服务器统一的接口名字
函数内参数变量类型和变量名 为客户端与服务器统一的接口
函数中只有最后一个参数是输出参数,前面的都是输入参数
若有多个输出信息,可定义结构体。
若没有输入参数 可将输入参数类型定义为void (将忽略void 类型的参数)
函数返回类型必须为 int,可以通过返回值判断接口函数执行情况
至此接口定义要注意的地方完毕,开始写接口定义文件。
本例中使用自定义的头文件,编写客户端程序,但服务器仍与上篇中服务器相同,以便说明两种方式的结果,接口可以从上篇中由wsdl生成的头文件中获得。
源码如下:

///////////////////mycalc.h///////////////////////////////
//gsoap ns service name: mycalc
//gsoap ns service style: rpc
//gsoap ns service namespace: urn:calc
//gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
int ns__add(double a, double b, double &result);
int ns__sub(double a, double b, double &result);
int ns__mul(double a, double b, double &result);
int ns__div(double a, double b, double &result);
int ns__pow(double a, double b, double &result);
///////////////////mycalc.h///////////////////////////////

上面文件中一定要注意 //gsoap ns service namespace: urn:calc 由于服务器中接口命名空间为calc 故此处必须是calc!
开始编译此头文件
运行 soapcpp2.exe mycalc.h
编译成功,写客户端程序
新建工程mycalcClient, 添加生成的文件,新建源文件mycalcClient.cpp
源码如下

//////////////////////mycalcClient.cpp/////////////////
#include <stdio.h>
#include "soapH.h"
#include "calc.nsmap"
const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";
int main(int argc, char **argv)
{
 struct soap soap;
 double a, b, result;
 if (argc < 4)
 { fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");
 exit(0);
 }
 soap_init(&soap);
 a = strtod(argv[2], NULL);
 b = strtod(argv[3], NULL);
 switch (*argv[1])
 { case 'a':
   soap_call_ns__add(&soap, server, "", a, b, result);
   break;
 case 's':
   soap_call_ns__sub(&soap, server, "", a, b, result);
   break;
 case 'm':
   soap_call_ns__mul(&soap, server, "", a, b, result);
   break;
 case 'd':
   soap_call_ns__div(&soap, server, "", a, b, result);
   break;
 case 'p':
   soap_call_ns__pow(&soap, server, "", a, b, result);
   break;
 default:
   fprintf(stderr, "Unknown command\n");
   exit(0);
 }
 if (soap.error)
 { soap_print_fault(&soap, stderr);
 exit(1);
 }
 else
 printf("result = %g\n", result);
 soap_destroy(&soap);
 soap_end(&soap);
 soap_done(&soap);
 return 0;
}
//////////////////////mycalcClient.cpp/////////////////

测试一下吧^_^
对比两种生成接口头文件的方法,选择那一种还要视情况而定
如果是已有写好的wsdl文档,当然是选择用wsdl2h工具来生成头文件了,毕竟这个方便的多,但在编译时,一定要检查警告信息,有些情况下生成的接口头件存在编译异常,例如复杂数据结构时就有可能出现结构体命名不符合gSoap命名规则(实际应用中遇至过,此时要手动修改这些命名)
简单说一下soap结构体初始化及清理
soap_init(&soap);//初始化
…………
soap_destroy(&soap);//结束清理操作
soap_end(&soap);
soap_done(&soap);
向导中这方面讲的比较细^_^
两篇都只是写了客户端的程序,因为是一直在与现成的服务器端通信,在此就没列出自己的服务器程序;

接口定义

,可参考《GSoap接口定义》。这里我将给出C#引用这个webserver所对应的接口形式。
gsoap是根据我们定义好的.h文件,然后用工具产生了我们所需的.c文件。所以我们必须根据gsoap的要求编写.h。
1. 单个参数的传出:
int ns__add( int a, int b, int *c );
需要说明的是,这里的ns__是必须的,必须以开始注释中的ns加两个下划线开始。返回值必须是int。
但是这里的int并不是接口的返回值,而是gsoap内部的返回值。真正的返回值是int *c。

 C#中对应的接口:  int add( int a, int b );  返回值就是上述的int *c参数。

2. 多个参数传出,在接口中必须使用结构体

 typedef char * xsd__string;
     typedef long   xsd__int;
     struct ns__personResponse{
         xsd__int age;
         xsd__string name;
         xsd__string address;
     };
     int ns__person( xsd__string buf_in, struct ns__personResponse * buf_out );
 在C#中,并不是我们所声明的这样。而是:int person( string buf_in, out string name, out string address );
 即,结构体中的第一个域会变成返回值,其他的变成一个个的输出参数。

3. 返回结构体。如果要返回结构图,那么必须在结构体中再套一层结构体:

 typedef char * xsd__string;
     typedef long   xsd__int;
     struct ns__person{
         xsd__int age;
         xsd__string name;
         xsd__string address;     
     };
     struct ns__personResponse{
         xsd__int ret;
         struct ns__person person;
     };
     int ns__person( xsd__string buf_in, struct ns__personResponse * buf_out );
 那么在C#中,看到的接口是这样的:int person( string buf_in, person对应的结构类 );

4. 接口中的下划线,如果接口中的交易名有下划线,必须这么声明:
int ns__echo_USCOREreverse( char * buf_in, char ** buf_out );

 那么,C#中实际上的接口名就是:string echo_reverse( string buf_in );

gsoap中返回字符串
1. 下面是一个逆转字符串的函数。

int ns__echo_USCOREreverse( char * buf_in, char ** buf_out );
     int ns__echo_USCOREreverse( struct soap *add_soap, char *buf_in, char **buf_out )
     {
        int i, j, len;
        printf( "ns__interface: in=[%s]\n", buf_in );

        len = strlen(buf_in);
        *buf_out = (char*)soap_malloc( add_soap, len+1 );
        for( i=len-1, j=0; i>=0; i--, j++ ){
            (*buf_out)[j] = buf_in[i];
        }
        (*buf_out)[j] = 0;

        return 0;
     }
 其中调用soap_malloc申请空间,并且将他赋给返回参数buf_out。这个空间会在调用soap_end时被释放。

gsoap传输中文。我使用utf-8编码格式来支持汉字的传输。
1. 设置gsoap为utf-8传输数据
soap_set_mode( &SmsWBS_soap, SOAP_C_UTFSTRING ); //设置编码
SmsWBS_soap.mode|=SOAP_C_UTFSTRING;

  1. 使用下面得函数转换我们的传输内容,即将我们的数据转成UTF-8编码:
int conv_charset( const char *dest, const char *src, char *input, size_t ilen, char *output, size_t olen )
     {
         int convlen = olen;
         iconv_t conv = iconv_open( dest, src );
         if( conv == (iconv_t) -1 )
             return -1;

          memset( output, 0, olen );
         if( iconv( conv, &input, &ilen, &output, &olen ) ){
             iconv_close(conv);
             return -1;
         }

         iconv_close(conv);
         return convlen-olen;
     }
 例子: conv_charset( "UTF-8", "GBK", "林学任.linxr", strlen("林学任.linxr"),  buf_out->name, 100 );

webserver发布
1. 在C#中,可以直接引用一个webserver,但是我们写得webserver如何能用被其引用呢。其实只要实现gsoap的fget回调函数即可:
SmsWBS_soap.fget = http_get;
2. http_get函数实现

int http_get(struct soap * soap)
     { 
         FILE *fd = NULL;

         char *s = strchr( soap->path, '?' );
         if( !s || strcmp( s, "?wsdl" ) ){
             return SOAP_GET_METHOD;
         }

         fd = fopen( "SmsWBS.wsdl", "rb" );
         if (!fd){
             return 404;
         }

         soap->http_content = "text/xml";
         soap_response(soap, SOAP_FILE);
         for (;;){ 
             size_t r = fread(soap->tmpbuf, 1, sizeof(soap->tmpbuf), fd);
             if( !r ){
                 break;
             }
             if( soap_send_raw( soap, soap->tmpbuf, r) ){
                 break; 
             }
         }

         fclose(fd);
         soap_end_send(soap);

         return SOAP_OK;
     }

Reference:[gsoap使用总结](http://www.cnblogs.com/linxr/archive/2011/10/17/2215285.html)

转自:https://blog.csdn.net/caowei880123/article/details/49129211

posted on 2018-09-28 15:00  苦涩的茶  阅读(882)  评论(0编辑  收藏  举报

// 1.生成目录索引列表