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 recompile 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