The Unofficial Newsletter of Delphi Users - by Robert
Vivrette
Inside the Delphi 3 Package (*.dpl)
by Wei Bao - wei.bao@bj.col.com.cn
Editors Note: This is a very interesting technique, but I can't vouch for its safety. If you have an adventurous streak and don't mind digging into the bowels of compiled code, then this technique might be useful to you.
One of the most important improvements of Delphi 3 is the introduction of packages. Now the components are registered into individual packages. And you can dynamically load different packages in the IDE for individual projects. The best example is the IDE, and I think every Delphi expert wants to know how the IDE does this. In this article, I'll open the secret.
If you have written a component yourself, you know that in the unit of your component you have a procedure named Register. One for each component's unit. But you never call it in your application. And you even can't find the machine code for the procedure! Who needs it? or who calls it?
The answer is in fact that the component is bound into the package you've defined for Delphi. After Delphi compiles the package's source, the package's binary format (*.dpl) is loaded by the IDE through the LoadPackage procedure call.
If you look at the source of LoadPackage(in sysutils.pas), you'll see that there's a call to Windows API LoadLibrary. LoadLibrary? This function is used to load DLL files?
Well, let's look at the DPL file...
If you run the command 'tdump -e XXX.DPL', you may see that the DPL file is DLL indeed. In its export section, you may find something like following:
...
------- -------- ----
0049 000058a8
@GetPackageInfoTable
0046 000058a8
Dclqrt30.@GetPackageInfoTable@51F89FF7
0045 000058b0
Dclqrt30.@PackageLoad@51F89FF7
0044 000058c0
Dclqrt30.@PackageUnload@51F89FF7
0047 000058c0 Finalize
0048 000058b0 Initialize
0042 00005638
QRNew.Finalization@51F89FF7
0041 00005668
QRNew.QRNew@00000000
* 0043 00005614
QRNew.Register@51F89FF7
0038 0000426c
qreport.ExecuteDesignVerb@4807F569
0036 00004f54
qreport.Finalization@51F89FF7
0040 00004054
qreport.GetDesignVerb@0F6FDFF6
...
(* is added by author)
And near the LoadPackage, you'll find a function called GetPackageInfo(in sysutils.pas). With this function, you can get the source file of a DPL within several lines:
procedure EveryUnit(const Name: string; NameType: TNameType; Flags:
Byte; Param: Pointer);
begin
if NameType = ntContainsUnit then
memo1.lines.add(name);
// you get the unit name here !!
end;
procedure GetUnitName;
var
HPack, flags : integer;
begin
HPack := LoadPackage('Some.DPL');
GetPackageInfo(HPack, @Hpack, flags, EveryUnit); //
the second parameter is not used now, but later.
end;
Yes!
Now you can load the package into your application space, but you can't use any components in it. If the package is written by you, you can solve this with the following method:
In the Initialization section of your component's unit, call registerClass(yourcomponent). The Initialization procedure is called after call to LoadLibrary in LoadPackage(see sysutils.pas). After doing this, you may create an instance of your component:
AComponent := TComponentClass(GetClass('yourComponent')).create(Self);
But if the DPL is from the third party, you won't have an opportunity to do this.
Now let's go back to the dump of DPL, look at the line begins with '*' . You see the DPL exports a function named 'QRNew.Register@51F89FF7'. It's obviously that the 'QRNEW' is the unit name. What's '51F89FF7'? Is it an address of the Register procedure? No, after you look through the total output of the dump, you'll find that they are always '51F89FF7'. So we can call it a magic number. It's a const for Delphi 3.0 at least.
If we want to use the component in the DPL, we should call the Register ourselves.
So we come to the exciting part:
We should know the unit name to call 'unitname.Register@51F89FF7'. As I've given before, we can get the unit name in procedure EveryUnit . The rest thing is familiar to those who've been working with Windows APIs for a long time.
The new version of EveryUnit is :
procedure EveryUnit(const Name: string; NameType: TNameType; Flags:
Byte; Param: Pointer);
var
PRegister : pprocedure;
begin
if NameType = ntContainsUnit then
begin
//memo1.lines.add(name);
// you get the unit name here !!
@PRegister :=
GetProcAddress(PInteger(Param)^, // get the
HPack
pchar(Name +
'.Register@51F89FF7'));
if @PRegister <> nil then
PRegister;
end;
end;
Now all these work fine? No, you'll see a error messagebox 'register component ..'. Why? Everything is here. Now let's look at the procedure RegisterComponents you always call in procedure Register(in classes.pas). A pointer to a procedure is checked, and it always equal nil in our application! Of course, you can see that the solution is simple, write our procedure and set the pointer to it.
procedure myRegisterComponentsProc(const Page: string;
ComponentClasses: array of TComponentClass);
var i : integer;
begin
for i := low(ComponentClasses) to High(ComponentClasses) do
RegisterClass(ComponentClasses[i]);
end;
initialization
RegisterComponentsProc:= MyRegisterComponentsProc;
Every thing is well ? No, if you compile the project without runtime package, you'll get the same error messagebox. Yes, to use DPLs dynamically you can only compile your project as a runtime package.
If you try to load DPLs that ship with Delphi 3.0, you'll meet other problems, try to solve them! And you can contact the author at wei.bao@bj.col.com.cn or wei.bao@usa.net. I'm very interested to talk about these technologies with any one!
If you want to take a look at the source code I used for these examples, you can download it here...