如何在Delphi中获取当前过程/函数的名称(作为字符串)

29

可以在过程/函数内部获取当前过程/函数的名称作为字符串吗?我想可能有一些在编译时展开的“宏”。

我的情况是这样的:我有很多个过程,都需要给定一个记录并检查其有效性,因此它们将记录传递给“验证程序”。 验证程序(对于所有过程来说是相同的)如果记录无效会引发异常,并且我希望异常消息中包含调用验证程序的函数/过程的名称(自然而然的)。

也就是说,我有:

procedure ValidateStruct(const Struct: TMyStruct; const Sender: string);
begin
 if <StructIsInvalid> then
    raise Exception.Create(Sender + ': Structure is invalid.');
end;

然后

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

...

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

如果我可以写出类似于以下的代码,那么它将会相对不那么容易出错:

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

...

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

每次编译器遇到 {$PROCNAME} 标记时,它会将这个“宏”替换为当前函数/过程的名称作为字符串文字。

更新

第一个方法的问题在于容易出错。例如,由于复制粘贴而犯错很容易:

  procedure SomeProc3(const Struct: TMyStruct);
  begin
    ValidateStruct(Struct, 'SomeProc1');
    ...
  end;
或者打字错误:
procedure SomeProc3(const Struct: TMyStruct);
begin
  ValidateStruct(Struct, 'SoemProc3');
  ...
end;

或者只是暂时的困惑:

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

这很理想,但据我所知它不存在 - 我已经搜索了类似的东西。我所知道的单一通用解决方案是使用嵌入到EXE中的调试信息来获取过程名称,但这会在运行时产生大量性能损失。当我需要类似的东西时,我编写了一个小程序,可以遍历我的PAS文件,并将某个表达式替换为一些文本,但对我来说,文件名+行号就足够了,我没有去找过程名。 - Cosmin Prund
5
在我看来,错别字和混淆并不是什么大问题。只要你的日志显示了某个标识名称,那这个名称究竟是什么并不重要。如果你实际上没有这个名称的功能,只需要使用grep命令查找,你就能在你正在寻找的函数中找到拼写错误名称的唯一出现。 - Rob Kennedy
顺便提一下,有一些工具可以帮助你完成这个任务。我熟悉的CodeSite就可以使用其系统向日志函数添加此类调用。 - mj2008
6个回答

14

我们正在做类似的事情,只依赖于一个约定:在最开始放置一个const SMethodName,其中包含函数名称
然后所有我们的程序都遵循相同的模板,并且我们在Assert和其他异常抛出中使用此常量。
由于常量与程序名称的接近程度,很少会有拼写错误或任何不一致的问题。
当然,这因人而异...

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;

6
我最终采用的解决方案。但如果未来版本的 Delphi 编译器支持简单宏的话,那将是非常好的。据我所见,这应该很容易实现。 - Andreas Rejbrand
也许当我们获得基于CLANG/LLVM的新Delphi编译器时,它会有这个功能。 - Warren P
关于宏,您可以查看 CN Pack,这是一个为 IDE 提供扩展的不错的集合。 - monnoo

8
我认为这是一个重复的问题:如何在Delphi 7中获取当前方法名? 那里的答案是,要这样做,您需要在项目中使用某种形式的调试信息,并使用例如JCL函数从中提取信息。

我补充一下,我没有使用D2009/2010中的新RTTI支持,但如果有一些聪明的方法可以使用它,那也不会让我感到惊讶。例如,这个示例展示了如何列出类的所有方法,每个方法都由TRttiMethod表示。它继承自TRttiNamedObject,其Name属性指定了反射实体的名称。我相信一定有一种方法可以获取当前所在的引用,即您当前所在的方法。这都是猜测,但可以试试看!


1
在项目中,您不需要任何调试信息。C或C++编译器并不是这样实现的。它所需的仅仅是编译器知道当前正在编译的子程序的名称,并且遇到预定义的符号(例如__FUNC__)时插入一个引用到包含该子程序名称的常量字符串中。这完全是编译时完成的。 - Rob K
@RobK 对于支持__FUNC__等的C编译器来说,这很好。Delphi编译器不支持,我们无法更改编译器的源代码,因此您的评论并没有真正帮助。但也许它可以解释为什么需要使用调试信息。 - David

3
没有编译时宏,但如果你包含足够的调试信息,你可以使用调用堆栈来找到它。请参见这个相同的问题

2
另一种实现此效果的方法是在特殊注释中输入源元数据,例如:
ValidateStruct(Struct, 'Blah'); // LOCAL_FUNCTION_NAME

然后在预编译构建事件中对您的源代码运行第三方工具,以查找带有“LOCAL_FUNCTION_NAME”注释的行,并将所有字符串文字替换为其中出现此类代码的方法名称,以便例如以下代码:

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

如果代码行在"SomeProc3"方法内部,编写这样的工具并在Delphi中进行文本替换也不会很困难,例如可以使用Python进行编写。自动完成替换意味着您永远不必担心同步问题。例如,您可以使用重构工具更改方法名称,然后在下一次编译器通过时自动更新字符串文字。类似于自定义源预处理器的东西。我给这个问题+1,我以前遇到过这种情况,特别是用于断言失败消息。我知道堆栈跟踪包含数据,但是在断言消息中包含例程名称可以使事情变得更加容易,手动执行会产生陈旧消息的危险,正如OP所指出的那样。编辑:其他答案中突出显示的JcdDebug.pas方法似乎比我的答案简单得多,前提是存在调试信息。

1
我也考虑过预处理。我会研究一下。然而,总的来说,我确实对第三方代码有些顾虑... - Andreas Rejbrand
一个带有存储功能的预编译器。 :-) - Warren P

0
我通过设计解决了类似的问题。你的例子让我感到困惑,因为你似乎已经在这样做了。
你可以像这样一次性包装你的验证函数:
procedure SomeValidateProc3(const Struct: TMyStruct);
  begin
    ValidateStruct(Struct, 'SomeProc3');
  end;

那么,不要重复调用:

ValidateStruct(Struct, 'SomeProc3");

你调用:

SomeValidateProc3(Struct);

如果您有拼写错误,编译器会捕捉到它:

SoemValidateProc3(Struct);

如果您为包装函数使用更有意义的名称,例如“ValidateName”,代码也会变得更易读。


你误解了情况。我有许多过程均以TMyStruct作为参数,并且所有这些过程都使用同一个验证器函数,即它们都检查相同的事情。 - Andreas Rejbrand
我的解决方案也适用于这个问题。只需将函数包装起来,不必再担心错别字。 - Marcus Adams

0
我认为你的方法是错误的: 首先,检查是否有错误,然后(也就是说:你需要调用者的名称)使用像JclDebug这样的工具,通过将返回地址从堆栈传递给它来获取调用者的名称。
从性能上讲,获取过程名称非常昂贵,因此只有在绝对必要时才需要这样做。

2
那取得名称的方式取决于您的方法。如果我在预编译事件中通过自定义解析器运行源代码,这对性能不会产生任何影响。 - Andreas Rejbrand
1
如果您运行任何预处理源代码的工具,那么您可能需要注意文件日期戳 - 这取决于您如何实现备份和版本控制。我个人会觉得整个源代码集的日期戳都设置为“今天”有点令人不安,但这只是我的想法。例如,Git不使用日期戳,所以它是可以的。 - rossmcm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接