延伸父应用
这个简单的插件不错,不过它不能做什么有用的事情。第二个例子就是纠正这个问题。
这个插件的目标就是在父应用程序的主菜单中加入一个项目。这个菜单项目,当被单
击时,就会执行插件内的一些代码。图6显示外壳程序的改进版,两个插件都已经加
载。在这个版本的外壳程序中,一个名为plug-in的新菜单项目,被添加到主菜单中。
插件会在运行时加入一个菜单项。
图6:加载了两个插件的外壳程序的改进版
为了实现这个目的,我们必须在插件dll中定义第二个接口。现有的dll只导出了一
个过程,describeplugin。第二个插件将声明一个叫做initplugin的过程。不过,在
这个过程可以在主应用程序中看到以前,必须修改loadplugin来配合它。
图7所示的代码展示了改进的过程。
procedure tfrmmain.loadplugin(sr: tsearchrec);
var
description: string;
libhandle: integer;
describeproc: tplugindescribe;
initproc: tplugininit;
begin
libhandle := loadlibrary(pchar(sr.name));
if libhandle <> 0 then
begin
// 查找 describeplugin.
describeproc := getprocaddress(libhandle,
cplugin_describe);
if assigned(describeproc) then
begin
// 调用 describeplugin.
describeproc(description);
memplugins.lines.add(description);
// 查找 initplugin.
initproc := getprocaddress(libhandle, cplugin_init);
if assigned(initproc) then
begin
// 调用 initplugin.
initproc(mnumain);
end;
end
else
begin
messagedlg('file "' + sr.name +
'" is not a valid plugin.',
mtinformation, [mbok], 0);
end;
end
else
begin
messagedlg('an error occurred loading the plugin "' +
sr.name + '".', mtinformation, [mbok], 0);
end;
end;
图 7: 改进过的loadplugin方法
如你所见,当getprocaddress第一次查找调用描述过程之后,又调用了一次
getprocaddress。这一次,我们要寻找的是常量cplugin_init,定义如下:
const
cplugin_init = 'initplugin';
返回值存储在tplugininit类型的变量中,定义如下:
type
tplugininit = procedure(parentmenu: tmainmenu); stdcall;
当initplugin方法被执行时,父应用程序的主菜单被当作一个参数传递给它。这个
过程可以按照自己的意愿修改菜单。由于所有getprocaddress的返回值都用assigned
测试,新版本的loadplugin过程仍然会加载不包含initplugin过程的第一个插件。在
这个过程中第一次调用寻找describeplugin方法会通过,第二次寻找initplugin会
无响应失败。
现在新的接口已经定义好了,可以为新的initplugin方法编写代码了。像原先一样,
新插件的实现代码存在于一个单独的单元中。图8显示了修改过的包含initplugin方法
的main.pas。
unit main;
interface
uses dialogs, menus;
type
tholder = class
public
procedure clickhandler(sender: tobject);
end;
procedure describeplugin(var desc: string);
export; stdcall;
procedure initplugin(parentmenu: tmainmenu);
export; stdcall;
var
holder: tholder;
implementation
procedure describeplugin(var desc: string);
begin
desc := 'test plugin 2 - menu test';
end;
procedure initplugin(parentmenu: tmainmenu);
var
i: tmenuitem;
begin
// 创建新菜单项.
i := newitem('plugin &test', scnone, false, true,
holder.clickhandler, 0, 'mnutest');
parentmenu.items[1].add(i);
end;
procedure tholder.clickhandler;
begin
showmessage('clicked!');
end;
initialization
holder := tholder.create;
finalization
holder.free;
end.
图 8: 第二个插件的代码
很明显,对原始插件的第一个改变就是增加了initplugin过程。像原先一样,带有
export关键字的原型被加入到单元顶端的列表中,过程名也被加入到工程源代码的
exports子句列表中。这个过程使用newitem函数创建一个新的菜单项,返回值是
tmenuitem对象。新菜单项通过下列语句被加入到应用程序主菜单中:
parentmenu.items[1].add(i);
在测试外壳主菜单上的items[1]是菜单项plug-in,所以这个语句在plugin菜单条
上添加一个叫plug-in test的菜单项。
为了处理对新菜单项的响应,作为它的第五个参数,newitem可以接受一个
tnotifyevent类型的过程,这个过程将在菜单项被点击时调用。不幸的是,按照定
义,这种类型的过程是一个对象方法,然而在我们的插件中并没有对象。如果我们想
用通常的指针来指向函数,那么得到的将只会是delphi编译器的抱怨。所以,唯一的
解决办法就是创建一个处理菜单点击的对象。这就是tholder类的用处。它只有一个方
法,是一个叫做clickhandler的过程。一个叫做holder的全局变量,在修改过的main.pas
的var段中被声明为tholder类型,并且在单元的initialization段中被创建。现在我们就