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

前端之家收集整理的这篇文章主要介绍了delphi – 为什么这个代码在本地声明TMemoryStream时失败,但在全局声明时有效?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
以下函数将所选文本用于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指令和访问冲突错误失败。

所以考虑到这一点,唯一的变化是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^,soBeginning);

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

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

解决方法

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

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

您将在网络上找到许多示例,其中人们将嵌套函数传递给回调。但所有这些例子都打破了documented的语言规则:

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values,nor can predefined procedures and functions.

您必须做的是停止使用嵌套函数进行回调。您需要将回调函数声明为具有全局作用域。通过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,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,1);
end;

猜你在找的Delphi相关文章