Board logo

Subject: 用Delphi开发WebService供C#调用的一点经验 [Print This Page]

Author: zjy    Time: 2011-10-9 16:54     Subject: 用Delphi开发WebService供C#调用的一点经验

最近在项目中用Delphi2010做了个独立运行的的WebService应用。

开发过程中遇到两个问题,网上能找到的资料比较少,后来花了点力气自己解决了,记录如下:

一、第一个问题是Delphi提供的WebService向导无法直接在独立的EXE中使用WebService。

解决的办法:
1、先用WebService向导创建一个新工程,其中有一个包含:

[Copy to clipboard]
CODE:
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;

这三个组件的 TWebModule 模块,手动将这个模块添加到现有的或新建的VCL应用程序中。

2、使用Indy提供的IdHTTPWebBrokerBridge作为WebService的HTTP服务器。
IdHTTPWebBrokerBridge从D7开始就不再随Delphi发布,我是从网上单独下载的,并针对D2010做了一些兼容性修改,改过的文件见附件。
PS:WebService接口方法被调用时,默认是在独立的线程中进行的。我在修改IdHTTPWebBrokerBridge时顺便加了一个同步到主线程中执行调用的功能,由SyncRun属性进行控制。

3、在第一步生成的WebModule中添加两个函数,完整的代码如下:

[Copy to clipboard]
CODE:
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描述中,如果函数带有返回值和返回型参数,则会将返回值的说明:
&lt;part name="return" type="xs:boolean"/&gt;
放在其它返回型参数的后面。
而wsdl.exe生成的cs文件中,并没有将return识别为返回值,而是将第一个返回型参数当作函数返回值,导致生成的接口声明与Delphi中的不一致。
如果在C#中按照生成的接口来调用,运行时C#程序会挂掉。
经抓包分析,发现Delphi程序在soap调用时,返回的数据中return是排在其它返回型参数前面的,与wsdl中描述的正好相反。

解决办法:
在网上查了很多资料,都没找到合适的办法,只好自己动手了。
打开Delphi自带的 WebServExp.pas 文件,XE2中叫 Soap.WebServExp.pas,查找 TWebServExp.AddMessages 方法,将里面的这段代码:

[Copy to clipboard]
CODE:
    { 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;

前后两段调一下,变成:

[Copy to clipboard]
CODE:
    { 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#直接调用就可以了。

Attachment: IdHTTPWebBrokerBridge.zip (2011-10-9 16:54, 13.45 K) / Download count 139
http://bbs.cnpack.org/attachment.php?aid=851
Author: walone    Time: 2014-6-11 11:37     Subject: 请问附件如何下载?


Author: lqmaster    Time: 2016-9-18 11:12     Subject: Delphi开发WebService后

用Delphi开发WebService后,老大发现没有,多个客户端调用时,会出现死锁现象。
我的QQ是65641392,希望得到老大的帮助,谢谢!




Welcome to CnPack Forum (http://bbs.cnpack.org/) Powered by Discuz! 5.0.0