Delphi调用WCF异构编程

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要接收多个系统发过来的数据,数据种类将来会有所增加,要保证其可用性和扩展性

Delphi调用WCF异构编程

      以前曾经测试过Delphi7+WCF的分布式开发架构,但那时Delphi7对WCF支持的不是很好,所以也就没有采用这个架构方案。目前来看如果只是使用Webservice的话,从需求和时间两个维度都不能满足项目的需求。就又想到了WCF,目前我们使用的Delphi版本是DelphiXE3,通过技术预研,我们发现DelphiXE3对WCF有了较好的支持。


二、开发过程

       关于WCF,网上已经有很多介绍的文章,这里就不再展开,直接进入主题,通过一个简单的调用案例,开始我们的Delphi+WCF编程之旅。

       开发环境:VS2013+DelphiXE3
1、为了提供服务,先用VS2013建立一个WCF服务库

Delphi调用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宿主程序

 Delphi调用WCF异构编程

2.1引用上面的服务类库和必要类库System.ServiceModel

Delphi调用WCF异构编程

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启动服务,准备对外提供服务

Delphi调用WCF异构编程

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程序启动起来以后,测试一下效果

Delphi调用WCF异构编程

使用浏览器访问:http://localhost:8801/WeatherService

Delphi调用WCF异构编程

使用浏览器访问: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程序,引用前面提供的服务

Delphi调用WCF异构编程

Delphi调用WCF异构编程

将生成一个服务调用的代理文件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引用并调用服务

Delphi调用WCF异构编程

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


喜欢博主的博文,请投出您宝贵的一票,支持一下博主评选博客之星。

http://vote.blog.csdn.net/blogstaritem/blogstar2013/shuaihj

上一篇:沟通在项目管理中的意义(下)


下一篇:linux系统设置时间同步