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 Uses 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 do not 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 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 ApplicationMany Developers Think That Delphi Packages Are A Place To Put Component - But a package can (and shop) Also Be used to modularize an application.
To show how to use packages to module your application we would result
Create a new Delphi application with 2 forms:.. Form1 and Form2 Remove Form2 from the auto-created form list in the Project | Options | Forms menu dialog Drop a button on Form1 and add the following code to the OnClick event handler: with TForm2 .Create (Application) DO
Begin
ShowModal;
FREE;
End; Remember to add unit2 to unit1's Uses clause. 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 wented to create Form2 in a reusable module?
The answer is - packages!
To create a package for form2 we will
Open the project manager (View | Project Manager).. Right-click on the Project Group and select "Add New Project ..." Select "Package" from the "New" items list You should now see the Package editor Select the " Contains "Item and Press the" button. Now use the "browse ..." Button to select "unit2.pas." The package surplend now contain the unit "unit2.pas." 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 .)
................ ..
SELECT The Project "Project1.exe" from the project manager by double-click on it. Right-Click and SELECT "Options ..." (you can also select project | options ... from the menu. Select the "packages" .. tab Check the "Build with runtime packages" check box Edit the edit-box in the "Runtime packages" section to read: "Vcl50; Package1" and OK the options NOTE: Do not remove Unit2 from the application 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. ......... ..
Delphi Achieves this by staticly linking in the package at compile time.
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 .
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 firmly unload the dll by using the freeelibrary 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: hw; stdcall;
VAR
HelpModule: hmodule;
Htmlhelp: tfnhtmlhelpa;
Begin
Result: = FALSE;
HelpModule: = loadingLibrary ('hhctrl.ocx');
IF HelpModule <> 0 THEN
Begin
@HTMLHELP: = GetProcaddress (HelpModule,
'Htmlhelpa');
IF @HTMLHELP <> nil dam
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 loading 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, unloaded the package utu.
Procedure unloadPackage (Module: hmodule);
Let us Go Back to Our EXAMPLE AND Make A Few Changes:
Select "Project1.exe" from the project manager. Right-click and select "Options ..." Select the "Packages" tab. Remove "Package1" from the "Runtime packages" edit-box section and OK the options. On Delphi's Toolbar, Click ON The "Remove File from Project" Button. Select "Unit2 | form2" from the list and "OK." 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.) onclick event. Add Two variables of type hmodule and tpersistentclass. var
PackageModule: hmodule;
AClass: TPersistentClass; Load the package Package1 by using the LoadPackage function PackageModule:.. = LoadPackage ( 'Package1.bpl'); Check that the Package Module is not 0 (zero) Create a persistent class using the GetClass function, passing it the name of the form within the package as its parameter: AClass:. = GetClass ( 'TForm2'); 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; Finally, unloaded the package: unloadPackage (packagemodule); save the project.here is The completion listing of the onclick event:
Procedure TFORM1.BUTTON1CLICK (Sender: TOBJECT);
VAR
PackageModule: hmodule;
ACLASS: TPERSISTENTCLASS;
Begin
PackageModule: = loadingpackage ('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 shouth in the package. Each unit in the package is loading and finalize.
Let's return to our eXample and make a few calls:
Double-click on "Package1.bpl" in the project manager;... This will activate the package editor Click on the symbol next to "Unit2" in the "Contains" section This will expand the unit tree Double-click on " Unit2.pas "to activate the unit's source code Scroll down to the end of the file and add an initialization section Register the form's class using the RegisterClass procedure: RegisterClass (TForm2); Add a finalization section Un-register the form's class... using the UnRegisterClass procedure: UnRegisterClass (TForm2); 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 Unloads 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 the aren't any instances of any
Of the classes from module instantiated, IF
So thrle. (this associces what
Classes area oowned 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
.
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 ur ign 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 StaticLink Into Our Application By Setting The Compile with package..
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 schement of how our application will function:
Using swappable packages: Another example of package usage is the creation of swappable packages One does not 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 ..................... ..
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. .............. ...
Download The Example Source Code of this Paper Here.
By Vino Rodriguesvinorodrigues@yahoo.com