Как я могу динамично ввести код в обработчики событий в Delphi?

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

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

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

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

Так этот код

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

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

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

  SomeProc;
end;

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

7
задан mjn 14 March 2010 в 09:43
поделиться

5 ответов

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

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 , и все вернется к исходному состоянию, когда вы очистите или освободите список объектов.

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

9
ответ дан 6 December 2019 в 14:03
поделиться

Я бы попробовал следующее:

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, и вы получите свою функциональность.

3
ответ дан 6 December 2019 в 14:03
поделиться

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

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

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

0
ответ дан 6 December 2019 в 14:03
поделиться

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

Предположим для аргументации, что вы хотите видеть все 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;

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

2
ответ дан 6 December 2019 в 14:03
поделиться

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

Для TDataSet:

я бы создал свежий TDataSource и указал его на экземпляр TDataSet. Затем я бы использовал компонент Data Aware и использовал TDataLink, чтобы фиксировать то, что вас интересует.

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

- jeroen

1
ответ дан 6 December 2019 в 14:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: