Как я могу динамически внедрить код в обработчики событий в Delphi? - PullRequest
7 голосов
/ 14 марта 2010

Для отладки / тестов производительности я хотел бы динамически добавлять код регистрации во все обработчики событий компонентов данного типа во время выполнения.

Например, для всех наборов данных в модуле данных мне нужно запустить код в событиях BeforeOpen и AfterOpen, чтобы зафиксировать время начала и записать прошедшее время в AfterOpen.

Я бы предпочел сделать это динамически (без подклассов компонентов), чтобы добавить его ко всем существующим модулям данных и формам с минимальными усилиями только при необходимости.

Итерация всех компонентов и фильтрация по их типу проста, но для компонентов, которым уже назначены обработчики событий, мне нужен способ сохранить существующие обработчики событий и назначить новый модифицированный обработчик событий, сначала выполняет регистрацию, а затем вызовет исходный код, который уже присутствовал.

Итак, этот код

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

во время выполнения станет

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

  SomeProc;
end;

Существует ли шаблон проектирования, который можно применить, или даже какой-то пример кода, который показывает, как реализовать это в Delphi?

Ответы [ 5 ]

9 голосов
/ 14 марта 2010

Вы можете использовать следующую схему для изменения набора данных:

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;

Внутри MyAfterOpen и MyBeforeOpen вы можете ввести свой код до, после или вокруг вызова исходного обработчика событий.

Соберите объекты-оболочки в TObjectList с помощью OwnsObjects := true и все вернется к оригиналу, когда вы очистите или освободите список объектов.

Внимание: Чтобы этот код работал, события должны быть уже подключены, когда вы создаете оболочки, и переназначение этих событий вручную запрещено.

3 голосов
/ 14 марта 2010

Я бы попробовал это:

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;

Присоедините один экземпляр TDataSetBeforeOpenStartTimeStorer к каждому TDataSet, и вы получите свою функциональность.

2 голосов
/ 14 марта 2010

Если функция или процедура в компоненте, который вы хотите «зацепить», объявлена ​​виртуальной или динамической, это можно сделать следующим образом:

Давайте предположим, что вы хотите увидеть все AfterOpen из TDataset. Этот обработчик событий вызывается из виртуального метода:

procedure TDataSet.DoAfterOpen;

Создать новый юнит UnitDatasetTester (набрал его вручную)

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;

Если вы не используете этот аппарат, все работает без регистрации. Если вы используете эту единицу в качестве единицы LASt в своем списке использований (по крайней мере, ПОСЛЕ того, как БД использует), у вас есть регистрация всех наборов данных в этой единице.

1 голос
/ 14 марта 2010

Не существует универсального способа сделать это без действительно очень низкого уровня.
По сути, вы пишете что-то вроде отладчика Delphi.

Для TDataSet:

Я бы создал новый TDataSource и указал бы на экземпляр TDataSet. Затем я хотел бы создать компонент Data Aware и использовать TDataLink для захвата интересующих вас вещей.

С нуля, это пара дней работы. Но вы можете начать с примера кода для моего сеанса конференции «Умный код с базами данных и элементами управления с учетом данных».
Смотрите ссылку Конференции, семинары и другие публичные выступления на wiert.wordpress.com для ссылки.

- Йерун

0 голосов
/ 15 марта 2010

Если вы хотите сделать это универсальным (и «быстрым и простым») способом, вы можете использовать обход и RTTI (RTTI: поиск опубликованных свойств события; обходной путь: подключить оригинальную функцию и перенаправить / отклонить ее к вашей собственной функции).

Я использую обход в своем профиле Delphi с открытым исходным кодом: http://code.google.com/p/asmprofiler/
(в моей общей функции профиля я использую ассемблер для сохранения стека, регистров процессора и т. д., чтобы он мог профилировать / перехватывать любую функцию).

Но если вы хотите более «интеллектуальный» способ (например, знания о beforeopen и afteropen), вам нужно проделать дополнительную работу: вам нужно создать специальный класс обработки для потомков TDataset и т. Д.

...