28

是否可以在过程/函数中以字符串形式获取当前过程/函数的名称?我想会有一些在编译时扩展的“宏”。

我的场景是这样的:我有很多程序都被赋予了记录,它们都需要从检查记录的有效性开始,因此它们将记录传递给“验证程序”。如果记录无效,验证器过程(所有过程都相同)会引发异常,并且我希望异常消息不包括验证器过程的名称,而是调用验证器的函数/过程的名称程序(自然)。

也就是说,我有

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

6 回答 6

12

我们正在做类似的事情,并且只依赖一个约定:在开头放置一个包含函数名称的constSMethodName
然后我们所有的例程都遵循相同的模板,并且我们在 Assert 和其他异常引发中使用这个 const。
由于 const 与例程名称接近,因此打字错误或任何差异都不会长期存在。
YMMV 当然...

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;
于 2010-05-12T17:40:19.943 回答
8

我认为这是这个问题的重复:如何在 Delphi 7 中获取当前方法的名称?

答案是这样做,您需要在项目中提供某种形式的调试信息,并使用例如JCL函数从中提取信息。

我要补充一点,我没有在 D2009/2010 中使用新的 RTTI 支持,但如果你可以用它做一些聪明的事情,我不会感到惊讶。例如,这向您展示了如何列出一个类的所有方法,并且每个方法都由一个TRttiMethod表示。这源自 TRttiNamedObject ,它具有“指定反射实体的名称”的 Name 属性。我确信必须有一种方法可以参考您当前所在的位置,即您当前所处的方法。这都是猜测,但请尝试一下!

于 2010-05-12T11:04:31.963 回答
2

没有编译时宏,但如果您包含足够的调试信息,您可以使用调用堆栈来查找它。看到同样的问题

于 2010-05-12T11:05:44.257 回答
2

另一种实现效果的方法是将源元数据输入到一个特殊的注释中,比如

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

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

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

如果代码行在“SomeProc3”方法内。例如,用 Python 编写这样的工具一点也不难,而且在 Delphi 中完成的文本替换也很容易。

自动完成替换意味着您永远不必担心同步。例如,您可以使用重构工具更改方法名称,然后您的字符串文字将在下一次编译器传递时自动更新。

类似于自定义源预处理器的东西。

我给这个问题打了+1,这是我以前遇到过很多次的情况,尤其是对于断言失败的消息。我知道堆栈跟踪包含数据,但是在断言消息中使用例程名称会使事情变得容易一些,并且正如 OP 指出的那样,手动执行会产生过时消息的危险。

编辑:如果JcdDebug.pas存在调试信息,其他答案中突出显示的方法似乎比我的答案简单得多。

于 2010-05-12T12:52:52.583 回答
0

我通过设计解决了类似的问题。您的示例使我感到困惑,因为您似乎已经在这样做了。

你像这样包装你的验证函数:

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

然后而不是反复调用:

ValidateStruct(Struct, 'SomeProc3");

你打电话:

SomeValidateProc3(Struct);

如果你有错字,编译器会捕捉到它:

SoemValidateProc3(Struct);

如果你为你的包装函数使用一个更有意义的名字,比如“ValidateName”,代码也会变得更易读。

于 2010-05-12T13:57:15.933 回答
0

我认为您这样做是错误的:首先,检查是否有错误,然后(即:您需要调用者的名称)使用诸如 JclDebug 之类的工具通过传递返回来获取调用者的名称从堆栈到它的地址。

获取过程名称在性能方面非常昂贵,因此您只想在绝对必要时才这样做。

于 2010-05-12T20:50:23.080 回答