29

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 invalid.');
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;
Facundo Casco
  • 10,065
  • 8
  • 42
  • 63
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • This would be sweet, but as far as I know it doesn't exist - and I did search for something similar. The single generic solution that I know of would be to use the debug information that's embedded into the EXE to get the name of the procedure, but this would be a big performance hit at run-time. When I needed something similar I wrote an tiny program that would go over my PAS files and replace an certain expression with some text, but to me file name + line number was enough, I didn't go for the procedure name. – Cosmin Prund May 12 '10 at 10:56
  • 5
    It's my experience that typos and confusion aren't a big deal. As long as your logs show some identifying name, it doesn't matter what that name is. If you don't really have a function by that name, just grep and you'll find the sole occurrence of the misspelled name in the function you were looking for anyway. – Rob Kennedy May 12 '10 at 15:19
  • FWIW, there are tools that will do this for you. I'm familiar with CodeSite which will add such calls to log functions using its system. – mj2008 May 06 '12 at 16:24

6 Answers6

13

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;
Francesca
  • 21,452
  • 4
  • 49
  • 90
  • 6
    ...and the solution I eventually settled for. But it would be great if a future version of the Delphi compiler could support simple macros. As far as I can see, this would be trivial to implement. – Andreas Rejbrand May 13 '10 at 14:42
  • Maybe when we get the new CLANG/LLVM based Delphi compiler it will have this. – Warren P Mar 28 '13 at 11:16
  • regarding macros you may check out cn pack, a nice collection of extensions for the ide – monnoo Feb 10 '21 at 09:30
8

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!

Community
  • 1
  • 1
David
  • 13,360
  • 7
  • 66
  • 130
  • 1
    You wouldn't need any sort of debug info in the project. That's not how it's implemented in C or C++ compilers. All it needs is for the compiler to know the name of the subroutine it's compiling at the moment, and when it encounters a predefined symbol, such as __FUNC__, insert a reference to a constant string containing the subroutine name. This is purely compile time. – Rob K Jan 26 '15 at 21:08
  • @RobK That's fine for C compilers, which support `__FUNC__` and similar. The Delphi compiler doesn't, and we can't change the source of the compiler, so your comment doesn't really help. But perhaps it gives some background to why using debug info is required. – David Jan 27 '15 at 09:26
3

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

Community
  • 1
  • 1
Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
2

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.

Caleb Hattingh
  • 9,005
  • 2
  • 31
  • 44
0

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.

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
  • You have misunderstood the situation. I have a number of procedures all taking a TMyStruct as argument, and all of these use *the same* validator function, i.e. they all check the same things. – Andreas Rejbrand May 12 '10 at 23:24
  • My solution works for this too. Just wrap the function and stop worrying about typos. – Marcus Adams May 13 '10 at 00:58
0

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.

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
  • 2
    Well, that depends on how you do get the name. If I run the source code through a custom parser on a pre-compile event, that will not have any impact on performance at all. – Andreas Rejbrand May 12 '10 at 21:22
  • 1
    If you run any tool that preprocesses sources then you might need to be careful about file datestamps - depending on how you implement your backup and version control. I personally would find it a bit disconcerting to find the datestamps of my entire sources set to "today", but that's just me. Git, for example, doesn't use datestamps so it would be OK. – rossmcm Dec 18 '18 at 19:09