如何解决DELPHI 6 Update2#的Bug问题?
如何解决DELPHI 6 Update2#的Bug问题?
DELPHI 6 Update 2# 的 Bug 及其解决方法 |
||||||||||||||||||||
Borland发布了对 Delphi 6 意义重大的第二个补丁,在SOAP/Web Service开发方面有了很大的增强,详见《DELPHI 6.02 抢先研究 -- BizSnap/SOAP/WebService 之四 -- 补丁2#的意义》。不过有一个方面我在那篇文章里没有说到,那就是用 SOAP 进行多层应用开发,当时我对这部分只是大致看了一下,没发现与补丁前相比有什么太多的不同,所以也没在意,直到不久前,一位叫 liaoqian 的网友给我发来 Mail 询问有关这方面的问题时,我才注意到 Delphi 6 Update 2# 在这部分有相当大的变化(当然不是表面上的),并且存在一个小 Bug 。 首先仿照《DELPHI 6 抢先研究 -- BizSnap/SOAP/WebService 之三 -- 用 SOAP 实现三层数据库应用》 中的例子做一个 WebService 三层数据应用。当出现上图所示的自动询问是否产生一个服务端接口的对话框时选择“No”,然后新建 SOAP Server Data Module ,其它操作与《之三》相同。完成之后在IE中输入:http://localhost:1024/Demo3.wadSoapDemo3/wsdl 将可以看到如下的接口列表,相比《之三》一文,可以看出增加了一个 IAppServerSOAP 接口,并且其 IWSDLPublish 接口的 Namespace URI 也有不同。
如果注意的话还可以发现一点问题,即:输入 http://localhost:1024/Demo3.wadSoapDemo3 不会看到 Delphi 6.02 写的一般 WebService 那样的一个页面,这有点不正常。 <?xml version="1.0" encoding='UTF-8'?> <soap-env:envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" > <soap-env:body> <soap-env:fault> <faultcode>SOAP-ENV:Server</faultcode> <faultstring> No invokable class registered that implements interface SOAPMidas.IAppServerSOAP of (soap action/path) "http://www.borland.com/namespaces/Types-IAppServerSOAP" </faultstring> </soap-env:fault> </soap-env:body> </soap-env:envelope>
注意其中 faultstring 标记中的信息,它说明了错误的原因是“没有可调用的已注册的 IAppServerSOAP 接口实现类”。如果不理睬这个问题,直接把 DataSetProvider1 写到 ClientDataSet1 的 ProviderName 属性中,再将 ClientDataSet1 的 Active 属性设置为 true 将直接出现一个异常,其内容便是上面的 faultstring 的内容。
附:《设置源码察看和调试》 通过跟踪调试发现异常发生在 SOAPHTTPPasInv 单元中的 Procedure THTTPSoapPascalInvoker.DispatchSOAP 中: ... if IntfInfo = nil then raise Exception.CreateFmt(SInvInterfaceNotReg, [SoapAction]) else if InvClassType = nil then RaiseNoIntfException(SoapAction) ... 因为其中 InvClassType 为 Nil ,所以导致了那个异常。这提供的信息和前面的出错信息其实是一样的:即 IAppServerSOAP 接口的实现类不存在或未注册。所以我又找到了 IAppServerSOAP 接口的定义,在 SOAPMidas 单元中: IAppServerSOAP = interface(IInvokable) ['{C99F4735-D6D2-495C-8CA2-E53E5A439E61}'] function SAS_ApplyUpdates(...): OleVariant; stdcall; function SAS_GetRecords(...): OleVariant; stdcall; function SAS_DataRequest(...): OleVariant; stdcall; function SAS_GetProviderNames: TWideStringDynArray; stdcall; function SAS_GetParams(...): OleVariant; stdcall; function SAS_RowRequest(...): OleVariant; stdcall; procedure SAS_Execute(...); stdcall; end;
熟悉 MIDAS 技术的人看了这段代码,立即可以发现它与 IAppServer 接口极其相似,只是在 IAppServer 中,那些方法都是以 AS_ 开头,而这里是以 SAS_ 开头。那么为什么既然已经有了一个 IAppServer 接口了, Borland 还要在 Update 2 加入这么一个 IAppServerSOAP 接口呢?在打补丁2之前,我也看过 SOAP 的部分代码,那时的 SOAP Server Data Module 是从 IAppServer 派生的,虽然 IAppServer 并不是从 IInvokable 派生的(按 Delphi 6 的要求,所有 SOAP 接口都要从 IInvokable 接口派生),但其实 IInvokable 是直接从 IInterface 派生的,与 IInterface 完全相同,而 IInterface 其实就是所有 COM 接口的祖先接口--IUnknown,而 IAppServer 是从 IDispatch 接口(是 COM 的一个主要接口,用于 Automation ,其祖先当然也是 IUnknown)派生出来的,所以直接用 IAppServer 也并无不可。 { IAppServerSOAP } function SAS_ApplyUpdates(...): OleVariant; virtual; stdcall; function SAS_GetRecords(...): OleVariant; virtual; stdcall; function SAS_DataRequest(...): OleVariant; virtual; stdcall; function SAS_GetProviderNames: TWideStringDynArray; virtual; stdcall; function SAS_GetParams(...): OleVariant; virtual; stdcall; function SAS_RowRequest(...): OleVariant; virtual; stdcall; procedure SAS_Execute(...); virtual; stdcall; 这里就是 IAppServerSOAP 接口的实现,由于所有的 SOAP Server Data Module 都是从 TSoapDataModule 派生的,所以 IAppServerSOAP 接口的实现类一定是已经实现的,但有没有注册呢?在我们的 SOAP Server Data Module 单元--SvrDMSoap 中有如下代码: procedure TSoapDemo3DMCreateInstance(out obj: TObject); begin obj := TSoapDemo3DM.Create(nil); end; initialization InvRegistry.RegisterInvokableClass(TSoapDemo3DM, TSoapDemo3DMCreateInstance);
显然它也已经注册了,但为什么会出错呢?看看 ISoapDemo3DM 接口的 WSDL 吧……怎么只有 AS_XXX ?没有 SAS_XXX ?原来如此,服务端只导出了 IAppServer 接口,并没有导出 IAppServerSOAP 接口。这就是问题所在! ISoapDemo3DM = interface(IAppServer) ['{4F618288-9F81-4090-81EF-4ACE0BF6D0BE}'] end; TSoapDemo3DM = class(TSoapDataModule, ISoapDemo3DM, IAppServer)
原来如此,原来 ISoapDemo3DM 居然还是从 IAppServer 接口派生的,显然这是 SOAP Server Data Module Wizard 的一个 Bug ,它还是按 IAppServer 接口的方法来生成 SOAP Server Data Module 的。知道问题的所在就好办了,将上面的代码中的两个 IAppServer 都改为 IAppServerSOAP ,然后重新编译。再来看看客户端程序,一切正常了, ISoapDemo3DM 的 WSDL 也正确地导出了 SAS_XXX ,特别是 hhttp://localhost:1024/Demo3.wadSoapDemo3 也可以看到标准的 Delphi 6.02 的 WebService 页面了。 [Mental Studio]猛禽 Apr.23-02 |