Для отладки / тесты производительности я хотел бы динамично добавить регистрирующийся код ко всем обработчикам событий компонентов данного типа во время выполнения.
Например, для всех Наборов данных в 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?
Вы можете использовать следующую схему для переназначения наборов данных:
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
, и все вернется к исходному состоянию, когда вы очистите или освободите список объектов.
Внимание: Для того, чтобы этот код работал, события должны быть связаны уже при создании оболочек, и ручное переназначение этих событий запрещено.
Я бы попробовал следующее:
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, и вы получите свою функциональность.
Если вы хотите сделать это обычным (и "быстрым и простым") способом, вы можете использовать обход и RTTI ( RTTI: поиск опубликованных свойств события; обход: перехватить исходную функцию и перенаправить / обойти ее к вашей собственной функции).
Я использую обход в моем профилировщике Delphi с открытым исходным кодом:
http://code.google.com/p/asmprofiler/
(в моей общей функции профиля я использую сборку для сохранения стек, регистры процессора и т. д., чтобы он мог профилировать / подключать любую функцию).
Но если вам нужен более «умный» способ (например, знание о beforeopen и afteropen), вам придется проделать некоторую дополнительную работу: вам нужно создать специальный класс обработки для потомков TDataset и т. Д.
Если функция или процедура в компоненте, который вы хотите «перехватить», объявлены виртуальными или динамическими, это можно сделать следующим образом манера:
Предположим для аргументации, что вы хотите видеть все 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 в своем списке использования (по крайней мере, ПОСЛЕ того, как использует БД), у вас действительно есть журналирование для всех наборов данных в этом модуле.
Не существует универсального способа сделать это без действительно очень низкого уровня.
По сути, вы должны написать что-нибудь в духе отладчика Delphi.
Для TDataSet:
я бы создал свежий TDataSource и указал его на экземпляр TDataSet. Затем я бы использовал компонент Data Aware и использовал TDataLink, чтобы фиксировать то, что вас интересует.
С нуля это пара дней работы. Но вы можете начать с примера кода для моего сеанса конференции «Умный код с базами данных и элементами управления с учетом данных».
См. Ссылку на моей странице конференций, семинаров и других публичных выступлений по адресу wiert.wordpress.com .
- jeroen