gpt4 book ai didi

delphi - 我们可以使用TDSProviderConnection替换进程内DataSnap应用程序的TLocalConnection吗?

转载 作者:行者123 更新时间:2023-12-03 15:26:13 26 4
gpt4 key购买 nike

我可以通过进程内的DataSnap应用程序访问服务器方法。单击here以获取详细信息。

但是,进程内数据快照应用程序还有另一方面。它是IAppServer或TDataSetProvider。

在Delphi 2009之前,我将TConnectionBroker与TLocalConnection一起用于进程内数据快照访问。新的Delphi 2009/2010 DataSnap允许我们使用TDSProviderConnection来作为
远程服务器。但是,我只能使它适用于TCP / HTTP连接。我不能将TDSProviderConnection用于进程内数据快照应用程序。它将提示“无效的指针操作”。

这是我的代码的样子:

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

DSProviderConnection1.SQLConnection := Q;
DSProviderConnection1.ServerClassName := 'TEmployeeServer';
DSProviderConnection1.Connected := True;

ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;
finally
o.Free;
Q.Free;
end;
end;


TEmployeeServer是TDSServerModule类的后代,由TDataSetProvider,TSQLDataSet和TSQLConnection连接在一起组成。

跟踪源代码后,我发现TSQLDataSet确实打开并遍历了数据集。问题的原因应该与以下两种使用TDBXNoOpRow的方法有关

function TDSVoidConnectionHandler.CreateDbxRow: TDBXStreamerRow;
begin
Result := TDBXNoOpRow.Create(DBXContext);
end;

function TDSServerCommand.CreateParameterRow: TDBXRow;
begin
Result := TDBXNoOpRow.Create(FDbxContext);
end;


TDBXNoOpRow实例将由

procedure TDBXStreamValue.SetRowValue;
begin
if FExtendedType then
begin
if FStreamStreamReader <> nil then
FDbxRow.SetStream(Self, FStreamStreamReader)
else if FByteStreamReader <> nil then
FDbxRow.SetStream(Self, FByteStreamReader)
else
inherited SetRowValue;
end else
inherited SetRowValue;
end;


由于TDBXNoOpRow并非全部,因此数据包不会通过上述方法进行传输。我怀疑这是使用进程内机制引起问题的原因。

我不确定是否可以抛弃TLocalConnection并用TDSProviderConnection替换进行中的DataSnap应用程序?我已经跟踪DBX源代码几天了,甚至找不到关于此问题的线索。

最佳答案

经典DataSnap

在Delphi 2009之前,我们可以将TLocalConnection或TSocketConnection与TConnectionBroker一起使用,以通过IAppServer接口进行进程内或进程外通信。还有更多支持IAppServer的DataSnap连接。有关详细信息,请查看Delphi帮助。

Delphi 2009中的新DataSnap

以前,TSQLConnection仅在DataSnap服务器中使用。在新的DataSnap中,我们可以在DataSnap客户端中使用TSQLConnection。有一个新的驱动程序DataSnap,它允许我们使用REST数据包为多层应用程序通过TCP或HTTP协议连接到DataSnap服务器。此外,我们可以通过TSQLConnection.DriverName使用connect to TDSSever(TDSServer.Name)进行进程内连接。这使我们受益于编写可扩展的多层DataSnap应用程序以使用服务器方法。有关更多详细信息,请参见此处。

在Delphi 2009/2010中,引入了一个新的DataSnap连接组件– TDSProviderConnection。顾名思义,它从DataSnap服务器提供提供程序。此连接需要一个TSQLConnection实例才能在客户端层中使用。因此,我们可以在进程内或进程外在客户端层使用单个TSQLConnection。并满足可扩展的多层DataSnap应用程序的设计理念。

Web上有很多演示视频或CodeRage视频,显示如何在DataSnap客户端层中使用TDSProviderConnection。但是,大多数示例仅显示过程外设计。在撰写本主题时,我再也找不到一个示例说明TDSProviderConnection在进程内设计中的用法。希望有更多来自其他著名或知名的Delphi粉丝。

起初,我认为使用TDSProviderConnection进行进程内设计很容易。但是我在遵守规则的同时会遇到问题。这些问题应与错误以及DataSnap框架的成熟设计有关。我将在这里展示如何解决这些问题。

设计一个DataSnap模块

首先,我们为该示例设计一个简单的DataSnap模块。这是一个TDSServerModule子代实例,具有2个组件:TDataSetProvider和TClientDataSet实例。使用TDSServerModule的原因是它将管理模块中定义的提供程序。

MySeverProvider.DFM

object ServerProvider: TServerProvider
OldCreateOrder = False
OnCreate = DSServerModuleCreate
Height = 225
Width = 474
object DataSetProvider1: TDataSetProvider
DataSet = ClientDataSet1
Left = 88
Top = 56
end
object ClientDataSet1: TClientDataSet
Aggregates = <>
Params = <>
Left = 200
Top = 56
end
end


MyServerProvider.PAS

type
TServerProvider = class(TDSServerModule)
DataSetProvider1: TDataSetProvider;
ClientDataSet1: TClientDataSet;
procedure DSServerModuleCreate(Sender: TObject);
end;

{$R *.dfm}

procedure TServerProvider.DSServerModuleCreate(Sender: TObject);
begin
ClientDataSet1.LoadFromFile('..\orders.cds');
end;


定义提供者模块的传输层

由于这是一个进程内应用程序,因此我们实际上不需要提供程序模块的物理传输层。我们这里需要的是一个TDSServer和一个TDSServerClass实例,它们有助于在以后阶段将提供程序传播到ClientDataSet。

var C: TDSServer:
D: TDSServerClass;
begin
C := TDSServer.Create(nil);
D := TDSServerClass.Create(nil);
try
C.Server := D;
C.OnGetClass := OnGetClass;
D.Start;

finally
D.Free;
C.Free;
end;
end;

procedure TForm1.OnGetClass(DSServerClass: TDSServerClass; var
PersistentClass: TPersistentClass);
begin
PersistentClass := TServerProvider;
end;


使用TDSProviderConnection来使用进程内DataSnap服务

我们开始在DataSnap上下文中连接所有东西以完成它:

var Q: TSQLConnection;
D: TDSServer;
C: TDSServerClass;
P: TServerProvider;
N: TDSProviderConnection;
begin
P := TServerProvider.Create(nil);
D := TDSServer.Create(nil);
C := TDSServerClass.Create(nil);
Q := TSQLConnection.Create(nil);
N := TDSProviderConnection.Create(nil);
try
C.Server := D;
C.OnGetClass := OnGetClass;

D.Start;

Q.DriverName := 'DSServer';
Q.LoginPrompt := False;
Q.Open;

N.SQLConnection := Q;
N.ServerClassName := 'TServerProvider';
N.Connected := True;

ClientDataSet1.RemoteServer := N;
ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;

ShowMessage(IntToStr(ClientDataSet1.RecordCount));
finally
N.Free;
Q.Free;
C.Free;
D.Free;
P.Free;
end;
end;


如果您使用的是Delphi 14.0.3513.24210版本或更早的版本,则会发现它不起作用,此后会引发“无效指针操作”异常。

我发现到目前为止所面临的所有问题和解决方法如下。

疑难解答:无效的指针操作

DSUtil.StreamToDataPacket中有一个错误。我已经在 QC#78666中提交了报告。

这是不更改DBX源代码的修复程序:

unit DSUtil.QC78666;

interface

implementation

uses SysUtils, Variants, VarUtils, ActiveX, Classes, DBXCommonResStrs, DSUtil,
CodeRedirect;

type
THeader = class
const
Empty = 1;
Variant = 2;
DataPacket = 3;
end;

PIntArray = ^TIntArray;
TIntArray = array[0..0] of Integer;

TVarFlag = (vfByRef, vfVariant);
TVarFlags = set of TVarFlag;

EInterpreterError = class(Exception);

TVariantStreamer = class
private
class function ReadArray(VType: Integer; const Data: TStream): OleVariant;
public
class function ReadVariant(out Flags: TVarFlags; const Data: TStream): OleVariant;
end;

const
EasyArrayTypes = [varSmallInt, varInteger, varSingle, varDouble, varCurrency,
varDate, varBoolean, varShortInt, varByte, varWord, varLongWord];

VariantSize: array[0..varLongWord] of Word = (0, 0, SizeOf(SmallInt), SizeOf(Integer),
SizeOf(Single), SizeOf(Double), SizeOf(Currency), SizeOf(TDateTime), 0, 0,
SizeOf(Integer), SizeOf(WordBool), 0, 0, 0, 0, SizeOf(ShortInt), SizeOf(Byte),
SizeOf(Word), SizeOf(LongWord));

class function TVariantStreamer.ReadArray(VType: Integer; const Data: TStream): OleVariant;
var
Flags: TVarFlags;
LoDim, HiDim, Indices, Bounds: PIntArray;
DimCount, VSize, i: Integer;
V: OleVariant;
LSafeArray: PSafeArray;
P: Pointer;
begin
VarClear(Result);
Data.Read(DimCount, SizeOf(DimCount));
VSize := DimCount * SizeOf(Integer);
GetMem(LoDim, VSize);
try
GetMem(HiDim, VSize);
try
Data.Read(LoDim^, VSize);
Data.Read(HiDim^, VSize);
GetMem(Bounds, VSize * 2);
try
for i := 0 to DimCount - 1 do
begin
Bounds[i * 2] := LoDim[i];
Bounds[i * 2 + 1] := HiDim[i];
end;
Result := VarArrayCreate(Slice(Bounds^,DimCount * 2), VType and varTypeMask);
finally
FreeMem(Bounds);
end;
if VType and varTypeMask in EasyArrayTypes then
begin
Data.Read(VSize, SizeOf(VSize));
P := VarArrayLock(Result);
try
Data.Read(P^, VSize);
finally
VarArrayUnlock(Result);
end;
end else
begin
LSafeArray := PSafeArray(TVarData(Result).VArray);
GetMem(Indices, VSize);
try
FillChar(Indices^, VSize, 0);
for I := 0 to DimCount - 1 do
Indices[I] := LoDim[I];
while True do
begin
V := ReadVariant(Flags, Data);
if VType and varTypeMask = varVariant then
SafeArrayCheck(SafeArrayPutElement(LSafeArray, Indices^, V))
else
SafeArrayCheck(SafeArrayPutElement(LSafeArray, Indices^, TVarData(V).VPointer^));
Inc(Indices[DimCount - 1]);
if Indices[DimCount - 1] > HiDim[DimCount - 1] then
for i := DimCount - 1 downto 0 do
if Indices[i] > HiDim[i] then
begin
if i = 0 then Exit;
Inc(Indices[i - 1]);
Indices[i] := LoDim[i];
end;
end;
finally
FreeMem(Indices);
end;
end;
finally
FreeMem(HiDim);
end;
finally
FreeMem(LoDim);
end;
end;

class function TVariantStreamer.ReadVariant(out Flags: TVarFlags; const Data: TStream): OleVariant;
var
I, VType: Integer;
W: WideString;
TmpFlags: TVarFlags;
begin
VarClear(Result);
Flags := [];
Data.Read(VType, SizeOf(VType));
if VType and varByRef = varByRef then
Include(Flags, vfByRef);
if VType = varByRef then
begin
Include(Flags, vfVariant);
Result := ReadVariant(TmpFlags, Data);
Exit;
end;
if vfByRef in Flags then
VType := VType xor varByRef;
if (VType and varArray) = varArray then
Result := ReadArray(VType, Data) else
case VType and varTypeMask of
varEmpty: VarClear(Result);
varNull: Result := NULL;
varOleStr:
begin
Data.Read(I, SizeOf(Integer));
SetLength(W, I);
Data.Read(W[1], I * 2);
Result := W;
end;
varDispatch, varUnknown:
raise EInterpreterError.CreateResFmt(@SBadVariantType,[IntToHex(VType,4)]);
else
TVarData(Result).VType := VType;
Data.Read(TVarData(Result).VPointer, VariantSize[VType and varTypeMask]);
end;
end;

procedure StreamToDataPacket(const Stream: TStream; out VarBytes: OleVariant);
var
P: Pointer;
ByteCount: Integer;
Size: Int64;
begin
Stream.Read(Size, 8);
ByteCount := Integer(Size);
if ByteCount > 0 then
begin
VarBytes := VarArrayCreate([0, ByteCount-1], varByte);
P := VarArrayLock(VarBytes);
try
// Stream.Position := 0; // QC#78666 "Mismatched in datapacket" with DSUtil.StreamToDataPacket
Stream.Read(P^, ByteCount);
Stream.Position := 0;
finally
VarArrayUnlock(VarBytes);
end;
end
else
VarBytes := Null;
end;

procedure StreamToVariantPatch(const Stream: TStream; out VariantValue: OleVariant);
var
Flags: TVarFlags;
Header: Byte;
begin
if Assigned(Stream) then
begin
Stream.Position := 0;
Stream.Read(Header, 1);
if Header = THeader.Variant then
VariantValue := TVariantStreamer.ReadVariant(Flags, Stream)
else if Header = THeader.DataPacket then
StreamToDataPacket(Stream, VariantValue)
else
Assert(false);
end;
end;

var QC78666: TCodeRedirect;

initialization
QC78666 := TCodeRedirect.Create(@StreamToVariant, @StreamToVariantPatch);
finalization
QC78666.Free;
end.


疑难解答:应用DSUtil.StreamToDataPacket补丁后,我仍然遇到“无效的指针操作”

我已经在 QC#78752中提出了这个问题。进程中的DataSnap创建TDSServerCommand的实例。 TDSServerCommand创建TDBXNoOpRow实例的方法:

function TDSServerCommand.CreateParameterRow: TDBXRow;
begin
Result := TDBXNoOpRow.Create(FDbxContext);
end;


TDBXNoOpRow中的大多数方法未实现。 TDBXNoOpRow类中有2种方法,在子序列操作中使用GetStream和SetStream。这就是导致异常的原因。

修复TDBXNoOpRow问题后,数据包将成功传输到ClientDataSet。

解决方法如下:

unit DBXCommonServer.QC78752;

interface

uses SysUtils, Classes, DBXCommon, DSCommonServer, DBXCommonTable;

type
TDSServerCommand_Patch = class(TDSServerCommand)
protected
function CreateParameterRowPatch: TDBXRow;
end;

TDBXNoOpRowPatch = class(TDBXNoOpRow)
private
function GetBytesFromStreamReader(const R: TDBXStreamReader; out Buf: TBytes): Integer;
protected
procedure GetStream(DbxValue: TDBXStreamValue; var Stream: TStream; var IsNull:
LongBool); override;
procedure SetStream(DbxValue: TDBXStreamValue; StreamReader: TDBXStreamReader);
override;
function UseExtendedTypes: Boolean; override;
end;

TDBXStreamValueAccess = class(TDBXByteArrayValue)
private
FStreamStreamReader: TDBXLookAheadStreamReader;
end;

implementation

uses CodeRedirect;

function TDSServerCommand_Patch.CreateParameterRowPatch: TDBXRow;
begin
Result := TDBXNoOpRowPatch.Create(FDbxContext);
end;

procedure TDBXNoOpRowPatch.GetStream(DbxValue: TDBXStreamValue; var Stream: TStream;
var IsNull: LongBool);
var iSize: integer;
B: TBytes;
begin
iSize := GetBytesFromStreamReader(TDBXStreamValueAccess(DbxValue).FStreamStreamReader, B);
IsNull := iSize = 0;
if not IsNull then begin
Stream := TMemoryStream.Create;
Stream.Write(B[0], iSize);
end;
end;

procedure TDBXNoOpRowPatch.SetStream(DbxValue: TDBXStreamValue; StreamReader:
TDBXStreamReader);
var B: TBytes;
iSize: integer;
begin
iSize := GetBytesFromStreamReader(StreamReader, B);
Dbxvalue.SetDynamicBytes(0, B, 0, iSize);
end;

function TDBXNoOpRowPatch.GetBytesFromStreamReader(const R: TDBXStreamReader; out Buf: TBytes):
Integer;
const BufSize = 50 * 1024;
var iPos: integer;
iRead: integer;
begin
Result := 0;
while not R.Eos do begin
SetLength(Buf, Result + BufSize);
iPos := Result;
iRead := R.Read(Buf, iPos, BufSize);
Inc(Result, iRead);
end;
SetLength(Buf, Result);
end;

function TDBXNoOpRowPatch.UseExtendedTypes: Boolean;
begin
Result := True;
end;

var QC78752: TCodeRedirect;

initialization
QC78752 := TCodeRedirect.Create(@TDSServerCommand_Patch.CreateParameterRow, @TDSServerCommand_Patch.CreateParameterRowPatch);
finalization
QC78752.Free;
end.


疑难解答:两个补丁均已应用且适用于该示例,但我仍然遇到“无效的指针操作”

此问题也出现在 QC#78752中。该问题是由于以下两种方法引起的:


过程TDBXStreamValue.SetValue
功能
TDBXLookAheadStreamReader.ConvertToMemoryStream:
TStream;


TDBXLookAheadStreamReader.ConvertToMemoryStream将托管的FStream对象返回到TDBXStreamValue.SetValue。此流对象成为TDBXStreamValue的另一个托管对象。事实证明,一个Stream对象由两个对象管理,并且当这两个对象试图释放该Stream对象时引发异常:

procedure TDBXStreamValue.SetValue(const Value: TDBXValue);
begin
if Value.IsNull then
SetNull
else
begin
SetStream(Value.GetStream(False), True);
end;
end;
function TDBXLookAheadStreamReader.ConvertToMemoryStream: TStream;
...
begin
if FStream = nil then
Result := nil
else
begin
Count := Size;
if not (FStream is TMemoryStream) then
begin
...
StreamTemp := FStream;
FStream := Stream;
FreeAndNil(StreamTemp);
end;
FStream.Seek(0, soFromBeginning);
FHasLookAheadByte := false;
Result := FStream;
end;
end;


解决方法如下:

unit DBXCommon.QC78752;

interface

implementation

uses SysUtils, Classes, DBXCommon, CodeRedirect;

type
TDBXLookAheadStreamReaderAccess = class(TDBXStreamReader)
private
FStream: TStream;
FEOS: Boolean;
FHasLookAheadByte: Boolean;
FLookAheadByte: Byte;
end;

TDBXLookAheadStreamReaderHelper = class helper for TDBXLookAheadStreamReader
private
function Accessor: TDBXLookAheadStreamReaderAccess;
public
function ConvertToMemoryStreamPatch: TStream;
end;

function TDBXLookAheadStreamReaderHelper.Accessor:
TDBXLookAheadStreamReaderAccess;
begin
Result := TDBXLookAheadStreamReaderAccess(Self);
end;

function TDBXLookAheadStreamReaderHelper.ConvertToMemoryStreamPatch: TStream;
var
Stream: TMemoryStream;
StreamTemp: TStream;
Count: Integer;
Buffer: TBytes;
ReadBytes: Integer;
begin
if Accessor.FStream = nil then
Result := nil
else
begin
Count := Size;
if not (Accessor.FStream is TMemoryStream) then
begin
Stream := TMemoryStream.Create;
if Count >= 0 then
Stream.SetSize(Count);
if Accessor.FHasLookAheadByte then
Stream.Write(Accessor.FLookAheadByte, 1);
SetLength(Buffer, 256);
while true do
begin
ReadBytes := Accessor.FStream.Read(Buffer, Length(Buffer));
if ReadBytes > 0 then
Stream.Write(Buffer, ReadBytes)
else
Break;
end;
StreamTemp := Accessor.FStream;
Accessor.FStream := Stream;
FreeAndNil(StreamTemp);
Result := Accessor.FStream;
end else begin
Stream := TMemoryStream.Create;
Accessor.FStream.Seek(0, soFromBeginning);
Stream.CopyFrom(Accessor.FStream, Accessor.FStream.Size);
end;
Stream.Seek(0, soFromBeginning);
Accessor.FHasLookAheadByte := false;

Result := Stream;
// Stream := TMemoryStream.Create;
// Stream.LoadFromStream(FStream);
// FStream.Seek(0, soFromBeginning);
// Result := Stream;
end;
end;

var QC78752: TCodeRedirect;

initialization
QC78752 := TCodeRedirect.Create(@TDBXLookAheadStreamReader.ConvertToMemoryStream, @TDBXLookAheadStreamReader.ConvertToMemoryStreamPatch);
finalization
QC78752.Free;
end.


疑难解答:关闭应用程序后遇到内存泄漏

TDSServerConnection中的进程内连接内存泄漏。我已经在 QC#78696中提交了一份报告。

解决方法是:

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 - 我们可以使用TDSProviderConnection替换进程内DataSnap应用程序的TLocalConnection吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1566582/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com