如何获取方法指针指向的方法的名称?

o.s*_*wab 8 delphi delegates function-pointers rtti

我已经定义了以下内容:

我在函数列表中放了几个带有此签名的函数.

要执行它们,我执行:

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;
Run Code Online (Sandbox Code Playgroud)

如果此函数引发异常,我如何在日志中获取原始函数的名称?

Hea*_*are 3

好吧,永远不要说永远:-)。此函数将返回作为参数传递的事件的方法名称(采用 <ClassType>.<MethodName> 形式,即 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;
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

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;
Run Code Online (Sandbox Code Playgroud)

我没有用上面的代码尝试过,但已经用我的 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;
Run Code Online (Sandbox Code Playgroud)

然后,您只需要为每个简单调用通用实现的事件编写一个特定的 MethodName,如果您将其标记为 INLINE,则很有可能它甚至不会产生额外的函数调用,而是直接调用它。

顺便说一句:我的回答很大程度上受到 Cosmin Prund 一年前在这个问题中给出的代码的影响:RTTI information for method pointer

如果您的 Delphi 没有定义 NativeInt (不记得他们具体何时实现它),只需将其定义为:

{$IFNDEF CPUX64 }
TYPE
  NativeInt = INTEGER;
{$ENDIF }
Run Code Online (Sandbox Code Playgroud)