Bob Swart (aka Dr.Bob)
Delphi en CORBA

Toen Delphi 4 uitkwam was één van de grootste "hype"-zaken de CORBA ondersteuning (in Delphi 4 Client/Server en later ook in Delphi 5 Enterprise), die dan ook vaak besproken werd in de pers en tijdens seminars. Nu de hype wat is gaan zakken, wordt het tijd om te kijken wat er nou daadwerkelijk aan CORBA support in Delphi zit, en dan blijkt dat het eigenlijk nog niet helemaal "af" is...

CORBA
CORBA staat voor Common Object Request Broker Architecture, en is een object geöriented communicatie architectuur tussen een client en een server. De communicatie wordt afgehandeld door een ORB (Object Request Broker) en IIOP (Internet InterORB Protocol). We gebruiken IDL (Interface Definition Language) als platform-onafhankelijke taal om de properties en methoden van CORBA objecten te specificeren. Methoden zijn hierbij gewoon funkties die door de server ge mplementeerd zijn en door client aangeroepen kunnen worden. Om de "koppeling" tot stand te brengen, moet de IDL worden gecompileerd tot stub code voor client (zodat we de methoden kunnen aanroepen) en skeleton code voor de server (zodat die de methoden kan implementeren). De CORBA Smart Agent zorgt ervoor dat een CORBA server toepassing "gevonden" kan worden door een CORBA client toepassing, zoals we straks zullen zien.
E n van de belangrijkste eigenschappen van CORBA is het feit dat het zowel platform- and taal-onafhankelijk is. Alhoewel we in dit artikel alleen maar Delphi op Win32 zullen zien, kun je middels CORBA ook een Delphi Win32 client aan een Linux Java Server knopen (in theorie, dan). Om dit alles te laten werken zullen de parameters van de methoden getransporteerd moeten worden (over het netwerk) in een formaat die taal/platform-onafhankelijk is. Het converteren van native naar het taal/platform-onafhankelijke formaat heet marshalling, en anderom is unmarshalling het proces om van het taal/platform-onafhankelijke formaat weer een native type te maken. Dit gebeurt overigens volledig achter de schermen, en is niet iets om ons druk over te maken.
Tot zover de theorie. Laten we eens zien hoe Delphi dit alles in praktijk brengt...

Delphi
Delphi heeft twee ingebouwde CORBA Wizards die de gebruiker ondersteunen bij het aanmaken van een nieuwe CORBA datamodule (voor tables, queries, etc) en een nieuw CORBA object (zonder Form of Datamodule). Beide Wizards zijn te vinden in de Object Repository, die we krijgen na een File | New in de Delphi IDE:

Een CORBA Data Module lijkt erg op een "normaal" Data Module, en zullen we nog wel een andere keer nader bekijken. Wat we deze keer gaan doen is kijken hoe we een eenvoudig CORBA Object kunnen gebruiken voor het maken van een CORBA Server en een CORBA Client, en hoe die twee dan met elkaar kunnen communiceren (middels CORBA, dus).
De CORBA Object Wizard vraagt om een ClassName (in ons geval gewoon SDGN - er zal vanzelf een T en een I voorgezet worden). We kunnen ook instancing opgeven (instance per client of shared instance), net als het threading model (single-threaded of multi-threaded). Ik heb voor beide de default waarden aangehouden, omdat we toch maar naar een klein voorbeeld gaan kijken.

Nadat we op de OK-knop klikken genereert Delphi een nieuwe unit voor ons (zonder Form of Datamodule), met de volgende code:

  unit Unit2;
  interface
  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, ComObj, StdVcl,
    CorbaObj, DrBob42_TLB;

  type
    TSDGN = class(TCorbaImplementation, ISDGN)
    private
      { Private declarations }
    public
      { Public declarations }
    end;

  implementation
  uses
    CorbInit;

  initialization
    TCorbaObjectFactory.Create('SDGNFactory', 'SDGN',
     'IDL:DrBob42/SDGNFactory:1.0', ISDGN,
      TSDGN, iMultiInstance, tmSingleThread);
  end.
Merk op dat de initialization sectie van deze unit al meteen het CORBA object aanmaakt. Dit is dus duidelijk de code voor een CORBA Server object (waar clients contact mee moeten zien te maken).
De class TSDGN is afgeleid van TCorbaImplementation en implementeert het ISDGN interface. Dit is een eerste aanwijzing dat CORBA in feite door (COM) interfaces is gerealiseerd in Delphi. Dat dat laatste het geval is wordt nog eens duidelijker door het feit dat het aanmaken van properties en methods voor ons TSDGN CORBA object eigenlijk alleen kan door gebruik te maken van de Type Library editor:

We zien dat de type library voor ons DrBob42 project zowel een ISDGN (Dispatch interface voor SDGN) als een SDGN Object zelf bevat. Nieuwe methoden en properties kunnen we alleen aan het interface toevoegen (zie screenshot). Laten we bijvoorbeeld een method "TheAnswer" toevoegen, met één parameter van type "long" en als modifier "[in]" (dat wil zeggen een "input" parameter. We kunnen ook [out] of zelfs [in/out] specificeren). In dit geval hebben we een [out] parameter genaamd "Answer" nodig:

De Text tab van de Type Library bevat nu de psuedo-IDL voor deze methode. Ik zeg met opzet "pseudo-IDL", omdat het zeker geen standard IDL is, maar een soort Pascal IDL (of een soort C++ IDL - je kan kiezen in de Tools | Environment Options - Type Library pagina). Voor bovenstaande methode is de psuedo-IDL als volgt:

  [id(0x00000001)]
  HRESULT _stdcall TheAnswer([out] long * Answer );
Dit lijkt een beetje op IDL, maar "puur" IDL zou het _stdcall keyword niet nodig hebben (dat natuurlijk de stdcall calling conventie van het Win32 platform aangeeft). Jammer, want het lijkt hierdoor ook iets moeilijker om Delphi IDL door een andere omgeving zoals JBuilder in te laten lezen om een client stub te laten genereren (daar komen we zo nog op terug). Daarnaast heb ik zelf nog geen goede manier gevonden om een "puur" IDL bestand van bijvoorbeeld JBuilder op zijn beurt in Delphi in te lezen. Wat we missen is een echte IDL2PAS en met name een PAS2IDL conversie-programma.
Delphi zelf biedt een goede ondersteuning voor het opstellen van een IDL-specificatie, maar voegt daar dan wel een DCOM-achtig sausje aan toe. Normale IDL-specificaties worden niet ondersteund. Er bestaat een oplossing (een freeware idl2pas) zou een equivalente werking moeten hebben ten opzichte van bijvoorbeeld de bij VisiBroker geleverde idl2java en idl2cpp. Omdat deze tool geen preprocessing ondersteund en ook geen types met een scope is dit echter nog niet een volledige oplossing. Hopelijk zullen we dit wel in een toekomstige versie van Delphi gaan zien.

CORBA Server
Afgezien van het IDL dompertje, kunnen we natuurlijk wel rustig verder gaan met het genereren van de Delphi server, door op de knop met de twee groene pijlen te drukken ("refresh implementation"), waarna de unit2 er als volgt uit komt te zien:

  unit Unit2;
  interface
  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, ComObj, StdVcl,
    CorbaObj, DrBob42_TLB;

  type
    TSDGN = class(TCorbaImplementation, ISDGN)
    private
      { Private declarations }
    public
      { Public declarations }
    protected
      procedure TheAnswer(out Answer: Integer); safecall;
    end;

  implementation
  uses
    CorbInit;

  procedure TSDGN.TheAnswer(out Answer: Integer);
  begin
    Answer := 42; { onze implementatie van de CORBA Server }
  end;

  initialization
    TCorbaObjectFactory.Create('SDGNFactory', 'SDGN',
     'IDL:DrBob42/SDGNFactory:1.0', ISDGN,
      TSDGN, iMultiInstance, tmSingleThread);
  end.
We bewaren dit project (als DrBob42.dpr) en compileren het. Voor we het uitvoeren, moeten we eerst de VisiBroker Smart Agent draaien (ik weet niet 100% zeker of het noodzakelijk is om dit erv r te doen, maar het werkt, dus doe ik het altijd wel). Als de Smart Agent eenmaal draait, kunnen we ook onze DrBob42 CORBA server de lucht in hijsen. Als je dit vanuit de Delphi IDE doet, kun je niet meteen met de CORBA client beginnen, dus kunnen we beter de CORBA server vanuit de Explorer starten (of zelfs een DOS Box). Vervolgens sluiten we het huidige project, en beginnen een nieuwe applicatie: onze CORBA client (bewaren als Client.dpr deze keer).
Om nu de CORBA client van dezelfde IDL definities gebruik te laten maken, zouden we eigenlijk gebruik moeten maken van de client stubs, die op basis van de IDL gegenereerd zouden zijn. Echter, omdat Delphi de CORBA laag via (D)COM realiseert, kunnen we volstaan met het toevoegen van de import unit van de (server) type library DrBob42_TLB.pas. Met Project | Add To Project kunnen we deze unit toevoegen:

Zorg er ook voor dat de unit met ons Form ook bij de Type Library import unit kan, want hierin staat de code (definities) die we nodig hebben om een instantie van de CORBA server te maken, en de interface routines (de methode TheAnswer) aan te roepen. Kortom, zet DrBob42_TLB in de uses clause van de interface sectie.
Nu zijn we er helemaal klaar voor. De CORBA server draait, de Smart Agent draait (zodat onze CORBA client de CORBA server kan vinden), we moeten alleen nog een instantie van de CORBA server door onze client laten maken. Hiervoor moeten we even in de Type Lirary import unit kijken (zeker als we een type library import unit van een onbekende Delphi CORBA server krijgen), en zoeken naar een Factory class met een CreateInstance class function. Deze ziet er in ons voorbeeld als volgt uit.

  TSDGNCorbaFactory = class
    class function CreateInstance(const InstanceName: string): ISDGN;
  end;
We zullen een aanroep naar CreateInstance moeten doen, met als InstanceName de naam van het Object, namelijk SDGN. Deze aanroep kunnen we in onze FormCreate plaatsen (van de CORBA client applicatie). We hebben dan een variable genaamd SDGN van type ISDGN nodig (in onze Form) om de "handle" naar de CORBA server vast te houden. In het FormDestroy event moeten we deze weer vrijgeven. Alles bijelkaar levert dit de volgende code op:
  unit Unit3;
  interface
  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    DrBob42_TLB;

  type
    TForm1 = class(TForm)
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
    private
      SDGN: ISDGN;
    end;

  var
    Form1: TForm1;

  implementation
  {$R *.DFM}

  procedure TForm1.FormCreate(Sender: TObject);
  begin
    SDGN := TSDGNCorbaFactory.CreateInstance('SDGN');
  end;

  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    SDGN := nil;
  end;

  end.
Het enige wat we nu nog moeten doen is een event handler schrijven die de methode TheAnswer van de CORBA server aanroept, en het antwoord terugkrijgt. Die methode kan natuurlijk worden aangeroepen door de SDGN instantie van de CORBA server te gebruiken:
  procedure TForm1.FormClick(Sender: TObject);
  var
    Answer: Integer;
  begin
    SDGN.TheAnswer(Answer);
    ShowMessage(Format('The Answer is..%d',[Answer]));
  end;
En uiteraard komt daar dan het enige juiste antwoord uit...

Conclusies
Delphi ondersteunt CORBA, zij het in een speciale smaak. We kunnen CORBA servers maken, psuedo IDL exporteren, die (soms met enige moeite) door andere omgevingen te gebruiken zijn (zoals JBuilder - alhoewel ik dat niet heb laten zien in dit artikel). Ook kunnen we CORBA clients maken, alhoewel dat (nog?) niet kan op basis van een IDL bestand van een andere CORBA server, aangezien de Delphi Type Library geen IDL kan importeren.
De implementatie van CORBA door middel van (D)COM Type Libraries werkt, maar komt me niet erg definitief over. Ik vraag me dan ook af wat er gebeurt met mijn Delphi CORBA projecten als bijvoorbeeld Delphi straks wel een IDL2PAS compiler zou hebben, en niet langer van (D)COM en Type Libraries afhangt voor mijn CORBA servers en clients. Wellicht zijn dat dan ook "legacy" applicaties geworden. Alleen de tijd zal het leren.
Gelukkig zijn er al wat uitspraken van borland.com medewerkers gehoord betreffende zo'n IDL2PAS compiler, en ook de nieuwste C++Builder 4 Enterprise bevat al een behoorlijk strakke CORBA ondersteuning met VisiBroker for C++ versie 3.3 (het wachten is nog op een echte VisiBroker for Delphi dus, want we hebben nu eigenlijk alleen maar VisiBroker for Java en VisiBroker for C++, die overigens niet geheel compatible zijn met elkaar).

Het zal duidelijk zijn, nog voor het eind van dit millenium krijgt dit verhaal een vervolg (en hopelijk een goede afloop). Tot die tijd beschouw ik de Delphi CORBA ondersteuning als aardig, maar nog niet volwassen.


Nawoord
In het bovenstaande artikel schreef ik over Delphi en CORBA. Mijn conclusie was dat de ondersteuning eigenlijk (nog) wat tegenviel. Inmiddels heb ik wat nadere experimenten met CORBA achter de rug, met name een project waarin we Delphi en JBuilder aan elkaar hebben "geknoopt". De ervaringen waren de moeite waard om deze keer even terug te komen op dit onderwerp.

Type Library
Eerst even een misverstandje de wereld uit helpen. Vorige keer beweerde ik dat de Delphi Type Library alleen maar Microsoft IDL en Pascal IDL ondersteunde (instelbaar met behulp van de Type Library tab in het Tools | Environment Options dialoogwindow). Dit is echter niet de volledige waarheid. De Type Library kan namelijk ook een IDL bestand genereren (met daarin de IDL representatie van de inhoud van de Type Library zelf). Dit exporteren gebeurt door op de meest rechter knop te drukken van de Type Library. Echter, dit genereert dus weer de Microsoft versie van de IDL (die JBuilder bijvoorbeeld niet kan lezen). Het blijkt dat we op het meest rechter driehoekje moeten klikken, waarna een drop-down menu verschijnt waarin we de keuze "Export to CORBA IDL" kunnen kiezen. Met de Type Library van volgende keer levert dit het volgende beeld:

delphi type library - export to corba idl

Dit genereert een "echt" IDL bestand, dat compatible is met datgene wat JBuilder kan lezen, namelijk het volgende:

  module DrBob42
  {
    interface ISDGN;

    interface ISDGN
    {
      void TheAnswer(out long Answer);
    };

    interface SDGNFactory
    {
      ISDGN CreateInstance(in string InstanceName);
    };
  };
Dit bestand is CORBA IDL dat door een omgeving als JBuilder kan worden omgezet in een server skeleton en client stub (met de IDL2Java tool). Hetzelfde geldt voor C++Builder, dat een IDL2CPP tool gebruikt. Zoals ik de vorige keer al schreef, heeft Delphi nog geen IDL2PAS, en dat betekent dat we de Type Library moeten gebruiken om de CORBA module en interface definities te "defini ren" in plaats van IDL te schrijven. Het feit dat we vervolgens IDL kunnen genereren, betekent dat Delphi eigenlijk met name geschikt is om CORBA Servers mee te maken (of in ieder geval om in Delphi met de Type Library de "IDL" te defini ren, en daarna de geexporteerde IDL te gebruiken in een andere omgeving om daar dan "native" server skeletons en/of client stubs mee te genereren.

JBuilder en CORBA
Het projekt waar ik in de inleiding over sprak bestond uit een Delphi CORBA Server, en een JBuilder CORBA Client. Het ge-exporteerde IDL bestand kon in JBuilder opgenomen worden om een client stub te genereren dat gebruikt kon worden om de Delphi Server aan te roepen. Dat werkt prima, zoals verwacht.
Echter, al snel bleek dat de JBuilder CORBA Client zelf ook gegevens en daarmee functionaliteit zou moeten beheren. En omdat we CORBA toch al als "communicatie protocol" gebruikte, leek het voor de hand te liggen om de JBuilder CORBA Client uit te breiden met CORBA Server functionaliteiten (middels een nieuw stukje IDL), en de Delphi CORBA Server ook te laten functioneren als CORBA Client.
Om in de stijl van ons vorige voorbeeld te blijven, ziet de JBuilder Server IDL er als volgt uit:

  module MrHaki
  {
    interface JBuilder
    {
      void TheQuestion(out string Question);
    };
  };
Deze keer hebben we een module MrHaki, met één interface JBuilder, en een methode genaamd TheQuestion die een string argument Question heeft (waar de server een waarde aan geeft). Het IDL type "string" is gebruikt voor de Question parameter.
In JBuilder is dit stukje IDL met een rechter-muisknop te compileren tot een nieuwe server skeleton (en client stub, maar die gebruiken we niet). De server skeleton wordt vervolgens in JBuilder ge mplementeerd, waarna het alleen nog wachten is op.... de Delphi CORBA Client om ermee te gaan praten.

Delphi CORBA Client
Gegeven het laatste stukje IDL voor de JBuilder CORBA Server, moeten we nu op de een of andere manier een Delphi CORBA Client zien te maken die ermee kan communiceren. Dat is makkelijker gezegd dan gedaan, want zoals ik de vorige keer al schreef, heeft de Delphi Type Library geen mogelijkheden om IDL te importeren.
Een (moeilijke) manier om toch van de Type Library gebruik te maken, is om een nieuw interface (JBuilder) toe te voegen, en te proberen om daarvoor een method TheQuestion met een string argument Question te defini ren. Helaas moeten we dan gaan raden welke type in de Type Library tot een "IDL string" zal leiden. We kunnen zelfs meerdere typen kiezen, waaronder PChar en WideString. Helaas zal de eerste niet werken (foutje in Delphi? of in JBuilder? of in het marshallen/unmarshallen van de parameter? Ik weet het niet, ik weet alleen dat het niet werkt), maar er is geen manier om daar achter te komen anders dan de uitputtende "trial-and-error" techniek.
Alhoewel het met enige moeite waarschijnlijk wel lukt om in de Type Library een definitie van de JBuilder interface te bouwen die exact dezelfde IDL produceert &egave;n daarnaast nog werkt ook, is het ook mogelijk om de communicate op een andere manier op te zetten. Tot nu toe hebben we ons bezig gehouden met zogenaamde "statische" stub binding, waarbij de namen van de routines en (de namen en types van) de parameters al tijdens compile-time bekend waren. Er is echter ook een mer dynamische manier (DII - Dynamic Interface Invocation), waarbij we niet van te voren het hele interface hoeven vast te leggen, en deze is met name geschikt voor het geval indien een Delphi CORBA Client gebruik moet maken van bestaande IDL definities uit een andere omgeving, zoals vanuit JBuilder in dit geval.

Dynamic Interface Invocation
Met behulp van DII kunnen we vanuit Delphi gebruik maken van een CORBA interface zonder dat dat in een Type Library hoeft te zijn opgenomen. Voorwaarde is echter wel dat er ergens in het netwerk een Interface Repository draait (een van de CORBA ondersteuning tools), waarin de IDL code voor het betreffende interface is geladen. Dat werd in dit geval aan de JBuilder kant geregeld - op dezelfde machine waar de JBuilder CORBA Server draait. Als er nu ten minste één Smart Agent draait, kan onze Delphi CORBA Client via de Smart Agent met de Interface Repository "praten" en een dynamische aanroep doen van een methode met argumenten die nog niet tijdens compile-time bekend waren. Zonder compile-time checks, dus, en als er ook maar iets fout staat werkt het niet (meer).
In ons geval, uitgaande van een draaide Interface Repository en Smart Agent, is de Delphi code om de JBuilder CORBA Server method TheQuestion aan te roepen als volgt (we maken hierbij gebruik van het TAny type; de CORBA versie van de "Variant"):

  uses
    CorbaObj; { voor het Orb object }
  var
    JBuilder: TAny;
    Question: WideString;
  begin
    JBuilder := Orb.Bind('IDL:JBuilder/CorbaServer:1.0');
    JBuilder.TheQuestion(Question);
    ShowMessage(Question)
  end;
Allereerst moeten we een Orb.Bind uitvoeren, die een instantie (object referentie) van de JBuilder CORBA Server oplevert (merk op dat we dus ook de volledige naam van deze JBuilder CORBA Server moeten weten). Daarna kunnen we van deze object referentie de methodes aanroepen die we wensen. Deze zijn echter wel case-sensitive (oftewel, TheQuestion is iets anders dan THEQUESTION, en het laatste zal niet werken).

Conclusies
Delphi ondersteunt CORBA, zij het met een speciale "type library" bijsmaak. We kunnen CORBA Servers maken, CORBA IDL exporteren, die door andere omgevingen zoals JBuilder te gebruiken zijn. Ook kunnen we CORBA Clients maken, alhoewel dat niet kan op basis van een IDL bestand van een andere CORBA server, aangezien de Delphi Type Library geen IDL kan importeren, maar wel door bijvoorbeeld gebruik te maken van Dynamic Interface Invocation.

Inmiddels is gebleken dat we in de Text tab van de Type Library de IDL kunnen aanpassen, om na een "Refresh Interface" de nieuwe Pascal code (en interfaces) over te houden. Ik heb dit echter nog niet aan de praat kunnen krijgen met een puur IDL bestand (gegenereerd door bijvoorbeeld JBuilder). De ontwikkelingen zijn echter nog steeds gaande, dus stay tuned...
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .


Dit artikel is eerder verschenen in SDGN Magazine #53 en #54 - 1999

This webpage © 1999-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.