views:

505

answers:

7

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;
A: 

I don't know anything about macros in delphi and I have not heard about any sort of metaprogramming support in delphi. However, maybe you can solve it differently. What is the problem with your argument approach? Are you concerned your strings will collide? If yes, than you should try some enumerated types.

Gabriel Ščerbák
See my updated question.
Andreas Rejbrand
+5  A: 

I think this is a duplicate of this question: http://stackoverflow.com/questions/1301254

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!

David M
A: 

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

Lars Truijens
+1  A: 

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.

cjrh
I also thought about pre-processing. I will look into that. In general, however, I *do* have something against 3rd-party code...
Andreas Rejbrand
A: 

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
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
My solution works for this too. Just wrap the function and stop worrying about typos.
Marcus Adams
+2  A: 

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;
François
This is clearly an improvment at least.
Andreas Rejbrand
...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
A: 

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
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