在Delphi XE6中使用数据管理单元传递TStream

在Delphi XE6中使用数据管理单元传递TStream,delphi,datasnap,Delphi,Datasnap,我需要使用DelphiXe6中的数据管理单元传递一些东西(TStream&TClientdataSet)。让我们从一个TStream开始-也许我在这里学到的是,我可以理解TClientDataSet 这是我的尝试,但它抛出了一个错误: 远程错误:访问模块DSServer.exe中地址0040801C处的违规 //not really doing anything with the RunReportObj yet, // just trying to test whether or not I

我需要使用DelphiXe6中的数据管理单元传递一些东西(
TStream
&
TClientdataSet
)。让我们从一个
TStream
开始-也许我在这里学到的是,我可以理解
TClientDataSet

这是我的尝试,但它抛出了一个错误:

远程错误:访问模块DSServer.exe中地址0040801C处的违规

//not really doing anything with the RunReportObj yet,
// just trying to test whether or not I can pass a TStream back first

function TServerMethods1.GetReport(RunReportObj: TRunReportObject): TStream;
var
  Stream: TMemoryStream;
  Writer: TBinaryWriter;
  Bytes: TBytes;
begin
  result := TMemoryStream.Create;
  try
    Writer := TBinaryWriter.Create(result);
    try
      Writer.Write(TEncoding.UTF8.GetBytes('Hello World' + sLineBreak));
    finally
      Writer.Free;
    end;
  finally
    Stream.Free;
  end;
end;
客户端演示(DSClient.exe)

ClientClassesUnit1.pas

function TServerMethods1Client.GetReport(RunReportObj: TRunReportObject): TStream;
begin
  if FGetReportCommand = nil then
  begin
    FGetReportCommand := FDBXConnection.CreateCommand;
    FGetReportCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FGetReportCommand.Text := 'TServerMethods1.GetReport';
    FGetReportCommand.Prepare;
  end;
  if not Assigned(RunReportObj) then
    FGetReportCommand.Parameters[0].Value.SetNull
  else
  begin
    FMarshal := TDBXClientCommand(FGetReportCommand.Parameters[0].ConnectionHandler).GetJSONMarshaler;
    try
      FGetReportCommand.Parameters[0].Value.SetJSONValue(FMarshal.Marshal(RunReportObj), True);
      if FInstanceOwner then
        RunReportObj.Free
    finally
      FreeAndNil(FMarshal)
    end
    end;
  FGetReportCommand.ExecuteUpdate;
  Result := FGetReportCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;
服务器演示(DSServer.exe)


我肯定我做了一些愚蠢的事情:)

你必须小心谁负责释放与DataSnap一起发送的对象
TServerMethods1.GetReport()
不应释放
结果,因为它必须先发送到客户端。另一方面,只要
FInstanceOwner
为true(默认情况下为true),客户端就不应释放从
TServerMethods1Client.GetReport()获取的
TStream

第一个条件更多地是偶然满足的,尽管正如David指出的,您正在释放未初始化的局部变量流

在目前无法实际测试的情况下,客户端的正确代码应该如下所示:

Procedure TForm8.Button1Click(Sender: TObject);
var
  RunReportObj: TRunReportObject;
  S: TStream;
  FS: TFileStream;
begin
  RunReportObj:= TRunReportObject.Create;
  RunReportObj.ID:= '10101';
  RunReportObj.ReportName:= 'Test';
  RunReportObj.ExportType:= 'PDF';
  S:= ClientModule1.ServerMethods1Client.GetReport(RunReportObj);
  S.Seek(0,soFromBeginning);
  FS:= TFileStream.Create(RunReportObj.ReportName + '.' + RunReportObj.ExportType, fmOpenWrite);;
  try
    FS.CopyFrom(S, S.Size);
  finally
    FS.Free;
  end;
end;
对于服务器端:

function TServerMethods1.GetReport(RunReportObj: TRunReportObject): TStream;
var
  Writer: TBinaryWriter;
  Bytes: TBytes;
begin
  result := TMemoryStream.Create;
  Writer := TBinaryWriter.Create(result);
  try
    Writer.Write(TEncoding.UTF8.GetBytes('Hello World' + sLineBreak));
  finally
    Writer.Free;
  end;
end;

问问自己,为什么连续两行分配给S。你泄露了第一条信息流。同样好,因为您实例化了TStream,它是一个抽象类。您真的要销毁第二个流吗?当前错误在TServerMethods1.GetReport的最后一行。对未初始化的变量调用free。您是否禁用了编译器警告。为什么?或者你没有阅读它们?一个有经验的程序员可以看到所有这些基本错误。但有些错误更难。没有经验的程序员不会发现它们。在所有情况下都需要调试。您现在需要做的是学习如何调试。您需要能够识别引发错误的代码行。在您能够有效地调试之前,您会遇到困难。我发现在客户端,
TServerMethods1Client.GetReport()
假设自己是
RunReportObj
的所有者,尽管创建它的是
Button1Click()
。如果在
GetReport()
中发生任何错误,
RunReportObj
将被泄漏,因为没有人会释放它
Button1Click()
应该在使用完毕后将其释放,并且
GetReport()
不应该调用
RunReportObj.free
。@RemyLebeau,说得好!唉,
TServerMethods1Client.GetReport
是自动创建的,下次任何编辑都可能消失。可能建议创建
TServerMethods1Client
,并将
InstanceOwner
设置为
false
。但是,我不确定这会带来什么其他后果。在服务器端,
GetReport()
应该在将
结果返回到位置0之前,将其搜索回位置0,客户端不应该负责搜索它。另外,可以使用
TStreamWriter
而不是
TBinaryWriter
,或者使用
TStringStream
而不是
TMemoryStream
来简化
GetReport()
。感谢大家的建设性批评、注释和回答。让我回顾一下你的建议和我的建议,我会做出相应的回应。
function TServerMethods1.GetReport(RunReportObj: TRunReportObject): TStream;
var
  Writer: TBinaryWriter;
  Bytes: TBytes;
begin
  result := TMemoryStream.Create;
  Writer := TBinaryWriter.Create(result);
  try
    Writer.Write(TEncoding.UTF8.GetBytes('Hello World' + sLineBreak));
  finally
    Writer.Free;
  end;
end;