您的位置:首页 > 其它

麻雀虽小五脏俱全--一个小项目的总结

2010-09-20 15:11 369 查看
最近,写一个程序,调用电信的web service接口,本来是个很小的项目,但是在做的过程中出现了一些问题,由于缺乏经验的问题。在此总结之。

项目虽小,但是五脏俱全。涉及了文件I/O的操作、字符编码转换、linux下c++使用web service、c++使用c#dll、网络通信和程序发布问题。

一)文件I/O

二)字符编码转换

三)linux下c++使用web service

1.安装gSoap

我使用gSOAP在Linux下调用web service,可以到:http://www.cs.fsu.edu/~engelen/soap.html下载,我使用的是gsoap_2.7.17.zip,下载下来解压缩,然后放到linux机器上。

然后安装:

# cd gsoap_2.7.17

# ./configure --prefix=/home/mengxy/gSOAP(指定安装路径)

# make

# make install

这样就安装完成了。

2.生成客户端存根程序和框架

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)

-C 仅生成客户端代码
-S 仅生成服务器端代码
-L 不要产生soapClientLib.c和soapServerLib.c文件
-c 产生纯C代码,否则是C++代码(与头文件有关)
-I 指定import路径(见上文)
-x 不要产生XML示例文件
-i 生成C++包装,客户端为xxxxProxy.h(.cpp),服务器端为xxxxService.h(.cpp)

执行命令:

# cd /home/mengxy/gSOAP/bin

# ./wsdl2h -o /home/mengxy/SC_WSFORSP/wsforsp.h http://218.6.254.34/WSForSP/WSForsp.asmx?WSDL
# ./soapcpp2 /home/mengxy/SC_WSFORSP/wsforsp.h

注:其中的路径和wsdl的地址请换上你自己用到的地址。

这样生成的文件在gSoap安装路径下的bin文件夹下,在我的电脑上是/home/mengxy/gSOAP/bin。

把如下文件拷贝到你的项目:

soapStub.h,soapH.h,stdsoap2.h

soapC.cpp,soapClient.cpp,stdsoap2.cpp
soapxxSoapProxy.h
xxSoap.nsmap

其中stdsoap2.h和stdsoap2.cpp在文件夹gsoap_2.7.17中。

3.代码示例

// sc_web_service.h
/*
title:	使用gsoap调用四川web service接口
author:	mengxy
date:	2010-09-13
description:
*/
#ifndef __SC_WEB_SERVICE_H__
#define __SC_WEB_SERVICE_H__
typedef struct send_radius_t END_RADIUS_QUE_BODY;
class SC_Web_Service_Impl;
class SC_Web_Service
{
public:
static SC_Web_Service * get_instance()
{
static SC_Web_Service instance;
return &instance;
}
~SC_Web_Service();

int send(SEND_RADIUS_QUE_BODY * body);
private:
SC_Web_Service();
SC_Web_Service(const SC_Web_Service &);
SC_Web_Service & operator=(const SC_Web_Service &);

SC_Web_Service_Impl * impl_;
};
#endif // __SC_WEB_SERVICE_H__


#include "Send_Msg_Define.h"
#include "sc_web_service.h"
//#include "../gsoap/soapStub.h"
#include "../gsoap/soapWSForSPSoapProxy.h"
#include "../gsoap/WSForSPSoap.nsmap"
#include <string>
class SC_Web_Service_Impl
{
public:
int send(SEND_RADIUS_QUE_BODY * body);
private:
WSForSPSoap ws_for_sp_;
_ns1__VnetUserUseLogService usr_use_log_;
_ns1__VnetUserUseLogServiceResponse usr_use_log_response_;
};
int SC_Web_Service_Impl::send(SEND_RADIUS_QUE_BODY * body)
{
if(NULL == body)
return -1;
std::string usr = body->user_;
std::string service_id = body->service_id_;
std::string service_item_id = body->service_item_id_;
std::string spid = body->spid_;
std::string ip_ = body->ip_;
std::string log_tm = body->log_tm_;
std::string tm_stamp = body->time_stamp_;
std::string authenticator = body->authenticator_;
usr_use_log_.user = &usr;
usr_use_log_.accounttype = body->account_type_;
usr_use_log_.Serviceid = &service_id;
usr_use_log_.ServiceItemid = &service_item_id;
usr_use_log_.Spid = &spid;
usr_use_log_.IP = &ip_;
usr_use_log_.LogTime = &log_tm;
usr_use_log_.TimeStamp = &tm_stamp;
usr_use_log_.Authenticator = &authenticator;
int rst = ws_for_sp_.__ns1__VnetUserUseLogService(&usr_use_log_, &usr_use_log_response_);
if(SOAP_OK == rst) {
if(-1 == usr_use_log_response_.VnetUserUseLogServiceResult->Result) {
//			std::cerr << "call web service failed.(" << *(usr_use_log_response_.VnetUserUseLogServiceResult->ErrorDescription) << ")/n";
return -1;
}
}else {
return -1;
}
return 0;
}
SC_Web_Service::SC_Web_Service() : impl_(new SC_Web_Service_Impl())
{
}
SC_Web_Service::~SC_Web_Service()
{
delete impl_;
}
int SC_Web_Service::send(SEND_RADIUS_QUE_BODY * body)
{
return impl_->send(body);
}


以上代码通过代理类WSForSPSoap调用了接口__ns1__VnetUserUseLogService。

4.makefile的注意点

在makefile中加入生成soapC.o、stdSoap2.o和soapClient.o的命令,链接生成程序的时候需要用这几个目标文件。

soapC.o:
g++ -o $(OBJ_PATH)/soapC.o $(CPPFLAGS) $(GSOAP_SRC_PATH)/soapC.cpp
stdSoap2.o:
g++ -o $(OBJ_PATH)/stdSoap2.o $(CPPFLAGS) $(GSOAP_SRC_PATH)/stdsoap2.cpp
soapClient.o:
g++ -o $(OBJ_PATH)/soapClient.o $(CPPFLAGS) $(GSOAP_SRC_PATH)/soapClient.cpp


ok,这样就可以了。

5.乱码问题解决

在使用过程中由于调用web service得到的字符串是Utf-8编码的,造成了乱码。查了下得到的字符串也不对,命名返回了十几个中文字符,然而字符串的长度才14,这不应该,于是google了一下,做了以下修改,在soapxxSoapProxy.h中修改proxy类,在我的项目中该类为WSForSPSoap,修改其构造函数,将其中的soap = soap_new();改为soap = soap_new1(SOAP_C_UTFSTRING),这样就可以得到正确的utf-8字符串,但是却不一定能够正确显示,由于我的i18n文件中LANG="zh_CN.GB18030",所以我在程序里又使用iconv函数族将utf-8字符串转换成GB18030字符串,至此可以正确显示了。

四)c++使用c# dll

先上代码。

#include "stdafx.h"
#include "PSO.h"
#using "VNetPSO.dll"
using namespace Microsoft::SCVNET::PSO;
using namespace System;
using namespace System::Runtime::InteropServices;
#pragma managed
#define CIPHERTEXT_MAX_LENGTH 255
//将非托管的ANSI字符串转换成托管字符串
String ^ UnmanagedStringA2ManagedString(char * pIn)
{
String^ strOut = Marshal::PtrToStringAnsi(static_cast<IntPtr>(pIn));
return strOut;
}
//将托管字符串转换成非托管的ANSI字符串
char * ManagedString2UnmanagedStringA(String ^ strIn)
{
IntPtr ip = Marshal::StringToHGlobalAnsi(strIn);
const char* pTemp = static_cast<const char*>(ip.ToPointer());
char *pOut = new char[strlen(pTemp)+1];
strcpy(pOut, pTemp);
Marshal::FreeHGlobal( ip );
return pOut;
}
void generateAuthenticator(char * src, char * key, char * & authenticator)
{
PSOCryptography ^cryptography = gcnew PSOCryptography();
String ^ t_src = UnmanagedStringA2ManagedString(src);
array<unsigned char> ^ keyarr = cryptography->HexStringToByteArray(UnmanagedStringA2ManagedString(key));
array<unsigned char> ^ IV = {1, 2, 3, 4, 5, 6, 7, 8};
array<unsigned char> ^ encrpted = gcnew array<unsigned char>(CIPHERTEXT_MAX_LENGTH);
cryptography->Encrypt(keyarr, IV, cryptography->ComputeHash(cryptography->ConvertStringToByteArray(t_src)), encrpted);
String ^ t_auth = cryptography->ToBase64String(encrpted);
authenticator = ManagedString2UnmanagedStringA(t_auth);
}


注意,这是在windows下vs2005环境下用c++使用c# dll。使用c# dll需要用到c++/cli,具体语法,请自查(不过满少的资料)。项目属性要设置/clr,General->Common language Runtime Support中进行该项设置。

这里有几个注意点:

1.“未处理的异常:System.IO.FileNotFoundException...",遇到此问题,请将c# dll放到可执行程序同目录下。

2.一开始我是直接建一个项目未win32控制台程序,然后按照这种方式使用c# dll,但是有莫名其妙的错误。所以查了一下,有人建议首先创建一个使用托管的dll项目调用c# dll的接口,然后创建一个非托管的应用程序项目,使用该dll。于是,我建了一个dll,项目属性设置了/clr,建了一个win32控制台程序,项目属性未设置/clr。

五)网络通信

六)程序发布中的问题

在将加密服务器发布时出现“应用程序配置不正确,应用程序未能启动...”。然后我对它进行排查,首先把用到的dll和.manifest文件到服务器上,还是同样的错误。于是打开.manifest文件查看程序所需要的依赖,发现需要Microsoft.VC80.CRT,于是拷贝开发机器上“vs2005/SDK/v2.0/BootStrapper/Packages/vcredist_x86”下的vcredist_x86.exe到服务器上安装。(也可以到“vs2005/VC/redist/x86/Microsoft.VC80.CRT”直接拷贝下面的dll和.manifest文件到服务器上)到这里,我认为万事大吉了,然后运行,又一次出现了错误,显示“应用程序正常初始化失败”,然后我仔细想想还缺少什么文件。由于我使用了电信提供的c# dll,所以我觉得可能是服务器没有安装.NET Framework,于是下载了Microsoft .NET Framework 2.0 版可再发行组件包(它用来安装运行针对 .NET Framework 2.0 版开发的应用程序时所需的 .NET Framework 运行库及相关文件),在服务器上进行安装,最终运行成功。

附:.manifest文件说明

缺少.manifest文件常常会造成“应用程序配置不正确,应用程序未能启动...”的问题。.manifest文件的作用是为了解决 以前windows上的“Dll 地狱” 问题而产生的新的DLL管理解决方案。大家知道,Dll是动态加载共享库,同一个Dll可能被多个程序所使用,而所谓“Dll 地狱”就是当不同程序依赖的Dll相同,但版本不同,于是造成了加载了不正确的Dll版本,造成程序出现错误。于是有了.manifest文件,它是一个程序集清单,每个程序都要有一个.manifest文件,里面列出其所需要的所有依赖。

网上有个朋友有个建议:在你的VC++安装目录下面的“.../VC/redist”目录下,有着所有发布所需要的vc库和.manifest文件。所以你想要发布一个程序最简单最安全的做法(不用担心用户电脑是否包含你所需要的库)就是把这个目录下面的相应的库的文件夹和你的可执行文件放在一起发布。

项目比较大,寻找这些依赖所需要的工作比较耗时繁琐的时候可以参照这位兄台的做法,项目比较小,将需要的依赖拷贝过去即可,没必要把该文件夹下所有东西都拷贝过去。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: