Dynamic Link Library (DLL)

Inleiding

Je bent bedreven in het programmeren in Delphi. Je hebt al de meest fantastische routines gemaakt.
Tevens ben je een liefhebber van het programma Excel (je bent ten slotte programmeur). En hoewel Excel veel te bieden heeft ontbreken er toch net een aantal dingetjes die je zelf in Delphi al hebt opgelost.

Je wilt de Delphi routines gebruiken in Excel, maar je weet niet hoe.
Hier komt de oplossing: Zet je routines in een DLL en koppel deze vervolgens met/aan Excel, of aan iedere andere Windows-applicatie waarin je kunt programmeren.

In dit hoofdstuk leer je de volgende zaken:

Wat is een Dll?

DLL staat voor Dynamic Link Library. Vrij vertaald dus een bibliotheek die je dynamisch kunt linken.

Vragen zijn nu: wat kan er allemaal in die bibliotheek staan, en hoe deze vervolgens dynamisch te linken.

In principe zijn er geen beperkingen over wat er in een Dll kan staan, dit kunnen procedures en functies zijn, maar ook volledige formulieren.

Over het linken zegt Microsoft het volgende: Wanneer een applicatie een routine uit een Dll aanroept zal Windows eerst kijken of deze Dll al in het geheugen staat. Als dit zo is, dan zal de applicatie deze Dll gebruiken. Als dat niet zo is, dan zal de applicatie de Dll eerst laden en hierna gebruiken.

Helaas is de praktijk net iets anders. Meestal zal een applicatie de Dll eerst laden, ook al heeft een andere applicatie dit ook al gedaan. Waarschijnlijk komt dat omdat iedere applicatie zijn eigen geheugen- ruimte krijgt toegewezen en de synchronisatie tussen de “openbare” geheugenruimtes niet helemaal goed werkt.

Dat laat onverlet dat er toch een aantal voordelen aan het gebruik van Dll’s zitten.

Zo kun je de code achter de routines aanpassen zonder dat dat gevolgen heeft voor andere applicaties die van de Dll gebruik maken, zolang je de naam van de routine en het aantal en types parameters ongemoeid laat.

Een ander voordeel is dat je routines in een programmeertaal ontwikkeld en deze vervolgens in andere applicaties kunt gebruiken.

Hoe maak je een Dll in Delphi?

In deze paragraaf gaan we een een Dll ontwikkelen met een 4-tal routines: Double, Tripple, KeerOm en Turn.

Double is een functie die een integer met 2 vermenigvuldigd.
Tripple is een functie die een integer met 3 vermenigvuldigd.
KeerOm is een functie de een string in omgekeerde volgorde retourneerd.
Turn doet hetzelfde als KeerOm, maar op een “Windows”-manier.

We zullen zien dat de functie Tripple de minste problemen oplevert.
De functie Double levert problemen op bij het gebruik in Excel.
De functie KeerOm levert allerlei vervelende en onoverkomenlijke problemen op die door
de functie Turn moeten worden opgelost.

De code voor de eerste 3 routines zijn simpel, bij de laatste routine geef ik uitleg.

– Start Delphi
– Kies File–>New…
– Kies Dll en klik op OK

Je krijgt nu een Project-scherm voor je beginnend met library Project1;.
Wanneer je het project bewaard, kies dan een goede naam, omdat die naam de naam van de Dll wordt.

Vervolgens zie je een lang commentaar dat gaat over het gebruik van (Pascal) strings in Dll’s. Hierover straks meer.
Daarna staat de Uses-clausule, de koppeling naar resources, begin en end.

Je zou je in dit project kunnen beperken tot de Dll-interface en de achterliggende code in een aparte unit maken en deze in de Uses-clausule kunnen aanroepen, maar je kunt ook de achterliggende code in dit project-venster kwijt.

Omdat het hier maar een paar routines betreft beperken we ons hier tot dit project-venster.

We beginnen maar eens met de functie Tripple:

– Tik in onder {$R *.RES}: function Tripple(n: integer): integer; stdcall;

De toevoeging van het woord stdcall is zeer belangrijk. Het heeft te maken met de manier waarop Pascal (Delpi) en Windows parameters behandelen. In Delphi gebeurt dit van links naar rechts, terwijl dit in Windows van rechts naar links gebeurt.
Het woord stdcall zorgt ervoor dat Delphi de parameters van rechts naar links behandeld.

– Maak de functie compleet:

begin
  Result := n * 3;
end;

Geen problemen verder, lijkt me.

– Maak nu de routines Double en Keerom:

function Double(n: integer): integer; stdcall;
begin
Result := n * 2;
end;

function KeerOm(s: string): string; stdcall;
  var
    t,l: integer;
    h: String;
begin
  l := Length(s);
  h := ”;
  for t := l downto 1 do
    h := h + s[t];
  Result := h;
end;

Omdat de functie KeerOm gebruikt maakt van (Pascal-) strings is het noodzakelijk om extra maatregelen te nemen. Strings in Windows zijn namelijk gedefinieerd als pointers naar Char en eindigen op een #0-karakter (null-termineted-strings). In Pascal en dus ook in Delphi zijn strings als een zelfstandig type gedefinieerd.
Om toch (enigszins) met strings via Dll’s te kunnen werken moet de Unit ShareMem worden gebruikt.
Deze Unit moet als eerste Unit in de Dll worden aangeroepen, alsmede als eerste Unit in de project-file van het project dat zo’n routine met stings gebruikt.

– Voeg ShareMem toe direct achter de Uses-clausule
– Verplaats de cursor onder de end; van de function KeerOm
– Voeg nu hieronder de volgende regels toe:

exports
  Triple, Double, Keerom;

Alles wat achter het woord exports staat is bruikbaar voor andere programma’s. Het is dus de interface van de Dll.
Dat betekent dus automatisch dat je ook andere routines in je Dll kunt maken die niet zichtbaar zijn voor de buitenwereld.

– Bewaar dit project onder de naam FirstDll.
– Kies vervolgens uit het menu Project de optie Build All Projects.

Delphi heeft nu van je project het uitvoerbare bestand FirstDll.dll gemaakt.

Hiermee besluiten we even dit voorbeeld en gaan kijken hoe we deze Dll kunnen gaan gebruiken.

Hoe gebruik je een Dll in Delphi?

Om een functie/procedure uit een Dll te gebruiken gebruik je voordat je de functie/procedure aanroept de volgende syntax (in de implementation-sectie):

function naam(parameters:types):type; stdcall; external ‘dllnaam.dll‘; of
procedure naam(parameters:types); stdcall; external ‘dllnaam.dll‘,
waarbij naam de naam van de functie/procedure is en dllnaam.dll de naam van de dll.

Daarna kun je deze functies/procedures gewoon gebruiken.

– Start een nieuw project in Delphi (File –> New Application)
– Plaats 4 Edit-boxen en 2 Buttons op het form
– Verander de Caption van de Buttons resp. in ‘x2, x3’ en ‘DraaiOm’
– Zorg voor (min of meer) dezelfde code als hieronder:

unit CallFirstDllUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Button1: TButton;
    Edit4: TEdit;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

function Double(n: integer): integer; stdcall; external ‘FirstDll.dll’;
function Tripple(n: integer): integer; stdcall; external ‘FirstDll.dll’;
function KeerOm(s: String): String; stdcall; external ‘FirstDll.dll’;

procedure TForm1.Button1Click(Sender: TObject);
  var t: integer;
begin
  t := StrToInt(Edit1.Text);
  Edit2.Text := IntToStr(Double(t));
  Edit3.Text := IntToStr(Tripple(t));
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Edit4.Text := KeerOm(Edit4.Text);
end;

end.

– Open het Project-venster van de applicatie (Project –> View Source)
– Zet direct achter Uses ShareMem, dus weer als eerste aan te roepen Unit
– Bewaar het (complete) project in dezelfde map als waar de dll staat

Wanneer je het project laat runnen en je een getal in Edit1 plaatst en hierna op de knop ‘x2, x3’ klikt, dan zal in Edit2 dat getal keer 2 komen en in Edit3 het getal keer 3.
Als je een woord of zin in Edit4 zet en daarna op de knop ‘DraaiOm’ klikt, zal het woord in Edit4 omdraaien.
Dat laatste gaat alleen maar goed als de Unit ShareMem steeds als eerste Unit wordt gebruikt, dus zowel in de Dll als in het Project dat van de Dll gebruik maakt.

Hoe gebruik je een Dll in Excel?

Voordat je een routine uit een Dll in Excel kunt gebruiken, moet Excel het gebruik van Macro’s toestaan.
Waarschijnlijk is dit standaard niet het geval.

– Open Excel
– Ga naar het menu Extra–>Macro–>Beveiliging…
– Stel het beveiligingsniveau in op Gemiddeld of op Laag

Bij de optie Gemiddeld zal Excel bij het openen van een werkboek met Macro’s toestemming vragen om deze te mogen uitvoeren.

Nu kunnen we aan de slag.

Net als in Delphi zullen we de routines uit de Dll in VBA moeten decalreren, voordat we deze kunnen gebruiken.
VBA (Visual Basic for Applications) is de achterliggende programmeertaal van de Office-paketten.
We zullen eerst de functie Tripple gaan gebruiken.
Om dit voor elkaar te krijgen zullen we de Dll vanuit een Module moeten aanroepen.

– Kies Extra–>Macro–>Visual Basic Editor of klik Alt+F11

Je komt nu in de VBA-editor van Excel.
Bovenin staat het menu alsmede een knoppenbalk.
Links staan het project-venster en het eigenschappen-venster.
De rest van de ruimte is voor de code.
Ieder werkblad heeft zijn eigen code-venster. Ook het werkboek heeft zijn eigen code-venster.
Wanneer je echter iets globaals wilt doen (dus voor alle werkbladen) neem je een Module, want deze heeft standaard een globaal karakter.

– Kies uit het menu Invoegen de optie Module

Als het goed is, heb je nu een module-venster gekregen, dat kun je zien in het project-venster links.
Nu kunnen we de functie Tripple uit FirstDll.dll gaan declareren.

De syntax in VBA is:

Declare Function/Sub <te gebruiken naam> Lib “<[pad+]dll-naam>” [Alias “<naam in Dll>”] [(<parameters>)] [As type],

Als de Alias ontbreekt dan moet de <te gebruiken naam> de naam in de Dll zijn.

Voor onze functie Tripple wordt het als volgt:

– Tik in het module-venster de volgende regel:

Declare Function Tripple Lib “d:\data\delphi\dll\FirstDll.dll” (ByVal n As Integer) As Integer

d:\data\delphi\dll is natuurlijk mijn pad, dat kan bij jou anders zijn.

Het woordje ByVal voor de parameter is belangrijk. In VBA zijn parameters standaard Var-parameters. In alle andere talen die ik ken is dit standaard niet het geval, zoals het hoort.
Het woordje ByVal maakt van de parameter een Value-parameter.
Met het woordje As geef je het type aan, danwel aan parameters, dan wel aan de functie’s.
Vergeet niet aan het einde van de regel op Enter te drukken, want dan pas gaat VBA de regel evalueren. Mocht er een fout in zitten, dan wordt dat meteen aan gegeven.

In principe is dit voldoende en kunnen we de functie Tripple in een cel van een werkblad als gewone Excel-functie gebruiken.

– Activeer een werkblad (dat kan via de taakbalk)
– Ga naar Cel B4
– Tik een getal in en druk op Enter
– In de cel daaronder: =Tripple(B4) en druk op Enter

Als het goed is staat daar nu het getal van B4 x 3.

Laten we nu de functie Double gaan invoegen.

– Activeer weer de VBA-editor
– Tik onder de eerste Declare-regel de volgende regel:

Declare Function Double Lib “d:\data\delphi\dll\FirstDll.dll” (ByVal n As Integer) As Integer

Wanneer je nu op Enter drukt reageert VBA met een foutmelding: Compileerfout: Verwacht: aanduiding.

Wat is hier aan de hand?

Het is zo in VBA dat Double een standaard-type is, zoals bijvoorbeeld ook Integer. Deze naam is dus beschermd en kan niet voor iets anders worden gebruikt.

Gelukkig biedt de syntax voor Declare al de oplossing. We moeten hier gebruik maken van de Alias.

– Verander de regel in:

Declare Function Dubbel Lib “d:\data\delphi\dll\FirstDll.dll” Alias “Double” (ByVal n As Integer) As Integer

Als je nu op Enter drukt blijkt alles goed te zijn.

– Keer terug naar het werkblad
– Tik in Cel B6 de formule: =Dubbel(B4) + enter

Alles werkt nu naar behoren.

Als laatste zouden we nu de functie KeerOm kunnen gaan toevoegen en vervolgens gebruiken, maar dat doen we niet omdat Excel daar vreselijke dingen van gaat doen. Bij mij eindigt dat in een fatal-hangup van het programma, dat wel in het geheugen actief blijft.
En dat dat zo vreselijk fout gaat heeft te maken met de manier waarop Delphi met strings omgaat versus de manier waarop Windows met strings omgaat.
In Delphi konden we dat nog rechtbreien met de Unit ShareMem, maar met Excel lukt ook dat niet.

Wanneer je met strings via Dll’s wilt werken moet je gebruik maken van de zogenaamde 0-terminated-strings.

Problemen oplossen

Het grote probleem doet zich dus voor bij het gebruik van strings.
Om deze problemen op te lossen moeten we in Delphi gebruik maken van tekenreeksen van het type PChar.

Het type PChar is niets anders dan een pointer!

Het vergt wat kennis om hier mee om te gaan.

Gelukkig biedt de Unit SysUtils een aantal routines die ons behulpzaam kunnen zijn.

Omdat PChar een pointer is, om precies te zijn een pointer naar Char, moeten we vooraf wel een buffer declareren waar de Char’s in kunnen. En daarvoor is het dan weer noodzakelijk dat je vooraf weet hoe groot de tekenreeks gaat worden.

We gaan nu een nieuwe functie Turn maken die de functie KeerOm op een Windows manier aanpakt.

– Open Delphi met het project FirstDll.dpr
– Voeg onder de implementatie van de functie KeerOm de volgende regels toe:

// PChar is een pointer naar Char(s).
// Het is daarom niet noodzakelijk om de parameter s als Var-parameter
// te declareren. De geheugenplaatsen worden nl. veranderd.
function Turn(s: PChar;buf: Cardinal): LongBool; stdcall;
// Cardinal en LongBool gebruiken voor compatibiliteit met Windows
var
  t: Cardinal;
  h: PChar; // is null-terminated-string
  c: Array [0..1] of Char;
begin
  buf := buf + 1; // nodig voor StrCopy, daar StrLen de #0 niet meetelt
  if StrLen(s) <= buf then
    begin
      GetMem(h,buf); // reserveer geheugen
      StrCopy(h,s);
      c[1] := #0; // nodig voor StrCat
      s[0] := #0; // maak s leeg
      for t := StrLen(h)-1 downto 0 do // h[StrLen(h)] = #0, h[0] is 1e karakter
        begin
          c[0] := h[t];
          StrCat(s,c);
        end;
      Result := True;
    end
  else
    Result := False;
end;

Hopelijk is het commentaar voldoende.

– Voeg de functie Turn toe aan de exports-clausule
– Bewaar het project en kies daarna voor Project–>Build All Projects

We hebben nu de Dll uitgebreid met de functie Turn.

Gebruik van Turn in Delphi

– Open in Delphi het project CallFirstDll
– Voeg een derde Button toe met de Caption Turn
– Dubbelklik op de knop om de Event-handler te (laten) creeren
– Ga naar de lege regel onder de declaratie-regel van de functie KeerOm …
– Tik in: function Turn(s: PChar;buf: Cardinal): LongBool; stdcall; external ‘FirstDll.dll’;
– Ga weer terug naar de Event-handler Button3Click
– Zorg ervoor dat de event-handler er als volgt uitziet:

procedure TForm1.Button3Click(Sender: TObject);
  var
    p: PChar;
    l: Cardinal;
begin
  p := PChar(Edit4.Text);
  l := StrLen(p);
  if Turn(p,l) then
    Edit4.Text := p
  else
    Edit4.Text := ‘False’;
end;

In de regel onder begin zie je een (type-) cast van string naar PChar. In de regel daaronder wordt de lengte van de tekenreeks bepaald. Denk eraan dat deze lengte de 0-terminator niet meetelt!
Dat wordt in ons geval gecorrigeerd door de functie Turn in de Dll.

– Bewaar, run en test het project.

De knop Turn doet nu, als het goed is, hetzelfde als de knop DraaiOm.

Gebruik van Turn in Excel

Om op een gemakkelijke manier van de functie Turn in Excel gebruik te kunnen maken, gaan we in Excel een nieuwe functie maken.

– Open Excel, eventueel met het bewaarde bestand uit een van de vorige opgaven
– Activeer de VBA-editor (Alt+F11)
– Activeer de module of voeg deze toe
– Declareer de functie Turn als volgt:
Declare Function Turn Lib “d:\data\delphi\dll\FirstDll.dll” (ByVal s As String, ByVal l As Long) As Boolean
– Ga naar de onderkant van de module
– Voeg de volgende regels toe:

Function KeerOm(ByVal tekst As String) As String
  If Turn(tekst, Len(tekst)) Then
    KeerOm = tekst
  Else
    KeerOm = “iets ging fout”
  End If
End Function

Hiermee hebben we in Excel een nieuwe functie KeerOm gemaakt die we vervolgens als gewone Excel-functie kunnen gaan gebruiken.

– Activeer een werkblad
– Tik in een cel (D8) een stuk tekst
– Ga naar de cel daaronder en tik in: =KeerOm(D8) + enter

Als het goed is werkt deze functie zonder problemen.

Tevens heb je nu geleerd hoe een Excel-werkblad-functie te maken. En die kennis krijg je voor niets erbij.

Hoe zet je een Form om in een Dll?

Het antwoord is simpel.

Maak een Dll-project met een functie of procedure die het gewenste Form creert, activeert en weer weggooit.

Hiermee ben ik feitelijk klaar met deze paragraaf, maar om te voorkomen dat jullie me nu massaal gaan mailen met vragen zal ik hier een compleet voorbeeld uitwerken.

Het idee achter dit voorbeeld heb ik overgenomen van de Delphi-goere, Marco Cantù. Zijn boeken zijn zeer leesbaar en leerzaam.

We gaan een Kleuren-dialoog-venster maken, waarmee RGB-kleuren kunnen worden gecomponeerd en die de RGB-kleurcode retourneerd wanneer er op de Ok-knop wordt geklikt.

Vervolgens gaan we een Dll-maken met een functie GetColor die het dialoogvenster weergeeft en de RGB-code retourneerd.

Dus laten we maar beginnen met het eenvoudige werk.

Bouw eens een Kleuren-dialoog-venster, zoiets als hieronder:

 

 

 

 

 

 

 

In de combobox “Kies kleur”, zitten een aantal standaard gedefinieerde kleuren van Delphi:

 

 

 

 

 

 

 

Hieronder een aantal tips die je kunnen helpen:

  • Een panel wordt gebruikt om de kleur weer te geven: property Color
  • Form.BorderStyle = bsDialog
  • Gebruik de functie RGB(r,g,b: byte) om de kleur samen te stellen: r-rood, g-groen, b-blauw
  • Voeg bij de public-sectie van TForm1 de variabele Kleur en OKleur van het type TColor toe
  • Gebruik de events Scroll en Change van de scrollbars
  • Gebruik voor de hex-waarde van RGB: format(‘#%x’,[RGB(r,g,b)]);
  • Bij de knoppen Ok en Cancel moet het Form worden gesloten
  • Als k de RGB-waarde is, dan rood=k mod 256, groen=Trunc(k/256) mod 256, blauw=Trunc(k/65536)
  • De variabele Kleur geeft de gekozen kleur terug

– Bewaar het project onder de naam ColorForm o.i.d.

Nu gaan we onze Kleuren-dialoog onderbrengen in een Dll.

– Start een nieuw Dll-project in Delphi (File–>New… DLL, Ok
– Bewaar het onder de naam Color
– Zorg voor onderstaande invulling:

library Color;

uses
  SysUtils,
  Classes,
  Forms,
  Dialogs,
  Controls,
  ColorFormUnit1;

{$R *.RES}

function GetColor(col: LongInt): LongInt; stdcall;
  var
    ColorForm: TForm1;
begin
  Result := col;
  try
    ColorForm := TForm1.Create(Application);
    try
      ColorForm.Kleur := col;
      ColorForm.ShowModal;
      Result := ColorForm.Kleur;
    finally
      ColorForm.Free;
    end;
  except
    on E: Exception do
      MessageDlg(‘Fout in Formulier:’ + E.Message,mtError,[mbOK],0);
    end;
end;

exports
  GetColor;

end.

Merk op dat we bij de Uses-clausule de Unit van de Kleuren-dialoog zetten.
Van belang is hier natuurlijk de functie GetColor.
Omdat we bij het gebruik van deze dialoog altijd een waarde willen terughebben beginnen we met het resultaat gelijk te maken aan de parameter Col.
Vervolgens proberen we de Kleuren-Form te creeren.
Omdat mijn KleurenDialoog de initiele kleur van de aanroepende applicatie wil aannemen gebruik ik de varabele Kleur.
Hierna laten we de dialoog als Modal-form weergeven; de aanroepende applicatie moet dus wachten totdat de dialoog is afgesloten. Zouden we dat niet doen dan moet er nog veel meer werk gedaan worden. Misschien iets voor een latere keer.
De gekozen kleur uit de dialoog moet vervolgens als resultaat van de functie GetColor worden geretourneerd.
Tot slot moet de dialoog worden vernietigd en vrij worden gegeven.
Omdat we ons toch op glas ijs begeven gieten we het een en ander in een try-exept-blok en try-finally-blok.

– Kies Project–>Build All Projects om hier een Dll van te maken

Nu zijn we klaar voor gebruik.

Hoe gebruik je een Dll-form?

In Delphi?

Je declareert een Dll met een form in Delphi op precies dezelfde manier als een Dll zonder form.
Ook het gebruik van de Form is hetzelfde, want zo hebben we onze Dll gemaakt! De form als een functie.

– Start een nieuw project in Delphi
– Zet op het form een knop
– Delcareer in de implementation-sectie de functie GetColor als volgt:
function GetColor(col: LongInt): LongInt; stdcall; external ‘Color.dll’;
– Zet in de Button1Click-event-handler de volgende regel:
Form1.Color := GetColor(Form1.Color);
– Bewaar en run het programma

Bij een klik op de knop wordt nu keurig het Kleuren-dialoog-venster geopend. Als je op Ok klikt verdwijnt de dialoog en krijgt je Form de kleur die je gekozen hebt. Klik je op Cancel dan gaat de Kleuren-dialoog ook weg, maar verandert de kleur van je Form niet.

In Excel?

Het wordt wat saai, maar ook hier is er eigenlijk niet iets anders aan de hand dan met een Dll zonder form.

– Open Excel met een werkboek
– Activeer de VBA-editor (Alt+F11)
– Activeer een module of voeg deze toe
– Declareer de functie GetColor als volgt:
Declare Function GetColor Lib “d:\data\delphi\dll\formdll\Color.dll” (ByVal c As Long) As Long
– Maak een procedure (dat heet in VBA een Sub) KleurCellen als volgt:

Sub KleurCellen()
  With Selection.Interior
    .Color = GetColor(.Color)
    .Pattern = xlSolid
    .PatternColorIndex = xlAutomatic
  End With
End Sub

Deze macro zorgt ervoor dat alle geselecteerde cellen van een werkblad de eventueel gekozen kleur krijgen.

– Activeer een werkblad en selecteer een of meerdere cellen
– Kies Extra–>Macro–>Macro’s… of klik Alt+F8
– Klik op KleurCellen en daarna op de knop Uitvoeren
– Kies of maak een kleur en klik op Ok

Is dit mooi of is dit mooi…

Tot slot

Ik hoop dat je bovenstaande informatie nuttig vond en dat je voor jezelf de mogelijkheden ziet.
Een uitbreiding van bovenstaande zou kunnen zijn om ook met modal-less-forms te kunnen werken, maar deze informatie is ook vast te vinden in één van de vele boeken van de al eerder genoemde Marco Cantù.