动态调用bpl,如何调用bpl中的procedure和function(200分)

to 京工之鸟,bbkxjy:
对于bpl的类输出我还只是理解一点点,我主要是实现以下功能:
不管是bpl还是dll中的form要是mdichild.这个已实现了,还有一个就是以前我问过的问题,
没有得到很好的答案,后来没有继续做了,这次想做好他,要实现的功能:
一个bplA中的datamodule的database联接到数据库;其他的bpl或是exe中的mdiform都使用bplA的
database.这样每一台机器不管启动多少个exe或是bpl都只有一个联接到数据库。exe调用bpl都
是动态的,我以前的都是用静态调用(直接uses),好占系统资源,因为公司有很多机器是老的没有
办法,只能改成动态调用(直接用loadpackage)。
请两位帮忙,我想用两种途径实现,一种就是interface.一种就是bpl或dll的类输出;
后一种我想用bpl好些,因为有些病毒喜欢删除dll.如果用bpl要好些吧!我昨天试了类输出
好象在共用datamodule时很有些问题,具体还没有查出来。
想问两位一下,一个exe注册(registclass)了一个"Ttestmodule"后,另一个exe是不是可以
用getclass()找得到,并且可以使用?
 
我又改了上面几句不通的地方。
"定义一个COM接口当然可以在别的EXE里调用。没必要这么复杂吧?"
我觉得真的要做好的话用com是最好的选择,看看word多神气。
如果我们的应用程序都做成这样子的话,以后就很好维护了。

 
Delphi中Package是一件非常强大的工具,用的好可以起到非常清晰的
划分程序结构的作用。因为他内建描述信息,可以和当前代码无缝集成起来,
可以保护包括类在内的任何元素,相当于VC中的MFC Extension DLL的作用。
但是一直以来的文章都只介绍静态连接的方法,这其实限制了Package的使用
因为静态连接的Package失去了其灵活性,可配置性等等。至于通过函数入口
方式访问,实在是大材小用,那不如直接用DLL还方便一些。
如何动态载入Package,使用其中的类、函数、变量等等?起始说穿了很
简单,就是做一个代理包。因为在一个Delphi程序中,每个unit只能存在
一份,否则发生冲突。要动态载入包,又得取得其中信息,又不能直接uses
包含信息的unit(否则引起冲突),解决办法是另外建一个代理包来作为桥梁
传递信息。下面是一个简单的例子,主程序使用到两个包,DemoPak包中有
一个简单的Form;RegPak是所谓的代理包,起到注册信息的作用。
主程序对RegPak静态使用(在Project Options里面设置了),对DemoPak
动态载入(通过LoadPackage),而DemoPak依赖于RegPak(requires),
并在初始化时向代理包RegPak注册自己的可用类,这里举例注册类信息,
你可以方便的改成注册其他信息

unit FormReg;

interface

uses
Classes, SysUtils, Forms, Contnrs;

type
TFormClass = class of TForm;

procedure RegisterFormClass(const AName: string
const AFormClass: TFormClass);
procedure UnregisterFormClass(const AName: string);
function FindFormClass(const AName: string): TFormClass;

implementation

var
g_lstNames: TStringList;
g_lstForms: TClassList;

procedure RegisterFormClass(const AName: string
const AFormClass: TFormClass);
begin
g_lstNames.Add(AName);
g_lstForms.Add(AFormClass);
end;

procedure UnregisterFormClass(const AName: string);
var
Index: Integer;
begin
Index := g_lstNames.IndexOf(AName);
if Index <> -1 then
begin
g_lstNames.Delete(Index);
g_lstForms.Delete(Index);
end;
end;

function FindFormClass(const AName: string): TFormClass;
var
I: Integer;
begin
for I := 0 to g_lstNames.Count - 1 do
begin
if g_lstNames = AName then
begin
Result := TFormClass(g_lstForms.Items);
Exit;
end;
end;
Result := nil;
end;

initialization
g_lstNames := TStringList.Create;
g_lstForms := TClassList.Create;

finalization
FreeAndNil(g_lstForms);
FreeAndNil(g_lstNames);

end.

以上是RegPak的主要代码,因为举例,代码很简陋。主要思想就是保存注册信息,
提供查询方法。让我们看看在DemoPak中的使用

unit AboutForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TfrmAbout = class(TForm)
lblAbout: TLabel;
private
public
end;

var
frmAbout: TfrmAbout;

implementation

uses FormReg;

{$R *.dfm}

initialization
RegisterFormClass(TfrmAbout.ClassName, TfrmAbout);

finalization
UnregisterFormClass(TfrmAbout.ClassName);

end.

在初始化时向RegPak的FormReg单元提交自己的类信息,因为每个Package在载入时
无论动态静态都会自动初始化,而RegPak被主程序静态引用,肯定已经初始化,所以直接
注册即可,非常简单。最后看看主程序中的使用

uses
FormReg;

procedure TfrmMain.btnAboutClick(Sender: TObject);
var
hModule: THandle;
begin
hModule := LoadPackage('DemoPak.bpl');
try
with FindFormClass('TfrmAbout').Create(nil) do
try
ShowModal;
finally
Free;
end;
finally
UnloadPackage(hModule);
end;
end;

动态载入需要的包,查询需要的类的信息,使用之,最后卸载包。

很简单吧 :) 起始很多东西都没有什么技术难度的,关键看你想不想得到 :)
 
代理包的使用有画蛇添足之嫌...
 
to flier:
你说的这个方法很不错,可如果是mdiform要如何实现呢?我曾试过,行不通,
我之所以用输出procdure和function是想解决mdiform。
 
To vc3000:
为何是画蛇添足?你有什么更好的办法吗?以我所知,
要从一个包中输出类或者其他不能通过getprocaddress
取得的信息是很困难的,而只使用函数输出,会使包的
灵活性大大下降,不如直接使用dll

To maming:
Delphi好像还没有什么不可能的,只有自己不知道的,
我把昨天的程序改了一下,显示mdi一点问题没有啊?

btw:mdi是已经淘汰了的技术,最好不要再用了,呵呵 :)

以下是代理包的注册程序

unit FormReg;

interface

uses
Classes, SysUtils, Forms, Contnrs;

type
TFormClass = class of TForm;

procedure RegisterFormClass(const AFormClass: TFormClass);
procedure UnregisterFormClass(const AFormClass: TFormClass);
function FindFormClass(const AName: string): TFormClass;

implementation

var
g_lstNames: TStringList;
g_lstForms: TClassList;

procedure RegisterFormClass(const AFormClass: TFormClass);
begin
g_lstNames.Add(AFormClass.ClassName);
g_lstForms.Add(AFormClass);
end;

procedure UnregisterFormClass(const AFormClass: TFormClass);
var
Index: Integer;
begin
Index := g_lstNames.IndexOf(AFormClass.ClassName);
if Index <> -1 then
begin
g_lstNames.Delete(Index);
g_lstForms.Delete(Index);
end;
end;

function FindFormClass(const AName: string): TFormClass;
var
I: Integer;
begin
for I := 0 to g_lstNames.Count - 1 do
begin
if g_lstNames = AName then
begin
Result := TFormClass(g_lstForms.Items);
Exit;
end;
end;
Result := nil;
end;

initialization
g_lstNames := TStringList.Create;
g_lstForms := TClassList.Create;

finalization
FreeAndNil(g_lstForms);
FreeAndNil(g_lstNames);

end.

以下是DemoPak程序,ChildForm是一个FormStyle为fsMDIChild的Form
unit ChildForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TfrmChild = class(TForm)
Label1: TLabel;
procedure FormClose(Sender: TObject
var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;

var
frmChild: TfrmChild;

implementation

{$R *.dfm}

uses FormReg;

procedure TfrmChild.FormClose(Sender: TObject
var Action: TCloseAction);
begin
Action := caMinimize;
end;

initialization
RegisterFormClass(TfrmChild);

finalization
UnregisterFormClass(TfrmChild);

end.

unit AboutForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TfrmAbout = class(TForm)
lblAbout: TLabel;
private
public
end;

var
frmAbout: TfrmAbout;

implementation
{$R *.dfm}

uses FormReg;

initialization
RegisterFormClass(TfrmAbout);

finalization
UnregisterFormClass(TfrmAbout);

end.

以下是主程序,FormStyle为fsMDIForm

unit MainForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus, Contnrs;

type
TfrmMain = class(TForm)
mnuMain: TMainMenu;
mnuFile: TMenuItem;
mnuFileNew: TMenuItem;
mnuHelp: TMenuItem;
mnuHelpAbout: TMenuItem;
procedure mnuHelpAboutClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure mnuFileNewClick(Sender: TObject);
private
m_hDemoPak: THandle;
m_lstChildForms: TObjectList;

function GetDemoPakHandle: THandle;

property DemoPakHandle: THandle read GetDemoPakHandle;
public
{ Public declarations }
end;

var
frmMain: TfrmMain;

implementation

{$R *.dfm}

uses
FormReg;

function TfrmMain.GetDemoPakHandle: THandle;
begin
if m_hDemoPak = 0 then
m_hDemoPak := LoadPackage('DemoPak.bpl');

Result := m_hDemoPak;
end;

procedure TfrmMain.mnuHelpAboutClick(Sender: TObject);
begin
if DemoPakHandle <> 0 then
with FindFormClass('TfrmAbout').Create(nil) do
try
ShowModal;
finally
Free;
end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
m_hDemoPak := 0;
m_lstChildForms := TObjectList.Create;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
m_lstChildForms.Free;

if m_hDemoPak <> 0 then
UnloadPackage(m_hDemoPak);
end;

procedure TfrmMain.mnuFileNewClick(Sender: TObject);
begin
if DemoPakHandle <> 0 then
m_lstChildForms.Add(FindFormClass('TfrmChild').Create(nil));
end;

end.
 
我觉得代理包没有必要。Classes.pas 中本来就通过 RegisterClass,UnRegisterClass 和
FindClass,GetClass 等例程提供了相同的类注册,查找机制。Classes.pas 被编译到
Vclx0.bpl 中,这个核心包就相当于上述的代理包。
to maming:
李维的例子很清楚的。你是不是遇到了在主 exe 中 FindClass(或 GetClass) 返回为 nil
的情况?如果是的话,你的主 exe 和被动态 Loaded 的 package 都必须选 "Build with
Runtime Package" 的编译选项,并且至少包括 VCLX0.bpl。这样 exe 和 bpl 才会使用
同一份 VCL 的代码,包括全局数据。RegisterClass 等就用了全局范围的数据 ClassList。
 
To bbkxjy:
我觉得你误解了我的意思。我之所以这么写,只是举例而已,为的是说明
如何和动态载入的包之间交换编译时未知的类型信息。你所说的RegisterClass
实际上也是使用的同样的原理,只是代理包是System所在的package而已
何况RegisterClass的使用有其限制,如注册的类必须是 TPersistent的子类等等
而且无法加入自己的信息,而且如果要用到函数怎么办?用到全局变量怎么办?
从灵活性,可维护性,可扩展性角度来看,如果你要动态加载包,建立自己的
代理机制是必须的,否则就得忍受诸多限制。

btw:其实vcl类似RegisterClass的使用太多了,自己翻翻VCL源码就知道了
李维他这么做也只是个投机取巧的办法,RegisterClass根本不是为了
使用动态包而设计的。
 
to 上面两位: 感谢你们的帮助,让我开了窍了。两位的方法结全起来很好用。
mdi已经没有人用了吗?那现在流行用sdi还是别的?
是不是outlookexpress啊!我没有注意那是如何做的,可否说说?

我正动态bpl调用在测试数据库方面的应用.
 
to flier:
我只是针对你的例子而言,对你给出的场景是不需要自己实现 RegisterClass 等的。
另外,你的代理包中少了一些东西,就是类似 classes.pas 中在 initialization,
finalization section 分别调用 RegisterModuleClass,UnRegisterModuleClasses 过程
那样,当 ClassList 中注册的类所属的模块被 UnLoad 时,需要从 ClassList 中删除这
些类,防止造成无效指针。李维的例子中也有类似的过程调用的。
 
to bbkxjy:
我在第一篇文章里应该已经说得很清楚了“因为举例,代码很简陋”
我之所以代码这么写,只是为了表述一个主要思想而已。如果是实际使用中
不可能这么简单,我以前一个BCB写的类似的动态载入包的程序中,光代理包
中的处理就有大几百行代码,怎么可能这么简陋?
至于注册和注销类信息,我的代码DemoPak.bpl里是如此写的
initialization
RegisterFormClass(TfrmAbout.ClassName, TfrmAbout);
finalization
UnregisterFormClass(TfrmAbout.ClassName);

 
为什么我收不到EMAIL通知啊???????????????????????????????????????????
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
:(
 
时间太长了,给分了,多谢各位了。
 
高手所见!
 
代理包确实有点画蛇添足的感觉,我的系统中主程序没有静态联接所谓的代理包,照样工作
得很好。也没有什么受限制。
输出函数或过程可以通过消息传递函数或过程指针实现。
 
我想再求助一下
我已經產生了 abc.bpl 文件
有主程序 main.exe 調用 abc.bpl 在我本機上執行通過
但我把 main.exe,abc.bpl copy 到另外一台電腦 c:/main/ 時
執行就不能動態調入 abc.bpl 不知為什么? 我把 abc.bpl copy 到$windows/system/
也是一樣的問題.
 
to hason:
你如果采用运行时包,那么还得那些你用到的*.bpl一起copy过去;
在delphi中运行程序,然后用那个module查看,用了那一些bpl.
 
噢!少了(rtl60.bpl) file
OK
TKS : maming
 
有没有完整的DEMO!!
 
http://www.playicq.com/dispdoc.php?t=27&amp;id=2801
 
顶部