在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、为什么要使用包? 答案很简单:因为包的功能强大。设计期包(design-time package)简化了自定义组件的发布和安装;而运行期包(run-time package)则更是给传统的程序设计注入了新鲜的力量。一旦把可重用的代码编译为运行期库中,你就可以在多个应用程序中共享它们。所有应用程序都可以通过包访问标准组件,Delphi自己就是这么干的。因为应用程序不必在可执行文件中单独复制一份组件库,这样就大大节省了系统资源和磁盘空间。此外,包还可以减少花费在编译上的时间,因为你只需编译应用程序特有的代码。 如果可以动态的使用包,那么我们还可以获得更多的好处。包提供了一种新颖的模块化方法来开发应用程序。有些时候你也许想把某些模块作为应用程序的可选部件,例如一个记帐系统附带一个可选的HR模块。某些情况下,你只需安装基本的应用程序,而在另外一些情况下你就可能需要额外安装HR模块。这种模块化的方法可以通过包技术很容易的实现。在过去,这只能通过动态装载DLL实现,但是使用Delphi的包技术,你就可以把应用程序的各个模块类型分别打“包”成捆。特别是从包中创建的类对象则属于应用程序所有,因此可以与应用程序中的对象交互。
二、运行期包与应用程序 许多开发者只把Delphi包看作放组件的地方,事实上包可以(而且也应该)应用于模块化应用程序设计。 为了演示如何用包来模块化你的应用程序,我们创建一个例子: 1、 新建一个具有两个窗体的Delphi程序:Form1和Form2; 2、 将Form2从自动创建窗体列表中移除(Project |Options | Forms); 3、 在Form1上放一个按钮,并且在按钮的OnClick事件处理器中输入如下代码: with TForm2.Create(Application) do begin ShowModal; Free; End; 4、记住添加Unit2到Unit1的uses子句中; 5、保存并运行工程。 我们创建了一个简单的应用程序,它显示一个带按钮的窗体,点击这个按钮则会创建并显示出另一个窗体。但是如果想将上述例子中的Form2包含在一个可重用模块中,并使它依然可以正常工作,我们该怎么办呢?答案是:包! 要为Form2创建包需要以下工作: 1、 打开工程管理器(View | Project Manager); 2 、右击Project Group,选择“Add NewProject...”; 3、在“New”项目列表中选择“Package”; 4、 现在你应该可以见到包编辑器; 5、选择“Contains”项目,然后点击“Add”按钮; 6、 然后点击“Browse...”按钮,并选择“Unit2.pas”; 7、现在包中应该包含了“Unit2.pas”单元; 8、 最后保存并编译包。 现在我们完成了这个包。在你的Project\BPL目录中应该有一个名叫“package1.bpl”的文件。(BPL是Borland Package Library的缩写,DCP是Delphi CompiledPackage 的缩写。) 这个包已经完成了。现在我们需要打开包选项开关 并重新编译原先的应用程序。 1、 在工程管理器中双击“Project1.exe”以选中该工程; 2、 右击并选择“Options...”(你也可以从菜单中选择Project | Options...); 3、 选中“Packages”选项页; 4、 选中“Build with runtime packages”检查框; 5、 编辑“Runtime packages”编辑框:“Vcl50;Package1”,并点击“OK”按钮; 6、 注意:不要从应用程序中移除Unit2; 7、 保存并运行应用程序。 应用程序会象从前一样运行,不过区别可以从文件的大小上看出来。 Project1.exe现在只有14K大小,而从前则是293K。如果你用资源浏览器查看EXE和BPL文件的内容,你就会发现Form2的DFM和代码现在都保存在包中。 Delphi在编译期完成对包的静态连接。(这就是为什么你不能从EXE工程中移除Unit2。) 想想你可以由此得到什么:你可以在包中创建一个数据访问模块,并且在更改数据访问规则时(比如从BDE连接转为ADO连接),稍作修改并重新发布这个包。或者,你可以在某个包中创建一个显示“此选项在当前版本中不可用”信息的窗体,然后在另一个同名的包中创建一个具有完整功能的窗体。现在我们不费吹灰之力就有了“Pro”和“Enterprise”两个版本的产品。 三、包的动态装载和卸载 在大多数情况下,静态连接的DLL或BPL已经可以满足要求了。但是如果我们不想发布BPL呢?“在指定目录中找不到动态链接库Package1.bpl”,这是在应用程序终止前,我们所能得到的唯一消息。或者,在模块化应用程序程序中,我们是否可以使用任意数量的插件? 我们需要在运行期动态连接到BPL。 对于DLL 来说,有一个简单的方法,就是使用LoadLibrary函数: function LoadLibrary(lpLibFileName: Pchar): HMODULE;stdcall; 装载了DLL之后,我们可以使用GetProcAddress函数来调用DLL的导出函数和方法: function GetProcAddress(hModule: HMODULE; lpProcName:LPCSTR): FARPROC; stdcall; 最后,我们使用FreeLibrary卸载DLL: function FreeLibrary(hLibModule: HMODULE): BOOL;stdcall; 下面这个例子中我们动态装载Microsoft的HtmlHelp库: function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer; var CallHelp: Boolean):Boolean; type TFNHtmlHelpA = function(hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT;dwData: Dword): HWND; stdcall; var HelpModule: Hmodule; HtmlHelp: TFNHtmlHelpA; begin Result := False; HelpModule := LoadLibrary('HHCTRL.OCX'); if HelpModule <> 0 then begin @HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA'); if @HtmlHelp <> nil then Result := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0; FreeLibrary(HelpModule); end; CallHelp := False; end; 四、动态装载BPL 我们可以用同样简单的方法来对付BPL,或者应该说基本上同样简单。 我们可以使用LoadPackage函数动态装载包: function LoadPackage(const Name: string): HMODULE; 然后使用GetClass 函数创建一个TPersistentClass类型对象: function GetClass(const AclassName: string):TPersistentClass; 完成所有操作后,使用UnLoadPackage(Module:HModule); 让我们对原来的代码作一些小小的改动: 1、 在工程管理器中选中“Project1.exe”; 2、 右击之并选择“Options...”; 3、 选中“Packages”选项页; 4 、 从“Runtime packages”编辑框中移除“Package1”,并点击OK按钮; 5、 在Delphi的工具栏中,点击“Remove file from project”按钮; 6、 选择“Unit2 | Form2”,并点击OK; 7、 现在在“Unit1.pas”的源代码中,从uses子句中移除Unit2; 8、 进入Button1 的OnClick时间代码中; 9、 添加两个HModule和TPersistentClass类型的变量: var PackageModule: HModule; AClass: TPersistentClass; 10、使用LoadPackage 函数装载Pacakge1包: PackageModule := LoadPackage('Package1.bpl'); 11、检查PackageModule是否为0; 12、使用GetClass函数创建一个持久类型: AClass := GetClass('TForm2'); 13、如果这个持久类型不为nil,我们就可以向从前一样创建并使用该类型的对象了: with TComponentClass(AClass).Create(Application) as TcustomForm do begin ShowModal; Free; end; 14、最后,使用UnloadPackage 过程卸载包: UnloadPackage(PackageModule); 15、保存工程。 下面是OnClick事件处理器的完整清单: procedure TForm1.Button1Click(Sender: Tobject); var PackageModule: HModule; AClass: TPersistentClass; begin PackageModule := LoadPackage('Package1.bpl'); if PackageModule <> 0 then begin AClass := GetClass('TForm2'); if AClass <> nil then with TComponentClass(AClass).Create(Application) as TcustomForm do begin ShowModal; Free; end; UnloadPackage(PackageModule); end; end; 不幸的是,并不是这样就万事大吉了。 问题在于,GetClass函数只能搜索到已经注册的类型。通常在窗体中引用的窗体类和组件类会在窗体装载时自动注册。但是在我们的例子中,窗体无法提前装载。那么我们在哪里注册类型呢?答案是,在包中。包中的每个单元都会在包装载的时候初始化,并在包卸载时清理。 现在回到我们的例子中: 1、 在工程管理器双击“Package1.bpl”; 2、 点击“Contains”部分“Unit2”旁的+号; 3、 双击“Unit2.pas”激活单元源代码编辑器; 4、 在文件的最后加入initialization部分; 5、 使用RegisterClass过程注册窗体的类型: RegisterClass(TForm2); 6、 添加一个finalization部分; 7、 使用UnRegisterClass过程反注册窗体的类型: UnRegisterClass(TForm2); 8、 最后,保存并编译包。 现在我们可以安全的运行“Project1”,它还会像从前一样工作,但是现在你可以随心所欲的装载包了。 五、尾声 记住,无论你是静态还是动态的使用包,都要打开Project | Options | Packages | Build with runtime packages 选项。在你卸载一个包之前,记得销毁所有该包中的类对象,并反注册所有已注册的类。 下面的过程可能会对你有所帮助: procedure DoUnloadPackage(Module: HModule); var i: Integer; M: TMemoryBasicInformation; begin for i := Application.ComponentCount - 1 downto 0 do begin VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M)); if (Module = 0) or (HMODULE(M.AllocationBase) = Module) then Application.Components[i].Free; end; UnregisterModuleClasses(Module); UnloadPackage(Module); end; 在装载包之前,应用程序需要知道所有已注册类的名字。改善这一情况的方法是建立一个注册机制,以便告诉应用程序所有由包注册的类的名字。 六、实例 多重包:包不支持循环引用。也就是说,一个单元不能引用一个已经引用了该单元的单元(嘿嘿)。这使得调用窗体中的某些值难以由被调用的方法设置。 解决这个问题的方法是,创建一些额外的包,这些包同时由调用对象和包中的对象引用。设想一下我们如何使Application成为所有窗体的拥有者?变量Application创建于Forms.pas 中,并包含在VCL50.bpl包中。你大概注意到了你的应用程序既要将VCL50.pas编译进来,也同时你的包也需要(require) VCL50。 在我们第三个例子中,我们设计一个应用程序来显示客户信息,并且可根据需要(动态)显示客户订单。 那么我们可以从哪里开始呢?像所有的数据库应用 程序一样,我们需要连接。我们创建一个主数据模块,包含一个 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论