今天早上看大富翁资料,看到以前小雨哥发的一个,觉得很不错WinAMP的插件结构至今都还是经典的插件框架模型,它的模块框架设计通常在外部
看来只输出一个以winampXXXGetHeader命名的函数(XXX是插件模块的种类名称)。首先设计插件函数。插件应用的特点是主程序可以控制插件程序的行为,但因为真
正的插件往往在主程序完成后才被开发出来,所以主程序要事先约定调用插件时的
调用函数,这种函数主要:a) 初始化函数
b) 退出函数
c) 配置函数初始化函数用于插件初始化,退出函数用于插件在退出前的清理。配置函数用于主
程序通知插件本次加载只做配置操作的目的。这些函数如果一个一个定义,像上面描述的,就需要定义分别定义三个唯一的名字,
在WinAMP中是使用结构体的形式来输出的,而不是对以上三个函数做一一定义,这样
真正的插件只要输出一个函数就可以了,这个函数就是我上面提到的: winampXXXGetHeader()通过对这个函数的调用,函数返回一个数据结构的指针,这个数据结构中则包含了上
述三个函数的真实地址,整个插件DLL就类似下面的样子:==========================================================================library Project1;uses
  Windows,
  SysUtils,
  Classes;{$R *.res}type
 PwinampXXXModule = ^TwinampXXXModule;
 TwinampXXXModule = record
   Desc:PChar;
   Parenthwnd:HWND;
   hDllInst:THandle; //HInstance;
   ConfigFunc:Pointer;
   InitFunc:Pointer;
   QuitFunc:Pointer;
   userData:Pointer;
 end;上面结构中:
   Desc:这个插件模块的描述,由插件填充
   Parenthwnd:父窗口句柄,用于消息通讯。由主程序填充
   hDllInst:模块实例。由主程序填充
   ConfigFunc:配置函数。由插件填充
   InitFunc:初始化函数。由插件填充
   QuitFunc:退出函数。由插件填充
   userData:用户数据。如果有的话,由主程序填充var
 mod1:TwinampXXXModule;function Config(module:PwinampXXXModule):integer;stdcall;
begin
 Result := 0;
end;function Init(module:PwinampXXXModule):integer;stdcall;
begin
 Result := 0;
end;function Quit(module:PwinampXXXModule):integer;stdcall;
begin
 Result := 0;
end;function winampXXXGetHeader():PwinampXXXModule;stdcall;
begin
  FillChar(mod1,sizeof(mod1),0);
  with mod1 do
  begin
    Desc:='插件描述';
    ConfigFunc:= @Config;
    InitFunc := @init;
    QuitFunc:= @Quit;
  end;
end;exports
  winampXXXGetHeader;begin
end.==========================================================================主程序调用winampXXXGetHeader获得TwinampXXXModule记录的指针,当主程序开始
调用这个记录中记录的init或者Config等函数时,首先对这个记录的Parenthwnd成员
进行填充,把自己的窗口句柄填到里面,当调用进入插件函数时,插件函数首先检查
传回来的参数是否是自己:主程序调用 winampXXXGetHeader:type
 PXXXModule = ^TXXXModule;
 TXXXModule = record
   Desc, Parenthwnd,
   hDllInst,ConfigFunc,
   InitFunc,QuitFunc,
   userData:Integer;
 end;type
 TBackCallFunc = function(module:PXXXModule):integer;stdcall;var
 p:PXXXModule;
 Func:TBackCallFunc;
begin
 p := winampXXXGetHeader;
 p^.Parenthwnd := Self.Handle;// 如果调用配置:
 Func := TBackCallFunc(Pointer(P^.ConfigFunc)^);
 if @Func <> nil then
  Func(p);// 如果调用初始化:
 Func := TBackCallFunc(Pointer(P^.InitFunc)^);
 if @Func <> nil then
  Func(p);// 如果调用退出:
 Func := TBackCallFunc(Pointer(P^.QuitFunc)^);
 if @Func <> nil then
  Func(p);end;==========================================================================插件收到调用后(例如调用了 Config 函数):function Config(module:PwinampXXXModule):integer;stdcall;
begin
 if module = mod1 then
 begin
  // 这里开始初始化并运行配置部分的代码
  // 最后返回运行的状态结果
  Result := 0;
 end
 esle Result := 1;
end;==========================================================================上面举例中重复写了 Module 的记录结构,但这个记录结构与真正的插件记录的指定类型
并不完全相同,这是没有关系的,只要保证记录结构的数据块大小一致就可以了,调用的
时候可以根据需要来强制转换类型。真实的插件设计实际上与上面举的样例非常相似,因为不知道记录结构最终按照什么样子
定义最好,通常是做一个基本抽象后,利用字节占位来保证记录块大小即可。按照上面的方法,就可以先设计主程序,后实现功能模块了,而实现的功能模块几乎不受
主程序的限制。需要说明的是 WinAMP 中还大量使用了消息机制,用于同步和通讯。在这种框架设计中,
使用消息来连接各个模块的运行还是非常有效的,比如初始化时,init 函数通常会发出一
个初始化开始的消息,来通知插件的主体程序代码进行初始化等等。有效归有效,是否是
最好的方法就不好说了,因为这个不涉及框架,仅仅是运行的触发条件和数据交互的方法。
所以不在这里讨论了(值得注意的是如果使用消息通讯,如果主程序与插件模块在同一个
线程中跑的话,对于 SendMessage 这类的消息发送,就需要开线程来发送,否则,呵呵,
等着死锁吧)。