How can I dynamically inject code into event handlers in Delphi?

前端 未结 5 2106
予麋鹿
予麋鹿 2020-12-17 00:31

For debugging / performance tests I would like to dynamically add logging code to all event handlers of components of a given type at run time.

For example, for all

相关标签:
5条回答
  • 2020-12-17 01:06

    If the function or procedure in the component you want to 'hook' is declard virtual or dynamic it can be done in the following manner:

    Let's assume for arguments sake that you wantto see all AfterOpen's from TDataset. This event handler is called from the virtual method:

    procedure TDataSet.DoAfterOpen;
    

    Create a new unit UnitDatasetTester (typed it in 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;
    

    If you do not use this unit all works without loggig. If you use this unit as the LASt unit in your uses list (at least AFTER the DB uses) you do have logging for all datasets in that unit.

    0 讨论(0)
  • 2020-12-17 01:12

    If you want to do it in a general purpose (and "quick and easy") way, you could use detouring and RTTI (RTTI: search for published event properties; detouring: hook original function and reroute/detour it to your own function).

    I use detouring in my open source Delphi profiler: http://code.google.com/p/asmprofiler/
    (in my general profile function I use assembly to preserve the stack, cpu registers, etc so it can profile/hook any function).

    But if you want a more "intelligent" way (like knowledge about beforeopen and afteropen) you have to do some additional work: you need to make a special handling class for TDataset descendants etc.

    0 讨论(0)
  • 2020-12-17 01:13

    I would try this:

    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;
    

    Attach one TDataSetBeforeOpenStartTimeStorer instance to every TDataSet and you'll have your functionality.

    0 讨论(0)
  • 2020-12-17 01:27

    You can use the following scheme to rewire the datasets:

    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;
    

    Inside MyAfterOpen and MyBeforeOpen you can bring in your code before, after or around the call to the original event handler.

    Collect the wrapper objects in a TObjectList with OwnsObjects := true and everything will revert to the original when you clear or free the objectlist.

    Caution: For this code to work the events have to be wired already when you create the wrappers and manually reassigning those events is forbidden.

    0 讨论(0)
  • 2020-12-17 01:30

    There is no generic way to do this without going really really low level.
    Basically you'd write something along the lines of the Delphi debugger.

    For TDataSet:

    I'd create a fresh TDataSource and point it to the TDataSet instance. Then I would use create a Data Aware component, and use the TDataLink to capture the things you are interested in.

    From scratch, this is a couple of days work. But you can get a head start with the sample code for my conference session "Smarter code with Databases and data aware controls".
    See my Conferences, seminars and other public appearances page at wiert.wordpress.com for the link.

    --jeroen

    0 讨论(0)
提交回复
热议问题