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
喜欢博主的博文,请投出您宝贵的一票,支持一下博主评选博客之星。