利用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 测试成功.
unit utObjEventAgent; interface uses System.Generics.Collections, DSharp.Core.Events, System.TypInfo, Classes; type
TEventLinker=class(DSharp.Core.Events.TEvent) //单个事件的多播器
protected
FLinkedObject: TObject;
FLinkedProperty: PPropInfo;
FOriginal:TMethod; FEventTypeData:PTypeData;
FEventName:String;
procedure MethodAdded(const Method: TMethod); override;
procedure MethodRemoved(const Method: TMethod); override;
procedure Notify(Sender: TObject; const Item: TMethod;
Action: System.Generics.Collections.TCollectionNotification); override;
property Owner;
property RefCount;
public
constructor Create(LinkedObj:TObject; LinkedPrpt:PPropInfo);
destructor Destroy; override;
end; TEventAgent=class //对象的事件多播代理
protected
FOwner:TObject;
FPropList: PPropList;
FNameList:TDictionary<String, TEventLinker>;
procedure Prepare; virtual;
procedure Clear;
public
constructor Create(aOwner:TObject); virtual;
destructor Destroy;override;
function GetEventCount: Int32;
function GetEventName(Index: Int32): PWideChar;
procedure AddEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 添加事件处理函数
procedure RemoveEventNotifier(EventName: String; const NotifierMethod: TMethod);overload; // 移除时间处理函数
end; implementation uses System.Rtti; { TEventLinker } constructor TEventLinker.Create(LinkedObj:TObject; LinkedPrpt:PPropInfo);
begin
inherited Create(LinkedPrpt.PropType^, nil);
FLinkedObject:=LinkedObj;
FLinkedProperty:=LinkedPrpt;
FEventName:=FLinkedProperty^.Name;
FOriginal:=GetMethodProp(FLinkedObject, FLinkedProperty);
SetMethodProp(FLinkedObject, FLinkedProperty, Self.GetInvoke);
if Assigned(FOriginal.Data) and Assigned(FOriginal.Code) then Add(FOriginal); //将原事件方法加入多播列表
end; destructor TEventLinker.Destroy;
begin
SetMethodProp(FLinkedObject, FLinkedProperty, FOriginal);
inherited;
end; procedure TEventLinker.MethodAdded(const Method: TMethod);
begin
end; procedure TEventLinker.MethodRemoved(const Method: TMethod);
begin
end; procedure TEventLinker.Notify(Sender: TObject; const Item: TMethod;
Action: System.Generics.Collections.TCollectionNotification);
begin
end; { TEventAgent } procedure TEventAgent.AddEventNotifier(EventName: String;
const NotifierMethod: TMethod);
var
V:TEventLinker;
begin
if FNameList.TryGetValue(EventName, V) then
begin
if V.IndexOf(NotifierMethod)< then
V.Add(NotifierMethod);
end;
end; procedure TEventAgent.Clear;
var
Item: TPair<String, TEventLinker>;
begin
for Item in FNameList do
Item.Value.Free;
FNameList.Clear;
if Assigned(FPropList) then FreeMem(FPropList);
end; constructor TEventAgent.Create(aOwner:TObject);
begin
inherited Create;
FNameList:=TDictionary<String, TEventLinker>.Create;
FOwner:=aOwner;
Prepare;
end; destructor TEventAgent.Destroy;
begin
Clear;
FNameList.Free;
inherited;
end; function TEventAgent.GetEventCount: Int32;
begin
Result:=FNameList.Count;
end; function TEventAgent.GetEventName(Index: Int32): PWideChar;
begin
Result:=PWideChar(FNameList.Keys.ToArray[Index]);
end; procedure TEventAgent.Prepare;
var
N, i:Integer;
Linker:TEventLinker;
Context: TRttiContext;
begin
Clear;
N:=GetPropList(FOwner.ClassInfo, FPropList);
for i := to N- do
if FPropList^[i].PropType^.Kind = tkMethod then
begin
if FPropList[i].GetProc=nil then Continue;
Linker:=TEventLinker.Create(FOwner, FPropList[i]);
Linker.FEventName:=FPropList[i].Name;
FNameList.Add(Linker.FEventName, Linker);
end;
end; procedure TEventAgent.RemoveEventNotifier(EventName: String;
const NotifierMethod: TMethod);
var
V:TEventLinker;
begin
if FNameList.TryGetValue(EventName, V) then
begin
V.Remove(NotifierMethod);
end;
end; end.
测试程序演示一个TButton被事件多播代理,其OnClick,OnMouseDown均有3个多播方法。
测试程序:
unit Unit1; interface uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, utObjEventAgent, DSharp.Core.Events, ObjAuto,
Vcl.StdCtrls; type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
procedure OnClick1(Sender:TObject);
procedure OnClick2(Sender:TObject);
procedure Button1MouseDown1(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button1MouseDown2(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
{ Public declarations }
FAgent:TEventAgent;
end; var
Form1: TForm1; implementation uses System.Rtti; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Button1Click');
end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add(Format('Clicked at (%d, %d)', [X, Y]));
end; procedure TForm1.Button1MouseDown1(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add('Button1MouseDown1')
end; procedure TForm1.Button1MouseDown2(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Memo1.Lines.Add('Button1MouseDown2')
end; procedure TForm1.FormCreate(Sender: TObject);
var
V:TNotifyEvent;
M:TMouseEvent;
begin
FAgent:=TEventAgent.Create(Button1);
V:= Self.OnClick1;
FAgent.AddEventNotifier('OnClick', TMethod(V));
V:= Self.OnClick2;
FAgent.AddEventNotifier('OnClick', TMethod(V));
M:= Self.Button1MouseDown1;
FAgent.AddEventNotifier('OnMouseDown', TMethod(M));
M:= Self.Button1MouseDown2;
FAgent.AddEventNotifier('OnMouseDown', TMethod(M));
end; procedure TForm1.OnClick1(Sender: TObject);
begin
Memo1.Lines.Add('OnClick1');
end; procedure TForm1.OnClick2(Sender: TObject);
begin
Memo1.Lines.Add('OnClick2');
end; end.
测试程序dfm文件
object Form1: TForm1
Left =
Top =
Caption = 'Form1'
ClientHeight =
ClientWidth =
OnCreate = FormCreate
object Button1: TButton
Left =
Top =
Width =
Height =
Caption = 'Button1'
OnClick = Button1Click
OnMouseDown = Button1MouseDown
end
object Memo1: TMemo
Left =
Top =
Width =
Height =
Lines.Strings = (
'Memo1')
end
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;
利用RTTI实现Delphi的多播事件代理研究的更多相关文章
- Atitit事件代理机制原理 基于css class的事件代理
Atitit事件代理机制原理 基于css class的事件代理 1.1. 在javasript中delegate这个词经常出现,看字面的意思,代理.委托1 1.2. 事件代理1 1.3. 代理标准化规 ...
- js中的事件委托或是事件代理详解
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- JS 事件代理
事件处理器:onclick.onmouseover.... 在传统的事件处理中,你需要为每一个元素添加或者是删除事件处理器.然而,事件处理器将有可能导致内存泄露或者是性能下降——你用得越多这种风险就越 ...
- javascript事件代理(委托)
之前有接触过事件代理,但是印象并不深刻.这次记下来加强印象. 用个大家比较常见的代码举例子: html dom结构: <ul id="ul1"> <li>0 ...
- JS中事件代理与委托
在javasript中delegate这个词经常出现,看字面的意思,代理.委托.那么它究竟在什么样的情况下使用?它的原理又是什么?在各种框架中,也经常能看到delegate相关的接口.这些接口又有什么 ...
- 关于JavaScript中的事件代理
今天面试某家公司Web前端开发岗位,前面的问题回答的都还算凑活,并且又问了一下昨天面试时做的一道数组去重问题的解题思路(关于数组去重问题,可以观赏我前几天写的:http://www.cnblogs.c ...
- JavaScript中事件委托(事件代理)详解
在JavaScript的事件中,存在事件委托(事件代理),那么什么是事件委托呢? 事件委托在生活中的例子: 有三个同事预计会在周一收到快递.为签收快递,有两种办法:一是三个人在公司门口等快递:二是委托 ...
- [转] js中的事件委托或是事件代理详解
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- 【转载】浅谈事件冒泡与事件捕获 - javascript 事件代理
原文:https://segmentfault.com/a/1190000000749838 事件冒泡与事件捕获 事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发 ...
随机推荐
- .net core系列之《从源码对Configuration的底层运行机制进行分析》
通过对Configuration源代码的分析从而来自定义一个配置数据源 1.用反编译工具来看看AddJsonFile()这个方法究竟干了什么,源代码如下: public static IConfigu ...
- Docker相关连接
docker-compose文档:https://docs.docker.com/compose/compose-file/ dockerfile文档:https://docs.docker.com/ ...
- linux的pthread_self与gettid的返回值和开销的区别
linux的pthread_self与gettid的返回值和开销的区别 linux的pthread_self与gettid的返回值和开销的区别 分类: 一些思考 2012-05-18 12:25 17 ...
- 串口编程 System.IO.Ports.SerialPort类
从Microsoft .Net 2.0版本以后,就默认提供了System.IO.Ports.SerialPort类,用户可以非常简单地编写少量代码就完成串口的信息收发程序.本文将介绍如何在PC端用C# ...
- 相同数据源情况下,使用Kafka实时消费数据 vs 离线环境下全部落表后处理数据,结果存在差异
原因分析: 当某个consumer宕机时,消费位点(例如2s提交一次)尚未提交到zookeeper,此时Kafka集群自动rebalance后另一consumer来接替该宕机consumer继续消费, ...
- hql to_number
select max(cast(o.ordernum,int)) from className o or select max(cast(o.ordernum as int)) from classN ...
- JavaScript的事件概述以及事件对象,事件流
事件处理程序 JavaScript 事件对象是由访问 Web 页面的用户引起的一系列操作,例如:用户点击页面上的某个按钮或者鼠标移动到页面的某个图片上而产生一系列的互动的反馈. 我们通过为指定事件绑定 ...
- BZOJ1880:[SDOI2009]Elaxia的路线(最短路,拓扑排序)
Description 最近,Elaxia和w**的关系特别好,他们很想整天在一起,但是大学的学习太紧张了,他们 必须合理地安排两个人在一起的时间.Elaxia和w**每天都要奔波于宿舍和实验室之间, ...
- 3、Dubbo-环境搭建
官方推荐使用 Zookeeper 注册中心 3.1).[windows]-安装zookeeper 开发中均在Linux中安装!!! 1.下载zookeeper 网址 https://archive.a ...
- 2016-2017-20155329 《Java程序设计》第9周学习总结
学号 2016-2017-20155329 <Java程序设计>第9周学习总结 教材学习内容总结 学习目标 了解JDBC架构 掌握JDBC架构 掌握反射与ClassLoader 了解自定义 ...