当前位置:编程学习 > Delphi >>

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

 

延伸父应用

这个简单的插件不错,不过它不能做什么有用的事情。第二个例子就是纠正这个问题。
这个插件的目标就是在父应用程序的主菜单中加入一个项目。这个菜单项目,当被单
击时,就会执行插件内的一些代码。图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;
 
inte易做图ce
 
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段中被创建。现在我们就
有一个对象了,我们可以拿它的方法(Holder.ClickHandler)当作NewItem函数的参数。
  搞了这一通,ClickHandler除了显示一个"Clicked!"消息对话框以外什么以没干。
也许这不怎么有趣,不过它仍然证明了一点:插件DLL成功的修改了父应用的主菜单,
表现了它的新用途。并且如同第一个例子一样,不管这个插件在不在应用程序都能执行。
  由于我们创建了一个对象来处理菜单点击,那么在不再需要这个插件时,就要释放这
个对象。修改后的单元中会在finalization段中处理这件事情。Finalization端时与
initialization段相对应的,如果前面有一个initialization段,那么在应用程序终
止时finalization段一定会得到执行。把下面的语句
Holder.Free
加到finalization段中,以确保Holder对象会被正确的释放。
  显而易见,虽然这个插件只是修改了外壳应用的主菜单,但是它可以轻易地操纵传
递到InitPlugin过程中的任何其他对象。如果有必要,插件也可以打开自己的对话框,
向列表框(List boxes)和树状视图(tree views)中添加项目,或者在画布(canvas)
中绘画。

事件驱动的插件

  到现在为止我们所描述的技术可以产生一种通用的扩展应用程序的方法。通过增加
新菜单、窗体和对话框,就可以实现全新的功能而不必对父应用做任何修改。不过仍
然有一个限制:这只是一种单侧(one-sided)机制。正如所看到的,系统依赖用户的
某些操作才能启动插件代码,比如点击菜单或者类似的动作。代码运行起来以后,又要
依靠另外一个用户动作来停止它,例如,关闭插件可能已经打开的窗体。克服这种缺
陷的一种可行的方法就是使插件可以响应父应用中的动作--模仿在Delphi中工作地
很好的事件驱动编程模型的确有效。
在最后一个例子插件中,我们将创建一种机制,插件可以藉此响应父应用中产生的事
件。通常情况下,可以通过判定需要触发哪些事件、在父应用中为每个事件创建一个
Tlist对象来实现。然后每个Tlist对象都被传递到插件的初始化过程中,如果插件想
在某个事件中执行动作,它就把负责执行的函数地址加入到对应的TList中。父应用在
适当的时刻循环这些函数指针的列表,按次序调用每个函数。通过这种方法,就为多
个插件在同一事件中执行动作提供了可能。
应用程序产生的事件完全依赖于程序已确定的功能。例如,一个TCP/IP网络应用程序
可能希望通过TclientSocket的onRead事件通知插件数据抵达,而一个图形应用程序可
能对调色板的变化更感兴

补充:软件开发 , Delphi ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,