miranda插件开发思想

  • Post author:
  • Post category:其他



http://www.sayme.cn/blog/article.asp?id=1

最近接触了一些插件式开发的软件,很是感兴趣。觉得插件式开发首先从软件结构上来说是清晰且完美的,而从开源思想来看,能够让众多的用户来在你的软件上叠加新的功能,这个力量将是惊人的。firefox等优秀软件的成功我认为可以算是插件式开始的成功。


Miranda IM (1) 事件处理机制

Miranda 是一个支持多协议的,运行于windows平台下的IM软件。其使用pure c语言编写,其架构体系支持插件方式加载,如多协议的支持msn,yahoo,gtalk等都以插件的方式加载,用户可以根据需要加载,同时皮肤界面相关组件如clist等也以插件的方式加载。

如上所述平台的实现以一些技术实现为基础,先要说的是miranda中的事件定义,通知机制。因为使用纯C编写的,所以不可能采用如com中的连接点等方式来作为事件通知,但miranda采用的事件通知方式,跟COM的连接点方式原理类似。所不同的是COM的实现如MFC,ATL有一套机制来实现,这样用户可以定义不同的事件接口函数。而在纯C的实现中,我们要保证程序的简洁实用,最简单的办法当然是所有事件函数的原型是一样的,这样有助于事件的管理和程序实现。其事件原型是 typedef int (*MIRANDAHOOK)(WPARAM,LPARAM) 有点类似于win32的消息处理函数。

1  事件定义:

HANDLE CreateHookableEvent(const char *name) 创建一个事件,即其他模块可以和这个事件绑定,到此事件发生时,通知相应模块。

static THookList *hook 。此链表记录通过上面函数创建的事件。创建的时候以一个hash算法将字符串转化为一个整数,这个整数为这个事件的唯一标识。

2 HookEvent:

即相当于COM 连接点的Advise的过程。

HANDLE HookEvent(const char *name,MIRANDAHOOK hookProc)

THookList结构中THookSubscriber* 记录通过HookEvent传递的事件函数指针,其也为一个链表。

3 卸载Hook

int UnhookEvent(HANDLE hHook)

调用此函数卸载通过HookEvent放上去的事件。

4 事件通知

当特定事件产生需要通知时,调用函数来实现事件回调并通知

int (*NotifyEventHooks)(HANDLE,WPARAM,LPARAM)

由THookSubscriber定义

typedef struct {

MIRANDAHOOK pfnHook;

HINSTANCE hOwner;

HWND hwnd;

UINT message;

} THookSubscriber;

可知其还可以实现一个win32消息的回调,即通过Windows消息的方式来通知那些通过HookEventMessage添加的窗口句柄。

综上所述,miranda在事件实现这块代码并不复杂,虽然不像COM的连接点那样灵活,但也基本上满足需要。

在分析Miranda插件体系之前,先看看一个简单的COM为基础的插件加载方式。



在这种以接口为基础的结构中,每个插件需要实现接口IPlugin接口,IPlugin接口有两个接口函数:

OnLoad

OnUnLoad

这两个函数很好理解,即主程序加载插件时调用OnLoad,卸载插件时调用OnUnLoad。

1 程序加载插件时,可以看到OnLoad的参数IPluginManager,这个参数是指向插件管理器的指针,插件实现方可以通过这个接口来获取其他接口(通过QueryObject接口函数),可以向这个管理器中加入新的接口(通过AddObject接口函数)

2 当此插件被卸载时,即OnUnLoad被调用时,插件实现方需要调用RemoveObject将自己加入到插件管理器中的接口删除。

3 添加和删除接口的通知。在插件式体系中,可能一些插件接口某些任务的完成或者开始依赖于其他插件的接口,即A插件需要得知我是什么时候可以得到B插件,一个简单的实现方式就是在B插件的接口加入到插件管理器中后,则显示的通知A接口。同时如果A插件向管理器中加入的接口,那么他在卸载的时候会将此接口删除,而删除前也需要做一个通知,通知所有使用此接口的插件停止使用此接口。这个可以使用连接点的方式来通知。

综上所述,一个简单的插件平台就搭建起来了,之所以先讲这个基于COM的插件平台,是发现Miranda实现的插件平台跟这种以COM为基础的插件平台类似。

下面说说Miranda的插件体系及具体实现:

Miranda类似上面所述的插件管理器称之为“service”,所有“service”函数存放在TServiceList 这个List中,其由以下函数完成:

1 HANDLE CreateServiceFunction(const char *name,MIRANDASERVICE serviceProc)

创建一个service函数,将其加入到全局的TServiceList列表中,其他接口也通过此name来调用这个加入的函数。

2  int DestroyServiceFunction(HANDLE hService)

删除一个service函数

3  int CallService(const char *name,WPARAM wParam,LPARAM lParam)

即通过name来调用全局service List中的函数。

由上面的分析可以得知,Miranda这种基于C的简单实现有以下弊端:

> 函数的唯一标识name,由自己定义,并自己保证其不重复,而COM中,使用GUID来定义从某种程度上程序来保证了其不重复。

> 其加入的函数原型MIRANDASERVICE 是统一的,即加入的函数函数原型是一致的。而在COM实现中,可以有更加灵活的接口函数加入。

> 没有通知机制,即这些函数的调用可能因为某插件被卸载而失败。也使得如果插件之间有相互的调用,则其加载有其先后顺序,不能做到无序加载。

在插件的加载时,由于插件一般都位于独立的dll中,所以在miranda的实现中使用了PLUGINLINK *link

来将主程序的主要函数传给插件方,PLUGINLINK实际上是一组函数指针组成的结构:

typedef struct {

HANDLE (*CreateHookableEvent)(const char *);

int (*DestroyHookableEvent)(HANDLE);

int (*NotifyEventHooks)(HANDLE,WPARAM,LPARAM);

HANDLE (*HookEvent)(const char *,MIRANDAHOOK);

HANDLE (*HookEventMessage)(const char *,HWND,UINT);

int (*UnhookEvent)(HANDLE);

HANDLE (*CreateServiceFunction)(const char *,MIRANDASERVICE);

HANDLE (*CreateTransientServiceFunction)(const char *,MIRANDASERVICE);

int (*DestroyServiceFunction)(HANDLE);

int (*CallService)(const char *,WPARAM,LPARAM);

int (*ServiceExists)(const char *);          //v0.1.0.1+

int (*CallServiceSync)(const char *,WPARAM,LPARAM);        //v0.3.3+

int (*CallFunctionAsync) (void (__stdcall *)(void *), void *);    //v0.3.4+

int (*SetHookDefaultForHookableEvent) (HANDLE, MIRANDAHOOK); // v0.3.4 (2004/09/15)

} PLUGINLINK;

这样给插件方提供了一种方式来调用service list中的函数,这个跟我们之前COM的简单插件模型中的OnLoad(IPluginManager*) 的想法不谋而合。

总的来说,前面讲的这两种插件方式总体思想是略有不同,COM的实现可扩展性更强,而miranda中的这个实现更加优雅简单。