科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件初探Delphi中的插件编程

初探Delphi中的插件编程

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

插件结构的编程需要一个插件容器来控制各DLL的运行情况,将划分好的每个子系统安排到一个DLL库文件中。

作者:caishaoting 来源:CSDN BLOG 2007年10月31日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
 容器程序的实现

  1、接口函数的引入

  调用DLL库中的函数有显式和隐式两种方式,显式调用更灵活,因此我们使用显示调用。在Delphi中需要为接口函数申明函数类型,然后实例化函数类型的实例,该实例实际是一个指向函数的指针,通过指针我们可以访问到函数并传递参数、获取返回值。在单元文件的Interface部分加入函数类的申明:

type

//定义接口函数类型,接口函数来自DLL接口

TShowDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):Boolean;stdcall;

TFreeDLLForm = Function(AHandle:THandle; ACaption: String; AUserID:string):boolean;stdcall;

  显示调用库函数需要如下几个步骤:

  1) 载入DLL库文件

  2) 获得函数地址

  3) 执行函数

  4) 释放DLL库

  接下来我们将详细讨论这几个步骤。

  2、载入DLL库文件

  通过调用API函数LoadLibrary可以将DLL库载入到内存中,在此我们不讨论DLL对内存管理的影响。LoadLibrary的参数是DLL文件的地址路径,如果载入成功会返回一个CARDINAL类型的变量作为DLL库的句柄;如果目标文件不存在或其他原因导致载入DLL文件失败会返回一个0。

  3、实例化接口函数

  获得接口函数指针的API函数为GetProcAddress(库文件句柄,函数名称),如果找到函数则会返回该函数的指针,如果失败则返回NIL。
使用上文定义的函数类型定义函数指针变量,然后使用@操作符获得函数地址,这样就可以使用指针变量访问函数。主要代码如下:

……
var
 ShowDLLForm: TShowDLLForm; //DLL接口函数实例
 FreeDLLForm: TFreeDLLForm;
begin
 try
 begin
  APlugin.ProcAddr := LoadLibrary(PChar(sPath));
  APlugin.FuncFreeAddr := GetProcAddress(APlugin.ProcAddr,’FreeDLLForm’);
  APlugin.FuncAddr := GetProcAddress(APlugin.ProcAddr ,’ShowDLLForm’);

  @ShowDLLForm:=APlugin.FuncAddr ;
  @FreeDLLForm:=APlugin.FuncFreeAddr;
  if ShowDllForm(Self.Handle, APlugin.Caption , APlugin.UserID) then
   Result:=True
   ……

  4、一个具体的实现方法

  为了结构化管理插件,方便今后的系统扩充,我们可以结合数据库记录可用的DLL信息,然后通过查询数据库记录动态访问DLL程序。

  1) 系统模块表设计

  对于MIS系统,可以利用已有的DBS条件建立一个系统模块表,记录DLL文件及映射到系统模块中的相关信息

字段名 作用 类型
AutoID 索引 INT
modAlias 模块别称 VARCHAR
modName 模块名称 VARCHAR
modWndClass 窗体唯一标识 VARCHAR
modFile DLL路径 VARCHAR
modMemo 备注 TEXT

  ·模块别称是用来在编程设计阶段统一命名的规则,特别是团队开发时可以供队员参考。

  ·模块名称将作为ACAPTION参数传递给SHOWDLLFORM函数作为DLL窗口的标题。

  ·窗体唯一标识是DLL子模块中主窗口的CLASSNAME,用来在运行时确定要控制的窗口。

  ·DLL路径保存DLL文件名称,程序中将转换为绝对路径。

  2) 插件信息数据结构

  定义一个记录插件相关信息的数据接口可以集中控制DLL插件。在Interface部分加入如下代码:

type

 //定义插件信息类

 TMyPlugins = class
 Caption:String; //DLL窗体标题
 DllFileName:String; //DLL文件路径
 WndClass:String; //窗体标识
 UserID:string; //用户名
 ProcAddr:THandle; //LOADLIBRARY载入的库句柄
 FuncAddr:Pointer; //SHOWDLLFORM函数指针
 FuncFreeAddr:Pointer; //FREEDLLFORM函数指针
end;

……

  为每个插件创建一个TMyPlugins的实例,下文会讨论对这些实例的初始化方法。

  3) 插件载入函数

  在本示例中DLL窗口是在HALL中触发打开子窗口的事件中载入并显示的。按钮事件触发后,先根据插件结构体实例判断DLL是否已经加载,如果已经加载,则控制窗口的显示或关闭;如果没有加载则访问数据表将字段赋值到插件结构体中,然后执行载入、获得指针的工作。

  局部代码如下

……
//-----------------------------------------

//Name: OpenPlugin

//Func: 插件信息类控制过程: 初始化==》设置权限==》载入DLL窗口

//Para: APlugin-TMyPlugins; sAlias别名; iFuncValue权限值

//Rtrn: N/A

//Auth: CST

//Date: 2005-6-2

//-----------------------------------------

procedure TFormHall.OpenPlugin(AFromActn: TAction ;APlugin:TMyPlugins; sAlias:string; sUserID:string);
 var hWndPlugin:HWnd;
begin
 
 //判断插件窗口是否已经载入 hWndPlugin:=FindWindow(PChar(APlugin.WndClass),nil);
 if hWndPlugin <> 0 then //插件窗口已经载入
 begin
  if not IsWindowVisible(hWndPlugin) then
  begin
   AFromActn.Checked := True;
   ShowWindow(hWndPlugin,SW_SHOWDEFAULT); //显示
  end
  else
  begin
   AFromActn.checked := False;
   ShowWindow(hWndPlugin,SW_HIDE) ;
  end;
  Exit; //离开创建插件过程
 end;

//初始化插件类实例

if not InitializeMyPlugins(APlugin,sAlias) then
begin
 showmessage(’初始化插件类错误。’);
 exit;
end;

//获得当前权限值

APlugin.UserID := sUserID;
//载入DLL窗口

if not LoadShowPluginForm(APlugin) then
begin
 showmessage(’载入中心插件出错。’);
 exit;
 end;
end;

//-----------------------------------------
//Name: InitializeMyPlugins
//Func: 初始化MYPLUGIN实例 (Caption | DllFileName | IsLoaded)
//Para: APlugin-TMyPlugins
//Rtrn: N/A
//Auth: CST
//Date: 2005-6-2
//-----------------------------------------

function TFormHall.InitializeMyPlugins(APlugin:TMyPlugins; sAlias:String):Boolean;
var
 strSQL:string;
 myDA:TMyDataAdapter;
begin
 Result:=False;
 myDA:=TMyDataAdapter.Create;
 strSQL:=’SELECT * FROM SystemModuleList WHERE modAlias=’+QuotedStr(sAlias);
 try
  myDA.RetrieveData(strSQL);
 except
  on E:Exception do
  begin
   result:=false;
   myDA.Free ;
   exit;
  end;
 end;
try
 begin
  with myDA.MyDataSet do
 begin
  if Not IsEmpty then
 begin
  APlugin.Caption:= FieldByName(’modName’).Value;
  APlugin.DllFileName := FieldByName(’modFile’).Value;
  APlugin.WndClass := FieldByName(’modWndClass’).Value ;
  result:=True;
 end;
Close;
 end; //end of with...do...
 end; //end of try
 except
  on E:Exception do
begin
 Result:=False;
 myDA.Free ;
 Exit;
 end; //end of exception
end; //end of try...except

 myDA.Free ;
end;



//-----------------------------------------

//Name: LoadShowPluginForm

//Func: 载入DLL插件并显示窗口

//Para: APlugin-TMyPlugins

//Rtrn: true-创建成功

//Auth: CST

//Date: 2005-6-2

//-----------------------------------------

function TFormHall.LoadShowPluginForm (const APlugin:TMyPlugins):boolean;

var
 ShowDLLForm: TShowDLLForm; //DLL接口函数实例
 FreeDLLForm: TFreeDLLForm;
 sPath:string; //DLL文件的完整路径
begin
 try
 begin
  sPath:=ExtractFilepath(Application.ExeName)+ ’plugins\’ + APlugin.DllFileName ;
  APlugin.ProcAddr := LoadLibrary(PChar(sPath));
  APlugin.FuncFreeAddr := GetProcAddress(APlugin.ProcAddr,’FreeDLLForm’);
  APlugin.FuncAddr := GetProcAddress(APlugin.ProcAddr ,’ShowDLLForm’);
  @ShowDLLForm:=APlugin.FuncAddr ;
  @FreeDLLForm:=APlugin.FuncFreeAddr;
  if ShowDllForm(Self.Handle, APlugin.Caption , APlugin.UserID) then
   Result:=True
  else
   Result:=False;
  end;
  except
   on E:Exception do
  begin
   Result:=False;
   ShowMessage(’载入插件模块错误,请检查PLUGINS目录里的文件是否完整。’);
  end;
 end;
end;

……

  4) DLL窗口控制

  正如3)中的代码说明的那样,DLL窗口的打开和关闭只是在表象层,关闭窗口并没有真正释放DLL窗口,只是调用API函数FindWindow根据窗口标识(就是Form.name)获得窗体句柄,用SHOWWINDOW函数的nCmdShow参数控制窗口显示/隐藏。

  其实这是我这个程序实现的不好的一个地方,如果在DLL窗口中使用Self.close方法会引起内存错误,实在能力有限没有办法解决,因此出此下策。所以每个DLL程序主窗口的关闭按钮都必须隐藏掉。 :-P

  5) DLL库的释放

  在程序退出时,必须根据插件信息实例逐一释放DLL库。释放DLL库的函数如下:

procedure TFormHall.ClosePlugin(aPLG:TMyPlugins);
var
 FreeDLLForm:TFreeDLLForm;
begin
 if aPLG.ProcAddr = 0 then exit;
 if aPLG.FuncFreeAddr = nil then exit;
 @FreeDLLForm:=aPLG.FuncFreeAddr;
 if not FreeDLLForm(Application.Handle,’’,’’) then
  showMessage(’err’);
end;

  小结

   我以上的方法中,因为有不少能力有限没有解决的问题,所以采用了一些看起来不太合理的掩饰方法,希望大家能在做了一点尝试后设计出更好的解决方法,我也希望能学到更多的好方法。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章