Reputation: 26660
I tried to make an event handler out of a normal procedure, like I always did for TNotifyEvent
, but this doesn't seem to work with TGetStrProc
. The handler receives an empty string parameter.
program ProcAsTGetStrProc;
uses
System.Classes, Winapi.Windows, System.SysUtils;
type
TMyObject = class
strict private
_onLog: TGetStrProc;
procedure _log(const msg: string);
public
procedure DoTheWork();
property OnLog: TGetStrProc read _onLog write _onLog;
end;
procedure mbox(msg: string);
begin
MessageBox(0, PWideChar(msg), 'Test', 0);
end;
procedure TMyObject.DoTheWork();
begin
_log('Doing the work');
end;
procedure TMyObject._log(const msg: string);
begin
mbox(Format('TMyObject._log: "%s"', [msg]));
if Assigned(_onLog) then _onLog(msg);
end;
procedure ProcLogging(const msg: string);
begin
mbox(Format('ProcLogging: "%s"', [msg]));
end;
function MakeMethod(Data, Code: Pointer): TMethod;
begin
Result.Data := Data;
Result.Code := Code;
end;
var
obj: TMyObject;
begin
obj := TMyObject.Create();
try
obj.OnLog := TGetStrProc(MakeMethod(nil, @ProcLogging));
obj.DoTheWork();
finally
obj.Free();
end;
end.
Expected output
TMyObject._log: "Doing the work"
ProcLogging: "Doing the work"
Actual output
TMyObject._log: "Doing the work"
ProcLogging: ""
What could be wrong here?
Upvotes: 2
Views: 192
Reputation: 596582
The signature of your standalone ProcLogging()
procedure is wrong.
TGetStrProc
is declared like this:
TGetStrProc = procedure(const S: string) of object;
An of object
reference expects a non-static class method to be assigned to it, which means there is an implicit Self
parameter involved. But there is no Self
parameter in your ProcLogging()
procedure.
Behind the scenes, of object
is implemented using the TMethod
record, where TMethod.Code
is a pointer to the start of the method's implementation code, and the TMethod.Data
is the value of the method's Self
parameter.
The statement
if Assigned(_onLog) then _onLog(msg);
Is roughly equivalent to:
if _onLog.Code <> nil then _onLog.Code(_onLog.Data, msg);
So, when you use TMethod
directly to call a standalone procedure, you have to declare the Self
parameter explicitly to receive the TMethod.Data
value and subsequent parameters correctly, eg:
procedure ProcLogging(Self: Pointer; const msg: string);
begin
mbox(Format('ProcLogging: "%s"', [msg]));
end;
Whatever you assign to the TMethod.Data
field will be passed as-is to the Self
parameter. Specifying nil
(as you are) is valid in this context, as long as the standalone procedure does not use it for anything.
Alternatively, you can use a non-instance class
method instead of a standalone procedure. A class
method also has an implicit Self
parameter (receiving a pointer to a class type rather than a pointer to an object instance), and thus is compatible with of object
. However, a class
method can be assigned as-is to an of object
reference, so you don't have to mess around with TMethod
directly, eg:
type
TLog = class
public
class procedure ProcLogging(const msg: string);
end;
class procedure TLog.ProcLogging(const msg: string);
begin
mbox(Format('ProcLogging: "%s"', [msg]));
end;
...
obj.OnLog := TLog.ProcLogging;
...
Upvotes: 3