最近在项目中用Delphi2010做了个独立运行的的WebService应用。
开发过程中遇到两个问题,网上能找到的资料比较少,后来花了点力气自己解决了,记录如下:
一、第一个问题是Delphi提供的WebService向导无法直接在独立的EXE中使用WebService。
解决的办法:
1、先用WebService向导创建一个新工程,其中有一个包含:
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
这三个组件的 TWebModule 模块,手动将这个模块添加到现有的或新建的VCL应用程序中。
2、使用Indy提供的IdHTTPWebBrokerBridge作为WebService的HTTP服务器。
IdHTTPWebBrokerBridge从D7开始就不再随Delphi发布,我是从网上单独下载的,并针对D2010做了一些兼容性修改,改过的文件见附件。
PS:WebService接口方法被调用时,默认是在独立的线程中进行的。我在修改IdHTTPWebBrokerBridge时顺便加了一个同步到主线程中执行调用的功能,由SyncRun属性进行控制。
3、在第一步生成的WebModule中添加两个函数,完整的代码如下:
unit SCWebMod;
interface
uses
SysUtils, Classes, HTTPApp, InvokeRegistry, WSDLIntf, TypInfo, WebServExp,
WSDLBind, XMLSchema, WSDLPub, SOAPPasInv, SOAPHTTPPasInv, SOAPHTTPDisp,
WebBrokerSOAP, IdHTTPWebBrokerBridge;
type
TSCWebModule = class(TWebModule)
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
procedure SCWebModuleDefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
// 启动WebService,在主窗体中调用
procedure SCStartWebService(Port: Integer = 6060);
// 停止WebService,程序关闭时会自动调用
procedure SCStopWebService;
implementation
uses WebReq;
{$R *.dfm}
var
FBridge: TIdHTTPWebBrokerBridge;
procedure SCStartWebService(Port: Integer = 6060);
begin
if FBridge <> nil then
Exit;
FBridge := TIdHTTPWebBrokerBridge.Create(nil);
FBridge.DefaultPort := Port;
FBridge.Active := True;
FBridge.SyncRun := True;
FBridge.RegisterWebModuleClass(TSCWebModule);
end;
procedure SCStopWebService;
begin
if FBridge <> nil then
FreeAndNil(FBridge);
end;
procedure TSCWebModule.SCWebModuleDefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WSDLHTMLPublish1.ServiceInfo(Sender, Request, Response, Handled);
end;
initialization
WebRequestHandler.WebModuleClass := TSCWebModule;
finalization
SCStopWebService;
end.
4、要发布的WebService接口按传统的方式编写,并用 InvRegistry.RegisterInterface 注册即可。
二、第二个问题是使用Delphi编写的WebService,如果一个带返回值的函数中使用了out/var型的参数,则C#无法正确调用。
问题分析:
我在C#中调用的方法是,使用VS 2005自带的命令行工具wsdl.exe生成一个C#的封装cs文件,再加入到C#工程中调用。
问题出现在Delphi生成的wsdl描述中,如果函数带有返回值和返回型参数,则会将返回值的说明:
<part name="return" type="xs:boolean"/>
放在其它返回型参数的后面。
而wsdl.exe生成的cs文件中,并没有将return识别为返回值,而是将第一个返回型参数当作函数返回值,导致生成的接口声明与Delphi中的不一致。
如果在C#中按照生成的接口来调用,运行时C#程序会挂掉。
经抓包分析,发现Delphi程序在soap调用时,返回的数据中return是排在其它返回型参数前面的,与wsdl中描述的正好相反。
解决办法:
在网上查了很多资料,都没找到合适的办法,只好自己动手了。
打开Delphi自带的 WebServExp.pas 文件,XE2中叫 Soap.WebServExp.pas,查找 TWebServExp.AddMessages 方法,将里面的这段代码:
{ Add Out parts }
{ Note: We always have a Message for the response - irrespective of return|out }
NewMessage := AddMessage(Messages, GetMessageName(MethodExtName, Methods, mtOutput));
Parts := NewMessage.Parts;
for Params := 0 to NoOfParams-2 do { Skip Self/this }
begin
{ pfOut or pfVar implies [out] parameter }
if ( (pfOut in ParamArray[Params].Flags) or (pfVar in ParamArray[Params].Flags) ) then
begin
ParamType := GetXMLSchemaType(ParamArray[Params].Info);
ParamExtName := InvRegistry.GetParamExternalName(IntfMD.Info, MethodExtName, ParamArray[Params].Name);
Parts.Add(ParamExtName,'',ParamType);
end;
end;
{ For Functions create a response }
if IntfMD.MDA[Methods].ResultInfo <> nil then
begin
ParamType := GetXMLSchemaType(IntfMD.MDA[Methods].ResultInfo);
Parts.Add(SReturn, '', ParamType);
end;
前后两段调一下,变成:
{ Add Out parts }
{ Note: We always have a Message for the response - irrespective of return|out }
NewMessage := AddMessage(Messages, GetMessageName(MethodExtName, Methods, mtOutput));
Parts := NewMessage.Parts;
{ For Functions create a response }
if IntfMD.MDA[Methods].ResultInfo <> nil then
begin
ParamType := GetXMLSchemaType(IntfMD.MDA[Methods].ResultInfo);
Parts.Add(SReturn, '', ParamType);
end;
for Params := 0 to NoOfParams-2 do { Skip Self/this }
begin
{ pfOut or pfVar implies [out] parameter }
if ( (pfOut in ParamArray[Params].Flags) or (pfVar in ParamArray[Params].Flags) ) then
begin
ParamType := GetXMLSchemaType(ParamArray[Params].Info);
ParamExtName := InvRegistry.GetParamExternalName(IntfMD.Info, MethodExtName, ParamArray[Params].Name);
Parts.Add(ParamExtName,'',ParamType);
end;
end;
再把这个修改后的单元另存一份,加入到自己的工程中编译。
重新运行后,会发现wsdl描述中,return的声明已经放到最前面,接下来在C#直接调用就可以了。