为什么在本地声明TMemoryStream时此代码失败但在全局声明时有效?

时间:2015-03-17 12:21:05

标签: delphi delphi-xe7

以下函数获取Richedit控件中的选定文本,写入回调函数内的TMemoryStream,然后以纯文本字符串形式返回原始rtf代码。

var
  MS: TMemoryStream; // declared globally and works.

implementation

function GetSelectedRTFCode(RichEdit: TRichedit): string;

  function RichEditCallBack(dwCookie: Longint; pbBuff: PByte;
    CB: Longint; var pCB: Pointer): Longint; stdcall;
  begin
    MS.WriteBuffer(pbBuff^, CB);
    Result := CB;
  end;

var
  EditStream: TEditStream;
  SL: TStringList;
begin
  MS := TMemoryStream.Create;
  try
    EditStream.dwCookie     := SF_RTF or SFF_SELECTION;
    EditStream.dwError      := 0;
    EditStream.pfnCallback  := @RichEditCallBack;
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, DWord(@EditStream));
    MS.Seek(0, soBeginning);

    SL := TStringList.Create;
    try
      SL.LoadFromStream(MS);
      Result := SL.Text;
    finally
      SL.Free;
    end;
  finally
    MS.Free;
  end;
end;

以上工作正常,没有任何错误。

但是,我尝试尽可能避免使用全局声明的变量,并将它们保留在需要它的过程或函数的本地,但由于某种原因,在MS: TMemoryStream;函数内声明GetSelectedRTFCode失败,并且使用了Priviliged Instruction和访问违规错误。

因此,考虑到这一点,下面唯一的变化是MS: TMemoryStream;在本地声明失败:

function GetSelectedRTFCode(RichEdit: TRichedit): string;
var
  MS: TMemoryStream; // declare here instead of globally but fails.

  function RichEditCallBack(dwCookie: Longint; pbBuff: PByte;
    CB: Longint; var pCB: Pointer): Longint; stdcall;
  begin
    MS.WriteBuffer(pbBuff^, CB);
    Result := CB;
  end;

var
  EditStream: TEditStream;
  SL: TStringList;
begin
  MS := TMemoryStream.Create;
  try
    EditStream.dwCookie     := SF_RTF or SFF_SELECTION;
    EditStream.dwError      := 0;
    EditStream.pfnCallback  := @RichEditCallBack;
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, DWord(@EditStream));
    MS.Seek(0, soBeginning);

    SL := TStringList.Create;
    try
      SL.LoadFromStream(MS);
      Result := SL.Text;
    finally
      SL.Free;
    end;
  finally
    MS.Free;
  end;
end;

为什么声明内存流变量全局工作,但在本地声明时失败?

1 个答案:

答案 0 :(得分:20)

问题是您使用嵌套函数作为回调是错误的。通过这种方式使用嵌套函数实现的机会适用于32位编译器,只要嵌套函数不引用周围函数的任何局部变量即可。

但是,只要嵌套函数引用任何此类局部变量,就必须传递一个额外的隐藏参数,以便嵌套函数可以访问周围的函数堆栈帧。对于64位编译器,总是传递一个隐藏的额外参数。

你会在网上找到许多人们展示传递嵌套函数作为回调的例子。但所有这些例子都违反了该语言的documented规则:

  

嵌套过程和函数(在其他例程中声明的例程)不能用作过程值,也不能用作预定义的过程和函数。

您必须做的是停止使用嵌套函数进行回调。您需要将回调函数声明为具有全局范围。通过dwCookie结构的EDITSTREAM成员传递内存流。

// This compiles now, but the callback implementation is wrong, see below

function RichEditCallBack(dwCookie: DWORD_PTR; pbBuff: PByte;
  CB: Longint; var pCB: Longint): Longint; stdcall;
var
  MS: TMemoryStream;
begin
  MS := TMemoryStream(dwCookie);
  MS.WriteBuffer(pbBuff^, CB);
  Result := CB;
end;

function GetSelectedRTFCode(RichEdit: TRichedit): string;
var
  MS: TMemoryStream;
  EditStream: TEditStream;
  SL: TStringList;
begin
  MS := TMemoryStream.Create;
  try
    EditStream.dwCookie     := DWORD_PTR(MS);
    EditStream.dwError      := 0;
    EditStream.pfnCallback  := RichEditCallBack;
    Richedit.Perform(EM_StreamOut, SF_RTF or SFF_SELECTION, LPARAM(@EditStream));
    MS.Seek(0, soBeginning);

    SL := TStringList.Create;
    try
      SL.LoadFromStream(MS);
      Result := SL.Text;
    finally
      SL.Free;
    end;
  finally
    MS.Free;
  end;
end;

特别注意我没有使用@运算符来获取回调函数的地址。在函数上使用@运算符会导致类型检查的抑制。如果你没有使用@运算符,那么编译器就能告诉你你的错误。

编译器会说:

[dcc32 Error] E2094 Local procedure/function 'RichEditCallBack' assigned to 
procedure variable

另请注意,您的代码错误地声明了最终参数的类型。它是Longint类型的参考参数。同样,除非您使用@来获取函数地址,否则编译器可以报告此情况并报告此情况。

第二个错误导致回调的实现。这是不正确的。返回值表示成功。值零用于表示成功,任何其他值表示失败。必须通过最终参数返回写入的字节数。你的回调应该是这样的:

function RichEditCallBack(dwCookie: DWORD_PTR; pbBuff: PByte;
  CB: Longint; var CBWritten: Longint): Longint; stdcall;
var
  MS: TMemoryStream;
begin
  MS := TMemoryStream(dwCookie);
  CBWritten := MS.Write(pbBuff^, CB);
  Result := IfThen(CB = CBWritten, 0, 1);
end;