[英]Delphi Assembler/RTTI-gurus: Can I obtain the memory address and type info of the implied Result variable in a function?
Consider this typical code for method tracing (simplified for illustration): 考虑这种典型的方法跟踪代码(简化说明):
type
IMethodTracer = interface
end;
TMethodTracer = class(TInterfacedObject, IMethodTracer)
private
FName: String;
FResultAddr: Pointer;
FResultType: PTypeInfo;
public
constructor Create(
const AName: String;
const AResultAddr: Pointer = nil;
const AResultType: PTypeInfo = nil);
destructor Destroy; override;
end;
constructor TMethodTracer.Create(
const AName: String;
const AResultAddr: Pointer;
const AResultType: PTypeInfo);
begin
inherited Create();
FName := AName;
FResultAddr := AResultAddr;
FResultType := AResultType;
Writeln('Entering ' + FName);
end;
destructor TMethodTracer.Destroy;
var
lSuffix: String;
lResVal: TValue;
begin
lSuffix := '';
if FResultAddr <> nil then
begin
//there's probably a more straight-forward to doing this, without involving TValue:
TValue.Make(FResultAddr, FResultType, lResVal);
lSuffix := ' - Result = ' + lResVal.AsString;
end;
Writeln('Leaving ' + FName + lSuffix);
inherited Destroy;
end;
function TraceMethod(
const AName: String;
const AResultAddr: Pointer;
const AResultType: PTypeInfo): IMethodTracer;
begin
Result := TMethodTracer.Create(AName, AResultAddr, AResultType);
end;
//////
function F1: String;
begin
TraceMethod('F1', @Result, TypeInfo(String));
Writeln('Doing some stuff...');
Result := 'Booyah!';
end;
F1();
This is working as intended. 这是按预期工作的。 The output is:
输出是:
Entering F1
进入F1
Doing some stuff...做一些事......
Leaving F1 - Result = Booyah!离开F1 - 结果= Booyah!
I am now looking for a way to minimize the number of required parameters for the call to TraceMethod()
, ideally allowing me to skip the Result
-related arguments altogether. 我现在正在寻找一种方法来最小化调用
TraceMethod()
所需的参数数量,理想情况下允许我完全跳过与Result
相关的参数。 I have no experience with assembler or stack layout myself but If I am not mistaken, judging by the "magic" I have seen other people do, at least the memory address of the implied magic Result
-variable should be obtainable somehow, shouldn't it? 我自己没有使用汇编程序或堆栈布局的经验但是如果我没有弄错,从我见过的其他人所做的“魔法”判断,至少隐含的魔法
Result
变量的内存地址应该可以以某种方式获得,不应该它? And possibly one can work from there to get at its type info, too? 也许有人可以从那里得到它的类型信息吗?
Of course, if it were possible to even determine the name of the "surrounding" function itself that would eliminate the need for passing arguments to TraceMethod
entirely... 当然,如果有可能甚至可以确定“周围”函数本身的名称,这将消除完全将参数传递给
TraceMethod
的需要......
I am using Delphi XE2, so all recently introduced language/framework features can be used. 我正在使用Delphi XE2,因此可以使用所有最近引入的语言/框架功能。
And before anyone mentions it: My actual code already uses CodeSite.EnterMethod
/ ExitMethod
instead of Writeln
-calls. 在任何人提到它之前:我的实际代码已经使用
CodeSite.EnterMethod
/ ExitMethod
而不是Writeln
。 I am also aware that this simplified example cannot handle complex types and performs no error handling whatsoever. 我也知道这个简化的例子不能处理复杂的类型,也不会执行任何错误处理。
Your best bet is truly to just pass in @Result
. 你最好的选择是真正传递
@Result
。 If you don't, then there's no guarantee that Result
even has an address. 如果不这样做,则无法保证
Result
甚至有地址。 Functions returning simple types like Integer
and Boolean
put the result in the EAX register. 返回简单类型(如
Integer
和Boolean
函数将结果放入EAX寄存器。 If there's no reason for the result to have an address, then the compiler won't allocate any memory for it. 如果没有理由让结果有地址,那么编译器就不会为它分配任何内存。 Using the expression
@Result
forces the compiler to give it an address. 使用
@Result
表达式强制编译器为其指定地址。
Merely knowing the address won't get you the return type, though. 但是,仅仅知道地址不会得到返回类型。 There might be a way to discover it with RTTI.
可能有一种方法可以通过RTTI发现它。 It would involve three steps:
这将涉及三个步骤:
Extract the class name from the method name. 从方法名称中提取类名称。 Then you can get the RTTI for that type .
然后,您可以获得该类型的RTTI 。 This would require the method name to include an unambiguous name for the class (including unit name).
这将要求方法名称包含类的明确名称(包括单元名称)。
Using the list of methods from that type, find the RTTI for the method. 使用该类型的方法列表 ,找到该方法的RTTI。 This will be complicated by the fact that the name doesn't necessarily uniquely identify a method.
由于名称不一定唯一地标识方法,因此这将变得复杂。 Overloads will all show the same name.
重载都将显示相同的名称。 (Rruz showed how to deal with RTTI of overloaded methods in the context of the
Invoke
method.) Also, the method name you get from the debug info won't necessarily match the RTTI name. (Rruz展示了如何在
Invoke
方法的上下文中处理重载方法的RTTI 。)此外,从调试信息中获得的方法名称不一定与RTTI名称匹配。
Instead of trying to match the name, you could instead loop over all the class's methods, searching for the one whose CodeAddress
property matches the address of the caller. 您可以改为遍历所有类的方法,而不是尝试匹配名称,搜索
CodeAddress
属性与调用方地址匹配的方法。 Determining how to get the address of the start of the caller (instead of the return address) is proving more difficult to find than I expected, though. 然而,确定如何获得调用者的起始地址(而不是返回地址)比我预期的更难找到。
Get the method's return type and use the Handle
property to finally get the PTypeInfo
value you want. 获取方法的返回类型并使用
Handle
属性最终获取所需的PTypeInfo
值。
Some of this feature is already included in our TSynLog
class , which works from Delphi 5 up to XE2. 其中一些功能已包含在我们的
TSynLog
类中 , 该类从Delphi 5到XE2都有效。
It is full Open Source, and working with Delphi XE2, so you have all the needed source code at hand. 它是完全开源的,并且使用Delphi XE2,因此您可以获得所需的所有源代码。 And it features exception interception and stack trace.
它具有异常拦截和堆栈跟踪功能。
It allows coding trace like this: 它允许编码跟踪如下:
procedure TestPeopleProc;
var People: TSQLRecordPeople;
Log: ISynLog;
begin
Log := TSQLLog.Enter;
People := TSQLRecordPeople.Create;
try
People.ID := 16;
People.FirstName := 'Louis';
People.LastName := 'Croivébaton';
People.YearOfBirth := 1754;
People.YearOfDeath := 1793;
Log.Log(sllInfo,People);
finally
People.Free;
end;
end;
It will be logged as such: 它将被记录为:
20120520 13172261 + 000E9F67 SynSelfTests.TestPeopleProc (784)
20120520 13172261 info {"TSQLRecordPeople(00AB92E0)":{"ID":16,"FirstName":"Louis","LastName":"Croivébaton","Data":"","YearOfBirth":1754,"YearOfDeath":1793}}
20120520 13172261 - 000EA005 SynSelfTests.TestPeopleProc (794) 00.002.229
That is: 那是:
interface
for the "auto-leave" function, just like you, but it won't make any memory allocation, since it uses a "fake reference count" trick; interface
,就像你一样,但它不会进行任何内存分配,因为它使用了“伪引用计数”技巧; Log
variable on the stack is optional: if you need only to trace the Enter/Leave of the method, just writing TSQLLog.Enter
is enough. Log
变量是可选的:如果只需要跟踪方法的Enter / Leave,只需编写TSQLLog.Enter
。 The Log
local variable is used to easily nest additional information within the method ( Log.Log(sllInfo,People)
). Log
局部变量用于在方法( Log.Log(sllInfo,People)
)中轻松嵌套其他信息。 Note that the debugging information (ie the method name and line numbers) are retrieved from a proprietary very optimized binary conversion of the .map
file generated at compile time. 请注意,调试信息(即方法名称和行号)是从编译时生成的
.map
文件的专有非常优化的二进制转换中检索的。 It will be much smaller than the .map
itself (eg 900 KB .map
-> 70 KB .mab which can be easily embedded within the exe), therefore smaller than the format used by JCL or MadExcept, and also smaller than the information embedded at compile time by Delphi. 这将是比小得多
.map
本身(例如900 KB .map
- >可以很容易地嵌入的exe内70 KB .mab),因此比由JCL或MadExcept使用的格式更小,并且也比嵌入的信息更小在Delphi编译时。
I do not think that having the "result" hard-coded within the "Enter" method is worth it. 我不认为在“Enter”方法中硬编码“结果”是值得的。 It will add a lot of coding (eg the conversion to
TValue
is time consuming), for a negligible benefit - most of the time, you'll need to know much more than the result content. 它会添加大量编码(例如转换为
TValue
非常耗时),但收益可以忽略不计 - 大部分时间,您需要了解的结果远远超过结果内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.