Как получить имя текущей процедуры/функции в Delphi (как строка)
Можно ли получить имя текущей процедуры/функции в виде строки в процедуре/функции? Я предполагаю, что будет некоторый "макрос", который расширяется во время компиляции.
Мой сценарий таков: у меня есть много процедур, которым дается запись, и все они должны начать с проверки действительности записи, и поэтому они передают запись в "процедуру проверки правильности". Процедура валидатора (то же самое для всех процедур) вызывает исключение, если запись недействительна, и я хочу, чтобы сообщение исключения включало не имя процедуры валидатора, а имя функции/процедуры, которая вызывала валидатор процедура (естественно).
То есть, я
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;
Ответы
Ответ 1
Мы делаем что-то подобное и полагаемся только на соглашение: ставим const SMethodName
с именем функции в самом начале.
Затем все наши подпрограммы следуют одному и тому же шаблону, и мы используем эту константу в Assert и другом воссоздании исключений.
Из-за близости 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;
Ответ 2
Я думаю, что это дубликат этого вопроса: Как получить имя текущего метода в Delphi 7?
Ответ на этот вопрос заключается в том, что для этого вам потребуется некоторая форма отладочной информации в вашем проекте и, например, использовать JCL для извлечения из него информации.
Я добавлю, что я не использовал новую поддержку RTTI в D2009/2010, но меня не удивило бы, если бы у вас было что-то умное. Например, это показывает вам, как перечислить все методы класса, и каждый метод представлен TRttiMethod. Это происходит от TRttiNamedObject, у которого есть Свойство Name, которое "определяет имя отраженного объекта" . Я уверен, что должен быть способ получить ссылку на то, где вы сейчас находитесь, то есть метод, в котором вы сейчас находитесь. Это все догадки, но попробуйте дать это!
Ответ 3
Нет макроса времени компиляции, но если вы включите достаточную информацию об отладке, вы можете использовать столбец, чтобы узнать его. См. этот же вопрос.
Ответ 4
Другой способ добиться эффекта - ввести исходные метаданные в специальный комментарий, например
ValidateStruct(Struct, 'Blah'); // LOCAL_FUNCTION_NAME
И затем запустите сторонний инструмент поверх вашего источника в событии предварительной компиляции, чтобы найти строки с "LOCAL_FUNCTION_NAME" в таком комментарии и заменить все строковые литералы на имя метода, в котором появляется такой код, например код становится
ValidateStruct(Struct, 'SomeProc3'); // LOCAL_FUNCTION_NAME
если строка кода находится внутри метода "SomeProc3". Было бы совсем не сложно написать такой инструмент в Python, например, и эта замена текста, сделанная в Delphi, тоже была бы легкой.
Выполнение подстановки автоматически означает, что вам никогда не придется беспокоиться о синхронизации. Например, вы можете использовать инструменты рефакторинга для изменения имен методов, а затем ваши строковые литералы будут автоматически обновляться на следующем проходе компилятора.
Что-то вроде пользовательского исходного процессора.
Я задал этот вопрос +1, это ситуация, которую я имел много раз раньше, особенно для сообщений об ошибках утверждения. Я знаю, что трассировка стека содержит данные, но наличие имени подпрограммы внутри сообщения подтверждения делает вещи немного легче, и при этом вручную создается опасность устаревших сообщений, как указывал OP.
EDIT: методы JcdDebug.pas
, выделенные в других ответах, намного проще, чем мой ответ, при условии, что информация об отладке присутствует.
Ответ 5
Я решил подобные проблемы с помощью дизайна. Ваш пример смущает меня, потому что вы, кажется, уже делаете это.
Вы завершаете свои функции проверки следующим образом:
procedure SomeValidateProc3(const Struct: TMyStruct);
begin
ValidateStruct(Struct, 'SomeProc3');
end;
Затем вместо повторного вызова:
ValidateStruct(Struct, 'SomeProc3");
Вы вызываете:
SomeValidateProc3(Struct);
Если у вас есть опечатка, компилятор поймает ее:
SoemValidateProc3(Struct);
Если вы используете более значимое имя для своих функций-оболочек, таких как "ValidateName", код становится более читаемым.
Ответ 6
Я думаю, вы делаете это неправильно:
Сначала проверьте, есть ли ошибка, и только тогда (то есть: вам нужно имя вызывающего) используйте какой-то инструмент, например JclDebug, чтобы получить имя вызывающего, передав ему адрес возврата из стека.
Получение названия процедуры очень дорогостоящей производительности, поэтому вы только хотите сделать это, когда это абсолютно необходимо.