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;
- 使用下面得函數轉換我們的傳輸內容,即將我們的數據轉成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的服務