WinAMP 插件框架(0分)

  • 主题发起人 主题发起人 小雨哥
  • 开始时间 开始时间

小雨哥

Unregistered / Unconfirmed
GUEST, unregistred user!
重要的:文中代码都是伪代码,没有正真上机编译过,在这里仅表达代码流程。
WinAMP 插件框架 (by 小雨哥)
==========================================================================
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 mod1do
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 这类的消息发送,就需要开线程来发送,否则,呵呵,
等着死锁吧)。
 
严重支持分享!基于消息的机制进行框架的解耦也是一种不错的方式啊。
 
后退
顶部