开发者

How To Get the Name of the Current Procedure/Function in Delphi (As a String)

开发者 https://www.devze.com 2022-12-29 20:35 出处:网络
Is it possible to obtain the name of the current procedure/function as a string, within a procedure/function? I suppose there would be some \"macro\" that is expanded at compile-time.

Is it possible to obtain the name of the current procedure/function as a string, within a procedure/function? I suppose there would be some "macro" that is expanded at compile-time.

My scenario is this: I have a lot of procedures that are given a record and they all need to start by checking the validity of the record, and so they pass the record to a "validator procedure". The validator procedure (the same one for all procedures) raises an exception if the record is invalid, and I want the message of the exception to include not the name of the validator procedure, but the name of the function/procedure that called the validator procedure (naturally).

That is, I have

procedure ValidateStruct(const Struct: TMyStruct; const Sender: string);
begin
 if <StructIsInvalid> then
    raise Exception.Create(Sender + ': Structure is inv开发者_JS百科alid.');
end;

and then

procedure SomeProc1(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, 'SomeProc1');
  ...
end;

...

procedure SomeProcN(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, 'SomeProcN');
  ...
end;

It would be somewhat less error-prone if I instead could write something like

procedure SomeProc1(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, {$PROCNAME});
  ...
end;

...

procedure SomeProcN(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, {$PROCNAME});
  ...
end;

and then each time the compiler encounters a {$PROCNAME}, it simply replaces the "macro" with the name of the current function/procedure as a string literal.

Update

The problem with the first approach is that it is error-prone. For instance, it happens easily that you get it wrong due to copy-paste:

  procedure SomeProc3(const Struct: TMyStruct);
  begin
    ValidateStruct(Struct, 'SomeProc1');
    ...
  end;

or typos:

procedure SomeProc3(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, 'SoemProc3');
  ...
end;

or just temporary confusion:

procedure SomeProc3(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, 'SameProc3');
  ...
end;


We are doing something similar and only rely on a convention: putting a const SMethodName holding the function name at the very beginning.
Then all our routines follow the same template, and we use this const in Assert and other Exception raising.
Because of the proximity of the const with the routine name, there is little chance a typo or any discrepancy would stay there for long.
YMMV of course...

procedure SomeProc1(const Struct: TMyStruct);
const
  SMethodName = 'SomeProc1';
begin
  ValidateStruct(Struct, SMethodName);
  ...
end;

...

procedure SomeProcN(const Struct: TMyStruct);
const
  SMethodName = 'SomeProcN';
begin
  ValidateStruct(Struct, SMethodName);
  ...
end;


I think this is a duplicate of this question: How to get current method's name in Delphi 7?

The answer there is that to do so, you need some form of debug info in your project, and to use, for example, the JCL functions to extract information from it.

I'll add that I haven't used the new RTTI support in D2009/2010, but it wouldn't surprise me if there was something clever you could do with it. For example, this shows you how to list all methods of a class, and each method is represented by a TRttiMethod. That descends from TRttiNamedObject which has a Name property which "specifies the name of the reflected entity". I'm sure there must be a way to get a reference to where you currently are, ie the method that you're currently in. This is all guesswork, but try giving that a go!


No compile time macro, but if you include enough debug information you can use the callstack to find it out. See this same question.


Another way to achieve the effect is to enter source metadata into a special comment like

ValidateStruct(Struct, 'Blah'); // LOCAL_FUNCTION_NAME

And then run a third-party tool over your source in a pre-compile build event to find lines with "LOCAL_FUNCTION_NAME" in such a comment, and replace all string literals with the method name in which such code appears, so that e.g. the code becomes

ValidateStruct(Struct, 'SomeProc3'); // LOCAL_FUNCTION_NAME

if the code line is inside the "SomeProc3" method. It would not be difficult at all to write such a tool in Python, for example, and this text substitution done in Delphi would be easy enough too.

Having the substitution done automatically means you never have to worry about synchronization. For example, you can use refactoring tools to change your method names, and then your string literals will be automatically updated on the next compiler pass.

Something like a custom source pre-processor.

I gave this question a +1, this is a situation I have had numerous times before, especially for messages for assertion failures. I know the stack trace contains the data, but having the routine name inside the assertion message makes things that little bit easier, and doing it manually creates the danger of stale messages, as the OP pointed out.

EDIT: The JcdDebug.pas methods as highlighted in other answers appear to be far simpler than my answer, provided that debug info is present.


I've solved similar problems through design. Your example confuses me because you seem to already be doing this.

You wrap your validation functions once like this:

procedure SomeValidateProc3(const Struct: TMyStruct);
  begin
    ValidateStruct(Struct, 'SomeProc3');
  end;

Then instead of repeatedly calling:

ValidateStruct(Struct, 'SomeProc3");

You call:

SomeValidateProc3(Struct);

If you have a typo, the compiler will catch it:

SoemValidateProc3(Struct);

If you use a more meaningful name for your wrapper functions like "ValidateName", the code becomes more readable also.


I think you are doing it the wrong way round: First, check whether there is an error and only then (that is: You need the name of the caller) use some tool like JclDebug to get the name of the caller by passing the return address from the stack to it.

Getting the procedure name is very expensive performance wise, so you only want to do it when absolutely necessary.

0

精彩评论

暂无评论...
验证码 换一张
取 消