Dynamische applicaties

Erik Stok (aka Baldo)

Er zijn op NL Delphi al veel vragen gesteld over het concept dynamische applicaties. Hoewel veel mensen, bewust of onbewust, een soort dynamische applicatie willen bouwen is er opvallend weinig literatuur over te vinden. En werkende voorbeelden zijn al helemaal schaars. Bij deze een poging daar verandering in aan te brengen...

Definitie

De meeste artikelen die ik over het onderwerp dynamische applicaties las vergaten eenvoudigweg te vertellen wat een dynamische applicatie eigenlijk is. Een dynamische applicatie is een applicatie die run-time qua samenstelling van functionaliteit kan wijzigen.

Een goed voorbeeld hiervan is de verkenner van Windows. Winzip breidt de functionaliteit van de verkenner uit door zich in het pop-up menu van de verkenner te plaatsen. De verkenner biedt faciliteiten om programma’s van derde partijen in te laten haken, de derde partij draagt zorg voor de implementatie van de code die de verkenner van informatie voorziet hoe en waar dit dynamische gedeelte dient te verschijnen.

Natuurlijk is het concept veel breder dan de manier waarop de verkenner werkt. Je kunt zelf in Delphi applicaties bouwen waarvan bijvoorbeeld delen van het menu dynamisch worden gevuld afhankelijk van wat de gebruiker aan functionaliteit heeft geïnstalleerd. Of je kunt bijvoorbeeld de lijst van rapporten bepalen op basis van de rapporten die geladen zijn.

Schematisch is een dynamische applicatie als volgt voor te stellen:

De voordelen van dynamische applicaties zijn duidelijk. Je kunt je applicatie in delen uitrollen. Gebruikers installeren alleen dat wat nodig is of dat waar ze een licentie voor hebben.

Een ander voordeel is dat je je applicatie kunt uitbreiden en alleen het dynamisch toe te voegen deel hoeft uit te rollen. Hetzelfde geldt voor eventuele patches in de dynamische onderdelen. Je hoeft alleen het aangepaste dynamische onderdeel opnieuw uit te rollen, niet de complete applicatie.

Een ander mogelijk voordeel is dat je derde partijen toestaat uitbreidingen op jouw applicatie te bouwen. Natuurlijk moet je goed afspreken wat jij aanbiedt aan de derde partij en wat jij van hem verwacht dat hij implementeert. WinAmp is een goed voorbeeld van een dynamische applicatie waar derde partijen veel voor implementeren.

Er zijn natuurlijk ook nadelen aan het maken van dynamische applicaties. Je zult goed in de gaten moeten houden wat er allemaal geïnstalleerd moet worden om de applicatie te laten werken.

Je zult ook een goede scheiding moeten maken tussen de verschillende onderdelen van je applicatie, want het ene onderdeel van de applicatie moet goed blijven functioneren als het andere onderdeel er niet is. Dat is iets waar je met statische applicaties wat slordiger mee om mag gaan.

Het veranderen van de afspraken wat een dynamisch onderdeel moet doen en wat jouw applicatie doet voor een dynamisch onderdeel is ook een risicovolle onderneming. Je zult zorg moeten dragen dat alleen dynamische onderdelen geladen worden die voldoen aan de afspraken die gelden voor deze versie van de applicatie.

Tevens moet er toezicht gehouden worden op de beveiliging van de applicatie. Als jij een dynamisch onderdeel voor je applicatie kan bouwen, dan kan een ander dat ook. En waar heeft die programmeur allemaal toegang toe als zijn onderdeel geladen wordt?

Technieken

Er zijn verschillende technische oplossingen mogelijk om dynamische applicaties te maken met Delphi. De drie meest besproken zijn dynamische applicaties op basis van dll’s, op basis van com en op basis van Delphi packages.

Het spreekt voor zich dat het statische deel van de applicatie een reguliere Delphi applicatie is. Delphi biedt gelukkig faciliteiten om dll’s, com-objecten en Delphi packages te maken, dus ook het dynamische deel van de applicatie kan in Delphi worden gebouwd.

Bij dynamische applicaties op basis van dll’s wordt het dynamische gedeelte van de applicatie in dll’s geïmplementeerd. Het voordeel van een dll oplossing is dat iedere programmeur die in staat is een dll te bouwen jouw applicatie kan uitbreiden, dus ook C++ programmeur kan uitbreidingen bouwen. Tevens kun je in een dll allerlei code insluiten zonder je zorgen te hoeven maken over conflicten met andere delen van de applicatie (unit1 in dll A heeft geen last van het feit dat er ook een unit1 in dll B bestaat).

Een groot nadeel van een dll oplossing vind ik persoonlijk dat ik rekening moet gaan houden met memory management zodra ik strings, complex types of objecten wilde doorgeven als parameters aan functies.

Tevens is het gebruik van singletons een stuk lastiger, omdat bij mijn implementatie van het singleton pattern iedere dll zijn eigen instantie maakte van de singleton. En dat is natuurlijk niet de bedoeling.

Ook ben ik niet in staat instanties te maken van classes gedefinieerd in dll’s buiten de dll waarin ze gedefinieerd zijn. De nadelen wegen me iets zwaarder dan de voordelen, dus voor dll’s kies ik in ieder geval niet.

Bij dynamische applicaties op basis van com wordt het dynamische gedeelte van de applicatie gebouwd in (in process) com-objecten. Bij communicatie met com-objecten liep ik al vrij snel tegen het probleem aan dat ik aan de com datatypes gebonden ben, tenzij ik mijn eigen datatypes bij com registreer. Dat vind ik wat te ver gaan.

Tevens is het registratieproces van com een vervelende bijkomstigheid. Bij een installatie moet ik er zorg voor dragen dat het com object geregistreerd raakt. En bij de-installatie moet alles weer worden teruggedraaid. En wat me aan het hele registratieproces echt tegenstaat is dat ik een applicatie-specifiek onderdeel voor heel Windows zou registreren. De scope van mijn com object zou zich moeten beperken tot mijn eigen applicatie en niet verder!

Ook com leek mij dus geen geschikte kandidaat voor mijn dynamische applicatie, aangezien ik geen voordelen zie ten opzichte van dll’s en die hebben al niet mijn voorkeur.

De dynamische applicatie oplossing met Delphi packages lijkt veel op die met dll’s, alleen in plaats van dll’s wordt er gebruik gemaakt van bpl’s (feitelijk is een bpl een luxe dll). Bij het gebruik van packages worden de nadelen van de dll’s echter opgeheven. Er is dan geen memory management, er kan wel gebruik gemaakt worden van singletons en classes kunnen wel over package-grenzen heen worden aangemaakt. Een bijkomend voordeel is dat de dynamische onderdelen van de applicatie gebruik maken van dezelfde Delphi packages als de applicatie zelf, dus de uiteindelijke executable en dll’s worden relatief kleiner.

Er is echter wel een nadeel aan packages. Je kunt niet in package A een unit1 en in package B ook een unit1 opnemen. Aangezien het toch een goede gewoonte is om units unieke namen te geven is dat voor mij geen beperkend nadeel. Bij het toestaan van dynamische onderdelen van derden is het natuurlijk wel een beperking om serieus rekening mee te houden.

Een ander nadeel van Delphi packages is de lastige deployment. Het uitrollen van een applicatie wordt een complexe taak omdat je wel alle noodzakelijke run-time packages moet uitrollen, zowel die van jezelf als die van Delphi. En het is geen eenvoudige taak om erachter te komen welke packages allemaal nodig zijn, want de foutmelding volgt vaak pas bij het opstarten van de functionaliteit.

Al met al kleven overal nadelen aan, maar de nadelen van de Delphi packages oplossing zijn voor mij het meest gemakkelijk op te lossen. Unieke naamgeving en een slimme dependency tool lossen deze nadelen op.

Het voorbeeld

Als voorbeeld van een dynamische applicatie gaan we de volgende applicatie maken:

DynamicApp is de applicatie die dynamisch kan worden uitgebreid qua functionaliteit. De message plugin is een dynamisch applicatie onderdeel dat een stuk functionaliteit implementeert dat een booschap toont. De form plugin is een dynamisch applicatie onderdeel dat twee stukken functionaliteit implementeert: het tonen van een modaal form en het tonen van één of meer niet modale forms.

De applicatie

Zoals al eerder gezegd is het de taak van de applicatie om faciliteiten te bieden om dynamische onderdelen zich in te laten haken. In feite sluit het statische gedeelte van de applicatie een contract met potentiële dynamische onderdelen waarin staat “zo kun je inhaken”. Het is een goede zaak om een dergelijk “contract” vast te leggen en interfaces zijn precies daarvoor bedoeld.

De applicatie zal het volgende interface gaan implementeren:

IApplication = interface(IInterface)
procedure RegisterMenuItem(Action: TAction);
procedure UnregisterMenuItem(Action: TAction);
end;

De RegisterMenuItem method stelt dynamische applicatie onderdelen in staat één of meer actions te registreren waarvan de functionaliteit in de OnExecute zal worden gestart. De applicatie draagt zorg voor de toegang naar deze actions, in het geval van het voorbeeld via het menu.

Natuurlijk zal bij het uit de lucht halen van de dynamische functionaliteit ook het item weer uit het menu moeten worden gehaald. Daar dient de UnregisterMenuItem method voor.

Een willekeurig object in de applicatie kan het IApplication interface implementeren. In het voorbeeld is ervoor gekozen om het main form zorg te laten dragen voor deze implementatie, want het main form is altijd beschikbaar gedurende het bestaan van de applicatie en tevens is het menu direct bij de hand.

TFrmMainForm = class(TForm, IApplication)
  ...
public
// De implementatie van IApplication
procedure RegisterMenuItem(Action: TAction);
procedure UnregisterMenuItem(Action: TAction);
end;

In het voorbeeld wordt voor elke action die zich registreert een nieuw menu item aangemaakt onder het menu item “Plugins”.

procedure TFrmMainForm.RegisterMenuItem(Action: TAction);
var
Mni : TMenuItem;
begin
// Maak een nieuw menu item aan
Mni := TMenuItem.Create(mnuMain);
// Zet de action ervan naar de binnengekomen action
Mni.Action := Action;
// Voeg het nieuwe item toe aan het plugins menu
mniPlugins.Add(Mni);
end;

Voor elk item dat zich de-registreert wordt het menu item weer opgeruimd.

procedure TFrmMainForm.UnregisterMenuItem(Action: TAction);
var
i : Integer;
begin
// Loop door het plugins menu
for i := 0 to mniPlugins.Count - 1 do
begin
// Kijk of het huidige menu item de action aan zich gekoppeld heeft
if mniPlugins.Items[i].Action = Action then
begin
// Zo ja, verwijder het menu item dan
mniPlugins.Items[i].Free;
// En stop de loop
Break;
end;
end;
end;

De applicatie is ook verantwoordelijk voor het laden van de dynamische onderdelen van de applicatie. Vaak zal bij het opstarten van de applicatie het laden van de dynamische onderdelen plaatsvinden, en bij het afsluiten worden de dynamische onderdelen weer uit de lucht gehaald. In het geval van het voorbeeld worden in de FormCreate de packages geladen en in de FormDestroy worden de packages weer uit de lucht gehaald.

procedure TFrmMainForm.FormCreate(Sender: TObject);
begin
// Registreer deze applicatie als de applicatie waar packages geregistreerd
// moeten worden
PackageManager.SetApplication(Self);
// Geef opdracht tot het loaden van de packages
PackageManager.LoadPackages(ChangeFileExt(Application.ExeName, '.txt'));
end;
procedure TFrmMainForm.FormDestroy(Sender: TObject);
begin
// Geef opdracht tot het unloaden van de packages
PackageManager.UnloadPackages;
// Geef aan dat er geen applicatie meer is
PackageManager.SetApplication(nil);
end;

Packages

Zoals ook al eerder gezegd draagt het dynamische onderdeel zorg voor de implementatie van de functionaliteit en hoe dit dynamische gedeelte dient te verschijnen. In het geval van het voorbeeld zal de OnExecute van ieder geregistreerde action de implementatie van de functionaliteit bevatten. De applicatie biedt maar één manier hoe de functionaliteit zich kan tonen, namelijk via de RegisterMenuItem method. Impliciete afspraak hierbij is dat de caption van de action zal bepalen welke tekst er in het menu van de applicatie zal worden getoond.

Als het package bij het laden zijn actions registreert bij de applicatie, en zijn actions bij het uit de lucht gaan weer de-registreert, dan hebben we een werkende dynamische applicatie. Het probleem is alleen: het package weet niets van de applicatie waarin hij geladen wordt! Het (de)registreren van de actions bij de applicatie kan dus niet in het package worden gebouwd tenzij het package kennis heeft van de applicatie.

De package manager

Hier komt het principe van de package manager om de hoek kijken. Als er een class zou zijn die kennis heeft van zowel de applicatie als de packages, dan zouden we via een instance van die class de communicatie tussen beiden op kunnen zetten. De package manager is zo’n class.

Ten behoeve van de application heeft de package manager drie methods geimplementeerd. De eerste method is SetApplication. Via deze method kan de applicatie zich registreren bij de package manager zodat de package manager in staat is om op de applicatie invloed uit te oefenen. De tweede method is LoadPackages. De LoadPackages method zorgt ervoor dat de packages geladen worden die in het via de parameter doorgegeven bestand staan opgegeven. De derde method is UnloadPackages. Zoals de naam al doet vermoeden zorgt deze method dat alle packages weer uit de lucht gehaald worden.

Ten behoeve van packages voorziet de package manager in twee methods: RegisterPackage en UnregisterPackage. Zodra een package zich registreert bij de packagemanager zal deze, gebruik makend van de methods die het package moet implementeren, er zorg voor dragen dat alle actions uit het package geregistreerd raken bij de application. Dit neemt de noodzaak van kennis van de applicatie bij de packages weg. De UnregisterPackage method doet precies het tegenovergestelde van RegisterPackage.

In de declaratie van RegisterPackage en UnregisterPackage is te zien dat gebruik gemaakt wordt van een package interface: IPackage. Het package interface is het “contract” dat het package afsluit met de applicatie waarop hij dynamische functionaliteit vormt. Het package garandeert dat het dit interface implementeert. Ook hier geldt dat elke willekeurig object in het package dit interface kan implementeren. In het voorbeeld is gekozen voor een datamodule, want daar kan makkelijk een actionlist in geplaatst worden.

IPackage = interface(IInterface)
function ActionCount: Integer;
function Action(Index: Integer): TAction;
end;

Met de package manager is nog een ander probleem opgelost. Als packages kennis moeten hebben van de applicatie, dan zouden ze dus kennis moeten hebben van het IApplication interface. De enige manier waarop dit het geval kan zijn is de iApplicationIntf unit op te nemen in de uses clause van een unit in het package. Dat veroorzaakt een probleem als de applicatie deze ook al in de uses clause van het main form heeft staan. Je kunt met packages immers niet tweemaal dezelfde unit in een package en/of applicatie gebruiken. Door de unit met het IApplication interface op te nemen in het package manager package is dit probleem uit de wereld geholpen. Zowel de applicatie als alle packages hebben een afhankelijkheid naar het package manager package en daarmee is er geen probleem meer van het dubbel gebruiken van units.

De packages zorgen ervoor dat bij het laden van het package een registratie plaatsvindt. Dit gebeurt door gebruik te maken van de initialization sectie van een unit. De initialization vindt plaats zodra een onderdeel geladen wordt waar de unit in de uses clause staat, in dit geval de package project unit.

initialization
// Maak de datamodule aan die het package interface implementeert
DtmPluginMessage := TDtmPluginMessage.Create(nil);

Zodra de functionaliteit implementerende datamodule aangemaakt wordt vindt de registratie plaats bij de package manager.

procedure TDtmPluginMessage.DataModuleCreate(Sender: TObject);
begin
// Als dit package wordt geladen, dan wordt in de initialization een instance
// van deze class gemaakt. Bij het aanmaken zal deze insance zich moeten
// registreren bij de package manager. De communicatie tussen het package
// en de package manager verloopt dan via de datamodule instance
PackageManager.RegisterPackage(Self);
end;

Zodra het package uit de lucht gehaald wordt vindt de finalization van de unit plaats. Deze finalization draagt zorg voor het opruimen van de datamodule die de dynamische funtionaliteit implementeert.

finalization
// Ruim de datamodule op
FreeAndNil(DtmPluginMessage);

In de destructor van de datamodule wordt zorg gedragen voor de-registratie bij de package manager.

procedure TDtmPluginMessage.DataModuleDestroy(Sender: TObject);
begin
// Als deze datamodule weer wordt opgeruimd moet hij zich ook weer
// de-registreren bij de package manager. Deze datamodule instance wordt
// opgeruimd in de finalization (dus bij het uit de lucht halen van het
// package)
PackageManager.UnregisterPackage(Self);
end;

Compileren

Voordat een applicatie met dergelijke afhankelijkheden gecompileerd kan worden zal er nogal wat ingesteld moeten worden voor de compiler. Er zijn zeker wat instellingen die moeten worden gedaan om alles goed te laten werken.

Het is verstandig om de applicatie onderdelen in verschillende mappen onder te verdelen. In het voorbeeld is daar ook gebruik van gemaakt.

Zorg ervoor dat de dcp van de package manager naar een map gecompileerd worden waar de dynamische onderdelen van de applicatie ook bij kunnen. In het geval van het voorbeeld wordt er voor alle packages gecompileerd naar de ../../bin map en de dcp’s worden naar de ../../dcp map gecompileerd. Door in de packages met dynamische onderdelen ../../dcp op te nemen in het search path, zal de .dcp van de package manager gevonden worden.

Bij de dynamische packages kan het package manager package dan opgenomen worden in de lijst van required packages. Dit zorgt ervoor dat alle units uit het package manager package die in een uses clause staan van een dynamisch package niet in het dynamische package gecompileerd worden, maar dat er voor deze units een verwijzing naar het package manager gebruikt wordt.

Omdat men naar mijn gevoel bij Borland denkt dat het goed kunnen werken met packages niet echt belangrijk is (de updates voor packages verschijnen altijd later of nooit) is aan de package editor weinig aandacht besteed. Als je voor het eerst met deze editor werkt hou dan rekening met het volgende: een unit opnemen in het package kun je doen door gebruik te maken van de ‘Add’ knop, maar voor het toevoegen van een requirement van een ander package wordt dezelfde ‘Add’ knop gebruikt. Het is echter niet zo dat je bij het toevoegen kunt kiezen welke van deze twee acties je wilt doen. Het is afhankelijk van wat je hebt geselecteerd in de treeview met de package inhoud. Heb je de ‘Contains’ node (of iets wat daar onder hangt) geselecteerd, dan voeg je met ‘Add’ een unit toe. Heb je de ‘Requires’ node (of iets wat daar onder hangt) geselecteerd, dan voeg je een requirement toe. Grappig detail hierbij: voor het verwijderen wordt weer geen onderscheid gemaakt.

Om de requirement naar het package manager package op te nemen in een dynamisch package, moet dus eerst de ‘Requires’ node geselecteerd worden. Vervolgens kan de PackageManagerPkg.dcp uit de map dcp geselecteerd worden.

De applicatie maakt ook gebruik van units uit de package manager. Deze moeten ook niet in de applicatie worden meegecompileerd om dezelfde reden als bij de packages. De applicatie zal dan ook gecompileerd worden met de optie ‘Build with runtime packages’.

Deze optie kan worden ingesteld door de applicatie te selecteren in de project group en via het menu Project - Options de tab Packages te selecteren.

Als een applicatie gecompileerd wordt met runtime packages, dan wordt daarmee bedoeld dat units die in de applicatie worden gebruikt die afkomstig zijn uit één van de packages genoemd in de opgegeven lijst van runtime packages niet worden meegecompileerd in de applicatie. In plaats daarvan wordt een verwijzing naar het overeenkomstige package opgenomen.

Om de hoeveelheid packages die uitgeleverd moeten worden te reduceren was mijn eerste gedachte om hier alleen het package manager package op te nemen. Dit kan echter niet, want de dynamische packages hebben requirements naar bepaalde runtime packages. Als daar units in zitten die ook in de applicatie gecompileerd worden dan is daar weer het eerder genoemde unit conflict.

Strikt genomen is het voldoende om de runtime package list van de applicatie op te bouwen uit alleen die packages die gebruikt worden vanuit de dynamische onderdelen. Let hierbij op dat als het package manager package required is in een bepaald dynamisch package, en in het package manager package is het vcl package required, dan is het vcl package dus ook impliciet required voor dat dynamische package.

Dat laatste brengt ons even op een zijspoor. Delphi gaat niet echt handig om met het aangeven welke packages required zijn als je een package compileert. Delphi geeft dan namelijk een overzicht van alle required packages, ook de impliciete required packages. Vaak is het voldoende om maar een aantal van de genoemde required packages op te nemen in de lijst van required packages en wordt de rest impliciet voldaan. Het is aan te raden goed te kijken welke packages expliciet nodig zijn om de lijst van required packages zo klein mogelijk te houden. Dit vergroot de overzichtelijkheid en indien een requirement vervalt worden ook eventuele implicitiete requirements opgeheven.

Even een simpel voorbeeld hiervan. Maak een nieuw package aan. Maak daar een nieuwe unit in aan. Zet in de uses clause van deze unit de unit Spin (de unit die hoort bij de spinedit van de samples tab). Als je het package compileert, dan zegt Delphi dat er required packages missen, namelijk Vcl en VclSmp. Vcl is echter alleen required omdat VclSmp required is. Annuleer de waarschuwing en voeg handmatig in de requires lijst het VclSmp package toe (te vinden in C:\Program Files\Borland\Delphi6\Lib). Als er nu wordt gecompileerd is er geen melding van het feit dat Vcl mist.

Maar nu weer terug naar de lijst van runtime packages voor de applicatie. Om het voorbeeld te laten werken voor zowel Delphi professional als Delphi enterprise (er is voor dit voorbeeld gebruik gemaakt van Delphi 6), bestaat de lijst van runtime packages uit de standaard lijst van runtime packages voor Delphi 6 professional. Het enige standaard package wat niet in de lijst is opgenomen is het office package omdat dit per installatie kan verschillen (office 97 of office 2000).

Aan de lijst is voor deze applicatie het package manager package toegevoegd (aan het einde van de lijst). Delphi heeft nog steeds geen fatsoenlijke editor voor deze lijst, wat aansluit bij het gevoel dat Borland het bouwen met packages niet belangrijk vindt. Toevoegen kan door gebruik te maken van de ‘Browse’ knop of door aan het einde van de lijst een puntkomma toe te voegen en de naam van het package daar achter op te nemen.

Installatie

We hebben nu een applicatie die compileert en werkt. Maar wat moet er nu precies bij de gebruiker worden geïnstalleerd om de applicatie te laten werken? Het antwoord op deze vraag is eenvoudig: de executable en alle noodzakelijke packages. Dat zijn dus de Delphi packages, eventuele third party component packages, applicatie packages (zoals de package manager) en de dynamische packages.

Het is redelijk eenvoudig om te achterhalen welke packages expliciet required zijn en dus welke er moeten worden uitgerold. Deze packages zijn immers terug te vinden in de requires lijst van de packages. Voor packages waarvoor een impliciete requirement geldt is dat echter een stuk lastiger. Eigenlijk is het nalopen van alle applicatiefunctionaliteit en kijken naar eventuele foutmeldingen het enige wat Delphi hiervoor standaard als oplossing biedt.

Er is echter een schaars aanbod van tools op het internet te vinden die helpen bij het bepalen van een lijst van uit te rollen packages. Ik vond persoonlijk alles wat ik tegenkwam onder de maat, dus heb ik zelf iets gemaakt. Deze tool genaamd Re-depend is terug te vinden op de Delphi sectie van mijn homepage. Door de executable en alle dynamische packages op te nemen in de scanlijst kan worden bepaald welke packages required zijn. Het is de bedoeling door te blijven gaan met uitbreiden van de lijst totdat alle noodzakelijke packages gevonden zijn.

Nadat bekend is welke bestanden er bij de gebruiker terecht moeten komen is er natuurlijk de vraag waar deze geïnstalleerd moeten worden. De meeste eenvoudige manier van installeren is alles in de applicatie map installeren. Dan kunnen altijd alle packages gevonden worden en is er nooit een probleem met verschillende versies van packages.

Een nadeel aan deze oplossing is dat voor alle applicaties die packages gebruiken een kopie van de Delphi packages in de applicatie map staan. Dat is een beetje zonde van de ruimte. Een optie is dan om de packages waar meerdere applicaties gebruik van maken te installeren in een map die toegankelijk is voor al deze applicaties. Vaak wordt de windows system map hiervoor gebruikt. Let er wel op dat deze manier van installeren alleen verstandig is voor packages waarvoor de kans op wijzigingen zeer gering is. Als er een nieuwe versie van een package geïnstalleerd wordt, dan kan dat gevolgen hebben voor alle applicaties die gebruik maken van dat package.

Conclusie

Delphi biedt alle faciliteiten om dynamische applicaties te bouwen. De Delphi packages bieden duidelijk de meest krachtige oplossing voor dynamische applicaties. Er zijn wat zaken waar rekening mee gehouden dient te worden alvorens dynamische applicaties kunnen worden gebouwd, maar “common Delphi knowledge” is voldoende om een succesvolle Delphi implementatie te maken. Sta wel stil bij de complexiteit die je jezelf op de hals haalt op het gebied van installatie en updates. Een goede versie administratie en een helder plan voor eventuele updates zijn echt een noodzaak om dynamische applicaties goed te laten werken.