Как получить имя метода, на который указывает указатель метода?

Я определил следующее:

  • указатель метода, который возвращает 0, если проверка прошла успешно, или код ошибки

    TValidationFunc = 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;

Как я могу получить имя моей исходной функции в моем журнале, если эта функция вызывает исключение?


person o.schwab    schedule 21.02.2014    source источник


Ответы (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;

Используйте это так:

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;

Я не пробовал это с приведенным выше кодом, но пробовал с FormCreate моей MainForm.

Есть небольшое предостережение: это будет работать только для методов, которые сгенерировали 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;

Затем вам нужно только закодировать конкретное имя метода для каждого события, которое просто вызывает общую реализацию, и если вы пометите его как INLINE, есть большая вероятность, что оно даже не вызовет дополнительный вызов функции, а вместо этого вызовет его напрямую.

Кстати: на мой ответ сильно повлиял код, данный Cosmin Prund год назад в этом вопросе: RTTI информация для указателя метода

Если в вашем Delphi не определен NativeInt (не помню, когда именно они его реализовали), просто определите его как:

{$IFNDEF CPUX64 }
TYPE
  NativeInt = INTEGER;
{$ENDIF }
person HeartWare    schedule 22.02.2014
comment
Кто сказал никогда? Это первый абзац моего ответа, воплощенный в коде. - person David Heffernan; 23.02.2014
comment
Кто сказал никогда? Я сделал... :-) - person HeartWare; 23.02.2014
comment
Большое спасибо, HeartWare, это именно то, что я искал!!! Когда я вижу решение, я думаю, что в то же время оно не такое уж сложное и вероятно, мне никогда не удастся его найти. - person o.schwab; 24.02.2014
comment
@o.schwab: Не забудьте принять ответ, который, по вашему мнению, помог вам больше всего в качестве решения. - person HeartWare; 24.02.2014

Самым простым решением было бы, как намекает Дэвид, сохранить имя вместе с указателем на функцию, например так:

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, так что, когда у вас есть одно, вы можете получить другое.

person HeartWare    schedule 21.02.2014
comment
Спасибо за ответ и пример кода. Дело в том, что когда я набираю такие вещи, как Functions.Add(Routine1,'Routine1'); тихий голос в моей голове говорит: чувак, ты что-то упускаешь... - person o.schwab; 22.02.2014
comment
Но эй! :-) Посмотрите на мой другой ответ - возможно, это решение вашей проблемы? - person HeartWare; 22.02.2014

Один из способов сделать это — перечислить возможные функции, ища одну с адресом, совпадающим с целью. Вы можете декодировать указатель экземпляра из указателя метода. Оттуда вы можете получить тип. И тогда RTTI может сделать все остальное.

Вы можете рассмотреть возможность сохранения имени, а также указателя метода в вашем списке. Это был бы простой и легкий способ продолжить.

Моя последняя мысль заключается в том, что вы можете включить исполняемую карту в свой проект и посмотреть там функцию. Например, это будет довольно просто, если вы уже используете madExcept, EurekaLog или какой-то аналогичный инструмент.

person David Heffernan    schedule 21.02.2014
comment
чтобы упростить мой пример, я сказал, что сохранил функцию в своем списке, но на самом деле моя функция находится в классе, поэтому я мог сохранить имя функции в своем классе. - person o.schwab; 21.02.2014
comment
Очевидно, вы храните указатель метода в своем списке, а не в классе. Вот что говорит код. Но вы действительно можете декодировать указатель метода и получить класс. - person David Heffernan; 21.02.2014
comment
Я не могу увеличить оценку вашего ответа (вот что вы получаете, когда у вас плохая репутация ;)), но большое спасибо за ваш быстрый и хороший ответ, Дэвид - person o.schwab; 21.02.2014
comment
Пожалуйста. Я уверен, что вы скоро получите несколько голосов. И я уверен, что другие ответят с большим количеством идей. - person David Heffernan; 21.02.2014