将普通过程绑定到TGetStrProc事件后参数为空的问题排查
TGetStrProc时参数为空的原因 首先得明确Delphi中普通过程指针和带of object的方法指针的本质区别——这就是问题的核心所在。
你尝试用MakeMethod(nil, @ProcLogging)把普通全局过程转成TGetStrProc类型,但TGetStrProc的定义是:
TGetStrProc = procedure(const S: string) of object;
带of object的方法指针在调用时,会自动把对象实例(也就是TMethod.Data的值)作为第一个隐藏参数压入调用栈,之后才会传递显式定义的const S: string参数。
而你的ProcLogging是普通全局过程,它的调用约定是直接接收显式参数const msg: string作为第一个(也是唯一一个)参数。当你通过方法指针调用它时,实际的调用栈逻辑是:
- 先压入
TMethod.Data的值(也就是你传入的nil) - 再压入真正要传递的字符串
'Doing the work'
但ProcLogging会把栈顶的第一个值(nil)当作自己的msg参数,而nil对于string类型来说等价于空字符串,这就是为什么你看到ProcLogging接收到的内容是空的。
有几种可行的解决方式,根据你的代码场景选择即可:
1. 写一个适配方法指针的包装过程
新增一个符合方法指针调用约定的适配器过程,在内部调用你的普通过程:
procedure LoggingAdapter(Self: TObject; const msg: string); begin ProcLogging(msg); // 忽略Self参数,直接传递真正的消息 end;
然后在赋值事件时,把MakeMethod的Code指向这个适配器:
obj.OnLog := TGetStrProc(MakeMethod(nil, @LoggingAdapter));
2. 修改TMyObject的事件类型(如果允许)
如果你的日志回调不需要绑定到对象实例,可以把OnLog的类型改成不带of object的普通过程类型:
type TGetStrProcNonObject = procedure(const S: string); TMyObject = class strict private _onLog: TGetStrProcNonObject; procedure _log(const msg: string); public procedure DoTheWork(); property OnLog: TGetStrProcNonObject read _onLog write _onLog; end;
这样直接赋值普通过程即可,完全不需要MakeMethod:
obj.OnLog := ProcLogging;
3. 把普通过程改成类的静态方法
如果你的日志逻辑适合封装成类的静态方法,也可以直接使用:
type TLogUtils = class public class procedure ProcLogging(const msg: string); static; end; class procedure TLogUtils.ProcLogging(const msg: string); begin mbox(Format('ProcLogging: "%s"', [msg])); end;
赋值时直接绑定静态方法即可,静态方法不需要实例,天然符合of object方法指针的调用约定:
obj.OnLog := TLogUtils.ProcLogging;
采用任意一种方案修改后,运行代码就能得到你预期的输出:
TMyObject._log: "Doing the work"
ProcLogging: "Doing the work"
内容的提问来源于stack exchange,提问作者Paul

