4

サーバー メソッドを定義します。

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

メソッド EchoString は、同等の Value 文字列を返します。

次に、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 をクライアントと共に「処理中」モデルとして展開したいと考えています。クライアントとサーバーのメソッドコードを変更せずにそれを達成するにはどうすればよいですか?

インプロセス接続を確立するには、TServerMethodClient.Create コンストラクターにどのパラメーターを渡す必要がありますか?

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

昔の DataSnap では、TLocalConnection を使用して、クライアント コードとサーバー コードの両方を変更せずに In Process アクセスを楽しむことができました。

4

2 に答える 2

5

DataSnap サーバー メソッドは、Delphi 2009 で導入されました。利用可能な DataSnap サーバー メソッドに関するほとんどのビデオまたはデモは、ソケット ベースのクライアント サーバー アクセス通信のみを紹介します。例: TCP または HTTP プロトコル。

ただし、DataSnap は、1 層、2 層、3 層、またはそれ以上の層モデルで機能するスケーラブルなデータ アクセス ソリューションとして設計されました。これまでに見たすべての例は、2 層または 3 層の設計に適しています。1層または進行中の設計について話している例は見つかりません。

実際、インプロセス サーバー メソッドを使用するのは非常に簡単です。ほとんどの手順は、アウトプロセス サーバー メソッドに似ています。

サーバー メソッドの定義

よく知られている EchoString() および Sum() サーバー メソッドを定義します。

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 をデータ モジュールにドロップします。OnGetClass イベントを TDSServerClass インスタンスに定義します。インプロセスでのみサーバー メソッドを使用するだけなので、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 です。

次に、サーバー メソッドを呼び出すインプロセス通信を開始するためにソケット接続を必要とする「DataSnap」の代わりに、ドライバ名として DSServer1 で TSQLConnection を使用できます。

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;

トラブルシューティング: インプロセス サーバー メソッドを使用した後にメモリ リークが発生する

これは、Delphi 2010 ビルド 14.0.3513.24210 で発生します。今後のリリースで修正される可能性があります。最新のステータスについては、QC#78696 を確認してください。「ReportMemoryLeaksOnShutdown := True;」を追加する必要があることに注意してください。リークレポートを表示するコードで。

メモリ リークは、インプロセス サーバー メソッドとは関係ありません。プロパティ ServerConnectionHandler が消費後に解放されないのは、クラス TDSServerConnection の問題であるはずです。

問題の修正は次のとおりです。

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 ビルド 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: インプロセス アプリケーションの実行時に複数のサーバー メソッドを使用すると、「無効なコマンド ハンドル」が発生する
于 2009-10-19T14:24:27.377 に答える
0

DataSnap:インプロセスサーバーメソッドを参照してください。

于 2009-10-14T09:38:51.223 に答える