Delphi调用WCF异构编程
老帅
一、项目背景
几年前,就开始使用Delphi进行分布式开发,最早用的方案是Delphi7+Webservice,在简单的应用场景下,也能够满足需求了。
喜欢博主的博文,请投出您宝贵的一票,支持一下博主评选博客之星。
http://vote.blog.csdn.net/blogstaritem/blogstar2013/shuaihj
目前有一个项目,主要的需求点如下:
1. 有N个系统
2. 其中有一个系统A为基础数据平台,要为其他系统提供数据服务
3. 这N个系统中,有用Java开发的Web,有用C#开发的Web,有用Delphi开发的桌面APP,还有用Android开发的手机APP,都要使用系统A提供的基础数据
4. 系统A虽然要部署在internet上,但是为私有服务,要考虑其安全性。
5. 系统A要接收多个系统发过来的数据,数据种类将来会有所增加,要保证其可用性和扩展性
以前曾经测试过Delphi7+WCF的分布式开发架构,但那时Delphi7对WCF支持的不是很好,所以也就没有采用这个架构方案。目前来看如果只是使用Webservice的话,从需求和时间两个维度都不能满足项目的需求。就又想到了WCF,目前我们使用的Delphi版本是DelphiXE3,通过技术预研,我们发现DelphiXE3对WCF有了较好的支持。
二、开发过程
关于WCF,网上已经有很多介绍的文章,这里就不再展开,直接进入主题,通过一个简单的调用案例,开始我们的Delphi+WCF编程之旅。
开发环境:VS2013+DelphiXE3
1、为了提供服务,先用VS2013建立一个WCF服务库
1.1确认服务协定IWeatherService
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WeatherWcfServiceLibrary { [ServiceContract] public interface IWeatherService { // 测试返回简单数据类型 [OperationContract] string GetWeatherDescription(int day); // 测试返回复杂数据类型(一定要注意,如果返回的是List,List中不能嵌套List,否则客户端无法识别) [OperationContract] WeatherData GetWeather(int day); // 测试参数回传(一定要注意,回传参数不用用out,否则客户端无法识别) [OperationContract] void FindWeather(ref WeatherData data); } [DataContract] public class WeatherData { string description = " "; [DataMember] public string Description { get { return description; } set { description = value; } } } }
1.2实现WeatherService服务类
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WeatherWcfServiceLibrary { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的类名“Service1”。 public class WeatherService : IWeatherService { public string GetWeatherDescription(int day) { return "第" + day.ToString() + "天的天气尚无预测!"; } public WeatherData GetWeather(int day) { WeatherData weatherData = new WeatherData(); weatherData.Description = "预告:第" + day.ToString() + "天的天气万里无云!"; return weatherData; } public void FindWeather(ref WeatherData data) { data.Description = "先生:正像你告诉我的,今天的天气真不错!"; } } }
2、为了使用上面的服务类库,需要使用VS2013建立一个WCF宿主程序
2.1引用上面的服务类库和必要类库System.ServiceModel
2.2在App.config文件中配置服务
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <!--定制服务行为,服务将使用这种行为--> <behaviors> <serviceBehaviors> <behavior name="Laoshuai.WeatherBehavior"> <!--允许外部获取元数据--> <serviceMetadata httpGetEnabled="true"/> <!--不包含详细错误信息--> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> <!--定制服务--> <services> <!--定制服务名称、行为(使用上面定制的行为)--> <service name="WeatherWcfServiceLibrary.WeatherService" behaviorConfiguration="Laoshuai.WeatherBehavior"> <!--定制服务基地址--> <host> <baseAddresses> <add baseAddress="http://localhost:8801/WeatherService"/> </baseAddresses> </host> <!--定制服务地址(为空则使用上面的基地址)、绑定类型、服务协定--> <endpoint address="" binding="basicHttpBinding" contract="WeatherWcfServiceLibrary.IWeatherService"/> <!--定制原数据,对外提供服务查找和引用--> <endpoint address="mex" binding="basicHttpBinding" contract="IMetadataExchange"/> </service> </services> </system.serviceModel> </configuration>
2.3启动服务,准备对外提供服务
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.ServiceModel; namespace WeatherWcfServiceApplication { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { // 创建一个宿主对象,使服务能运行在这个宿主中 ServiceHost host = new ServiceHost(typeof(WeatherWcfServiceLibrary.WeatherService)); // 打开服务 if (host.State != CommunicationState.Opening) { host.Open(); this.Text = "服务状态:" + host.State; } } } }
2.4程序启动起来以后,测试一下效果
使用浏览器访问:http://localhost:8801/WeatherService
使用浏览器访问:http://localhost:8801/WeatherService?wsdl
将看到以前直接调用WebService时,一样的XML页面
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" name="WeatherService" targetNamespace="http://tempuri.org/"> <wsdl:types> <xsd:schema targetNamespace="http://tempuri.org/Imports"> <xsd:import schemaLocation="http://localhost:8801/WeatherService?xsd=xsd0" namespace="http://tempuri.org/"/> <xsd:import schemaLocation="http://localhost:8801/WeatherService?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/> <xsd:import schemaLocation="http://localhost:8801/WeatherService?xsd=xsd2" namespace="http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary"/> </xsd:schema> </wsdl:types> <wsdl:message name="IWeatherService_GetWeatherDescription_InputMessage"> <wsdl:part name="parameters" element="tns:GetWeatherDescription"/> </wsdl:message> <wsdl:message name="IWeatherService_GetWeatherDescription_OutputMessage"> <wsdl:part name="parameters" element="tns:GetWeatherDescriptionResponse"/> </wsdl:message> <wsdl:message name="IWeatherService_GetWeather_InputMessage"> <wsdl:part name="parameters" element="tns:GetWeather"/> </wsdl:message> <wsdl:message name="IWeatherService_GetWeather_OutputMessage"> <wsdl:part name="parameters" element="tns:GetWeatherResponse"/> </wsdl:message> <wsdl:message name="IWeatherService_FindWeather_InputMessage"> <wsdl:part name="parameters" element="tns:FindWeather"/> </wsdl:message> <wsdl:message name="IWeatherService_FindWeather_OutputMessage"> <wsdl:part name="parameters" element="tns:FindWeatherResponse"/> </wsdl:message> <wsdl:portType name="IWeatherService"> <wsdl:operation name="GetWeatherDescription"> <wsdl:input wsaw:Action="http://tempuri.org/IWeatherService/GetWeatherDescription" message="tns:IWeatherService_GetWeatherDescription_InputMessage"/> <wsdl:output wsaw:Action="http://tempuri.org/IWeatherService/GetWeatherDescriptionResponse" message="tns:IWeatherService_GetWeatherDescription_OutputMessage"/> </wsdl:operation> <wsdl:operation name="GetWeather"> <wsdl:input wsaw:Action="http://tempuri.org/IWeatherService/GetWeather" message="tns:IWeatherService_GetWeather_InputMessage"/> <wsdl:output wsaw:Action="http://tempuri.org/IWeatherService/GetWeatherResponse" message="tns:IWeatherService_GetWeather_OutputMessage"/> </wsdl:operation> <wsdl:operation name="FindWeather"> <wsdl:input wsaw:Action="http://tempuri.org/IWeatherService/FindWeather" message="tns:IWeatherService_FindWeather_InputMessage"/> <wsdl:output wsaw:Action="http://tempuri.org/IWeatherService/FindWeatherResponse" message="tns:IWeatherService_FindWeather_OutputMessage"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="BasicHttpBinding_IWeatherService" type="tns:IWeatherService"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="GetWeatherDescription"> <soap:operation soapAction="http://tempuri.org/IWeatherService/GetWeatherDescription" style="document"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> <wsdl:operation name="GetWeather"> <soap:operation soapAction="http://tempuri.org/IWeatherService/GetWeather" style="document"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> <wsdl:operation name="FindWeather"> <soap:operation soapAction="http://tempuri.org/IWeatherService/FindWeather" style="document"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="WeatherService"> <wsdl:port name="BasicHttpBinding_IWeatherService" binding="tns:BasicHttpBinding_IWeatherService"> <soap:address location="http://localhost:8801/WeatherService"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
3.使用DelphiXE3调用这个WCF服务
3.1建立一个WinForm程序,引用前面提供的服务
将生成一个服务调用的代理文件WeatherService.pas,源码如下
// ************************************************************************ // // The types declared in this file were generated from data read from the // WSDL File described below: // WSDL : http://localhost:8801/WeatherService?wsdl // >Import : http://localhost:8801/WeatherService?wsdl>0 // >Import : http://localhost:8801/WeatherService?xsd=xsd0 // >Import : http://localhost:8801/WeatherService?xsd=xsd2 // >Import : http://localhost:8801/WeatherService?xsd=xsd1 // Encoding : utf-8 // Version : 1.0 // (2014-01-08 11:40:10 - - $Rev: 52705 $) // ************************************************************************ // unit WeatherService; interface uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns; const IS_OPTN = $0001; IS_NLBL = $0004; IS_REF = $0080; type // ************************************************************************ // // The following types, referred to in the WSDL document are not being represented // in this file. They are either aliases[@] of other types represented or were referred // to but never[!] declared in the document. The types from the latter category // typically map to predefined/known XML or Embarcadero types; however, they could also // indicate incorrect WSDL documents that failed to declare or import a schema type. // ************************************************************************ // // !:string - "http://www.w3.org/2001/XMLSchema"[Gbl] // !:int - "http://www.w3.org/2001/XMLSchema"[Gbl] WeatherData2 = class; { "http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary"[GblCplx] } WeatherData = class; { "http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary"[GblElm] } // ************************************************************************ // // XML : WeatherData, global, <complexType> // Namespace : http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary // ************************************************************************ // WeatherData2 = class(TRemotable) private FDescription: string; FDescription_Specified: boolean; procedure SetDescription(Index: Integer; const Astring: string); function Description_Specified(Index: Integer): boolean; published property Description: string Index (IS_OPTN or IS_NLBL) read FDescription write SetDescription stored Description_Specified; end; // ************************************************************************ // // XML : WeatherData, global, <element> // Namespace : http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary // ************************************************************************ // WeatherData = class(WeatherData2) private published end; // ************************************************************************ // // Namespace : http://tempuri.org/ // soapAction: http://tempuri.org/IWeatherService/%operationName% // transport : http://schemas.xmlsoap.org/soap/http // style : document // use : literal // binding : BasicHttpBinding_IWeatherService // service : WeatherService // port : BasicHttpBinding_IWeatherService // URL : http://localhost:8801/WeatherService // ************************************************************************ // IWeatherService = interface(IInvokable) ['{791CBBA3-0C97-0B5E-6A23-77A0CBF082AF}'] function GetWeatherDescription(const day: Integer): string; stdcall; function GetWeather(const day: Integer): WeatherData2; stdcall; procedure FindWeather(const data: WeatherData2); stdcall; end; function GetIWeatherService(UseWSDL: Boolean=System.False; Addr: string=''; HTTPRIO: THTTPRIO = nil): IWeatherService; implementation uses SysUtils; function GetIWeatherService(UseWSDL: Boolean; Addr: string; HTTPRIO: THTTPRIO): IWeatherService; const defWSDL = 'http://localhost:8801/WeatherService?wsdl'; defURL = 'http://localhost:8801/WeatherService'; defSvc = 'WeatherService'; defPrt = 'BasicHttpBinding_IWeatherService'; var RIO: THTTPRIO; begin Result := nil; if (Addr = '') then begin if UseWSDL then Addr := defWSDL else Addr := defURL; end; if HTTPRIO = nil then RIO := THTTPRIO.Create(nil) else RIO := HTTPRIO; try Result := (RIO as IWeatherService); if UseWSDL then begin RIO.WSDLLocation := Addr; RIO.Service := defSvc; RIO.Port := defPrt; end else RIO.URL := Addr; finally if (Result = nil) and (HTTPRIO = nil) then RIO.Free; end; end; procedure WeatherData2.SetDescription(Index: Integer; const Astring: string); begin FDescription := Astring; FDescription_Specified := True; end; function WeatherData2.Description_Specified(Index: Integer): boolean; begin Result := FDescription_Specified; end; initialization { IWeatherService } InvRegistry.RegisterInterface(TypeInfo(IWeatherService), 'http://tempuri.org/', 'utf-8'); InvRegistry.RegisterDefaultSOAPAction(TypeInfo(IWeatherService), 'http://tempuri.org/IWeatherService/%operationName%'); InvRegistry.RegisterInvokeOptions(TypeInfo(IWeatherService), ioDocument); { IWeatherService.GetWeatherDescription } InvRegistry.RegisterMethodInfo(TypeInfo(IWeatherService), 'GetWeatherDescription', '', '[ReturnName="GetWeatherDescriptionResult"]', IS_OPTN or IS_NLBL); InvRegistry.RegisterParamInfo(TypeInfo(IWeatherService), 'GetWeatherDescription', 'GetWeatherDescriptionResult', '', '', IS_NLBL); { IWeatherService.GetWeather } InvRegistry.RegisterMethodInfo(TypeInfo(IWeatherService), 'GetWeather', '', '[ReturnName="GetWeatherResult"]', IS_OPTN or IS_NLBL); InvRegistry.RegisterParamInfo(TypeInfo(IWeatherService), 'GetWeather', 'GetWeatherResult', '', '[Namespace="http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary"]', IS_NLBL); { IWeatherService.FindWeather } InvRegistry.RegisterParamInfo(TypeInfo(IWeatherService), 'FindWeather', 'data', '', '[Namespace="http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary"]', IS_NLBL); RemClassRegistry.RegisterXSClass(WeatherData2, 'http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary', 'WeatherData2', 'WeatherData'); RemClassRegistry.RegisterXSClass(WeatherData, 'http://schemas.datacontract.org/2004/07/WeatherWcfServiceLibrary', 'WeatherData'); end.
3.2引用并调用服务
unit FormWeatherClient; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, WeatherService; type TFrmWeatherClient = class(TForm) btn1: TButton; edt1: TEdit; btn2: TButton; edt2: TEdit; btn3: TButton; edt3: TEdit; procedure btn1Click(Sender: TObject); procedure btn2Click(Sender: TObject); procedure btn3Click(Sender: TObject); private { Private declarations } FWeatherService: IWeatherService; function GetWeatherService: IWeatherService; public { Public declarations } end; var FrmWeatherClient: TFrmWeatherClient; implementation {$R *.dfm} procedure TFrmWeatherClient.btn1Click(Sender: TObject); begin edt1.Text := GetWeatherService.GetWeatherDescription(2); end; procedure TFrmWeatherClient.btn2Click(Sender: TObject); var data: WeatherData2; begin data := GetWeatherService.GetWeather(2); edt2.Text := data.Description; data.Free; end; procedure TFrmWeatherClient.btn3Click(Sender: TObject); var data: WeatherData2; begin data := WeatherData2.Create; GetWeatherService.FindWeather(data); edt3.Text := data.Description; end; function TFrmWeatherClient.GetWeatherService: IWeatherService; begin if not Assigned(FWeatherService) then FWeatherService := WeatherService.GetIWeatherService(); Result := FWeatherService; end; end.
至此,一个DelphiXE3+WCF异构编程的流程就完成了。
源码下载:http://download.csdn.net/detail/shuaihj/6823535
喜欢博主的博文,请投出您宝贵的一票,支持一下博主评选博客之星。