【发布时间】:2013-02-05 10:16:55
【问题描述】:
是否可以获得关于TMethod 的RTTI 信息?
我可以通过
Instance := TObject(Method.Data);
所以我可以获得实例的 RTTI 类型,但我怎样才能获得正确的 TRttiMethod?我想检查使用方法指针传入的方法的属性。
【问题讨论】:
标签: delphi delphi-xe2 rtti
是否可以获得关于TMethod 的RTTI 信息?
我可以通过
Instance := TObject(Method.Data);
所以我可以获得实例的 RTTI 类型,但我怎样才能获得正确的 TRttiMethod?我想检查使用方法指针传入的方法的属性。
【问题讨论】:
标签: delphi delphi-xe2 rtti
这种方法在理论上可行,并且在实践中会有很好的改变,但有几件事可能会阻止您获得TRttiMethod。
TMethod 记录显示 Data: Pointer,而不是 TObject。这意味着可能有除TObject 之外的其他东西作为Data!这是一个严重的问题,因为如果 Data 不是 TObject,那么尝试从中提取 RTTI 将导致运行时错误。{$RTTI} 停止为公共或已发布成员生成 RTTI。这两个问题对于我们在 Delphi 中的通常类型的事件实现来说不是问题(在 Object Inspector 中双击事件的名称并填写代码),但我不认为你在谈论“vanila”实现。没有多少人会用属性来装饰默认事件处理程序!
演示以上所有内容的代码:
program Project15;
{$APPTYPE CONSOLE}
uses
SysUtils, RTTI;
type
// Closure/Event type
TEventType = procedure of object;
// An object that has a method compatible with the declaration above
TImplementation = class
private
procedure PrivateImplementation;
public
procedure HasRtti;
procedure GetPrivateImpEvent(out Ev:TEventType);
end;
TRecord = record
procedure RecordProc;
end;
// an object that has a compatible method but provides no RTTI
{$RTTI EXPLICIT METHODS([])}
TNoRttiImplementation = class
public
procedure NoRttiAvailable;
end;
procedure TImplementation.GetPrivateImpEvent(out Ev:TEventType);
begin
Ev := PrivateImplementation;
end;
procedure TImplementation.HasRtti;
begin
WriteLn('HasRtti');
end;
procedure TNoRttiImplementation.NoRttiAvailable;
begin
WriteLn('No RTTI Available');
end;
procedure TRecord.RecordProc;
begin
WriteLn('This is written from TRecord.RecordProc');
end;
procedure TImplementation.PrivateImplementation;
begin
WriteLn('PrivateImplementation');
end;
procedure TotalyFakeImplementation(Instance:Pointer);
begin
WriteLn('Totaly fake implementation, TMethod.Data is nil');
end;
procedure SomethingAboutMethod(X: TEventType);
var Ctx: TRttiContext;
Typ: TRttiType;
Method: TRttiMethod;
Found: Boolean;
begin
WriteLn('Invoke the method to prove it works:');
X;
// Try extract information about the event
Ctx := TRttiContext.Create;
try
Typ := Ctx.GetType(TObject(TMethod(X).Data).ClassType);
Found := False;
for Method in Typ.GetMethods do
if Method.CodeAddress = TMethod(X).Code then
begin
// Got the Method!
WriteLn('Found method: ' + Typ.Name + '.' + Method.Name);
Found := True;
end;
if not Found then
WriteLn('Method not found.');
finally Ctx.Free;
end;
end;
var Ev: TEventType;
R: TRecord;
begin
try
try
WriteLn('First test, using a method that has RTTI available:');
SomethingAboutMethod(TImplementation.Create.HasRtti);
WriteLn;
WriteLn('Second test, using a method that has NO rtti available:');
SomethingAboutMethod(TNoRttiImplementation.Create.NoRttiAvailable);
WriteLn;
WriteLn('Third test, private method, default settings:');
TImplementation.Create.GetPrivateImpEvent(Ev);
SomethingAboutMethod(Ev);
WriteLn;
WriteLn('Assign event handler using handler from a record');
try
SomethingAboutMethod(R.RecordProc);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
WriteLn('Assign event handler using static procedure');
try
TMethod(Ev).Data := nil;
TMethod(Ev).Code := @TotalyFakeImplementation;
SomethingAboutMethod(Ev);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
finally ReadLn;
end;
end.
【讨论】:
CodeAddress 似乎正是我想要的...我会尝试并稍后提供反馈。
procedure of object 转换为TMethod 来存储它,所以TMethod 后面总是有一个对象实例。第二个问题确实可能是个问题。
procedure of object 只是语法,您可以将记录的方法分配给事件处理程序就好了。没有黑客,没有编译器警告。我将再次更新代码以包含两种会产生运行时错误的情况。
TMethod.Data进行假设,两者都会产生运行时错误@
@ 运算符,那么你做错了。)TMethod.Data 成员将是TObject 或TClass 引用(或者显然是一个记录指针)。在设计时,它可以保存 IDE 为事件处理程序选择的任何簿记信息。