|
插件是Adobe Photoshop 、Winamp 等软件中重要的技术。在Photoshop 中有“滤镜”功能组,在该功能组中可对图像进行多种效果的处理。人们开发出新的滤镜功能,无须发布一个新版本Photoshop 或重新编译Photoshop ,这一切的解决方案就是使用插件。
1. 插件本质与特征
插件本质上是一种应用工程的扩展,它具有以下特性:应用工程可自动加载符合插件特征的任意文件(如:扩展名为plg) ,如指定文件存在相应的接口函数,则工程可加载;否则,如指定文件只是碰巧符合插件特征,则自动放弃。Photoshop 中的“滤镜”即是将新内容复制到工程目录下,Photoshop 自动将其加载在主工程中而并非发布一个新版本的Photo2shop 来支持新的滤镜功能。由此可见插件实际提供了一种可方便自己、或第三方开发商扩展功能的途径。
2. 插件开发
插件实际上就是动态链接库,其开发技术和动态链接库有很大相似性,但其接口函数应符合主工程插件接口声明。插件是通过一个特定的接口与父应用程序交互的,而这个接口将根据实际需要来定义,因此首先最重要的事情就是搞清楚其应用程序到底需要扩展哪些功能。下面将进一步以创建插件,展示其与父应用程序相交互的方式来完成在Delphi 中对实现插件技术的研究。
2. 1 创建支持插件的父应用程序首先创建一个扩展名为’. plg’的动态链接库。该动态链接库中定义一个接口函数“procedure InitMenu ( MainMenuIndex : integer ; PlugInMenu : TMainMenu) ;”(注意:该函数实际上是在主工程设计时设计的,在DLL 中只是申明了一个主工程中定义的接口函数,并完成该接口函数定义的功能而已) 。此接口函数完成在主功能菜单中增加一个菜单项,并完成点击该项后的操作。插件外壳程序(支持插件的父应用程序) 与普通应用程序之间的唯一不同就在于工程源文件中加载插件文件的代码。插件通过LoadPlugInFiles 过程加载到这个测试外壳中,这个过程在主窗口的FormCreate 事件中调用,其代码见附1 。该过程使用API 函数FindFirst 和FindNext 在应用程序所在目录中查找插件文件。其中FindFirst 是指完成找到指定的文件,如果找到,则将指定的文件信息,存放在TsearchRec 中,并把FileFound 置为0 。FindNext ,FindClose 必须与FindFirst 配合使用。如果不配合使用,有可能丢失指定的文件并信息。注意用完后一定要释放TsearchRec ,一般使用保护模块确保释放完全。当发现具有指定插件特征的文件时, 就可调用附2 中的LoadPlugIn( sr) 过程将其加载。先使用API 函数LoadLibrary ,导出动态库,如果成功,将返回非0 值。再使用GetProcAddressAPI 函数,导出动态库中的接口函数,如果导出失败,则返回值为NULL。在此过程中相关加载时的出错信息,也需要在该函数中完成。LoadPlugIn 方法展示了插件机制的核心。首先,插件被写成DLL ;其次,通过LoadLibrary API 它被动态地加载。一旦DLL 被加载,API 调用GetProcAddress ,返回一个指向指针。在此例中,插件仅包含一个名为InitMenu 的过程,其中过程名的大小写非常重要,传递到GetProcAddress 的名称也必须与包含在DLL 中的例程名称完全一致。如果在DLL 中没有找到请求的例程,GetProcAddree 将返回nil ,这样就允许使用Assigned 函数测定返回值。GetProcAddress 的返回值被存储在一个变量中, InitMenu ,属于TInitMenu类型。声明:
type
TInitMenu = procedure (MainMenuIndex : integer ;
PlugInMenu : TMainMenu) ; stdcall ;
由于过程存在于DLL 内部,因此需要使用stdcall 指示字。要调用刚刚获得的过程,只需要使用保存地址的变量作为过程名,后面跟上任何参数。就例程而言,声明:
InitMenu(1 ,MainMenu1)
2. 2 构造插件
创建好了父应用程序,现在创建希望加载的插件。插件文件是一个标准的Delphi DLL ,所以从Delphi IDE 中创建一个新DLL 工程,保存它。以下即此范例插件的工程源文件。
library PlugIn ;
uses
SysUtils ,
Classes ,
Unit1 in ’Unit1. pas’;
{ $E plg}
{ $R 3 . res}
exports
InitMenu ;
begin
end.
虽然插件是一个DLL 文件,但是没有必要一定要给它一个. DLL 的扩展名。当父应用程序寻找要加载的文件时,新的扩展名可以作为特定的文件掩模。通过使用别的扩展名( 例子使用了3 . plg) ,你可以在一定程度上确信应用程序只会载入相应的文件。编译指示字$X 可以实现这个改变,也可以通过Project Options 对话框的Application 页来设置扩展名。
例子插件的代码并不是很复杂。附3 显示了包含在一个新单元中的代码。声明导出函数时,一定要使用Stdcall 关键字。该关键字表明参数压栈方式为windows 标准压栈方式。在主程序一端也要申明该关键字。此外由于事件定义在Delphi 中只能存在于对象中,所以定义一个名叫Holder 的类,在该类中完成点击事件定义。注意,InitMenu 原型与外壳应用程序中的TInitMenu 类型相一致,使用附加的export保留字指定该过程将被导出。被导出的过程名称也将会出现在主工程源代码的exports 段中。综上所述,插件是一个DLL 文件,本质上是一种应用工程的扩展。插件开发技术和动态链接库的区别在于其接口函数应符合主工程插件的接口声明。诚然,插件并不是作为一种通用的解决方案。有些情况下额外的复杂度无法验证其正确性,或者应用程序压根儿就不打算把自身搞成几块可扩展的单元,还有或者一些其它的方法也可以达成同样的效果。但是至少在程序开发的灵活性、适用性,商业性等方面插件技术具有其重要的意义。
附1 :寻找插件
{ 在应用程序目录下查找插件}
procedure TForm1.LoadPlugInFiles ;
var
sr : TSearchRec ;
FileFound : integer ;
begin
try
FileFound : = FindFirst ( ExtractFilePath (Application. Exe2
Name) + ’3 . plg’,0 ,sr) ;
while FileFound = 0 do
begin
LoadPlugIn( sr) ;
FileFound : = FindNext ( sr) ;
end ;
finally
FindClose ( sr) ;
end ;
end ;
附2 :载入插件
{ 加载指定的插件}
procedure TForm1.LoadPlugIn( sr : TSearchRec) ;
var
LibHandle : THandle ;
InitMenu : TInitMenu ;
begin
LibHandle : = LoadLibrary(pChar ( sr.Name) ) ;
if LibHandle < > 0 then
begin
InitMenu : = GetProcAddress ( LibHandle ,’Init2
Menu’) ;
if Assigned( InitMenu) then
begin
InitMenu(1 ,MainMenu1) ;
end ;
end ;
end ;
附3 :例子插件的主程序
unit Unit1 ;
interface
uses windows , Menus , Dialogs , Classes ;
type
THolder = class
public
procedure MenuClick(Sender : TObject) ;
end ;
procedure InitMenu ( MainItemIndex : integer ; PlugIn2
Menu : TMainMenu) ; stdcall ;export ;
var
Holder : THolder ;
implementation
procedure InitMenu ( MainItemIndex : integer ; PlugIn2
Menu : TMainMenu) ;
var
tmpItem: TMenuItem;
begin
tmpItem: = TMenuItem. Create(PluginMenu) ;
tmpItem. Caption : = ’Test’;
tmpItem.OnClick : = Holder.MenuClick ;
PlugInMenu. Items[MainItemIndex] .Add(tmpItem) ;
end ;
{ THolder }
procedure THolder.MenuClick(Sender : TObject) ;
begin
ShowMessage (’plugin - click’) ;
end ;
initialization
Holder : = THolder. Create ;
finalization
Holder. Free ;
end |
|