Reputation: 101
I have defined the following :
a method pointer that returns 0 if a verification is OK or an error code
TValidationFunc = Function(AParam: TAnObject): integer Of Object;
a list of functions to perform:
Functions: TObjectList < TValidationFunc>;
I put several functions with this signature in my Functions list.
To execute them I perform:
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;
How can I get the name of my original function in my log, in case this function raises an exception?
Upvotes: 9
Views: 3901
Reputation: 8261
Well, never say never :-). This function will return the method name (in the form <ClassType>.<MethodName> ie. TMainForm.FormCreate) for an event that you pass it as a parameter. Unfortunately, you can't use an untyped parameter to allow any kind of event to be passed in, but must code a specific routine for each method signature you want to be able to "decode":
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;
Use it like this:
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;
I haven't tried it with the above code, but have tried it with my MainForm's FormCreate.
There is a slight caveat: This will only work for methods that have RTTI generated, and only from Delphi 2010 and up (where they heavily increased the the amount of data available to RTTI). So to make sure it works, you should put the methods you want to track in a PUBLISHED section, as these methods always (by default) will have RTTI generated.
If you want it to be a bit more general, you can use this construct:
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;
Then you only need to code a specific MethodName for each event that simply calls on to the general implementation, and if you mark it as INLINE there's a good chance that it won't even incur an extra function call, but instead call it directly.
BTW: My reply is heavily influenced by the code given by Cosmin Prund a year ago in this question: RTTI information for method pointer
In case your Delphi doesn't have NativeInt defined (can't remember when exactly they implemented it), just define it as:
{$IFNDEF CPUX64 }
TYPE
NativeInt = INTEGER;
{$ENDIF }
Upvotes: 3
Reputation: 8261
The easiest solution would be - as David hints at - to store the name along with the function pointer, like this:
TYPE
TValidationFunc = Function(AParam: TAnObject): integer Of Object;
TFunctions = TDictionary<TValidationFunc,String>;
VAR
Functions : TFunctions;
populate the list:
Functions:=TFunctions.Create;
Functions.Add(Routine1,'Routine1');
Functions.Add(Routine2,'Routine2');
and then when you run through it:
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;
This way, you "link" the validation function with a name in a TDictionary, so that when you have the one, you can obtain the other.
Upvotes: 4
Reputation: 613451
One way you could do this would be to enumerate possible functions looking for one with an address that matched the target. You can decode the instance pointer from the method pointer. From there you can obtain the type. And then RTTI can do the rest.
You might consider storing the name as well as the method pointer in your list. That would be a simple and easy way to proceed.
My final thought is that you could include the executable map in your project and look the function up there. For example this would be quite simple if you already use madExcept, EurekaLog or some similar tool.
Upvotes: 2