delphi - 如何将方法指针作为窗口消息参数发送?

标签 delphi function-pointers delphi-10.3-rio

我想调用一个通过窗口消息作为参数发送的方法。我尝试了下面的示例...但是在执行 DoWork 方法时出现访问冲突。我认为过程 CallBack 变量在消息处理程序中没有正确重建。在 Delphi documentation它说“方法指针”变量有 2 个指针:“这些类型代表方法指针。方法指针实际上是一对指针;第一个存储方法的地址,第二个存储对方法的引用方法所属的对象。“...但我不知道如何访问第二个指针,然后用这两个指针重建变量...

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TCallBackNotify = function(CallBack: Pointer = nil): Boolean of object;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure Test(var Msg: TMessage); message WM_USER;
    function DoWork(CallBack: Pointer = nil): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Test(var Msg: TMessage);
var CallBack: TCallBackNotify;
begin
 if Msg.Msg = WM_USER then begin
  @CallBack:= Pointer(Msg.WParam);
  CallBack;
 end;
end;

function TForm1.DoWork(CallBack: Pointer = nil): Boolean;
begin
 Caption:= 'It''s working !';
end;

procedure TForm1.Button1Click(Sender: TObject);
var CallBack: TCallBackNotify;
begin
 CallBack:= DoWork;
 PostMessage(Handle, WM_USER, WPARAM(@CallBack) , 0);
end;

end.

最佳答案

But I don't know how to access the second pointer, and then reconstruct the variable with those two pointers...

来自 internal data formats 的文档:

A method pointer is stored as a 32-bit pointer to the entry point of a method, followed by a 32-bit pointer to an object.

在 32 位应用程序中。对于 64 位应用程序,同样适用,但当然是 64 位指针。

这实际上是使用指针算术访问这些指针所需知道的全部内容,但使用预定义的 TMethod 会更好。记录:

如果M是任何方法指针,则TMethod(M).Code是代码(过程)指针,TMethod(M).Data 是数据(对象)指针。

如果您想发送或发布方法指针,您可以对这两个指针使用 LPARAM 和 WPARAM,也可以将单个指针发送到包含这两个指针的单个记录(或对象)。请记住,发布消息时,您不能传递局部变量的地址,因为该变量可能会在收件人收到之前超出范围。


当你写的时候

var
  CallBack: TCallBackNotify;
begin
  CallBack := DoWork;
  PostMessage(Handle, WM_USER, WPARAM(@CallBack) , 0);

您正在发送@CallBack。由于CallBack被声明为方法指针,因此@CallBack就是代码指针,即@CallBack = TMethod(CallBack).Code。因此,数据指针根本不发送。

作为比较,@@CallBack 是本地 CallBack 变量的地址。如果您将 CallBack 声明为 TMethod(普通记录),则该地址将仅表示为 @CallBack

为了说明这一点,

procedure TForm1.Button1Click(Sender: TObject);
var
  CallBack: TCallBackNotify;
  CallBackRec: TMethod absolute CallBack;
begin
  CallBack := DoWork;
  ShowMessage(
    'Data = ' + NativeInt(CallBackRec.Data).ToString + #13#10 +
    ' = Self = ' + NativeInt(Self).ToString + #13#10 +
    'Code = ' + NativeInt(CallBackRec.Code).ToString + #13#10 +
    ' = @CallBack = ' + NativeInt(@CallBack).ToString + #13#10 +
    '@@CallBack = ' + NativeInt(@@CallBack).ToString + #13#10 +
    ' = @CallBackRec = ' + NativeInt(@CallBackRec).ToString + #13#10
  );
end;

可能会产生

Data            = 17376288
 = Self         = 17376288
Code            = 6278088
 = @CallBack    = 6278088
@@CallBack      = 1635636
 = @CallBackRec = 1635636

因此,要发送一个方法,您可以这样做,例如,

procedure TForm1.Button1Click(Sender: TObject);
var
  CallBack: TCallBackNotify;
begin
  CallBack := DoWork;
  SendMessage(Handle, WM_USER, WParam(@@CallBack), 0);
end;

procedure TForm1.Test(var Msg: TMessage);
var
  CallBack: TCallBackNotify;
begin
  if Msg.Msg = WM_USER then
  begin
    CallBack := TCallBackNotify(PMethod(Msg.WParam)^);
    CallBack;
  end;
end;

由于SendMessage是同步的,因此只有接收者处理完消息后才会返回;特别是,SendMessageTForm1.Test 返回之前不会返回。因此,CallBack 局部变量在此处理期间将处于事件状态,因此在此期间使用指向该变量的指针是完全安全的。

另一方面,如果您使用立即返回的 PostMessage发布消息,那么如果幸运的话您将会崩溃,否则可能会导致内存损坏。事实上,然后 Button1Click 将到达其 endCallBack 局部变量将被丢弃,只有稍后 TForm1.Test run -- 被赋予一个悬空指针。

关于delphi - 如何将方法指针作为窗口消息参数发送?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66929870/

相关文章:

file - 如何将文本文件保存到特定文件夹

delphi - 创建运行时TTabItem,firemonkey

delphi - 如何在运行时调整控件的提示属性?

c++ - 为什么指向函数的指针不能绑定(bind)到左值引用,而函数可以?

c++ - 使用传递函数指针调用 _beginthreadx

date - 如何获取dll文件的编译日期?

delphi - 将数据从一个数据集结构移动到另一个数据集结构的更快方法(在 TDatasetProvider 中)

c++ - 当 B 类是 C++ 中 A 类的成员时,我访问 B 类中 A 类的公共(public)成员的正确方法是什么?

delphi - 禁用编辑器选项卡上的关闭图标?

linux - 在 CentOS_7 1908 上使用 FMXLinux 时出现错误