利用RTTI实现Delphi的多播事件代理研究
我们知道Delphi的每个对象可以包含多个Property,Property中可以是方法,例如TButton.OnClick属性。Delphi提供的仅仅是
一对一的设置,无法直接让TButton.OnClick去调用多个方法,而Java中采用Listener模式有类似AddListener方法提供多播。
Delphi多播的思想源于Allen Bauer的Blog:http://blogs.embarcadero.com/abauer/2008/08/15/38865,
cnWizard的武稀松大侠在此思想基础上实现了Win32的Delphi多播机制见:http://www.raysoftware.cn/?p=44#comment-2442,并且应用于cnWizard;
开源项目DSharp实现了更加完整的多播机制,可提供基于接口的多播,见:https://bitbucket.org/sglienke/dsharp
本人希望借鉴前人的基础上,实现一个对象的事件多播代理,即TEventAgent是一个TObject的事件多播代理器,将一个TObject传给TEventAgent后, TEventAgent扫描TObject所有事件,并为每个事件提供多播功能。
下面程序是一个简单示例,引用了 DSharp.Core.Events.pas单元,并在Delphi XE3 测试成功.
1 unit utObjEventAgent; 2 3 interface 4 5 uses System.Generics.Collections, DSharp.Core.Events, System.TypInfo, Classes; 6 7 type 8 TEventLinker=class(DSharp.Core.Events.TEvent) //单个事件的多播器 9 protected 10 FLinkedObject: TObject; 11 FLinkedProperty: PPropInfo; 12 FOriginal:TMethod; 13 14 FEventTypeData:PTypeData; 15 FEventName:String; 16 procedure MethodAdded(const Method: TMethod); override; 17 procedure MethodRemoved(const Method: TMethod); override; 18 procedure Notify(Sender: TObject; const Item: TMethod; 19 Action: System.Generics.Collections.TCollectionNotification); override; 20 property Owner; 21 property RefCount; 22 public 23 constructor Create(LinkedObj:TObject; LinkedPrpt:PPropInfo); 24 destructor Destroy; override; 25 end; 26 27 TEventAgent=class //对象的事件多播代理 28 protected 29 FOwner:TObject; 30 FPropList: PPropList; 31 FNameList:TDictionary<String, TEventLinker>; 32 procedure Prepare; virtual; 33 procedure Clear; 34 public 35 constructor Create(aOwner:TObject); virtual; 36 destructor Destroy;override; 37 function GetEventCount: Int32; 38 function GetEventName(Index: Int32): PWideChar; 39 procedure AddEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 添加事件处理函数 40 procedure RemoveEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 移除时间处理函数 41 end; 42 43 implementation 44 45 uses System.Rtti; 46 47 { TEventLinker } 48 49 constructor TEventLinker.Create(LinkedObj:TObject; LinkedPrpt:PPropInfo); 50 begin 51 inherited Create(LinkedPrpt.PropType^, nil); 52 FLinkedObject:=LinkedObj; 53 FLinkedProperty:=LinkedPrpt; 54 FEventName:=FLinkedProperty^.Name; 55 FOriginal:=GetMethodProp(FLinkedObject, FLinkedProperty); 56 SetMethodProp(FLinkedObject, FLinkedProperty, Self.GetInvoke); 57 if Assigned(FOriginal.Data) and Assigned(FOriginal.Code) then Add(FOriginal); //将原事件方法加入多播列表 58 end; 59 60 destructor TEventLinker.Destroy; 61 begin 62 SetMethodProp(FLinkedObject, FLinkedProperty, FOriginal); 63 inherited; 64 end; 65 66 procedure TEventLinker.MethodAdded(const Method: TMethod); 67 begin 68 end; 69 70 procedure TEventLinker.MethodRemoved(const Method: TMethod); 71 begin 72 end; 73 74 procedure TEventLinker.Notify(Sender: TObject; const Item: TMethod; 75 Action: System.Generics.Collections.TCollectionNotification); 76 begin 77 end; 78 79 { TEventAgent } 80 81 procedure TEventAgent.AddEventNotifier(EventName: String; 82 const NotifierMethod: TMethod); 83 var 84 V:TEventLinker; 85 begin 86 if FNameList.TryGetValue(EventName, V) then 87 begin 88 if V.IndexOf(NotifierMethod)<0 then 89 V.Add(NotifierMethod); 90 end; 91 end; 92 93 procedure TEventAgent.Clear; 94 var 95 Item: TPair<String, TEventLinker>; 96 begin 97 for Item in FNameList do 98 Item.Value.Free; 99 FNameList.Clear; 100 if Assigned(FPropList) then FreeMem(FPropList); 101 end; 102 103 constructor TEventAgent.Create(aOwner:TObject); 104 begin 105 inherited Create; 106 FNameList:=TDictionary<String, TEventLinker>.Create; 107 FOwner:=aOwner; 108 Prepare; 109 end; 110 111 destructor TEventAgent.Destroy; 112 begin 113 Clear; 114 FNameList.Free; 115 inherited; 116 end; 117 118 function TEventAgent.GetEventCount: Int32; 119 begin 120 Result:=FNameList.Count; 121 end; 122 123 function TEventAgent.GetEventName(Index: Int32): PWideChar; 124 begin 125 Result:=PWideChar(FNameList.Keys.ToArray[Index]); 126 end; 127 128 procedure TEventAgent.Prepare; 129 var 130 N, i:Integer; 131 Linker:TEventLinker; 132 Context: TRttiContext; 133 begin 134 Clear; 135 N:=GetPropList(FOwner.ClassInfo, FPropList); 136 for i := 0 to N-1 do 137 if FPropList^[i].PropType^.Kind = tkMethod then 138 begin 139 if FPropList[i].GetProc=nil then Continue; 140 Linker:=TEventLinker.Create(FOwner, FPropList[i]); 141 Linker.FEventName:=FPropList[i].Name; 142 FNameList.Add(Linker.FEventName, Linker); 143 end; 144 end; 145 146 147 procedure TEventAgent.RemoveEventNotifier(EventName: String; 148 const NotifierMethod: TMethod); 149 var 150 V:TEventLinker; 151 begin 152 if FNameList.TryGetValue(EventName, V) then 153 begin 154 V.Remove(NotifierMethod); 155 end; 156 end; 157 158 end.
测试程序演示一个TButton被事件多播代理,其OnClick,OnMouseDown均有3个多播方法。
测试程序:
1 unit Unit1; 2 3 interface 4 5 uses 6 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 7 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, utObjEventAgent, DSharp.Core.Events, ObjAuto, 8 Vcl.StdCtrls; 9 10 type 11 TForm1 = class(TForm) 12 Button1: TButton; 13 Memo1: TMemo; 14 procedure FormCreate(Sender: TObject); 15 procedure Button1Click(Sender: TObject); 16 procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; 17 Shift: TShiftState; X, Y: Integer); 18 private 19 { Private declarations } 20 procedure OnClick1(Sender:TObject); 21 procedure OnClick2(Sender:TObject); 22 procedure Button1MouseDown1(Sender: TObject; Button: TMouseButton; 23 Shift: TShiftState; X, Y: Integer); 24 procedure Button1MouseDown2(Sender: TObject; Button: TMouseButton; 25 Shift: TShiftState; X, Y: Integer); 26 public 27 { Public declarations } 28 FAgent:TEventAgent; 29 end; 30 31 var 32 Form1: TForm1; 33 34 implementation 35 36 uses System.Rtti; 37 38 {$R *.dfm} 39 40 procedure TForm1.Button1Click(Sender: TObject); 41 begin 42 Memo1.Lines.Add('Button1Click'); 43 end; 44 45 procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton; 46 Shift: TShiftState; X, Y: Integer); 47 begin 48 Memo1.Lines.Add(Format('Clicked at (%d, %d)', [X, Y])); 49 end; 50 51 procedure TForm1.Button1MouseDown1(Sender: TObject; Button: TMouseButton; 52 Shift: TShiftState; X, Y: Integer); 53 begin 54 Memo1.Lines.Add('Button1MouseDown1') 55 end; 56 57 procedure TForm1.Button1MouseDown2(Sender: TObject; Button: TMouseButton; 58 Shift: TShiftState; X, Y: Integer); 59 begin 60 Memo1.Lines.Add('Button1MouseDown2') 61 end; 62 63 procedure TForm1.FormCreate(Sender: TObject); 64 var 65 V:TNotifyEvent; 66 M:TMouseEvent; 67 begin 68 FAgent:=TEventAgent.Create(Button1); 69 V:= Self.OnClick1; 70 FAgent.AddEventNotifier('OnClick', TMethod(V)); 71 V:= Self.OnClick2; 72 FAgent.AddEventNotifier('OnClick', TMethod(V)); 73 M:= Self.Button1MouseDown1; 74 FAgent.AddEventNotifier('OnMouseDown', TMethod(M)); 75 M:= Self.Button1MouseDown2; 76 FAgent.AddEventNotifier('OnMouseDown', TMethod(M)); 77 end; 78 79 procedure TForm1.OnClick1(Sender: TObject); 80 begin 81 Memo1.Lines.Add('OnClick1'); 82 end; 83 84 procedure TForm1.OnClick2(Sender: TObject); 85 begin 86 Memo1.Lines.Add('OnClick2'); 87 end; 88 89 end.
测试程序dfm文件
1 object Form1: TForm1 2 Left = 0 3 Top = 0 4 Caption = 'Form1' 5 ClientHeight = 311 6 ClientWidth = 643 7 OnCreate = FormCreate 8 object Button1: TButton 9 Left = 88 10 Top = 56 11 Width = 75 12 Height = 25 13 Caption = 'Button1' 14 OnClick = Button1Click 15 OnMouseDown = Button1MouseDown 16 end 17 object Memo1: TMemo 18 Left = 264 19 Top = 32 20 Width = 329 21 Height = 225 22 Lines.Strings = ( 23 'Memo1') 24 end 25 end
我的多播代理机制原理是,将所代理对象的所有事件指向代理器对应的函数,由此函数再以此调用多个回调函数。
1.当所代理事件没有任何事件回调时,多播代理不会修改事件函数指针,原对象此事件回调仍然为nil,
2.当所代理事件已经有事件回调函数指针,多播代理会将自己替换原函数指针,并且将原函数指针加入多播列表中.
我的多播机制有如下特点:
1.兼容Delphi的事件回调机制,因此对于老的程序,不用怎么修改,就能被回调多个函数,实现多播。
2.此多播机制不限于界面对象,可代理任何对象,只要此对象有放入public或published的事件property属性,均被自动代理,无所谓其传入的参数是什么类型及有多少个。
3.用户的对象如果需要多播功能,仅需要按照单个事件模式设计即可,多播代理自动帮他实现多播。
再举例1:
比如我们网络通讯假设用的是TTcpClient,从服务器接收数据。接收来的数据进行处理,处理过程有很多,比如有的模块需要存盘到文件,有的处理模块进行数据转发,有的模块需要进行解码分析。
如果使用多播,则可以简单的方法实现。
假如原来的网络程序仅实现了数据存储功能,需要增加解码处理功能,我们不需要修改原来的程序,增加解码模块即可:
1.新建一个DataModule, 放上一个TTcpClient,设置要连接的服务器端口地址
unit Unit2; interface uses System.SysUtils, System.Classes, Web.Win.Sockets, utObjEventAgent; type TDataModule2 = class(TDataModule) TcpClient1: TTcpClient; procedure DataModuleCreate(Sender: TObject); procedure DataModuleDestroy(Sender: TObject); private { Private declarations } public { Public declarations } FLink:TEventAgent; end; var DataModule2: TDataModule2; implementation {%CLASSGROUP 'Vcl.Controls.TControl'} {$R *.dfm} procedure TDataModule2.DataModuleCreate(Sender: TObject); begin FLink:=TEventAgent.Create(TcpClient1); TcpClient1.Active:=True; end; procedure TDataModule2.DataModuleDestroy(Sender: TObject); begin FLink.Free; end; end.
2.接着,只需在不同的模块去接收你的数据,例如数据存储模块:
unit Unit3; interface uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets; type TPersistModule=class protected FStream:TFileStream; private procedure OnDataReceive(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer); public constructor Create; destructor Destroy;override; end; implementation { TPersistModule } constructor TPersistModule.Create; var V:TSocketDataEvent; begin inherited Create; FStream:=TFileStream.Create('C:\test.dat', fmCreate); V:= Self.OnDataReceive; DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V)); end; destructor TPersistModule.Destroy; var V:TSocketDataEvent; begin V:= Self.OnDataReceive; DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V)); FStream.Free; inherited; end; procedure TPersistModule.OnDataReceive(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer); begin FStream.Write(Buf^, DataLen); end; end.
3.数据解码模块
unit Unit4; interface uses utObjEventAgent, Unit2, Classes, Web.Win.Sockets, utDecoder; type TDecodeModule=class protected FDecoder:TDecoder; private procedure OnData(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer); public constructor Create; destructor Destroy;override; end; implementation { TDecodeModule } constructor TDecodeModule.Create; var V:TSocketDataEvent; begin inherited Create; FDecoder:=TDecoder.Create V:= Self.OnData; DataModule2.FLink.AddEventNotifier('OnReceive', TMethod(V)); end; destructor TDecodeModule.Destroy; var V:TSocketDataEvent; begin V:= Self.OnData; DataModule2.FLink.RemoveEventNotifier('OnReceive', TMethod(V)); Fdecoder.Free; inherited; end; procedure TDecodeModule.OnData(Sender: TObject; Buf: PAnsiChar; var DataLen: Integer); begin FDecoder.Decode(Pointer(Buf), DataLen); end; end.
再举例2:
借用 “Delphi 实现事件侦听与触发”的例子:
const evtDataChanged = 'evtDataChanged'; //数据处理类, 用于提供数据 TOnData=procedure( Name, City, CellPhone:String; Age: Integer ) of Object; TNwDataClass = class( TObject) private FOnData:TOnData; public Link:TEventAgent; constructor Create; destructor Destroy;override; procedure AddData( Name, City, CellPhone:String; Age: Integer ); property OnData:TOnData read FOnData write FOnData; end; //界面显示类 TNwInterface = class( TForm ) procedure FormCreate( Sender: TObject ); procedure FormDestroy( Sender: TObject ); protected procedure OnEvent( Name, City, CellPhone:String; Age: Integer ); procedure OnEvent2( Name, City, CellPhone:String; Age: Integer ); public procedure AddDataToList( Name, City, CellPhone:String; Age: Integer); procedure AddDataToFile( Name, City, CellPhone:String; Age: Integer ); end; // TNwDataClass 应该有一个全局的实例, 用于提供数据. 在下面的代码中, 就以 // instanceDataClass 为这个实例 implementation { TNwDataClass } constructor TNwDataClass.Create; begin inherited Create; Link:=TEventAgent.Create(Self); end; destructor TNwDataClass.Destroy; begin Link.Free; inherited; end; procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer ); begin //数据处理代码,忽视Link的存在 if Assigned(FOnData) then FOnData(Name, City, CellPhone, Age); end; { TNwInterface } procedure TNwInterface.FormCreate( Sender: TObject ); var V:TOnData; begin V:= Self.OnEvent; instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V)); V:= Self.OnEvent2; instanceDataClass.Link.AddEventNotifier('OnData', TMethod(V)); end; procedure TNwInterface.FormDestroy( Sender: TObject ); var V:TOnData; begin V:= Self.OnEvent; instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V)); V:= Self.OnEvent2; instanceDataClass.Link.RemoveEventNotifier('OnData', TMethod(V)); end; procedure TNwInterface.OnEvent( Name, City, CellPhone:String; Age: Integer ); begin AddDataToList( Name, City, CellPhone, Age); end; procedure TNwInterface.OnEvent2( Name, City, CellPhone:String; Age: Integer ); begin AddDataToFile( Name, City, CellPhone, Age); end; procedure TNwInterface.AddDataToList( Name, City, CellPhone:String; Age: Integer ); begin //用于处理显示数据的代码. end; procedure TNwInterface.AddDataToFile( Name, City, CellPhone:String; Age: Integer ); begin //用于保存数据的代码. end;