1. 程式人生 > >C#訪問gsoap的服務

C#訪問gsoap的服務

view 清理 linu nvl spec dash 內容 collect ast

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

C#訪問gsoap的服務