2009-10-09 52 views
4

我定義服務器方法:如何飲用過程服務器法的DataSnap 2010

TServerMethod = class(TPersistent) 
public 
    function EchoString(Value: string): string; 
end; 

的方法EchoString返回一個等值的字符串。

然後,我使用TDSTCPServerTransport和TDSServer和TDSServerClass來包裝服務器方法。

在客戶端中,創建一個的DataSnap設爲TSQLConnection並生成TServerMethodProxy客戶端類:

function TServerMethodClient.EchoString(Value: string): string; 
begin 
    if FEchoStringCommand = nil then 
    begin 
    FEchoStringCommand := FDBXConnection.CreateCommand; 
    FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod; 
    FEchoStringCommand.Text := 'TServerMethod.EchoString'; 
    FEchoStringCommand.Prepare; 
    end; 
    FEchoStringCommand.Parameters[0].Value.SetWideString(Value); 
    FEchoStringCommand.ExecuteUpdate; 
    Result := FEchoStringCommand.Parameters[1].Value.GetWideString; 
end; 

我能夠消耗經由在客戶端應用程序的TCP連接的EchoString方法:

var o: TServerMethodClient; 
begin 
    o := TSeverMethodClient.Create(SQLConnection1.DBXConnection); 
    try 
    ShowMessage(o.EchoString('Hello')); 
    finally 
    o.Free; 
    end; 
end; 

上面場景使用TCP/IP作爲通信協議。

但是,我希望將我的ServerMethod與我的客戶一起部署爲「In Process」模型。我如何在不更改客戶端和服務器方法代碼的情況下實現這一目標

我應該將哪個參數傳遞給TServerMethodClient.Create構造函數以建立進程內連接?

o := TSeverMethodClient.Create(SQLConnection1.DBXConnection); 

在舊的DataSnap日,我們可以使用TLocalConnection享受In Process訪問權限,而無需更改客戶端和服務器代碼。

回答

5

DataSnap服務器方法在Delphi 2009中引入。大多數關於DataSnap服務器方法的視頻或演示僅介紹基於套接字的客戶端服務器訪問通信。例如:TCP或HTTP協議。

但是,DataSnap被設計爲一個可擴展的數據訪問解決方案,能夠與一個,兩個,三個或更多層模型一起工作。我們迄今爲止看到的所有例子都適用於2層或3層設計。我找不到任何關於1層或進程內設計的例子。

確實,使用進程內服務器方法非常簡單。大多數步驟與進程外服務器方法類似。

定義服務器方法

定義一個衆所周知EchoString()和總和()服務器方法:

unit MyServerMethod; 

interface 

uses Classes, DBXCommon; 

type 
    {$MethodInfo On} 
    TMyServerMethod = class(TPersistent) 
    public 
    function EchoString(Value: string): string; 
    function Sum(const a, b: integer): integer; 
    end; 
    {$MethodInfo Off} 

implementation 

function TMyServerMethod.EchoString(Value: string): string; 
begin 
    Result := Value; 
end; 

function TMyServerMethod.Sum(const a, b: integer): integer; 
begin 
    Result := a + b; 
end; 

end. 

定義DataModule的訪問服務器方法

像往常一樣將TDSServer和TDSServerClass拖放到數據模塊。爲TDSServerClass實例定義一個OnGetClass事件。請注意,您不需要刪除任何傳輸組件,如TDSTCPServerTransport或TDSHTTPServer,因爲我們只想使用服務器方法進行進程內處理。

object MyServerMethodDataModule1: TMyServerMethodDataModule 
    OldCreateOrder = False 
    Height = 293 
    Width = 419 
    object DSServer1: TDSServer 
    AutoStart = True 
    HideDSAdmin = False 
    Left = 64 
    Top = 40 
    end 
    object DSServerClass1: TDSServerClass 
    OnGetClass = DSServerClass1GetClass 
    Server = DSServer1 
    LifeCycle = 'Server' 
    Left = 64 
    Top = 112 
    end 
end 

unit MyServerMethodDataModule;

uses MyServerMethod; 

procedure TMyServerMethodDataModule.DSServerClass1GetClass(
    DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); 
begin 
    PersistentClass := TMyServerMethod; 
end; 

生成服務器方法客戶端類

這是不容易生成服務器方法客戶端類的設計過程中的服務器。您可以嘗試您熟悉的任何方法,將您的服務器方法連接到TCP或HTTP傳輸服務,啓動服務並嘗試以任何方式生成客戶端類。

// 
// Created by the DataSnap proxy generator. 
// 

unit DataSnapProxyClient; 

interface 

uses DBXCommon, DBXJSON, Classes, SysUtils, DB, SqlExpr, DBXDBReaders; 

type 
    TMyServerMethodClient = class 
    private 
    FDBXConnection: TDBXConnection; 
    FInstanceOwner: Boolean; 
    FEchoStringCommand: TDBXCommand; 
    public 
    constructor Create(ADBXConnection: TDBXConnection); overload; 
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload; 
    destructor Destroy; override; 
    function EchoString(Value: string): string; 
    function Sum(const a, b: integer): integer; 
    end; 

implementation 

function TMyServerMethodClient.EchoString(Value: string): string; 
begin 
    if FEchoStringCommand = nil then 
    begin 
    FEchoStringCommand := FDBXConnection.CreateCommand; 
    FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod; 
    FEchoStringCommand.Text := 'TMyServerMethod.EchoString'; 
    FEchoStringCommand.Prepare; 
    end; 
    FEchoStringCommand.Parameters[0].Value.SetWideString(Value); 
    FEchoStringCommand.ExecuteUpdate; 
    Result := FEchoStringCommand.Parameters[1].Value.GetWideString; 
end; 

function TMyServerMethodClient.Sum(a: Integer; b: Integer): Integer; 
begin 
    if FSumCommand = nil then 
    begin 
    FSumCommand := FDBXConnection.CreateCommand; 
    FSumCommand.CommandType := TDBXCommandTypes.DSServerMethod; 
    FSumCommand.Text := 'TMyServerMethod.Sum'; 
    FSumCommand.Prepare; 
    end; 
    FSumCommand.Parameters[0].Value.SetInt32(a); 
    FSumCommand.Parameters[1].Value.SetInt32(b); 
    FSumCommand.ExecuteUpdate; 
    Result := FSumCommand.Parameters[2].Value.GetInt32; 
end; 

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection); 
begin 
    inherited Create; 
    if ADBXConnection = nil then 
    raise EInvalidOperation.Create('Connection cannot be nil. Make sure the connection has been opened.'); 
    FDBXConnection := ADBXConnection; 
    FInstanceOwner := True; 
end; 

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); 
begin 
    inherited Create; 
    if ADBXConnection = nil then 
    raise EInvalidOperation.Create('Connection cannot be nil. Make sure the connection has been opened.'); 
    FDBXConnection := ADBXConnection; 
    FInstanceOwner := AInstanceOwner; 
end; 

destructor TMyServerMethodClient.Destroy; 
begin 
    FreeAndNil(FEchoStringCommand); 
    inherited; 
end; 

end. 

通過進程

你可以從下面的代碼中看到,有沒有不同的訪問服務器方法進程和外的工藝設計調用服務器方法。

首先,您創建一個datasnap服務器的即時。這會將DSServer註冊到TDBXDriverRegistry。例如DSServer1在這種情況下。

然後,您可以使用帶DSServer1的TSQLConnection作爲驅動程序名稱,而不是「DataSnap」,它需要套接字連接來啓動調用服務器方法的進程內通信。

var o: TMyServerMethodDataModule; 
    Q: TSQLConnection; 
    c: TMyServerMethodClient; 
begin 
    o := TMyServerMethodDataModule.Create(Self); 
    Q := TSQLConnection.Create(Self); 
    try 
    Q.DriverName := 'DSServer1'; 
    Q.LoginPrompt := False; 
    Q.Open; 

    c := TMyServerMethodClient.Create(Q.DBXConnection); 
    try 
     ShowMessage(c.EchoString('Hello')); 
    finally 
     c.Free; 
    end; 

    finally 
    o.Free; 
    Q.Free; 
    end; 
end; 

故障排除:出現內存泄漏消耗進程內服務器的方法

這發生在德爾福2010構建14.0.3513.24210後。它在未來的版本中可能已經修復。您可以檢查QC#78696以瞭解最新狀態。請注意,您需要在代碼中添加「ReportMemoryLeaksOnShutdown:= True;」以顯示泄漏報告。

內存泄漏與進程內服務器方法沒有關係。它應該是類TDSServerConnection中的一個問題,其中ServerConnectionHandler屬性在使用後不會釋放。

這裏是針對該問題的解決:

unit DSServer.QC78696; 

interface 

implementation 

uses SysUtils, 
    DBXCommon, DSServer, DSCommonServer, DBXMessageHandlerCommon, DBXSqlScanner, 
    DBXTransport, 
    CodeRedirect; 

type 
    TDSServerConnectionHandlerAccess = class(TDBXConnectionHandler) 
    FConProperties: TDBXProperties; 
    FConHandle: Integer; 
    FServer: TDSCustomServer; 
    FDatabaseConnectionHandler: TObject; 
    FHasServerConnection: Boolean; 
    FInstanceProvider: TDSHashtableInstanceProvider; 
    FCommandHandlers: TDBXCommandHandlerArray; 
    FLastCommandHandler: Integer; 
    FNextHandler: TDBXConnectionHandler; 
    FErrorMessage: TDBXErrorMessage; 
    FScanner: TDBXSqlScanner; 
    FDbxConnection: TDBXConnection; 
    FTransport: TDSServerTransport; 
    FChannel: TDbxChannel; 
    FCreateInstanceEventObject: TDSCreateInstanceEventObject; 
    FDestroyInstanceEventObject: TDSDestroyInstanceEventObject; 
    FPrepareEventObject: TDSPrepareEventObject; 
    FConnectEventObject: TDSConnectEventObject; 
    FErrorEventObject: TDSErrorEventObject; 
    FServerCon: TDSServerConnection; 
    end; 

    TDSServerConnectionPatch = class(TDSServerConnection) 
    public 
    destructor Destroy; override; 
    end; 

    TDSServerDriverPatch = class(TDSServerDriver) 
    protected 
    function CreateConnectionPatch(ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection; 
    end; 

destructor TDSServerConnectionPatch.Destroy; 
begin 
    inherited Destroy; 
    TDSServerConnectionHandlerAccess(ServerConnectionHandler).FServerCon := nil; 
    ServerConnectionHandler.Free; 
end; 

function TDSServerDriverPatch.CreateConnectionPatch(
    ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection; 
begin 
    Result := TDSServerConnectionPatch.Create(ConnectionBuilder); 
end; 

var QC78696: TCodeRedirect; 

initialization 
    QC78696 := TCodeRedirect.Create(@TDSServerDriverPatch.CreateConnection, @TDSServerDriverPatch.CreateConnectionPatch); 
finalization 
    QC78696.Free; 
end. 

疑難解答:遭遇「無效命令句柄」消耗在運行一個以上的服務器的方法用於過程應用

這發生在當Delphi 2010 build 14.0.3513.24210。它在未來的版本中可能已經修復。您可以檢查QC#78698以瞭解最新狀態。

要重放此問題,可能會消耗服務器方法爲:

c := TMyServerMethodClient.Create(Q.DBXConnection); 
try 
    ShowMessage(c.EchoString('Hello')); 
    ShowMessage(IntToStr(c.Sum(100, 200))); 
finally 
    c.Free; 
end; 

或該:

c := TMyServerMethodClient.Create(Q.DBXConnection); 
try 
    ShowMessage(c.EchoString('Hello')); 
    ShowMessage(IntToStr(c.Sum(100, 200))); 
    ShowMessage(c.EchoString('Hello')); 
finally 
    c.Free; 
end; 

下面是問題

unit DSServer.QC78698; 

interface 

implementation 

uses SysUtils, Classes, 
    DBXCommon, DBXMessageHandlerCommon, DSCommonServer, DSServer, 
    CodeRedirect; 

type 
    TDSServerCommandAccess = class(TDBXCommand) 
    private 
    FConHandler: TDSServerConnectionHandler; 
    FServerCon: TDSServerConnection; 
    FRowsAffected: Int64; 
    FServerParameterList: TDBXParameterList; 
    end; 

    TDSServerCommandPatch = class(TDSServerCommand) 
    private 
    FCommandHandle: integer; 
    function Accessor: TDSServerCommandAccess; 
    private 
    procedure ExecutePatch; 
    protected 
    procedure DerivedClose; override; 
    function DerivedExecuteQuery: TDBXReader; override; 
    procedure DerivedExecuteUpdate; override; 
    function DerivedGetNextReader: TDBXReader; override; 
    procedure DerivedPrepare; override; 
    end; 

    TDSServerConnectionPatch = class(TDSServerConnection) 
    public 
    function CreateCommand: TDBXCommand; override; 
    end; 

    TDSServerDriverPatch = class(TDSServerDriver) 
    private 
    function CreateServerCommandPatch(DbxContext: TDBXContext; Connection: 
     TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand; 
    public 
    constructor Create(DBXDriverDef: TDBXDriverDef); override; 
    end; 

constructor TDSServerDriverPatch.Create(DBXDriverDef: TDBXDriverDef); 
begin 
    FCommandFactories := TStringList.Create; 
    rpr; 
    InitDriverProperties(TDBXProperties.Create); 
    // '' makes this the default command factory. 
    // 
    AddCommandFactory('', CreateServerCommandPatch); 
end; 

function TDSServerDriverPatch.CreateServerCommandPatch(DbxContext: TDBXContext; 
    Connection: TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand; 
var 
    ServerConnection: TDSServerConnection; 
begin 
    ServerConnection := Connection as TDSServerConnection; 
    Result := TDSServerCommandPatch.Create(DbxContext, ServerConnection, TDSServerHelp.GetServerConnectionHandler(ServerConnection)); 
end; 

function TDSServerCommandPatch.Accessor: TDSServerCommandAccess; 
begin 
    Result := TDSServerCommandAccess(Self); 
end; 

procedure TDSServerCommandPatch.DerivedClose; 
var 
    Message: TDBXCommandCloseMessage; 
begin 
    Message := Accessor.FServerCon.CommandCloseMessage; 
    Message.CommandHandle := FCommandHandle; 
    Message.HandleMessage(Accessor.FConHandler); 
end; 

function TDSServerCommandPatch.DerivedExecuteQuery: TDBXReader; 
var 
    List: TDBXParameterList; 
    Parameter: TDBXParameter; 
    Reader: TDBXReader; 
begin 
    ExecutePatch; 
    List := Parameters; 
    if (List <> nil) and (List.Count > 0) then 
    begin 
    Parameter := List.Parameter[List.Count - 1]; 
    if Parameter.DataType = TDBXDataTypes.TableType then 
    begin 
     Reader := Parameter.Value.GetDBXReader; 
     Parameter.Value.SetNull; 
     Exit(Reader); 
    end; 
    end; 
    Result := nil; 
end; 

procedure TDSServerCommandPatch.DerivedExecuteUpdate; 
begin 
    ExecutePatch; 
end; 

function TDSServerCommandPatch.DerivedGetNextReader: TDBXReader; 
var 
    Message: TDBXNextResultMessage; 
begin 
    Message := Accessor.FServerCon.NextResultMessage; 
    Message.CommandHandle := FCommandHandle; 
    Message.HandleMessage(Accessor.FConHandler); 
    Result := Message.NextResult; 
end; 

procedure TDSServerCommandPatch.DerivedPrepare; 
begin 
    inherited; 
    FCommandHandle := Accessor.FServerCon.PrepareMessage.CommandHandle; 
end; 

procedure TDSServerCommandPatch.ExecutePatch; 
var 
    Count: Integer; 
    Ordinal: Integer; 
    Params: TDBXParameterList; 
    CommandParams: TDBXParameterList; 
    Message: TDBXExecuteMessage; 
begin 
    Message := Accessor.FServerCon.ExecuteMessage; 
    if not IsPrepared then 
    Prepare; 
    for ordinal := 0 to Parameters.Count - 1 do 
    Accessor.FServerParameterList.Parameter[Ordinal].Value.SetValue(Parameters.Parameter[Ordinal].Value); 
    Message.Command := Text; 
    Message.CommandType := CommandType; 
    Message.CommandHandle := FCommandHandle; 
    Message.Parameters := Parameters; 
    Message.HandleMessage(Accessor.FConHandler); 
    Params := Message.Parameters; 
    CommandParams := Parameters; 
    if Params <> nil then 
    begin 
    Count := Params.Count; 
    if Count > 0 then 
     for ordinal := 0 to Count - 1 do 
     begin 
     CommandParams.Parameter[Ordinal].Value.SetValue(Params.Parameter[Ordinal].Value); 
     Params.Parameter[Ordinal].Value.SetNull; 
     end; 
    end; 
    Accessor.FRowsAffected := Message.RowsAffected; 
end; 

function TDSServerConnectionPatch.CreateCommand: TDBXCommand; 
var 
    Command: TDSServerCommand; 
begin 
    Command := TDSServerCommandPatch.Create(FDbxContext, self, ServerConnectionHandler); 
    Result := Command; 
end; 

var QC78698: TCodeRedirect; 

initialization 
    QC78698 := TCodeRedirect.Create(@TDSServerConnection.CreateCommand, @TDSServerConnectionPatch.CreateCommand); 
finalization 
    QC78698.Free; 
end. 

參考修復:

  1. QC#78696:內存泄漏 TDSServerConnection用於過程 連接
  2. QC#78698:遭遇「無效命令 把手」用於 消耗在運行多於一個 服務器方法當在進程的應用程序
0
+0

謝謝。這是我今天發佈的博客。我只想在這裏更新。 – 2009-10-14 14:05:29

+0

哎呀! :-)工作很好。 – 2009-10-14 14:36:21

相關問題