2010-03-14 22 views
7

Để gỡ lỗi/kiểm tra hiệu suất Tôi muốn tự động thêm mã đăng nhập vào tất cả các trình xử lý sự kiện của các thành phần của một loại đã cho vào lúc chạy.Làm thế nào tôi có thể tự động chèn mã vào trình xử lý sự kiện trong Delphi?

Ví dụ, đối với tất cả Tập dữ liệu trong một Datamodule, tôi cần chạy mã trong các sự kiện BeforeOpenAfterOpen để nắm bắt thời gian bắt đầu và để ghi lại thời gian đã trôi qua trong AfterOpen.

Tôi muốn làm điều này động (không có phân lớp thành phần), để tôi có thể thêm điều này vào tất cả các mô hình và biểu mẫu hiện có với nỗ lực tối thiểu chỉ khi cần. Việc thay đổi tất cả các thành phần và lọc theo loại của chúng là dễ dàng, nhưng đối với các thành phần đã được gán sự kiện, tôi cần một cách để lưu các trình xử lý sự kiện hiện có và gán trình xử lý sự kiện mới được sửa đổi. đăng nhập và sau đó sẽ gọi mã gốc đã có sẵn.

Vì vậy, mã này

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

tại thời gian chạy sẽ trở thành

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

    SomeProc; 
end; 

Có một mẫu thiết kế có thể được áp dụng, hoặc thậm chí một số mã ví dụ trong đó cho thấy làm thế nào để thực hiện điều này trong Delphi?

+0

Bạn không đề cập đến DBMS bạn đang sử dụng. Nhưng với tư cách là một cách tiếp cận hoàn toàn khác, bạn có cân nhắc sử dụng DB Profiler không? Ví dụ. Sử dụng trình biên dịch SQL Server, bạn có rất nhiều tính linh hoạt và có thể thấy các chi tiết bên trong không được bao hàm bởi BeforeOpen/AfterOpen. –

Trả lời

9

Bạn có thể sử dụng các chương trình sau đây để ReWire các bộ dữ liệu:

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; 

Bên MyAfterOpenMyBeforeOpen bạn có thể mang trong mã của bạn trước, sau hoặc xung quanh các cuộc gọi đến xử lý sự kiện ban đầu.

Thu thập các đối tượng bao bọc trong TObjectList với OwnsObjects := true và mọi thứ sẽ hoàn nguyên về bản gốc khi bạn xóa hoặc giải phóng danh sách đối tượng.

Thận trọng: Để mã này hoạt động, sự kiện phải được kết nối khi bạn tạo trình bao bọc và gán lại các sự kiện đó theo cách thủ công bị cấm.

+0

Tôi vừa mới thấy rằng LukLed có một ý tưởng tương tự. –

+0

@Uwe Raabe: +1 để có mô tả thú vị. Tôi sử dụng giải pháp này mọi lúc để đồng bộ hóa Chèn/Chỉnh sửa/Đăng trong bộ dữ liệu với quan hệ 1-1. Gọi Chèn/Chỉnh sửa/Đăng trong một trình kích hoạt Chèn/Chỉnh sửa/Đăng trong giây. Hoạt động khá tốt. – LukLed

+0

Lợi thế: bạn không cần đơn vị ve với tên rõ ràng. Nhược điểm: xâm nhập nhiều hơn: bạn cần phải thêm mã vào dự án của bạn để móc các sự kiện ngay cả khi sử dụng một lớp riêng biệt. –

1

Không có cách chung để làm điều này mà không thực sự thực sự ở mức độ thấp.
Về cơ bản bạn sẽ viết một cái gì đó dọc theo dòng của trình gỡ lỗi Delphi.

Đối với TDataSet:

Tôi muốn tạo một TDataSource mới và trỏ nó vào thể hiện TDataSet. Sau đó, tôi sẽ sử dụng tạo thành phần Data Aware và sử dụng TDataLink để nắm bắt những thứ bạn quan tâm.

Từ đầu, đây là một vài ngày làm việc. Nhưng bạn có thể bắt đầu với mã mẫu cho phiên họp của tôi "Mã thông minh hơn với Cơ sở dữ liệu và các điều khiển nhận biết dữ liệu".
Xem số Conferences, seminars and other public appearances page tại số wiert.wordpress.com cho liên kết.

--jeroen

2

Nếu hàm hoặc thủ tục trong các thành phần bạn muốn 'móc' là declard ảo hoặc động nó có thể được thực hiện theo cách sau đây:

Giả sử cho các đối số lợi ích mà bạn muốn xem tất cả AfterOpen từ TDataset. xử lý sự kiện này được gọi là từ phương pháp ảo:

procedure TDataSet.DoAfterOpen; 

Tạo một đơn vị mới UnitDatasetTester (gõ nó trong sổ tay)

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; 

Nếu bạn không sử dụng đơn vị này tất cả các công trình mà không loggig. Nếu bạn sử dụng đơn vị này làm đơn vị LASt trong danh sách sử dụng của bạn (ít nhất là SAU DB sử dụng), bạn có đăng nhập tất cả các tập dữ liệu trong đơn vị đó.

+1

Phương pháp khác là sao chép đơn vị db và thêm bản sao vào thư mục dự án của bạn (đảm bảo rằng nó nằm trong dự án của bạn). Bây giờ hãy chỉnh sửa tệp db.pas này để thêm nhật ký bạn muốn. Tạo dự án thứ hai không có đơn vị này được sử dụng và một dự án khác, nhưng sử dụng tất cả các đơn vị khác từ dự án đầu tiên giống nhau. –

+2

Mẹo hay, nhưng tôi sẽ ghi nhật ký có điều kiện (trên chế độ xây dựng, công cụ biên dịch hoặc biến) và luôn xây dựng dự án với đơn vị này. Thêm và loại bỏ các đơn vị theo yêu cầu (và đưa chúng vào đúng không gian) quá dễ bị lỗi. – mghie

+0

@mghie: bạn có thể làm điều đó bằng cách sử dụng các ứng dụng bọc trong một điều kiện. Nhưng đó không phải là một phần của câu hỏi. Có thể đã đề cập đến nó mặc dù. –

3

tôi sẽ cố gắng này:

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; 

Đính kèm dụ một TDataSetBeforeOpenStartTimeStorer cho mọi TDataSet và bạn sẽ có chức năng của mình.

0

Nếu bạn muốn thực hiện nó theo cách thông thường (và "nhanh chóng và dễ dàng"), bạn có thể sử dụng đường vòng và RTTI (RTTI: tìm kiếm các thuộc tính sự kiện đã xuất bản; hủy bỏ: móc hàm gốc và định tuyến lại/ngắt đường đi với chức năng của riêng bạn).

tôi sử dụng detouring trong mã nguồn mở Delphi profiler tôi: http://code.google.com/p/asmprofiler/
(trong chức năng hồ sơ chung của tôi, tôi sử dụng lắp ráp để bảo vệ chồng, thanh ghi CPU, vv vì vậy nó có thể cấu hình/treo bất kỳ chức năng).

Nhưng nếu bạn muốn có một cách "thông minh" hơn (như kiến ​​thức về beforeopen và afteropen) bạn phải làm một số công việc bổ sung: bạn cần phải thực hiện một lớp xử lý đặc biệt cho hậu duệ TDataset, vv

Các vấn đề liên quan