That is correct. A BPL is a DLL. (But not all DLLs are BPLs.) > But I still found some different, such as that I can create a > object from the Host exe and that pass to a BPL and modify it safely, but > if I do same to a dll, I can not modify any referenced property of the object. When you use packages, there is only ever one copy of any unit in memory. One copy of Forms, one copy of SysUtils, one copy of System (well, most of it), one copy of StdCtrls, etc. All class-related operations, such as the "is" and "as" operators, rely on class references. Class references are actually just addresses. They point to definitions for the layouts of the classes' internals. (They point to what's called the virtual-method table, the VMT.) Two classes are the same if they point to the same VMT -- if the addresses are equal. When you have a class defined in the EXE's copy of StdCtrls and the same class defined in a DLL's copy of StdCtrls, those classes will really have different addresses. The "is" and "as" operators won't work with cross-module clases. But when you use packages, there is only one copy of the class, kept in vcl70.bpl, so all modules that reference that package will share a single class definition. Another factor is the memory manager in System. All string allocations ultimately call GetMem and FreeMem. If the EXE allocates a string, it uses its own GetMem. It then passes the string to a DLL, and the DLL might try to free it. The DLL will call its own copy of FreeMem, which won't have access to the EXE's memory-manager structures, and you'll get errors. With packages, everything will use the same memory manager from rtl70.bpl. (This can also be solved by using a shared memory manager between the EXE and the DLL; ShareMem is one example. That won't solve the class-comparison problem, though.) Above, I said the major difference between BPLs and DLLs is the number of exported functions. With a DLL, the only things exported are what appear in the "exports" clause that you write yourself. With a BPL, everything from all the units' "interface" sections gets exported, including global variables and class definitions. Also exported are the addresses of the "initialization" and "finalization" sections. And, internally, that is indeed the major difference. A BPL exports the functions necessary for the RTL to recognize the file as being a BPL and not just a generic DLL. If you call LoadPackage, i will call LoadLibrary to load it like a normal DLL, and then it will call all the package's units' initialization sections and do a fe other housekeeping operations. Calling a package's functions generates the same kind of assembler code as is generated when you call a DLL function.
一般我们编写编译一个DELPHI应用程序时,会产生一个EXE文件,也就是一个独立的WINDOWS应用程序。很重要的一点:区别于Visual Basic,DELPHI产生的是预先包裹的应用程序是不需要大量的运行库(DLL's)。假设:打开Delphi默认的工程(只有一个空白form),F9她将编译生成一个大约295 KB (Delphi 5)的可执行文件。然后打开Project | Options,把‘Build with runtime packages’选上再编译一下,EXE文件大小就只有15 KB左右了。我们编译一个DELPHI应用程序时默认地没有选择'Build with runtime packages',编译器将把程序运行所需要的代码直接写入你的EXE文件中,因此产生的程序是一个相对独立的程序,并不需要任何附属的支持文件(例如动态运行库文件DLL),这也就知道了为什么DELPHI产生的应用程序为什么都那么大。要建立尽可能小的DELPHI程序,方法之一就要充分发挥Borland package libraries的作用,简称BPL。先说什么是包?简而言之,一个包就是一个在DELPHI的IDE环境中被DELPHI应用程序共享的特殊的动态链接库。包允许我们通过多级应用将我们的程序的一部分当做一个分离的模块供其他应用程序来共享。包大致可分为运行期包(Run-time packages)和设计期包(Design-time packages):运行期包-当运行程序时提供VCL和库函数的支持,操作上很类似标准的动态链接库。设计期包-用来在DELPHI的IDE环境安装控件和为控件建立特殊的属性编辑器。设计期包允许包含控件、属性和控件编辑器等等,在IDE环境中,这类包是程序设计所必需的,也仅仅是DELPHI使用,并不和开发的应用程序一起分发。知道这些包的运用,我们也就知道了运行期包是如何做处理和它们对DELPHI程序员有什么帮助了。有一点必须说明:想很好地运用包并不要求你先成为一个成熟的控件编写者。DELPHI编程初学者也可以和应该尝试去接触包的概念,这将有利于你更好地理解包和DELPHI的工作关系。
前几天做过的试验,bpl 和 dll 差不多。 delphi 下解耦,请使用“接口” unit uMain;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type IMyTest = interface // 主接口 ['{9A1E99F7-5B77-4E31-991E-46DEAD30B650}'] procedure Hellow;stdcall; function Hello(const a,b:Integer):Integer;stdcall; procedure SetBase(const X:Integer);stdcall; end; IMySub = interface // 后来发现需要拓展一些内容 ['{81936A15-DC1A-49B7-B5DC-791C0C5A35F9}'] procedure ttt;stdcall; end; TMyTest = class(TInterfacedObject,IMyTest,IMySub) // 接口实现类 private FBase:Integer; public procedure Hellow;stdcall; function Hello(const a,b:Integer):Integer;stdcall; procedure SetBase(const X:Integer);stdcall; procedure ttt;stdcall; end; IMyDLLTest = interface // 这里接口的实现在 dll 中 ['{D40DF4C9-4CFA-44E7-A1A3-0B2668BF1502}'] procedure Hello;stdcall; end; TForm1 = class(TForm) Button1: TButton; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } end;var Form1: TForm1; _Test:procedure (const intf);stdcall; _RegApp:procedure (App:TApplication;out intf);stdcall; //procedure Test(const intf);stdcall;external 'MyDll.dll';implementation{$R *.dfm}{ TMyTest }function TMyTest.Hello(const a, b: Integer): Integer; begin Result := a + b + FBase; end;procedure TMyTest.Hellow; begin ShowMessage('Hellow'); end;procedure TMyTest.SetBase(const X: Integer); begin FBase := x; end;procedure TMyTest.ttt; begin ShowMessage('ttt'); end;procedure TForm1.Button1Click(Sender: TObject); var intf:IMyTest; begin // 这里创建实例,给 dll 中用 intf := TMyTest.Create; _Test(intf); ShowMessage(IntToStr(intf.Hello(1,2))); intf := nil; end;procedure TForm1.Button2Click(Sender: TObject); var intf:IMyDLLTest; begin // 从 dll 中找到实现类 _RegApp(Application,intf); if intf <> nil then intf.Hello; end;procedure TForm1.Button3Click(Sender: TObject); var hh:THandle;pp:TProcedure; begin hh := LoadPackage('MyBPL.bpl'); if hh <> 0 then begin @pp := GetProcAddress(hh,'@Ubpl@DuoTest$qqrv'); if @pp <> nil then pp; end; end; procedure TForm1.FormCreate(Sender: TObject); var hh:THandle; begin hh := LoadLibrary('MyDll.dll'); @_Test := GetProcAddress(hh,'Test'); @_RegApp := GetProcAddress(hh,'RegApp');end;end.
library MyDll;{ Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }uses SysUtils, Classes,Dialogs, Forms;{$R *.res}type IMyTest = interface ['{9A1E99F7-5B77-4E31-991E-46DEAD30B650}'] procedure Hellow;stdcall; function Hello(const a,b:Integer):Integer;stdcall; procedure SetBase(const X:Integer);stdcall; end; IMySub = interface ['{81936A15-DC1A-49B7-B5DC-791C0C5A35F9}'] procedure ttt;stdcall; end; IMyDLLTest = interface ['{D40DF4C9-4CFA-44E7-A1A3-0B2668BF1502}'] procedure Hello;stdcall; end; TMyDLLTest = class(TInterfacedObject,IMyDLLTest) procedure Hello;stdcall; end;var _App:TApplication;procedure Test(const intf);stdcall; var ii:IMySub; begin IMyTest(intf).SetBase(3); IMyTest(intf).Hellow; Supports(IMyTest(intf),IMySub,ii); ii.ttt; end;procedure RegApp(App:TApplication;out intf);stdcall; begin _App := App; Supports(TMyDLLTest.Create,IMyDLLTest,intf); end;{ TMyDLLTest }procedure TMyDLLTest.Hello; begin ShowMessage(_App.MainForm.Caption); end;procedure DuoTest;stdcall; begin ShowMessage('DuoTest'); end;exports Test, RegApp, DuoTest;
begin end.
unit uBPL;interfaceuses Dialogs;procedure DuoTest;implementationprocedure DuoTest; begin ShowMessage('DuoTest'); end;end.
That is correct. A BPL is a DLL. (But not all DLLs are BPLs.) > But I still found some different, such as that I can create a > object from the Host exe and that pass to a BPL and modify it safely, but > if I do same to a dll, I can not modify any referenced property of the object. When you use packages, there is only ever one copy of any unit in memory. One copy of Forms, one copy of SysUtils, one copy of System (well, most of it), one copy of StdCtrls, etc. All class-related operations, such as the "is" and "as" operators, rely on class references. Class references are actually just addresses. They point to definitions for the layouts of the classes' internals. (They point to what's called the virtual-method table, the VMT.) Two classes are the same if they point to the same VMT -- if the addresses are equal. When you have a class defined in the EXE's copy of StdCtrls and the same class defined in a DLL's copy of StdCtrls, those classes will really have different addresses. The "is" and "as" operators won't work with cross-module clases. But when you use packages, there is only one copy of the class, kept in vcl70.bpl, so all modules that reference that package will share a single class definition. Another factor is the memory manager in System. All string allocations ultimately call GetMem and FreeMem. If the EXE allocates a string, it uses its own GetMem. It then passes the string to a DLL, and the DLL might try to free it. The DLL will call its own copy of FreeMem, which won't have access to the EXE's memory-manager structures, and you'll get errors. With packages, everything will use the same memory manager from rtl70.bpl. (This can also be solved by using a shared memory manager between the EXE and the DLL; ShareMem is one example. That won't solve the class-comparison problem, though.)
> object from the Host exe and that pass to a BPL and modify it safely, but
> if I do same to a dll, I can not modify any referenced property of the object. When you use packages, there is only ever one copy of any unit in
memory. One copy of Forms, one copy of SysUtils, one copy of System
(well, most of it), one copy of StdCtrls, etc. All class-related operations, such as the "is" and "as" operators, rely
on class references. Class references are actually just addresses. They
point to definitions for the layouts of the classes' internals. (They
point to what's called the virtual-method table, the VMT.) Two classes
are the same if they point to the same VMT -- if the addresses are equal. When you have a class defined in the EXE's copy of StdCtrls and the same
class defined in a DLL's copy of StdCtrls, those classes will really
have different addresses. The "is" and "as" operators won't work with
cross-module clases. But when you use packages, there is only one copy
of the class, kept in vcl70.bpl, so all modules that reference that
package will share a single class definition. Another factor is the memory manager in System. All string allocations
ultimately call GetMem and FreeMem. If the EXE allocates a string, it
uses its own GetMem. It then passes the string to a DLL, and the DLL
might try to free it. The DLL will call its own copy of FreeMem, which
won't have access to the EXE's memory-manager structures, and you'll get
errors. With packages, everything will use the same memory manager from
rtl70.bpl. (This can also be solved by using a shared memory manager
between the EXE and the DLL; ShareMem is one example. That won't solve
the class-comparison problem, though.) Above, I said the major difference between BPLs and DLLs is the number
of exported functions. With a DLL, the only things exported are what
appear in the "exports" clause that you write yourself. With a BPL,
everything from all the units' "interface" sections gets exported,
including global variables and class definitions. Also exported are the
addresses of the "initialization" and "finalization" sections. And,
internally, that is indeed the major difference. A BPL exports the
functions necessary for the RTL to recognize the file as being a BPL and
not just a generic DLL. If you call LoadPackage, i will call LoadLibrary
to load it like a normal DLL, and then it will call all the package's
units' initialization sections and do a fe other housekeeping
operations. Calling a package's functions generates the same kind of
assembler code as is generated when you call a DLL function.
包裹的版本问题当你想升级你的动态链接库时(改变其中一些执行函数),你可以简单地编译产生新的程序文件,并上载新版本文件,所有正在使用该动态链接库的应用程序仍将工作(除非你已经将存在的旧版本程序去除)。换个角度来讲,在升级新包裹文件的同时,不要忘记升级程序的执行文件。正如你所了解的,包裹文件就是一个单元文件(units)的集合,所有编译过的单元文件(DCU)都含有版本信息,因此,除非我们有单元文件的源码(source),否则我们不能在DELPHI4或5中使用编译过的单元,所以一旦我们改变了单元文件中接口部分uses 子句中列举出的任一单元文件,该文件就需要重新编译。编译器将检查DCU文件的版本信息,并决定单元是否需要重新编译。因此我们不能在DELPHI5编译的应用程序中使用在DELPHI6下编译的包,任何为你的应用程序服务的包和你的应用程序必须在相同环境下编译。因此,当给包裹命名的时要保留包裹名中包含有DELPHI的版本信息(如'AboutDP50',其中50就代表Delphi 5)。这可以有效防止文件版本的冲突问题,也可以避免很多不必要的麻烦,包使用者可以更清楚包的版本和包裹适用于哪个DELPHI编译器。如果你要分发运行期或设计期包给其他DELPHI程序员,建议同时提供了.DCP(含有包的头信息和各个单元文件)和.BPL文件,还有包中所包含的所有单元文件的.DCU文件。
dll更容易被其他语言调用。
优点:BPL可以导出类信息。用BPL可以共享类库。
缺点:不同BPL在引用同一单元时,会使Package引用关系变得复杂。
LiraryModule := LoadLibrary('bin\ProjDll.dll');
if LiraryModule <> 0 then
try
AClass := GetClass('TForm1'); {ProjDll.dll中注册了TForm1类}
if AClass <> nil then
with TFormClass(AClass).Create(Application) do
try
ShowModal;
finally
Free;
end;
finally
FreeLibrary(LiraryModule);
end;
delphi 下解耦,请使用“接口”
unit uMain;interfaceuses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;type
IMyTest = interface // 主接口
['{9A1E99F7-5B77-4E31-991E-46DEAD30B650}']
procedure Hellow;stdcall;
function Hello(const a,b:Integer):Integer;stdcall;
procedure SetBase(const X:Integer);stdcall;
end; IMySub = interface // 后来发现需要拓展一些内容
['{81936A15-DC1A-49B7-B5DC-791C0C5A35F9}']
procedure ttt;stdcall;
end; TMyTest = class(TInterfacedObject,IMyTest,IMySub) // 接口实现类
private
FBase:Integer;
public
procedure Hellow;stdcall;
function Hello(const a,b:Integer):Integer;stdcall;
procedure SetBase(const X:Integer);stdcall; procedure ttt;stdcall;
end; IMyDLLTest = interface // 这里接口的实现在 dll 中
['{D40DF4C9-4CFA-44E7-A1A3-0B2668BF1502}']
procedure Hello;stdcall;
end; TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;var
Form1: TForm1;
_Test:procedure (const intf);stdcall;
_RegApp:procedure (App:TApplication;out intf);stdcall;
//procedure Test(const intf);stdcall;external 'MyDll.dll';implementation{$R *.dfm}{ TMyTest }function TMyTest.Hello(const a, b: Integer): Integer;
begin
Result := a + b + FBase;
end;procedure TMyTest.Hellow;
begin
ShowMessage('Hellow');
end;procedure TMyTest.SetBase(const X: Integer);
begin
FBase := x;
end;procedure TMyTest.ttt;
begin
ShowMessage('ttt');
end;procedure TForm1.Button1Click(Sender: TObject);
var intf:IMyTest;
begin
// 这里创建实例,给 dll 中用
intf := TMyTest.Create;
_Test(intf);
ShowMessage(IntToStr(intf.Hello(1,2)));
intf := nil;
end;procedure TForm1.Button2Click(Sender: TObject);
var intf:IMyDLLTest;
begin
// 从 dll 中找到实现类
_RegApp(Application,intf);
if intf <> nil then
intf.Hello;
end;procedure TForm1.Button3Click(Sender: TObject);
var hh:THandle;pp:TProcedure;
begin
hh := LoadPackage('MyBPL.bpl');
if hh <> 0 then
begin
@pp := GetProcAddress(hh,'@Ubpl@DuoTest$qqrv');
if @pp <> nil then
pp;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var hh:THandle;
begin
hh := LoadLibrary('MyDll.dll');
@_Test := GetProcAddress(hh,'Test');
@_RegApp := GetProcAddress(hh,'RegApp');end;end.
library MyDll;{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }uses
SysUtils,
Classes,Dialogs,
Forms;{$R *.res}type
IMyTest = interface
['{9A1E99F7-5B77-4E31-991E-46DEAD30B650}']
procedure Hellow;stdcall;
function Hello(const a,b:Integer):Integer;stdcall;
procedure SetBase(const X:Integer);stdcall;
end; IMySub = interface
['{81936A15-DC1A-49B7-B5DC-791C0C5A35F9}']
procedure ttt;stdcall;
end; IMyDLLTest = interface
['{D40DF4C9-4CFA-44E7-A1A3-0B2668BF1502}']
procedure Hello;stdcall;
end; TMyDLLTest = class(TInterfacedObject,IMyDLLTest)
procedure Hello;stdcall;
end;var
_App:TApplication;procedure Test(const intf);stdcall;
var ii:IMySub;
begin
IMyTest(intf).SetBase(3);
IMyTest(intf).Hellow;
Supports(IMyTest(intf),IMySub,ii);
ii.ttt;
end;procedure RegApp(App:TApplication;out intf);stdcall;
begin
_App := App;
Supports(TMyDLLTest.Create,IMyDLLTest,intf);
end;{ TMyDLLTest }procedure TMyDLLTest.Hello;
begin
ShowMessage(_App.MainForm.Caption);
end;procedure DuoTest;stdcall;
begin
ShowMessage('DuoTest');
end;exports
Test,
RegApp,
DuoTest;
begin
end.
unit uBPL;interfaceuses Dialogs;procedure DuoTest;implementationprocedure DuoTest;
begin
ShowMessage('DuoTest');
end;end.
1、DLL 语言无关,自然与Delphi版本无关了。 BPL则不一样,D2010编译的程序加载D7编译的BPL是会出错的。
2、DLL可以被其他语言调用。BPL仅限于对应版本的Delphi编译器。BPL的好处,无论动态加载或静态加载:
1、多个BPL可以方便自然地共享全局变量。DLL不能或者说要很复杂地做才能在多个DLL间共享全局变量。
2、类的支持。BPL有类的信息,可以方便地从其他BPL中继承类,包括Form继承。DLL不行。
3、BPL中可以地选择延迟加载其他DLL,编程起来与静态加载一样,DLL中加载其他DLL时,只能作静态或动态二选一。说的不一定对,只是这个问题与动态或静态加载关系并不大。
只谈动态加载,我对你提出的“BPL的好处 1 2 3”都很好奇,能否描述一下使用方法或代码示意一下。
“只是这个问题与动态或静态加载关系并不大”,这句话我真的不赞同。
> object from the Host exe and that pass to a BPL and modify it safely, but
> if I do same to a dll, I can not modify any referenced property of the object. When you use packages, there is only ever one copy of any unit in
memory. One copy of Forms, one copy of SysUtils, one copy of System
(well, most of it), one copy of StdCtrls, etc. All class-related operations, such as the "is" and "as" operators, rely
on class references. Class references are actually just addresses. They
point to definitions for the layouts of the classes' internals. (They
point to what's called the virtual-method table, the VMT.) Two classes
are the same if they point to the same VMT -- if the addresses are equal. When you have a class defined in the EXE's copy of StdCtrls and the same
class defined in a DLL's copy of StdCtrls, those classes will really
have different addresses. The "is" and "as" operators won't work with
cross-module clases. But when you use packages, there is only one copy
of the class, kept in vcl70.bpl, so all modules that reference that
package will share a single class definition. Another factor is the memory manager in System. All string allocations
ultimately call GetMem and FreeMem. If the EXE allocates a string, it
uses its own GetMem. It then passes the string to a DLL, and the DLL
might try to free it. The DLL will call its own copy of FreeMem, which
won't have access to the EXE's memory-manager structures, and you'll get
errors. With packages, everything will use the same memory manager from
rtl70.bpl. (This can also be solved by using a shared memory manager
between the EXE and the DLL; ShareMem is one example. That won't solve
the class-comparison problem, though.)
dll更容易被其他语言调用。
请教一下, 动态加载时,全局变量如何共享? 编译时经常提示我AAA.pas已包含BBB.pas.