Вы находитесь на странице: 1из 7

Making a single Delphi Custom Package

Introduction

This is my attempt at explaining how to create Delphi custom single package, why would you want to do
this you ask?, well using packages comes with some advantages.

1. Compiler Speed.
2. Smaller EXE's & DLL's.
3. DLL plug ins etc, very easy.

But unfortunately if you compile just using the standard packages, you end up having to re-distribute lots
of BPL's.

Now what would be nice, is the advantages of BPL's without the dis-advantages of BPL hell. And this
document will hopefully help explain how to do this.

The BPL we will be creating is RunTime only, you could create a RunTime/DesignTime package but this
does require it uses at least the VCL packages so for now we'll just create a Runtime only.

Also note this document is based on Delphi 7, but should work with earlier versions too.

How Many BPL's?

You might not know this, but if you compile with packages and create a simple form, your application will
in fact use 2 BPL's, and this is just for a Form. You can see this by pressing F8 to run your program then
goto Project|Information for project. Here you will see that RTL70 & VCL70 are required. Now load up one
of you big projects that uses other third party component, and compile with packages, and see how many
packages are are required now. As you can see keeping track of all these, and having to redistribute
them etc becomes a pain.

Getting Started

Now when creating a custom package, what we don't want is any VCL or Third party units getting
compiled into our DLL & EXE's, these units will want to be placed inside our custom package. So first
thing to do is remove any references to any VCL or Third party units. Goto Tools|Environment Options
and make a copy of the Library path for later, once you have a copy of the Library replace it with just the
BPL directory. Your Environment Dialog should look similar to->

1/7
Making a single Delphi Custom Package

The reason we blank out the Library path and only leave the BPL directory is because when compiling
with Packages our EXE don't require the them, and doing so will make sure our package gets compiled
with all the required units that our DLL & EXE's require. But we still need the BPL directory so Delphi can
find our new BPL.

Creating the package

Ok, lets create our package. As you found out earlier even creating a Delphi App that has just a Form on
it will require 2 BPL's, where going to create our own package that will make it only require 1. Goto
File|New|Other and select package. Now save this package and call it MyPkg, you will notice that Delphi
would have automatically added the RTL.DCP to the requires section remove this, as were trying to
create a single package. If you try to compile this package you should get an error about System.pas not
found, don't worry this is what we want. The Library path we made a copy of earlier paste this into the
the packages Options|Directories|Conditionals Search path. Now if you try and compile the package again
it should compile without problems. So far this package is pretty useless it has no components at all
included, so the next stage is adding our required components. Now the easy way to do this is by trying
to compile an Application using our newly created Pkg, from a standard blank Delphi program with a
Form, goto Project|Options|Packages and tick the Build with packages checkbox and place into the
textbox MyPkg. Something like->

If you try and compile this project you should get an error that it cannot find sysinit.dcu, now this is the
only unit that cannot be placed into a package, most likely because it's the unit that's required to
maintain packages in the first place. Now a simple solution to this is to place the SysInit.dcu file into your
Projects/BPL directory as this is still on your Environment|Library Path. SysInit.Dcu can be found in your
Delphi/Lib Directory make a copy of this an place in your Delphi/Project/Bpl directory. Now recompile
again, if everything works as expected it should now complain that Forms.dcu cannot be found. Now this
give us a hint to what unit's are required inside out Custom Package :), So from inside our Custom
Package add a unit called MyPkgUnits and simply add the Forms to the uses Clause. eg->

unit myPkgUnits;

interface

uses forms;

implementation

2/7
Making a single Delphi Custom Package

end.

Re-Compile the package, Delphi will them come up with a Dialog saying that VCL and RTL packages are
required to make it compatible with other installed packages, well we don't want it to be compatible with
other installed packages so press the cancel button here, another dialog will then appear saying that if
these changes are not applied errors will occur, press the Yes buttons as errors will not occur,
unfortunately every time you re-compile this package Delphi is going to come up with these 2 dialogs, I
don't know of any way to get around this unfortunately.

Now go back to your Project and re-compile, if everything works as expected it now should compile
without any problems. And if you press F8 and look at the required BPL's there should be just the 1 single
package called MyPkg.

Of course your not going to just create programs that use Forms and a couple of standard components,
so now open up a more complicated application or add some third party components to the test project
and re-compile. Delphi will automatically complain that it cannot find some DCU or PAS file make a note
of these units and simple add them to the uses clause of the MyPkgUnit.pas, re-compile the package then
the application, if it complains about another unit just keep on adding them to the package, and re-do
until the EXE compiles without errors.

Example-> lets add a TnxTable to our Test form, if we re-compile it should complain that it cannot find
db.dcu. Now we could add db to our uses clause in our MyPkgUnit.pas, but actually we can save some
time here by adding nxDb to our uses clause because Delphi will then implicitly add db for us. So now re-
compile our package remembering to press Cancel then Yes to the dialogs that appear. Recompile our
Project, and heh presto, we have a Delphi form that's got a TnxTable on it and still only using a single
package. If you look at the EXE & BPL you will notice how small the EXE is and how big the Package is,
on mine the EXE is 27K & the BPL is about 1.5Meg, the BPL is large because implicitly it has compiled into
a lot of VCL code. If like me you will end up with a one Big Bpl that you use for all your application,
currently mine is about 14Meg, but this includes a lot of Third party components. A little trick if you want
to build a package with all your components you will ever use, just simple create a dummy project and
add all the components to a form, and do the compile-recompile package procedure.

One thing to keep an eye on, when adding third party components etc, Delphi will automatically alter
your Environment/Library path, keep an eye on this and make sure it's only got the BPL directory listed.

DLL Plug ins

As mentioned earlier using a single package, makes making a DLL plug in system easy. First you might
ask why would you want to use DLL's, as you might have found it's possible to use BPL's as plug ins. Well
one problem with BPL's is that they all have to share the same Namespace, what I mean by this is all unit
names have to be unique, IOW: you couldn't have a Unit called main.pas in 2 BPL's and have them
loaded at the same time. Using DLL's you get your own private namespace, this is very handy as you can
just copy an existing plug in you've created and slightly modify it and you'll be able to load them both
without worrying about Unit name conflicts. In fact in this article I'm going to do just that, I'll show a
very simple plug in system that loads some DLL's that contain a Form, each DLL will be just a copy that
we'll slightly change so that the form is different, adding a few controls and maybe changing the color
etc. Later you will also see how mixing DLL's and interfaces are a match made in heaven. Also you will
notice we don't even have to use the Exports directive.

Our plug in manger

First thing we need is some sort of plug in manager. So create a file called myPluginManager.pas and
paste the following code. Then add this to your single custom package and Build.

unit myPluginManager;

interface

uses windows,classes,sysutils,dialogs;

type
TPluginManager = class

3/7
Making a single Delphi Custom Package

private
fPlugins:TInterfaceList;
public
procedure LoadDLLs(mask:string);
property Plugins:TInterfaceList read fPlugins;
procedure AddPlugin(I:IInterface);
constructor Create;
destructor Destroy; override;
end;

function PluginManager:TPluginManager;

implementation

var
_PluginManager:TPluginManager;

function PluginManager:TPluginManager;
begin
if not assigned(_PluginManager) then
_PluginManager:=TPluginManager.Create;
result:=_PluginManager;
end;

{ TPluginManager }

procedure TPluginManager.AddPlugin(I: IInterface);


begin
fPlugins.Add(I);
end;

constructor TPluginManager.Create;
begin
inherited Create;
fPlugins:=TInterfaceList.Create;
end;

destructor TPluginManager.Destroy;
begin
fPlugins.Free;
inherited;
end;

procedure TPluginManager.LoadDLLs(mask: string);


var
sr:TSearchRec;
ff:string;
begin
if FindFirst(mask,0,sr) = 0 then begin
repeat
ff:=includetrailingbackslash(extractfilepath(mask))+sr.name;
if LoadLibrary(pchar(ff)) = 0 then
RaiseLastWin32Error;
until FindNext(sr)<>0;
end;
end;

initialization
finalization
if assigned(_PluginManager) then
_PluginManager.free;
end.

The reason we add it to our custom package is because we want this to be in the same namespace as the
EXE & DLL's. IOW: Our Plugin manager is common to all DLL's & EXE.

4/7
Making a single Delphi Custom Package

The plug in interface

Earler I mentioned about interfaces, now these are very handy when creating plugins, especially as your
plugin system grows. We will use interfaces to expose what our DLL supports. So copy the following code
and save as MyPluginFormInterface.pas

unit myPluginFormInterface;

interface

uses forms;

type
IMyPluginFormInterface = interface
['{03D6BB1C-A31C-4D47-AAA3-06C8CD94ADB4}']
function CreateForm:TForm;
function PluginName:string;
end;

implementation

end.

Here you will see I've created 2 simple functions that our DLL will need to implement so that it supports
our Plugin Form, the PluginName we will use to give it a nice name that we can show, and CreateForm
will be to create an instance of our Form. Note: this unit does'nt need to be compiled into our Custom
package but it does need to be accessable from our EXE & DLL's.

Our Main EXE

Ok, here we will create our EXE that later will load up our DLL plug ins. So goto Delphi and create a new
Application called TestPlugin. Add a TMainMenu with a menu item called Plugins. In the uses clause add
myPluginManager & myPluginFormInterface. And on the OnCreate event add the following code->

procedure TForm1.FormCreate(Sender: TObject);


var
lp:integer;
PF:IMyPluginFormInterface;
m:TMenuItem;
begin
pluginmanager.LoadDLLs(includetrailingbackslash(extractfilepath(paramstr(0)))+'*.dll');
for lp:=0 to PluginManager.Plugins.Count-1 do begin
if supports(PluginManager.Plugins[lp],IMyPluginFormInterface,PF) then
begin
m:=TMenuItem.Create(self);
m.tag:=lp;
m.Caption:=PF.PluginName;
m.OnClick:=PluginMenuClick;
plugins1.Add(m);
end;
end;
end;
Also add a method to the form called PluginMenuClick(Sender: TObject);
procedure TForm1.PluginMenuClick(Sender: TObject);
var
f:TForm;
PF:IMyPluginFormInterface;
m:TMenuItem;
begin
m:=Sender as TMenuItem;
if supports(PluginManager.Plugins[m.tag],IMyPluginFormInterface,PF) then begin
f:=PF.CreateForm;
if not f.visible then f.show;
end;
end;

5/7
Making a single Delphi Custom Package

Now compile this EXE making sure you compile it with your Custom Package. If you run this not much
will happen yet, as we've yet to create our Plugins. :)

Our Plug in DLL

Ok, now here's the fun part, lets create some plugins. Create a new project using Delphi's DLL Wizard,
save this in a directory under our EXE called plugin1, also you may as well save the project as plugin1.
Delphi's DLL Wizard give you some comments about DLL memory management & Sharemem, ignore it!!
in fact why not delete the comment as our DLL plugin wont have this problem :)

The part that stops us having to use sharemen is you also make sure you compile this DLL using our
Custom Package, so goto project/Options/packages and make sure were going to be compile this DLL
with our Custom Package too. The reason this works is because our DLL & EXE are using our Custom
Package there going to be using the same Instance of the Memory Manager and as such ShareMen is not
needed, neat eh?. Also while in Project/Options change the Output Directory to ..\ to make our DLL's get
placed in our EXE's directory.

Because our plug ins are going to show some Forms, lets make a form, so goto File/New/Form modify
this form change it's color & caption etc. Set the forms name to fMyForm and save as MyForm inside our
Plugin1 directory.

Now lets make our DLL a plug in.. :) Paste the following code into the Projects Source->

library plugin1;

uses
SysUtils,
Classes,
forms,
myPluginManager,
myPluginFormInterface,
myForm in 'myForm.pas' {fMyForm};

{$R *.res}
type
TMyPlugin = class(TInterfacedObject,ImyPluginFormInterface)
function CreateForm:TForm;
function PluginName:string;
end;

{ TMyPlugin }

function TMyPlugin.CreateForm: TForm;


begin
result:=TfMyForm.Create(nil);
end;

function TMyPlugin.PluginName: string;


begin
result:='My Plugin Form One';
end;

begin
PluginManager.AddPlugin(TMyPlugin.Create);
end.

Now compile your DLL, if everything works as expected you should have a DLL called Plugin1.dll in your
EXE's directory arround 18K in size.

Go back to your EXE project and run, if everything went well you should now see a menu option under
plugins called "My Plugin Form One" if you click this it should then create an instance of your Form.
Now the bit I really like :), now make a copy of your Plugin1 directory, and say call it Plugin2, Open the
Project and Save the Project as Plugin2, modify your fmyForm form, eg. change it's color etc. Here you
might want to change the return of PluginName so that it appears differently on the menu. Compile and
run you Main EXE, you now should have 2 menu options that create 2 different looking forms. You may
also want to use explorer and delete the plugin1.Dpr etc from the directory.

6/7
Making a single Delphi Custom Package

Of course the example is not that much use, but hopefully you can see how easy it is to expand on for
use in a real live system.

Ok, that's it, I hope you find it usefull, and before you know it, you'll be doing much more fancy plug ins
that this :). In writing this article I've tried to keep things as simple as possible, there are lots of places
this system could be enhanced, eg. the ability to keep a cache of supported interfaces that a perticalar
DLL supports, here you could then Load DLL's on demand etc. Using reference counted Objects it could
also be possible to dynamically unload DLL's too , but I'll maybe leave that too another day.

Fonte.: http://www.saxon.co.uk/SinglePkg/

7/7