Delphi 插件(Plug-ins)创建、调试与使用应用程序扩展(二)[1]

[入库:2005年8月18日] [更新:2007年3月24日]

本文简介:选择自 jxd_2001 的 blog

 

延伸父应用

这个简单的插件不错,不过它不能做什么有用的事情。第二个例子就是纠正这个问题。
这个插件的目标就是在父应用程序的主菜单中加入一个项目。这个菜单项目,当被单
击时,就会执行插件内的一些代码。图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段中被创建。现在我们就

本文关键:Delphi控件杂项
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top