Bob Swart (aka Dr.Bob)
Met Janeva van CORBA naar .NET

Hoe je een Delphi CORBA Server met een C# .NET Client laat praten

In dit artikel zal ik laten zien hoe Borland's Janeva gebruikt kan worden om de CORBA en J2EE wereld met die van .NET te verbinden. Ik zal Borland Delphi 7 gebruiken om een CORBA Server toepassing te bouwen, en daarna C#Builder - ook van Borland - om hier een .NET client aan te verbinden. Als "lijm" maak ik dan gebruik van Janeva (en inderdaad, ook dat is een product van Borland). Aan het eind van het artikel vertel ik nog wat meer van Janeva, en wat je er allemaal nog meer mee kan.

CORBA IDL
CORBA staat voor Common Object Request Broker Architecture, en is een cross-language en cross-platform standaard om gedistribueerde toepassingen te maken. Hierbij is de interface defnitie van groot belang, want dit is het contract waar zowel de client als de server aan gebonden zijn. Bij CORBA toepassingen is dit contract opgesteld in IDL, de zogeheten Intermediate Definition Language. In praktijk ben ik CORBA niet op zo heel veel plaatsen tegengekomen (en ook niet onder .NET), maar waar het wel relatief veel wordt toegepast is in de (internationale) financiële wereld. Als zeer vereenvoudigd voorbeeld zal ik dan nu ook een bankrekening object nemen en als CORBA object implementeren. De IDL definitie van Account.idl is hieronder te zien. Zoals je kunt zien, lijkt IDL een beetje op java, C++ en C#, maar zelfs voor Delphi ontwikkelaars is het niet moeilijk om te lezen wat de bedoeling is (of zelf een IDL definitie op te stellen). Het is dan ook een platform en ontwikkelomgeving-onafhankelijk taaltje. CORBA-ondersteunende ontwikkelomgeving kunnen op basis van een IDL bestand de benodigde source code genereren voor de betreffence ontwikkelomgeving (en zo kennen we idl2pas voor Delphi, maar ook idl2cpp voor C++ en idl2java voor Java ontwikkelomgevingen).

  module Bank
  {
    exception AccountOverRun
    {
      string Reason;
      float value;
    };

    interface Account
    {
      float balance();
      void deposit(in float value);
      void withdraw(in float value) raises (AccountOverRun);
    };
  };
Zoals te zien is in Account.idl, heb ik een module Bank, met daarin een interface Account en een exception AccountOverRun. Het interface biedt de clients drie methodes aan: balance om het saldo terug te geven, deposit om geld te storten, en withdraw om geld weer op te nemen. Deze laatste kan potentieel de AccountOverRun exception opleveren, als we meer geld proberen op te nemen dan er nog op de rekening staat (dit is een hele strenge bank: rood staan mag niet. Of misschien is dan juist wel een domme bank, want zo verdienen ze minder aan de debetrente).

Delphi 7 CORBA Server
Nu we de IDL gezien hebben met de definitie van het CORBA object, wordt het tijd om de Win32 CORBA Server te bouwen. Hier hebben we de Enterprise Editie van Delphi 7 voor nodig, en moet je ook VisiBroker for Delphi geïnstalleerd hebben (dit kun je tijdens installatie aangeven - maar je kunt het ook achteraf nog installeren van de Delphi 7 CD-ROM). Als CORBA geïnstalleerd is, vindt je In de Delphi 7 Object Repository (zie Figuur 1) een tweetal wizards: eentje voor een CORBA Client Application, en een voor de CORBA Server Application. De Client doen we vandaag in C#Builder, dus die zullen we niet gebruiken deze keer.

Delphi 7 Object Repository

Als je de CORBA Server Application wizard start, krijg je een dialoog waarin je kunt kiezen of je een Windows of een Console applicatie wilt, en kun je er een of meerdere IDL files aan toevoegen. De Options tab geeft je de mogelijkheid om een aantal opties in te stellen die bij het genereren van Delphi code uit de IDL definities nuttig kunnen zijn.

IDL2Pas Create Server Dialog

Als je op de Generate knop drukt, genereert de IDL2PAS compiler een viertal Delphi units op basis van de definities in het IDL bestand. Van deze vier units moet je de _impl.pas gebruiken om de implementatie van het CORBA object in te schrijven. De overige units bevatten de Interface Definitie, Server Skeleton, en Client Stub code die voor de rest van het verhaal niet relevant zijn. In de gegenereerde Bank_impl.pas kunnen we de CORBA server als volgt implementeren:

  type
    TAccount = class(TInterfacedObject, Bank_i.Account)
    protected
      FBalance: Double;

  implementation

  constructor TAccount.Create;
  begin
    inherited;
    FBalance := 0
  end;

  function  TAccount.balance : Single;
  begin
    writeln('TAccount.balance = ',FBalance:1:2);
    Result := FBalance
  end;

  procedure TAccount.deposit ( const value : Single);
  begin
    writeln('TAccount.deposit of ',value:1:2);
    FBalance := FBalance + value
  end;

  procedure TAccount.withdraw ( const value : Single);
  begin
    if value <= FBalance then
    begin
      writeln('TAccount.withdraw of ',value:1:2);
      FBalance := FBalance - value
    end
    else
    begin
      writeln('FAILED TAccount.withdraw of ',value:1:2);
      raise EAccountOverRun.Create('Not enough balance for withdraw!', value)
    end
  end;
Merk op dat de withdraw methode inderdaad controleert of er genoeg geld op de rekening staat, en anders de EAccountOverRun exception zal raisen. Behalve de implementatie van het TAccount CORBA object, moeten we ook in het project zelf aangeven dat dit CORBA object aangemaakt wordt en gaat staan wachten op clients die het willen gebruiken. Toevallig (niet helemaal natuurlijk) bevat het gegenereerde project al code - nog in commentaar - die gebruik maakt van een TAccount object, dus we hoeven die alleen nog maar uit commentaar te halen:
  program CorbaServer;
  {$APPTYPE CONSOLE}
  uses
    SysUtils, CORBA, Bank_c, Bank_i, Bank_impl, Bank_s;

  var
    Acct : Account;
  begin
    CorbaInitialize;
    Acct := TAccountSkeleton.Create('Bob Swart', TAccount.Create);
    Writeln('Server Object Created...'); Writeln;
    BOA.ObjIsReady(Acct as _Object);
    Writeln('Server is ready...');
    BOA.ImplIsReady;
  end.
We kunnen nu de Delphi 7 CORBA Server compileren en draaien, en ons klaarmaken om er een C# client mee te laten praten. Hierbij zal Janeva ons een handje moeten helpen, onder meer door een IDL2CS aan te bieden om de IDL definitie naar C# om te zetten. Dat is echter pas de eerste stap, de Janeva runtime assemblies zullen ons daarnaast daadwerkelijk in staat stellen om een verbinding te maken met de CORBA server toepassing en de CORBA objecten die daarin beschikbaar zijn.

C#Builder
Ook dit deel is het makkelijkst uit te leggen door het gewoon te doen: start C#Builder, en doe File | New om een New C# Application te maken, bijvoorbeeld met de naam CorbaClient.

New C#Builder Application

Als je Janeva met C#Builder meteen hebt geïnstalleerd, dan heb je ook een plugin meegekregen die Janeva vanuit de Project Manager beschikbaar maakt. Net als het toevoegen van een normale (assembly) Reference en een Web Reference (voor SOAP web services), krijgen we met Janeva de mogelijkheid tot het toevoegen van een J2EE Reference of een CORBA Reference.

Add CORBA Reference

Na het kiezen van een CORBA Reference kun je het IDL bestand aan het C# project toevoegen (de account.idl), en wordt er een C# import unit account.cs gegenereerd. Hierbinnen zit een namespace Bank, met verschillende classes (en helper classes) om een instantie van het CORBA object te maken en te gebruiken. Dit zullen we in de WinForm.cs unit doen. Voeg eerst zowel de CORBA als de Bank namespace toe aan de using clause, en in de WinForm class moeten we dan vast een placeholder variabele van type Bank.Account plaatsen. Deze kunnen we automatisch initialiseren door de Bind methode van de AccountHelper aan te roepen, als volgt:

  using CORBA; // BS
  using Bank;  // BS

  namespace CorbaClient
  {
    public class WinForm : System.Windows.Forms.Form
    {
      private Bank.Account MyAccount = AccountHelper.Bind("Bob Swart"); // BS

Janeva 1.0 en 2.5
Voor Janeva 1.0 (die bij C#Builder zelf zat) en versie 2.5 (die vlak daarna uitkwam) werkt dit prima, afgezien van het fet dat we de ORB nog niet geïnitialiseerd hebben. Voor gebruikers van Janeva 1.0 en 2.5 moet dus voor de Bind nog het volgende gebeuren:

    string[] args = new string[] {"-vbroker.agent.port", "14000"};
    private CORBA.ORB orb = CORBA.ORB.Init(args);
    private Bank.Account MyAccount = AccountHelper.Bind("Bob Swart");
Wie echter al Janeva 6.0 heeft gedownload en gebruikt, zal tot de ontdekking komen dat de aanroep van AccountHelper.Bind niet langer mogelijk is. Tot mijn grote verbazing werkt mijn bestaande Janeva client niet langer met versie 6.0. Enig onderzoek leerde mij dat Janeva 1.0 en 2.5 wat CORBA betreft alleen de zgn. Basic Object Adapter (BOA) ondersteunen, iets wat Delphi 7 ook standaard ondersteunt. Janeva 6.0 bevat daarnaast ook ondersteuning voor de Portable Object Adapter (POA) - een nieuwere en geavanceerdere versie van de Basic Object Adapter. Het gevolg is echter dat Janeva 6.0 ook meteen de POA ondersteuning als default instelling heeft, en je dus moeite moet doen om de code voor de BOA (via de Bind methode) terug te krijgen. Bijkomend probleem is dat het lastiger is om de de POA in Delphi 7 te gebruiken. Bovendien zou de kracht van Janeva juist moeten zijn dat je de server helemaal niet aan hoeft te passen (of dat nou een CORBA BOA, POA or zelfs J2EE object is). Dus daar blijven we af.

Janeva 6.0
Voor Janeva 6.0 kunnen we niet langer de CORBA.ORB gebruiken op de manier zoals ik gewend was. In plaats daarvan moet ik in het .config bestand aangeven dat we de OS Agent willen gebruiken, en op welke poort (standaard is dat port 14000 - als je dat wilt veranderen moet je dat natuurlijk zowel in de CORBA server als de Janeva client doen). Het .config bestand voor ons voorbeeld ziet er als volgt uit:

  <configuration>
    <configSections>
      <section name="janeva" type="Janeva.Settings, Borland.Janeva.Runtime"/>
    </configSections>
    <janeva>
      <agent enabled="true" port="14000"/>
    </janeva>
  </configuration>
Blijft over het gebruik van de BOA. Hiervoor moeten we de IDL2CS command-line tool aanroepen met de -bind vlag. Helaas is dat niet mogelijk met de IDE integratie, dus kunnen we niet vanuit de C#Builder IDE de account.idl in account.cs omzetten (het kan wel, maar dan krijgen we niet de benodigde Bind methods te zien). Op de command-line moeten we dus zelf IDL2CS als volgt aanroepen:
  idl2cs -o account.cs -bind -root_dir .
  account.idl
Het gevolg is dat de account.cs overschreven wordt, deze keer inclusief de Bind methods die representatief zijn voor het gebruik van de BOA in plaats van de POA.

Implementatie
Als we vanuit de C# client de koppeling hebben gelegd met het CORBA object (via de door Janeva gegenereerde code en assemblies), is het moeilijkste achter de rug, en kunnen we daadwerkelijk gebruik gaan maken van het CORBA object. Zet een TextBox en drie Buttons op het WinForm, noem de TextBox tbAmount, en de drie Buttons resp. btnBalance, btnDeposit en btnWithdraw. De OnClick event handlers van de drie buttons kunnen we nu als volgt invullen (en de code insight van C#Builder helpt ons hierbij, voor het geval er een CORBA object is geïmporteerd waarvan we niet precies de interface definitie uit ons hoofd weten).

    private void btnBalance_Click(object sender, System.EventArgs e)
    {
      tbAmount.Text = MyAccount.Balance().ToString();
    }

    private void btnDeposit_Click(object sender, System.EventArgs e)
    {
      MyAccount.Deposit(Convert.ToSingle(tbAmount.Text));
    }

    private void btnWithdraw_Click(object sender, System.EventArgs e)
    {
      try
      {
        MyAccount.Withdraw(Convert.ToSingle(tbAmount.Text));
      }
      catch (AccountOverRun ex)
      {
        MessageBox.Show(ex.Reason);
      }
    }
De drie methoden bestaan eigenlijk alleen maar uit het aanroepen van het MyAccount object, met uitzondering van de btnWithdraw_Click waar we potentieel te maken krijgen met een CORBA exception (de AccountOverRun) die door het CORBA object wordt gegooid. Het mooie van CORBA exceptions is dat ook die taal en platform onafhankelijk zijn, dus aan de server kant doet een Delphi 7 programma een raise van een CORBA exception, en via de VisiBroker Smart Agent (de CORBA ORB) en de Janeva runtime assemblies komt die als CORBA exception bij de C# client om aldaar als een normale AccountOverRun exception afgehandeld te kunnen worden. Pas als je hebt gecontroleerd of de exceptions goed werken weet je zeker dat de CORBA connection helemaal in orde is.

Janeva
Janeva stelt je in staat om een bestaande CORBA of J2EE server te gebruiken in een .NET omgeving zonder daarbij de server aan te hoeven passen, wat enorm scheelt in de stabiliteit van bestaande systemen ("if it ain't broke, don't fix it"). In dit artikel heb ik laten zien hoe eenvoudig het is om een CORBA object door een C# client te laten gebruiken. Janeva verzorgde niet alleen de IDL2CS maar ook de marshalling met de Janeva runtime assemblies (namelijk de Borland.Janeva.Services.dll en de Borland.Janeva.Runtime.dll). Deze assemblies moet je ook deployen met de client applicatie, uiteraard, en daar heb je een Janeva deployment licentie voor nodig. Die kost geld, terwijl een Janeva ontwikkelaarslicentie gratis is (daar moet je je alleen voor registreren na de laatste versie van Janeva zelf gedownload te hebben van de Borland website). Naast CORBA ondersteunt Janeva ook J2EE (Enterprise JavaBeans) die daarbij kunnen draaien in verschillende omgevingen, zoals Borland Enterprise Server (BES) uiteraard, maar ook Oracle, WebLogic en WebSphere worden nu ondersteund. Er is daarvoor ook een speciale JAVA2CS, die de EJB definitie omzet naar C# import code. Wie zich afvraagt waarom er niet gewoon een web service om de CORBA server of J2EE Enterprise JavaBean geschreven kan worden, moet de white papers op de Borland website eens lezen. Een web service biedt dezelfde functionaliteit, maar is minder efficient omdat daarbij iedere aanroep via SOAP en XML verloopt, terwijl de Janeva assemblies ervoor zorgen dat de marshalling van en naar CORBA (of J2EE) naar .NET op een binare en snelle wijze verloopt, zonder daarbij een extra laag in te bouwen (de Janeva assembly is onderdeel van de client toepassing, dus niet een aparte web service laag die er tussen komt te hangen). Voor meer informatie verwijs ik graag naar de Borland website of de Janeva newsgroup.

Wie nog vragen of opmerkingen heeft, kan die altijd per e-mail aan mij kwijt. Wie op de hoogte wil blijven van de laatste ontwikkelingen zou daarnaast zeker eens moeten overwegen om mijn Delphi for .NET clinic bij te wonen.


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