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

Delphi 为什么这段代码在本地声明TMemoryStream时失败,但在全局声明时有效?,delphi,delphi-xe7,Delphi,Delphi Xe7,以下函数在Richedit控件中获取所选文本,在回调函数中写入TMemoryStream,然后将原始rtf代码作为纯文本字符串返回 var MS: TMemoryStream; // declared globally and works. implementation function GetSelectedRTFCode(RichEdit: TRichedit): string; function RichEditCallBack(dwCookie: Longint; pbBu

以下函数在
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:TMemoryStreamGetSelectedRTFCode
函数中的code>失败,出现特权指令和访问冲突错误

考虑到这一点,下面唯一的变化是
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;

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

问题在于将嵌套函数用作回调是错误的。如果以这种方式使用嵌套函数实现,则只要嵌套函数不引用周围函数的任何局部变量,32位编译器就可以工作

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

您将在web上找到许多示例,其中人们演示如何将嵌套函数作为回调传递。但所有这些例子都违反了语言规则:

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

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

// 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
这只是一个系统,然后需要数学。我没提是因为我怀疑你知道在哪里可以找到它。也可以使用if语句完成。您也可以。顺便说一句,EMBT似乎正在接近回调的正确原型(还不正确,但更接近)。自Delphi XE3以来,它发生了变化。也许XE8最终会是正确的。@TLama啊,错误翻译的Win32原型的乐趣。在这种情况下,它并不重要,因为它是0,或者不是0。这是一个很好的答案,它详细、信息丰富且易于理解。我从你的回答中学到了很多,非常感谢。
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;