在本例中使用gsoap中的ISAPI extension 模块(mod_gsoap.dll).
本例中运行环境为WinXP SP3(32位) IIS5.1
本例中的开发工具为Microsoft Visual Studio 2008 SP1(32位)中文版
gSoap安装目录为D:/gsoap-2.8
在上一节中我已经已经配置好gsoap运行环境,现在实现一个简单的时间服务.
1. 数据类型
定义数据类型,数据类型定义请参考
gSOAP 2.8.1 User Guide(http://www.cs.fsu.edu/~engelen/soapdoc2.html#tth_sEc11.3.3) How to Represent Primitive C/C++ Types as XSD Type,下列为常用的数据类型:
typedef char * xsd__anyURI //表示统一资源标识符
typedef char * xsd__base64Binary; //表示Base64编码的二进制数据
typedef bool xsd__boolean; //表示BOOL类型
typedef char xsd__byte; //表示有符号字节(范围为-127~127)
typedef time_t xsd__dateTime; //表示日期和时间,ISO 8601扩展格式CCYY-MM-DDThh:mm:ss
// CC表示世纪
// YY表示年
// MM表示月份
// DD表示当前月的第几天,天数可以为负数,默认为正数,如-03--9
// T为日期时间分隔符
// hh:mm:ss分别代表小时:分钟:秒
// 日期和时间范围为-2037年之间,否则time_t值为-1
typedef char * xsd__dateTime; //表示日期和时间,使用这个类型应由应用和读取和设置日期时间格式
typedef char * xsd__date; //表示日期
typedef double xsd__decimal; //表示任一精度的小数,建议使用double类型
typedef double xsd__double; //表示位双精度浮点数(IEEE )
typedef char * xsd__duration; //表示持续时间,ISO 8601扩展格式PnYn MnDTnH nMnS
// nY表示年数
// nM表示月数
// nD表示天数
// T为日期时间分隔符
// nH表示小时数
// nM表示分钟数
// ns表示秒数
typedef float xsd__float; //表示位单精度浮点数(IEEE )
typedef char * xsd__hexBinary; //表示任意进制编码的二进制数据
typedef int xsd__int; //表示位整数,需要编译器支持
typedef long xsd__int; //表示位整数,范围-2147483648至
typedef long long xsd__integer; //表示位整数
typedef long long xsd__long; //表示位整数,范围-9223372036854775808至
typedef LONG64 xsd__long; //表示位整数
typedef long long xsd__negativeInteger; //表示位整数
typedef unsigned long long xsd__nonNegativeInteger; //表示无符号位整数
typedef long long xsd__nonPositiveInteger; //表示位整数
typedef char * xsd__normalizedString; //表示字符串,但字符串中不包含回车(#xD),换行(#xA)和制表符((#x9)
typedef unsigned long long xsd__positiveInteger; //表示无符号位整数
typedef short xsd__short; //表示位整数,范围-32768至
typedef char * xsd__string; //表示字符串
typedef wchar_t * xsd__string; //表示字符串
typedef wchar_t * xsd__string_; //表示字符串,同时使用char *和wchar_t *类型字符串时,加一个下划线表示wchar_t *
typedef char * xsd__time; //表示时间hh:mm:ss.sss
typedef char * xsd__token; //表示字符串,但字符串中不包含回车(#xA),制表符((#x9)和空格符(#x20)
typedef unsigned char xsd__unsignedByte; //表示位无符号整数,范围至
typedef unsigned int xsd__unsignedInt; //表示位无符号整数,范围至(需要编译器支持)
typedef unsigned long xsd__unsignedInt; //表示位无符号整数,范围至
typedef unsigned long long xsd__unsignedLong; //表示位无符号整数,范围至
typedef ULONG64 xsd__unsignedLong; //表示位无符号整数,Visual C++支持
typedef unsigned short xsd__unsignedShort; //表示位无符号整数,范围至
2. 编写头文件
//gsoap ns service namespace: urn: query
//gsoap ns service location: http://localhost/gsoap/mod_gsoap.dll?currentTime
typedef time_t xsd__dateTime;
typedef char * xsd__string;
int ns__currentTime(xsd__dateTime& rsp);
int ns__helloworld(xsd__string req,xsd__string &rsp);
注意事项:头文件最上面的不是注释,而是用于配置命名空间和访问地址,而非单纯的注释;详细配置说明可以参考gSOAP 2.8.1 User Guide.
gSoap对“_”和“__”(下划线、双下划线)有特殊用法,接口定义时函数名前要加上命名空间名和双下划线.
举例:加法运算
int add(int num1, int num2, int &num3);
在接口头文件定义时要写成如下格式
int ns__add(int num1, int nmu2, int &num3);
其中
"ns"是命名空间名称,此名称可以自定义成其它名字.
"__"是编译时识别符号,如果没有的话xml文件是编译不出来的.
"_"(单下划线)定义名称中要慎用,自己定义的变量或函数名称中使用下划线的地方要在下划线后加上"USCORE";
例 定义 ns__add_sum(int num_1, int num2, int &num_3);
要写成如下形式
ns__add_USCOREsum(int num_USCORE1, int num2, int &num_USCORE3);
保存编写的头文件(D:/Temp/项目/gSoap/Query.h)
3. 使用soapcpp2编译头文件
在上一讲中我们已经把gSoap安装在D:/gsoap-2.8中,但是没有设置运行环境,复制D:/gsoap-2.8/gsoap/bin/win32/soapcpp2.exe、D:/gsoap-2.8/gsoap/stdsoap2.h和D:/gsoap-2.8/gsoap/ stdsoap2.cpp到D:/Temp/项目/gSoap文件夹中.为了使用方便,我们采用批处理方法编译头文件.在D:/Temp/项目/gSoap中新建Server.bat和Clear.bat.
Server.bat,内容为
soapcpp2 -S Query.h
Clear.bat内容为
del *.xml
del ns.nsmap
del ns.wsdl
del ns.xsd
del soapC.cpp
del soapH.h
del soapObject.h
del soapServer.cpp
del soapServerLib.cpp
del soapStub.h
执行Server.bat,后生成ns.currentTime.req.xml、ns.currentTime.res.xml、ns.helloworld.req.xml、ns.helloworld.res.xml、ns.nsmap、ns.wsdl、ns.xsd、soapC.cpp、soapH.h、soapObject.h、soapServer.cpp、soapServerLib.cpp、soapStub.h。
注意生城的文件数量和接口方法相关,如果新增了接口方法必须修改Clear.bat中的内容.
4. 创建项目
打开Microsoft Visual Studio 2008.选择文件-新建-项目,在新建项目对话框中项目类型选择Visual C++ - Win32,在模板中选择Win32项目,项目名称为Query,位置为D:/Temp/项目/gSoap/Query.不要勾选创建解决方案的目录,如图
确定后打开Win32应用程序向导,这里采用默认设置单击下一步
在应用程序设置这里选择应用程序类型为DLL,附加选项选择空项目,单击完成
为使用方便我们在VS解决方案资源管理器中创建三个筛选器gSoap、gSoapEx和Tools,创建方法在解决方案资源管理器中点击项目Query,然后鼠标右键弹出快捷菜单-添加-新建筛选器.
在筛选器gSoapEx中添加现有文件D:/Temp/项目/gSoap/stdsoap2.h和D:/Temp/项目/gSoap/stdsoap2.cpp.
在筛选器gSoapEx中新建模块定义文件stdsoap2.def,内容为
; stdsoap2.def : Declares the module parameters for a gsoap server DLL.
;DESCRIPTION 'gosap server dll'
EXPORTS
; we must export what the gsoap isapi extension needs for loading us and working with us.
soap_init PRIVATE
soap_serve PRIVATE
soap_delete PRIVATE
soap_done PRIVATE
soap_end PRIVATE
soap_register_plugin_arg PRIVATE
soap_lookup_plugin PRIVATE
mod_gsoap_init PRIVATE ; initializer called before loading
mod_gsoap_terminate PRIVATE ; called before uploading
在解决方案资源管理器中右键点击项目Query选择属性,打开属性页对话框,选择配置属性-连接器-输入,找到模块定义文件输入stdsoap2.def.如下图
在筛选器gSoap中添加现有文件
ns.nsmap
ns.wsdl
soapC.cpp
soapH.h
soapObject.h
soapServer.cpp
soapStub.h
添加完成后开打ns.wsdl,找到<SOAP:address>节点,修改<SOAP:address 为
<SOAP:address location="http://localhost/gsoap/mod_gsoap.dll?Query"/>,节点属性location 说明了WebServic的地图,客户端引用时访问的地址就是http://localhost/gsoap/mod_gsoap.dll?Query
在筛选器Tools中添加现有文件Server.bat和Clear.bat
在头文件中添加现有项Query.h(也可以不添加)
在源文件中添加新项Query.cpp,添加完成后如下图:
编译项目Query,出现以下错误
错误 3 fatal error LNK1120: 2 个无法解析的外部命令 D:/Temp/项目/gSoap/Query/Debug/Query.lib
错误 1 error LNK2001: 无法解析的外部符号 mod_gsoap_init d:/Temp/项目/gSoap/Query/stdsoap2.def 1
错误 2 error LNK2001: 无法解析的外部符号 mod_gsoap_terminate d:/Temp/项目/gSoap/Query/stdsoap2.def 1
错误原因是因为还没有实现Query.h和stdsoap2.def中定义的方法.
5. 方法定义
打开Query.cpp添加以下内容,然后编译Query,项目生成成功.
#include "soapH.h"
#include "ns.nsmap"
extern "C"
{
/** This function is called by mod_gsoap after the dll was successfully loaded and before processing begins.
You can do any one-time initialization here.
*/
int mod_gsoap_init()
{
// todo: add your initialization code here
return SOAP_OK;
}
/** This function is called after all processing was done before dll is unloaded.
You can do any cleanup here.
*/
int mod_gsoap_terminate()
{
// todo: add your termination code here
return SOAP_OK;
}
}
int ns__currentTime(struct soap *soap,xsd__dateTime& rsp)
{
return SOAP_OK;
}
int ns__helloworld(struct soap *soap,xsd__string req,xsd__string &rsp)
{
return SOAP_OK;
}
说明:
int mod_gsoap_init() 当前模块加载到IIS时执行的方法,例如在这里创建数据库连接池等.
int mod_gsoap_terminate() 当模块从IIS卸载时执行的方法,例如关闭并释放数据数据库连接池等.
ns__currentTime和ns__helloworld就是我们在Query.h中声明的WebService接口方法.
mod_gsoap_init()和mod_gsoap_terminate()方法是在模块定义文件stdsoap2.def中定义的,如果你不需要主这两个方法,首先删除stdsoap2.def客户的定义,然后删除下列内容
extern "C"
{
/** This function is called by mod_gsoap after the dll was successfully loaded and before processing begins.
You can do any one-time initialization here.
*/
int mod_gsoap_init()
{
// todo: add your initialization code here
return SOAP_OK;
}
/** This function is called after all processing was done before dll is unloaded.
You can do any cleanup here.
*/
int mod_gsoap_terminate()
{
// todo: add your termination code here
return SOAP_OK;
}
}
6. 实现头文件中定义的方法
int ns__currentTime(struct soap *soap,xsd__dateTime& rsp)
{
//获取当前日期和时间
rsp = time(0);
return SOAP_OK;
}
//注意req只接收英文字符串和符号,如果是中文则输出为乱码
int ns__helloworld(struct soap *soap,xsd__string req,xsd__string &rsp)
{
std::string::size_type dwMemSize(0);
std::string strVal;
//设置输出的消息
strVal.assign(req);
strVal.append(" Hello world!");
//为输出参数分配内存
//soap_malloc分配的内存在soap_end中释放
//使用时只需要分配,并不需要释放资源
dwMemSize = ( strVal.size() + 1 ) * sizeof(char);
rsp = (char*)soap_malloc( soap,dwMemSize );
memset( rsp,0x0,dwMemSize);
memcpy_s( rsp,dwMemSize,strVal.c_str(),dwMemSize);
return SOAP_OK;
}
编译项目Query,编译成功后将"D:/Temp/项目/gSoap/Query/Debug/Query.dll复制到D:/WEB/gsoap中.
如里出现”复制文件或文件夹出错”对话框则必须重启IIS后复制. 重启IIS命令 iisrese
7. 自动部署到WEB
由于每次编译项目成功后我们都必须把模块Query.dll复制Web目录中,手工复制容易忘记并且比较麻烦,可以在VS中项目设置为自动部署,在解决方案资源管理器中右键点击项目Query选择属性,打开属性页对话框,选择配置属性-生成事件-生成后事件,在命令行中输入
iisreset /STOP
copy "D:/Temp/项目/gSoap/Query/Debug/Query.dll" "D:/WEB/gsoap"
iisreset /START
说明:
首先停止IIS,这是因为模块加载到IIS后当前模块被IIS占有用,无法删除.
复制代码到Web目录
重新启动IIS
每次生成成功后就自动更新Web目录中的文件.
8. 测试
打开VS2008,新建一个C#控制台应用程序,添另一个服务引用D:/Temp/项目/gSoap/Query/ ns.wsdl,找到服务引用后单击确定完成.
修改Main函数为
static void Main(string[] args)
{
try
{
ServiceReference1.ServicePortType s = new ServiceReference1.ServicePortTypeClient();
ServiceReference1.currentTimeResponse response1 = s.currentTime(new ServiceReference1.currentTimeRequest() );
ServiceReference1.helloworldRequest request2 = new ServiceReference1.helloworldRequest(new ServiceReference1.helloworldRequestBody("lxg"));
ServiceReference1.helloworldResponse response2 = s.helloworld(request2);
System.Console.WriteLine(response1.rsp);
System.Console.WriteLine(response2.Body.rsp);
}
catch (Exception exp)
{
System.Console.WriteLine(exp.Message);
}
}
编译并运行程序,打印出当前服务器时间和”xxx Hello world!”,至此一个简单的gSoap服务器开发完成,下一讲中谈谈中文乱码和”ISAPI extension - mod_gsoap.dll”调试技术.