Dynamic packages in Delphi

Dynamic packages in Delphi

Abstract:A paper on how to create and use dynamic packages in Delphi. By Vino Rodrigues.

Any discussion of advanced package use in Delphi must raise the question: Why use packages at all?

Design-time packages simplify the tasks of distributing and installing custom components. Runtime packages, which are optional, offer several advantages over conventional programming. By compiling reused code into a runtime library, you can share it among applications. For example, all of your applications -- including Delphi itself -- can access standard components through packages. Since the applications don't have separate copies of the component library bound into their executables, the executables are much smaller-saving both system resources and hard disk storage. Moreover, packages allow faster compilation because only code unique to the application is compiled with each build.

Packages are even better when they are used dynamically. Packages offer a modular library approach to developing applications. At time those modules may become an optional entity of your application. Take for example an accounting system with an optional HR module. For some installations you will need to install just the base application -- for others you will install both the base application and the HR module. This level of modularization can be easily achieved by just including the optional package to the install base. In the past this was usually achieved with dynamically loaded DLLs, but with Delphi's package technology it is easy to make your modular classes part and parcel of your application. Classes created from packages become application-owned and thus can interact with your application classes.

Runtime packages and your application

Many developers think that Delphi packages are a place to put component -- but a package can (and should) also be used to modularize an application.

To show how to use packages to modularize your application we will create an example:

  1. Create a new Delphi application with 2 forms: Form1 and Form2.
  2. Remove Form2 from the auto-created form list in the Project | Options | Forms menu dialog.
  3. Drop a button on Form1 and add the following code to the OnClick event handler:
    with TForm2.Create(Application) do
    begin
      ShowModal;
      Free;
    end;
  4. Remember to add Unit2 to Unit1's uses clause.
  5. Save and run the project.

We have created a simple application that shows a form with a button that shows another form when it is clicked.

But what if we wanted to create Form2 in a reusable module?

The answer is -- PACKAGES!

To create a package for Form2 we will:

  1. Open the project manager (View | Project Manager)
  2. Right-click on the Project Group and select "Add New Project..."
  3. Select "Package" from the "New" items list.
  4. You should now see the Package editor.
  5. Select the "Contains" item and press the "Add" button.
  6. Now use the "Browse..." button to select "Unit2.pas."
  7. The package should now contain the unit "Unit2.pas."
  8. Now save and compile the package.

The package is now complete. You should have a file called "package1.bpl" in your Projects BPL directory. (The BPL is the Borland Package Library; the DCP is the Delphi Compiled Package -- sort of like the DCU of a PAS file.)

That's all that need to be done to the package. We now need to compile the original application with the package option switched on.

  1. Select the project "Project1.exe" from the Project Manager by double-clicking on it.
  2. Right-click and select "Options..." (You can also select Project | Options... from the menu.
  3. Select the "Packages" tab.
  4. Check the "Build with runtime packages" check box.
  5. Edit the edit-box in the "Runtime packages" section to read: "Vcl50;Package1" and OK the options.  
  6. NOTE: Do not remove Unit2 from the application.
  7. Save and run the application.

The application will run and behave just like before -- the difference can be seen in the file size. Project1.exe is now only 14K as apposed to the original 293K. If you use a resource explorer to view the contents of the EXE and the BPL you will find that both the DFM and the code for Form2 now reside in the package.

Delphi achieves this by statically linking in the package at compile time. (That's why you shouldn't remove the unit from the EXE project.)

Just think of what can be achieved by doing this: One could create data modules in packages and quickly modify their source and only distribute the new package when our data-access rules have changed, like when we move from BDE based connectivity to ADO. Or, we could create a from that show's a "this option is not available in this version" message in one package, and then a similarly named form that has functionality in a same-named package. We will then have a "Pro" and "Enterprise" version of our product without much effort.

Dynamic load and unload of packages

Statically linked DLLs and BPLs work fine in most cases, but what if we decide not to deploy the BPL? We would get a "The dynamic link library Package1.bpl could not be found in the specified path..." error and our application would stop functioning. Or what if, in our modular application, we wanted to have numerous plug-in like modules?

We need a way to dynamically link to the BPL at runtime.

With DLLs this is a simple process of using the LoadLibrary function.

function LoadLibrary(
  lpLibFileName: PChar): HMODULE; stdcall;

Once the DLL is loaded we can call exported functions and procedures within the DLL by using the GetProcAddress function.

function GetProcAddress(hModule: HMODULE;
  lpProcName: LPCSTR): FARPROC; stdcall;

We finally unload the dll by using the FreeLibrary function.

function FreeLibrary(hLibModule: HMODULE): BOOL;
  stdcall;

In this example we will dynamically load Microsoft's HtmlHelp library:

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;

Dynamically loaded BPLs

BPLs are just as simple. Well almost.

We dynamically load the package by using the LoadPackage function.

function LoadPackage(const Name: string): HMODULE;

We create TPersistentClass of the class we wish to instantiate by using the GetClass function.

function GetClass(const AClassName: string):
  TPersistentClass;

Instantiate an object of the loaded class and use it.

And when we are done, unload the package using the UnloadPackage procedure.

procedure UnloadPackage(Module: HMODULE);

Let us go back to our example and make a few changes:

  1. Select "Project1.exe" from the project manager.
  2. Right-click and select "Options..."
  3. Select the "Packages" tab.
  4. Remove "Package1" from the "Runtime packages" edit-box section and OK the options.
  5. On Delphi's toolbar, click on the "Remove file from project" button.
  6. Select "Unit2 | Form2" from the list and then "OK."
  7. Now go to the "Unit1.pas" source and remove Unit2 from its uses clause. (These steps are required to remove any link to Unit2 and the package we wish to load dynamically.)
  8. Go to the source of Button1's OnClick event.
  9. Add two variables of type HModule and TPersistentClass.
    var
      PackageModule: HModule;
      AClass: TPersistentClass;
  10. Load the package Package1 by using the LoadPackage function.
      PackageModule := LoadPackage('Package1.bpl');
  11. Check that the Package Module is not 0 (zero).
  12. Create a persistent class using the GetClass function, passing it the name of the form within the package as its parameter:
      AClass := GetClass('TForm2');
  13. If the persistent class is not nil, create and use an instance of the class just a before.
      with TComponentClass(AClass).Create(Application)
        as TCustomForm do
      begin
        ShowModal;
        Free;
      end;
  14. Finally, unload the package using the UnloadPackage procedure:
      UnloadPackage(PackageModule);
  15. Save the project.

Here is the complete listing of the OnClick event:

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;

Unfortunately that's not the end of it.

The problem is that the GetClass function requires the class to be registered before the function can find it. Usually form classes and component classes that are referenced in a form declaration (instance variables) are automatically registered when the form is loaded. But the form isn't loaded yet. So where should we register the class? The answer: in the package. Each unit in the package is initialized when the package is loaded and finalized when the package is unloaded.

Let's return to our example and make a few changes:

  1. Double-click on "Package1.bpl" in the project manager; this will activate the package editor.
  2. Click on the + symbol next to "Unit2" in the "Contains" section. This will expand the unit tree.
  3. Double-click on "Unit2.pas" to activate the unit's source code.
  4. Scroll down to the end of the file and add an initialization section.
  5. Register the form's class using the RegisterClass procedure:
      RegisterClass(TForm2);
  6. Add a finalization section.
  7. Un-register the form's class using the UnRegisterClass procedure:
      UnRegisterClass(TForm2);
  8. Finally, save and compile the package.

Now we can safely run the "Project1" application - it will function just as before, but with the added benefit of being able to load the package when you want to.

Finally

Make sure you compile any project that uses packages (static or dynamic) with runtime packages turned on: "Project | Options | Packages | Build with runtime packages."

You must be careful that when you unload a package you destroy any objects using those classes and un-register any classes that were registered.

This procedure may help:

procedure DoUnloadPackage(Module: HModule);
var
  i: Integer;
  M: TMemoryBasicInformation;
begin
  { Make sure there aren't any instances of any
    of the classes from Module instantiated, if
    so then free them.  (This assumes that the
    classes are owned by the application) }

  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;

An application requires "knowledge" of the registered class names prior to loading the package. One way to improve this would be to create a registration mechanism to inform the application of all the class names registered by the package.

PRACTICAL EXAMPLES

Using multiple packages: Packages do not support cyclic referencing. That is, a unit cannot use another unit that already uses it. This makes it rather difficult to set value in the calling form.

The answer lies in using additional packages the both the calling object and the packaged object use. How else do you think we then set up "Application" as the owner to all our forms? The variable "Application" resides in Forms.pas, which in turn is packaged into VCL50.bpl. You will notice that your application compiles with VCL50 and your package requires VCL50.

We can use this methodology for our own package design.

In our third example we will design an application that shows customer information and optionally (dynamic load) shows customer orders.

Where can we start? Well, like all database applications, we will need connectivity. So we will create a main data module that will contain a TDataBase connection. Then we will place this data module in a package (I'll call it cst_main).

Now in our application we will create the customer form and use the DataModuleMain that we will statically link into our application by setting the compile with packages to include VCL50 and cst_main.

We then create a new package (cst_ordr) that will contain our customer orders form and data module and require our cst_main. We will then write code in our main application to dynamically load this package. Since the main data module is already loaded when the dynamic package gets loaded, it will use the application's instance of the main data module.

This is a schematic of how our application will function: 

Using swappable packages: Another example of package usage is the creation of swappable packages. One doesn't even need to use dynamically loaded packages for this! Let us assume we have the need to distribute a trail version of our application with an expiry or trial period. How would we go about doing this?

First we would create a "splash" form -- something that would show a graphic and the word "Trial" on it that will display when the application starts up. Then we would create an "about" form that would display information about our application. Lastly, we would create a function that would be called at some time to test for expiry. Then we would bundle these two forms and the function into a package and deploy it with our trial version.

For our "paid for" version we would also create "splash" and "about" forms, remembering to put the same class names (even the case), and the test function (this one doing nothing) into a package with the same name as our trial package.

What, may you ask, will this help? Well -- just think of it -- we could distribute a trial version publicly. Then when a client purchases the application we only need to send the non-trial package. This will shrink our distribution process to only one complete install and a registered package upgrade.

Packages open up many doors in the Delphi and C++Builder development world. They enable a true modular design without the overhead of passing window handles, callbacks, and other technologies in DLLs. This in turn will shorten our development cycle for modular programming. All we need do is let Delphi's package technology do the work for us.

Download the example source code of this paper here.

By Vino Rodrigues
vinorodrigues@yahoo.com



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 这个错误提示表示当前的软件包管理渠道中找不到所需的包。这可能是由于您尝试安装的软件包不存在于您正在使用的渠道中,或者您需要添加其他渠道来获取所需的软件包。解决此问题的方法包括: 1. 检查您输入的软件包名称是否正确,或者尝试使用其他名称搜索软件包。 2. 确认您正在使用的渠道是否包含所需的软件包,如果不包含,可以尝试添加其他渠道或更新渠道。 3. 如果您正在使用conda进行软件包管理,可以尝试使用以下命令来搜索其他可用的渠道: ``` conda config --add channels channel_name ``` 其中 `channel_name` 是您要添加的渠道名称。 4. 如果您正在使用其他软件包管理工具,可以参考其官方文档以了解如何添加其他渠道来获取所需的软件包。 ### 回答2: “packagenotfounderror: packages missing in current channels” 错误通常出现在Python环境的安装、更新或升级过程中。它意味着您的程序包库中可能缺少某些程序包。 这个错误通常出现在您在尝试安装、更新或升级某个Python程序包时。Python使用包管理器(如pip)来管理程序包,但这些包的可访问性取决于您的包库(也称为channels)中所包含的程序包。 针对这个问题,我们可以通过以下步骤来解决: 1. 使用conda或pip清除缓存 这是修复常见的包管理错误的有效方法之一,因为它会清除缓存文件并重新下载需要的程序包。使用以下命令清除缓存: pip cache purge # pip清除缓存 conda clean --all # conda清除缓存 2. 更新程序包库(channels) 更新程序包库(channels)可以解决某些包未找到的情况。使用以下命令更新程序包库: conda update --all # conda更新所有已安装的程序包 pip install --upgrade pip # pip更新 3. 安装丢失的程序包 如果某个程序包确实在库中缺失,那么我们可以尝试手动安装。使用以下命令安装程序包,其中packageName是您要安装的程序包的名称: conda install packageName # conda安装 pip install packageName # pip安装 总之,常见的packagenotfounderror错误通常是由程序包库缺少某些包,或因缓存文件和程序包库更新等原因而导致的。使用上述步骤中的任何一种方法都可以帮助您解决该问题。 ### 回答3: packagenotfounderror表示在当前的软件包通道中缺少某些软件包。当我们在使用某个软件或者编写某个程序时,如果需要使用特定的软件包,但是在当前的软件包通道中找不到该软件包,就会提示packagenotfounderror错误。 这个错误通常会导致我们不能完成某些操作或者程序运行异常。解决该错误的方法是找到缺少的软件包并将其安装。首先,需要确认软件包的名称是否正确。然后,我们需要寻找其他来源,例如第三方的软件仓库或者之前使用的历史仓库。 如果仍然找不到软件包,我们可以考虑手动安装该软件包。这种方法可能需要下载软件包的源代码,粘贴或者通过命令行安装软件包。在手动安装时需要注意依赖关系问题,确保所有的依赖关系都满足。 此外,我们还可以尝试升级软件包管理器或者添加其他仓库来解决packagenotfounderror错误。 总之,我们可以通过确认软件包名称是否正确、寻找其他来源、手动安装软件包、升级软件包管理器或者添加其他仓库等方式来解决packagenotfounderror错误。尽管这个错误可能会引起一些操作上的不便,但是在解决该错误后,我们可以顺利完成我们的操作并且程序可以正常运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jokerman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值