我定义了以下内容:
方法指针,如果验证正常或错误代码
,则返回0TValidationFunc = Function(AParam: TAnObject): integer Of Object;
要执行的功能列表:
Functions: TObjectList < TValidationFunc>;
我在函数列表中放了几个带有此签名的函数。
要执行它们,我执行:
For valid In Functions Do
Begin
res := -1;
Try
res := valid(MyObject);
Except
On E: Exception Do
Log('Error in function ??? : ' + E.Message, TNiveauLog.Error, 'PHVL');
End;
Result := Result And (res = 0);
End;
如果此函数引发异常,我如何在日志中获取原始函数的名称?
答案 0 :(得分:4)
最简单的解决方案是 - 正如David暗示的那样 - 将名称与函数指针一起存储,如下所示:
TYPE
TValidationFunc = Function(AParam: TAnObject): integer Of Object;
TFunctions = TDictionary<TValidationFunc,String>;
VAR
Functions : TFunctions;
填充列表:
Functions:=TFunctions.Create;
Functions.Add(Routine1,'Routine1');
Functions.Add(Routine2,'Routine2');
然后当你经历它时:
For valid In Functions.Keys Do Begin
Try
res := valid(MyObject);
Except
On E: Exception Do Begin
Log('Error in function ' + Functions[valid] + ' : ' + E.Message, TNiveauLog.Error, 'PHVL');
res := -1;
End;
End;
Result := Result And (res = 0);
End;
这样,您可以将验证函数与TDictionary中的名称“链接”起来,这样当您拥有该名称时,您就可以获得另一个。
答案 1 :(得分:3)
好吧,永远不要说永远:-)。对于将其作为参数传递的事件,此函数将返回方法名称(格式为&lt; ClassType&gt;。&lt; MethodName&gt; ie.TMainForm.FormCreate)。遗憾的是,您不能使用无类型参数来允许传入任何类型的事件,但必须为您希望能够“解码”的每个方法签名编写特定例程:
FUNCTION MethodName(Event : TValidationFunc) : STRING;
VAR
M : TMethod ABSOLUTE Event;
O : TObject;
CTX : TRttiContext;
TYP : TRttiType;
RTM : TRttiMethod;
OK : BOOLEAN;
BEGIN
O:=M.Data;
TRY
OK:=O IS TObject;
Result:=O.ClassName
EXCEPT
OK:=FALSE
END;
IF OK THEN BEGIN
CTX:=TRttiContext.Create;
TRY
TYP:=CTX.GetType(O.ClassType);
FOR RTM IN TYP.GetMethods DO
IF RTM.CodeAddress=M.Code THEN
EXIT(O.ClassName+'.'+RTM.Name)
FINALLY
CTX.Free
END
END;
Result:=IntToHex(NativeInt(M.Code),SizeOf(NativeInt)*2)
END;
像这样使用:
For valid In Functions Doc Begin
res := -1;
Try
res := valid(MyObject);
Except
On E: Exception Do
Log('Error in function '+MethodName(valid)+' : ' + E.Message, TNiveauLog.Error, 'PHVL');
End;
Result := Result And (res = 0);
End;
我没有使用上面的代码尝试过,但是我已经使用我的MainForm的FormCreate进行了尝试。
有一点需要注意:这只适用于生成RTTI的方法,仅适用于Delphi 2010及更高版本(它们大大增加了RTTI可用的数据量)。因此,为了确保它有效,您应该将要跟踪的方法放在PUBLISHED部分中,因为这些方法始终(默认情况下)将生成RTTI。
如果你想要它更通用一点,你可以使用这个结构:
FUNCTION MethodName(CONST M : TMethod) : STRING; OVERLOAD;
VAR
O : TObject;
CTX : TRttiContext;
TYP : TRttiType;
RTM : TRttiMethod;
OK : BOOLEAN;
BEGIN
O:=M.Data;
TRY
OK:=O IS TObject;
Result:=O.ClassName
EXCEPT
OK:=FALSE
END;
IF OK THEN BEGIN
CTX:=TRttiContext.Create;
TRY
TYP:=CTX.GetType(O.ClassType);
FOR RTM IN TYP.GetMethods DO
IF RTM.CodeAddress=M.Code THEN
EXIT(O.ClassName+'.'+RTM.Name)
FINALLY
CTX.Free
END
END;
Result:=IntToHex(NativeInt(M.Code),SizeOf(NativeInt)*2)
END;
FUNCTION MethodName(Event : TValidationFunc) : STRING; OVERLOAD; INLINE;
BEGIN
Result:=MethodName(TMethod(Event))
END;
然后你只需要为每个只调用一般实现的事件编写一个特定的MethodName,如果你把它标记为INLINE,它很可能甚至不会产生额外的函数调用,而是调用直接。
顺便说一句:我的答复很大程度上受到Cosmin Prund一年前在这个问题中给出的代码的影响:RTTI information for method pointer
如果您的Delphi没有定义NativeInt(无法记住它们何时实现),只需将其定义为:
{$IFNDEF CPUX64 }
TYPE
NativeInt = INTEGER;
{$ENDIF }
答案 2 :(得分:1)
您可以这样做的一种方法是枚举可能的函数,寻找具有与目标匹配的地址的函数。您可以从方法指针解码实例指针。从那里你可以获得类型。然后RTTI可以完成剩下的工作。
您可以考虑在列表中存储名称和方法指针。这将是一个简单易行的方法。
我最后的想法是,您可以在项目中包含可执行地图,并在那里查看功能。例如,如果您已经使用了madExcept,EurekaLog或类似的工具,这将非常简单。