2010-03-14 9 views
7

Para las pruebas de depuración/rendimiento, me gustaría agregar dinámicamente el código de registro a todos los controladores de eventos de los componentes de un tipo determinado en tiempo de ejecución.¿Cómo puedo inyectar código dinámicamente en controladores de eventos en Delphi?

Por ejemplo, para todos los conjuntos de datos en un módulo de datos, necesito ejecutar código en los eventos BeforeOpen y AfterOpen para capturar la hora de inicio y registrar el tiempo transcurrido en AfterOpen.

Preferiría hacer esto de forma dinámica (sin subclases de componentes), de modo que pueda agregar esto a todos los módulos de datos y formularios existentes con un mínimo esfuerzo solo cuando sea necesario.

iteración todos los componentes y filtrado por su tipo es fácil, pero para los componentes que ya tienen controladores de eventos asignados, necesito una manera de tienda de los controladores de eventos existentes, y asignar un nuevo controlador de eventos modificado que primero hace el logging y luego invocará el código original que ya estaba presente.

lo que este código

procedure TMyDatamodule.OnBeforeOpen(Sender: TDataset); 
begin 
    SomeProc; 
end; 

en tiempo de ejecución se convertiría en

procedure TMyDatamodule.OnBeforeOpen(Sender: TDataset); 
begin 
    StoreStartTime(Sender); // injected code 

    SomeProc; 
end; 

¿Hay un patrón de diseño que se puede aplicar, o incluso un código de ejemplo que muestra cómo implementar esto en Delphi?

+0

Usted no lo hace menciona qué DBMS estás usando. Pero como un enfoque completamente diferente, ¿ha considerado usar un DB Profiler? P.ej. Al usar el perfilador de SQL Server, tiene mucha flexibilidad y podría ver detalles internos que no están cubiertos por BeforeOpen/AfterOpen. –

Respuesta

9

Puede utilizar el siguiente esquema de volver a cablear los conjuntos de datos:

type 
    TDataSetEventWrapper = class 
    private 
    FDataSet: TDataSet; 
    FOrgAfterOpen: TDataSetNotifyEvent; 
    FOrgBeforeOpen: TDataSetNotifyEvent; 
    procedure MyAfterOpen(DataSet: TDataSet); 
    procedure MyBeforeOpen(DataSet: TDataSet); 
    protected 
    property DataSet: TDataSet read FDataSet; 
    public 
    constructor Create(ADataSet: TDataSet); 
    destructor Destroy; override; 
    end; 

constructor TDataSetEventWrapper.Create(ADataSet: TDataSet); 
begin 
    Assert(ADataSet <> nil); 
    inherited Create; 
    FDataSet := ADataSet; 
    FOrgAfterOpen := FDataSet.AfterOpen; 
    FOrgBeforeOpen := FDataSet.BeforeOpen; 
    FDataSet.AfterOpen := MyAfterOpen; 
    FDataSet.BeforeOpen := MyBeforeOpen; 
end; 

destructor TDataSetEventWrapper.Destroy; 
begin 
    FDataSet.AfterOpen := FOrgAfterOpen; 
    FDataSet.BeforeOpen := FOrgBeforeOpen; 
    inherited; 
end; 

procedure TDataSetEventWrapper.MyBeforeOpen(DataSet: TDataSet); 
begin 
    if Assigned(FOrgBeforeOpen) then 
    FOrgBeforeOpen(DataSet); 
end; 

procedure TDataSetEventWrapper.MyAfterOpen(DataSet: TDataSet); 
begin 
    if Assigned(FOrgAfterOpen) then 
    FOrgAfterOpen(DataSet); 
end; 

Dentro MyAfterOpen y MyBeforeOpen se puede llevar en su código antes, después o alrededor de la llamada al controlador de eventos originales.

Recoge los objetos que los contienen en una TObjectList con OwnsObjects := true y todo volverá a su posición original al claro o liberar el objectlist.

Precaución: Para que este código funcione, los eventos deben estar cableados cuando se crean los contenedores y la reasignación manual de esos eventos está prohibida.

+0

Acabo de ver que LukLed tuvo una idea similar. –

+0

@Uwe Raabe: +1 para una bonita descripción. Uso esta solución todo el tiempo para sincronizar Insertar/Editar/Publicar en conjuntos de datos con una relación de 1-1. Llamar Insertar/Editar/Publicar en uno activa Insertar/Editar/Publicar en el segundo. Funciona bastante bien – LukLed

+0

Ventaja: no necesita marcas de unidad con nombres contiguos. Desventaja: más intrusivo: necesita agregar código a su proyecto para enganchar los eventos incluso cuando usa una clase separada. –

1

No hay una manera genérica de hacer esto sin ir realmente muy bajo nivel.
Básicamente, escribiría algo similar al depurador Delphi.

Para TDataSet:

que crearía un TDataSource fresca y el punto a la instancia TDataSet. Luego usaría crear un componente Data Aware y usar TDataLink para capturar las cosas que le interesan.

Desde cero, este es un par de días de trabajo. Pero puede tener una ventaja con el código de muestra para mi sesión de conferencia "Código más inteligente con bases de datos y controles de datos conscientes".
Vea mi Conferences, seminars and other public appearances page en wiert.wordpress.com para el enlace.

--jeroen

2

Si la función o procedimiento en el componente que desea 'gancho' es declard virtual o dinámico se puede hacer de la siguiente manera:

Asumamos por motivo de las discusiones que se quiero ver todos los AfterOpen de TDataset. Este controlador de eventos se llama desde el método virtual:

procedure TDataSet.DoAfterOpen; 

Crear una nueva unidad UnitDatasetTester (tecleado en el manual)

unit UnitDatasetTester; 

interface 

uses 
    DB; 

type 
    TDataset = class(DB.TDataset) 
    protected 
    procedure DoAfterOpen; override; 
    end; 

implementation 

uses 
    MySpecialLoggingUnit; 

procedure TDataset.DoAfterOpen; 
begin 
    inherited; 
    SpecialLog.Add('Hello world'); 
end; 

Si no se utiliza esta unidad todas las obras sin loggig. Si usa esta unidad como la ÚLTIMA unidad en su lista de usos (al menos DESPUÉS de que la BD lo use), usted tiene un registro para todos los conjuntos de datos en esa unidad.

+1

Otro método es copiar la unidad db y agregar la copia en el directorio del proyecto (asegúrese de que esté en su proyecto). Ahora edite este archivo db.pas para agregar el registro que desea. Cree un segundo proyecto sin usar esta unidad y un proyecto diferente diretorio pero usando todas las otras unidades del primer proyecto de la misma. –

+2

Bonito consejo, pero haría el registro condicional (en el modo de construcción, un conmutador de compilación o una variable) y siempre construir el proyecto con esta unidad. Agregar y eliminar unidades bajo demanda (y ponerlas en el espacio correcto) es demasiado propenso a errores. – mghie

+0

@mghie: puedes hacer eso haciendo los usos envueltos dentro de un condicional. Pero eso no es parte de la pregunta. Podría haberlo mencionado sin embargo. –

3

me gustaría probar esto:

TDataSetBeforeOpenStartTimeStorer = class(TObject) 

constructor Create(MyDataModule : TMyDatamodule); 
begin 
    OldBeforeOpen := MyDatamodule.OnBeforeOpen; 
    MyDatamodule.OnBeforeOpen = NewBeforeOpen; 
end; 

procedure NewBeforeOpen(Sender: TDataset); 
begin 
    StoreStartTime(Sender); 
    if Assigned(OldBeforeOpen) then 
    OldBeforeOpen(Sender); 
end; 

Adjuntar ejemplo, uno a cada TDataSetBeforeOpenStartTimeStorer TDataSet y usted tendrá su funcionalidad.

0

Si desea hacerlo de una manera general (y "rápida y fácil"), puede utilizar el desvío y RTTI (RTTI: buscar propiedades de eventos publicados; desvío: enganchar la función original y redirigir/desviarlo para su propia función).

utilizo desviándose en mi código abierto Delphi perfilador: http://code.google.com/p/asmprofiler/
(en mi función perfil general utilizo montaje para preservar la pila, registros de la CPU, etc., así que puede perfil/conectar cualquier función).

Pero si quieres una manera más "inteligentes" (como el conocimiento sobre beforeopen y afteropen) que tiene que hacer algún trabajo adicional: hay que hacer una clase de manejo especial para TDataset descendientes etc.

Cuestiones relacionadas