2010-05-12 15 views
20

¿Es posible obtener el nombre del procedimiento/función actual como una cadena, dentro de un procedimiento/función? Supongo que habría algún "macro" que se expande en tiempo de compilación.Cómo obtener el nombre del procedimiento/función actual en Delphi (como una cadena)

Mi caso es el siguiente: Tengo muchos procedimientos que tienen un registro y todos deben comenzar por verificar la validez del registro, por lo que pasan el registro a un "procedimiento de validación". El procedimiento de validación (el mismo para todos los procedimientos) genera una excepción si el registro no es válido, y quiero que el mensaje de la excepción no incluya el nombre del procedimiento del validador, sino el nombre de la función/procedimiento que llamó al validador procedimiento (naturalmente).

Es decir, tengo

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

y luego

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

... 

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

Sería algo menos propenso a errores si en cambio podría escribir algo así como

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

... 

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

y luego cada uno vez que el compilador encuentra {$ PROCNAME}, simplemente reemplaza la "macro" con el nombre de la corriente función/procedimiento como un literal de cadena.

actualización

El problema con el primer enfoque es que es propenso a errores. Por ejemplo, sucede fácilmente que se obtiene por un error de copiar y pegar:

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

o errores tipográficos:

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

o confusión sólo temporal:

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

Esto sería dulce, pero hasta donde yo sé, no existe, y sí busqué algo similar. La única solución genérica que conozco sería utilizar la información de depuración que está incrustada en el EXE para obtener el nombre del procedimiento, pero esto sería un gran golpe de rendimiento en el tiempo de ejecución. Cuando necesitaba algo similar, escribía un pequeño programa que repasaba mis archivos PAS y reemplazaba cierta expresión con texto, pero para mí el nombre del archivo + número de línea era suficiente, no elegí el nombre del procedimiento. –

+4

Según mi experiencia, los errores tipográficos y la confusión no son un gran problema. Siempre que sus registros muestren algún nombre de identificación, no importa cuál sea ese nombre. Si realmente no tienes una función con ese nombre, simplemente grep y encontrarás la única ocurrencia del nombre mal escrito en la función que estabas buscando de todos modos. –

+0

FWIW, hay herramientas que lo harán por usted. Estoy familiarizado con CodeSite que agregará esas llamadas a funciones de registro usando su sistema. – mj2008

Respuesta

9

Estamos haciendo algo similar y solo confiamos en una convención: poner const sosteniendo el nombre de la función al comienzo.
Luego todas nuestras rutinas siguen la misma plantilla, y usamos esta const en Assert y otras excepciones.
Debido a la proximidad de la const con el nombre de rutina, hay pocas posibilidades de que un error tipográfico o cualquier discrepancia permanezca allí por mucho tiempo.
YMMV por supuesto ...

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; 
+0

Esto es claramente una mejora al menos. –

+3

... y la solución con la que finalmente me conformé. Pero sería genial si una versión futura del compilador Delphi pudiera soportar macros simples. Por lo que puedo ver, esto sería trivial de implementar. –

+0

Tal vez cuando obtengamos el nuevo compilador Delphi basado en CLANG/LLVM, tenga esto. –

8

creo que esto es un duplicado de esta pregunta: How to get current method's name in Delphi 7?

La respuesta es que para hacerlo, necesita un poco forma de información de depuración en su proyecto, y para usar, por ejemplo, las funciones JCL para extraer información del mismo.

Añadiré que no he usado el nuevo soporte RTTI en D2009/2010, pero no me sorprendería si hubiera algo inteligente que pudieras hacer con él. Por ejemplo, esto le muestra cómo list all methods of a class, y cada método está representado por un TRttiMethod. Eso desciende de TRttiNamedObject que tiene un Name property which "specifies the name of the reflected entity". Estoy seguro de que debe haber una manera de obtener una referencia de dónde se encuentra actualmente, es decir, el método en el que se encuentra actualmente. Esto es todo una suposición, ¡pero trate de probarlo!

+0

No necesitaría ningún tipo de información de depuración en el proyecto. No es así como se implementa en los compiladores C o C++. Todo lo que necesita es que el compilador sepa el nombre de la subrutina que está compilando en ese momento, y cuando encuentre un símbolo predefinido, como __FUNC__, inserte una referencia a una cadena constante que contenga el nombre de la subrutina. Este es un tiempo puramente de compilación. –

+0

@RobK Eso está bien para los compiladores C, que admiten '__FUNC__' y similares. El compilador Delphi no lo hace, y no podemos cambiar la fuente del compilador, por lo que su comentario realmente no ayuda. Pero tal vez proporciona algunos antecedentes de por qué se requiere el uso de información de depuración. –

2

No hay macro de tiempo de compilación, pero si incluye suficiente información de depuración, puede usar la pila de llamadas para averiguarlo. Ver this same question.

2

Otra forma de lograr el efecto es introducir metadatos de origen en un comentario especial como

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

Y a continuación, ejecutar una herramienta de terceros sobre su origen en un evento de construcción pre-compilar para encontrar líneas con " LOCAL_FUNCTION_NAME "en dicho comentario, y reemplace todos los literales de cadena con el nombre del método en el que aparece dicho código, de modo que, por ejemplo, el código se convierte en

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

si la línea de código está dentro del método "SomeProc3". No sería difícil escribir una herramienta como esta en Python, por ejemplo, y esta sustitución de texto hecha en Delphi también sería bastante fácil.

Tener la sustitución realizada automáticamente significa que nunca tendrá que preocuparse por la sincronización. Por ejemplo, puede usar herramientas de refactorización para cambiar los nombres de sus métodos, y luego sus literales de cadena se actualizarán automáticamente en el siguiente pase de compilación.

Algo así como un preprocesador de fuente personalizado.

He dado a esta pregunta un +1, esta es una situación que he tenido varias veces antes, especialmente para los mensajes de fallas de afirmación. Sé que el seguimiento de la pila contiene los datos, pero tener el nombre de rutina dentro del mensaje de afirmación hace las cosas un poco más fáciles, y hacerlo manualmente crea el peligro de mensajes obsoletos, como señaló el OP.

EDIT: Los métodos JcdDebug.pas como se destacan en otras respuestas parecen ser mucho más simples que mi respuesta, siempre que la información de depuración esté presente.

+1

También pensé en el procesamiento previo. Voy a investigar eso. En general, sin embargo, I * do * tiene algo en contra del código de terceros ... –

+0

Un precompilador con almacenamiento. :-) –

0

He resuelto problemas similares a través del diseño. Tu ejemplo me confunde porque parece que ya estás haciendo esto.

Se envuelve sus funciones de validación de una vez así:

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

A continuación, en lugar de llamar repetidamente:

ValidateStruct(Struct, 'SomeProc3"); 

llame:

SomeValidateProc3(Struct); 

Si usted tiene un error tipográfico, la el compilador lo detectará:

SoemValidateProc3(Struct); 

Si usa un nombre más significativo para las funciones de su contenedor como "ValidateName", el código también se vuelve más legible.

+0

Ha malentendido la situación. Tengo una serie de procedimientos que toman un TMyStruct como argumento, y todos usan * la misma función de validador, es decir, todos verifican las mismas cosas. –

+0

Mi solución también funciona para esto. Simplemente ajuste la función y deje de preocuparse por los errores tipográficos. –

0

creo que lo están haciendo al revés: En primer lugar, compruebe si hay un error y sólo entonces (es decir: Es necesario el nombre de la persona que llama) utilizar alguna herramienta como JclDebug para obtener el nombre de la persona que llama pasando la dirección de retorno de la pila a la misma.

Obtener el nombre del procedimiento es muy costoso en cuanto al rendimiento, por lo que solo quiere hacerlo cuando sea absolutamente necesario.

+2

Bueno, eso depende de cómo se obtiene el nombre. Si ejecuto el código fuente a través de un analizador personalizado en un evento de precompilación, eso no tendrá ningún impacto en el rendimiento en absoluto. –

Cuestiones relacionadas