Programmieren mit CORBA und C++

Martin Kompf

CORBA ist ein Mittel zur relativ komfortablen Realisierung verteilter Software. Dieses Tutorial beschreibt den Einstieg in die Verwendung von CORBA in C++ Programmen. Fr die Programmbeispiele (Download) wird die freie CORBA Implementierung MICO verwendet.

Inhalt

Grundlagen

CORBA (Common Object Request Broker Architecture) ist ein Standard, der Mittel zur Kommunikation zwischen verschiedenen Prozessen beschreibt. Im Gegensatz zu den herkmmlichen Interprozess-Kommunikationsmethoden wie Sockets oder Memory Mapped Files enthebt CORBA den Softwareentwickler von der Aufgabe, sich um Details, wie Kommunikationsprotokolle und Datenformate, kmmern zu mssen. Stattdessen bietet CORBA die dem C++ Programmierer gewohnte Sicht auf Objekte, die ber Methodenaufrufe miteinander kommunizieren.

Die CORBA Implementierung kmmert sich dann um das Konvertieren und Verpacken der Daten, die an die Methode bergeben worden, um das Verschicken des Methodenaufrufes ber das Netzwerk an das Zielobjekt, um das dortige Entpacken und Rckkonvertieren der Daten und den eigentlichen Methodenaufruf auf dem Zielobjekt. Wenn die entsprechende Methode Daten an den Aufrufer zurckliefern soll, dann luft das ganze nochmal in umgekehrter Richtung ab. Durch diesen - vor dem Anwender versteckten - Ablauf ist es ohne weiteres mglich, zum Beispiel in einem unter Linux laufenden Javaprogramm ein Objekt zu verwenden, welches in C++ implementiert wurde und auf einem Windows NT Rechner residiert.

CORBA ist nur ein Standard - keine Implementierung. Es gibt jedoch diverse Implementierungen dieser Architektur von den unterschiedlichsten Herstellern. Fr die in diesem Tutorial enthaltenen Beispielprogramme wurde die Version 2.3.11 von MICO verwendet. MICO ist eine voll funktionsfhige und frei verfgbare CORBA Implementierung unter dem GNU Copyleft.

Die Benutzung von CORBA und MICO soll anhand einer kleinen verteilten Applikation zur Verwaltung von Telefonnummern demonstriert werden. Diese besteht aus einem Server, der Paare von Namen und dazugehriger Telefonnummer speichert, und aus Clients, die zum Eintragen neuer Namen/Nummern sowie zur Recherche dienen. Als Entwicklungsumgebung dienten MICO 2.3.11 unter Linux mit dem gcc 3 sowie Windows 2000 mit dem Cygwin Toolkit 1.1. Bevor mit den Beispielen gearbeitet werden kann, muss MICO zunchst installiert werden. In vielen Linuxdistributionen wie Debian oder Suse ist eine MICO Entwicklungsumgebung als installierbares Paket enthalten. Alternativ kann man sich den Sourcecode downloaden, bersetzten und installieren.

Interfacedefinition

Der erste Schritt beim Erstellen einer CORBA Anwendung ist die Definition aller bentigten Interfaces. ber diese Interfaces tauschen Client und Server spter ihre Daten aus. Die Interfacedefinitionen erfolgen dabei in einer besonderen Sprache, der Interface Definition Language (IDL). Diese hnelt in ihrer Syntax stark einer C++ Klassendefinition. Die angestrebte Beispielapplikation kommt mit einem einzigen, einfachen Interface namens IPBook aus. Dieses enthlt zwei Methoden: Eine zum Hinzufgen eines neuen Telefonbucheintrages und eine zum Suchen nach Namen. Die in IDL geschriebene Interfacedefinition wird in eine Datei IPBook.idl gespeichert und sieht folgendermaen aus:

#ifndef _IPBook_idl
#define _IPBook_idl

/**
 * Interface for a very simple phone book
 **/
interface IPBook {
    /* Add an entry */
    void addEntry( in string name, in string number);
        
    /* Search for an entry */
    string searchEntry( in string name);
};

#endif

Der wichtigste Unterschied zu einer C++ Klassendefinition ist, dass bei den Methodenparametern die Richtung der Datenbergabe angegeben werden muss. Die verwendete Spezifikation in bedeutet, dass Daten ausschlielich vom Aufrufenden zum Objekt transportiert werden, analog bedeutet out dann einen Datentransport zurck zum Aufrufenden und inout einen bidirektionalen Datentransfer. Ein Returnvalue (wie string bei searchEntry) bedeutet natrlich immer, dass Daten zum Aufrufer zurck bertragen werden, out muss hier nicht angegeben werden.

Noch ein Wort zum generellen Design von CORBA Interfaces: Hier sollte man sich immer vor Augen halten, dass jeder Methodenaufruf eine Netzwerkbertragung ist! Gerade wenn nur wenige Bytes an Nutzdaten bertragen werden, entsteht ein betrchtlicher Overhead. Hten muss man sich an dieser Stelle vor Theoretikern des objektorientierten Entwurfs, die alles und jedes in Objekte verpacken.

So wre es in unserer Beispielapplikation vom objektorientierten Standpunkt durchaus sinnvoll, ein zustzliches Interface Entry mit Methoden wie setName(), getName(), setNumber() und so weiter zu definieren. Die Funktion addEntry() in IPBook wrde dann ein Entry Objekt als Parameter bekommen. Resultat dieses Designs wre die dreifache Anzahl von entfernten Methodenaufrufen, die alle ber das Netz gehen. Ein absoluter Performancekiller!

Beim Entwurf von CORBA Interfaces gilt also:

Implementierung des Serverobjektes

Nach der Erstellung der Interfacedefinition ist der nchste Schritt die Implementierung der Serverobjekte (Servants). Ein Serverobjekt muss einerseits die Applikationslogik implementieren - die Funktionen addEntry() und searchEntry() mssen mit Leben gefllt werde. Andererseits muss das Serverobjekt auch die Verbindung zur CORBA Implementierung, genauer gesagt dem Object Request Broker (ORB), der fr die Vermittlung der verteilten Funktionsaufrufe verantwortlich ist, herstellen. Fr diese Verbindung gibt es verschiedene Objekt Adapter. Der ltere ist der Basic Object Adapter (BOA). Seit dem CORBA Standard 2.2 gibt es den umfangreicheren und flexibleren Portable Object Adapter POA. Obwohl MICO in der aktuellen Version beide Adapter zur Verfgung stellt, soll im folgenden ausschlielich auf die Verwendung des modernen POA eingegangen werden.

Bei der Implementierung der Serverobjekte nimmt der in MICO enthaltene IDL Prozessor dem Programmierer eine Menge Arbeit ab. Der Aufruf des IDL Prozessors erfolgt an der Kommandozeile, zuerst sollte man hier mittels

. /usr/local/lib/mico-setup.sh

die korrekte Umgebung einstellen (falls MICO im Verzeichnis /usr/local installiert wurde und die bash verwendet wird - ansonsten muss diese Zeile entsprechend modifiziert werden). Der Aufruf

idl --poa --c++-suffix cpp IPBook.idl

erzeugt aus der Interfacedefinitionsdatei IPBook.idl die C++ Dateien IPBook.h und IPBook.cpp.

Diese Dateien enthalten bereits einen groen Teil der Implementierung des Serverobjektes. Unter anderem stellen diese Dateien eine Klasse POA_IPBook zur Verfgung, die als Basisklasse fr das Serverobjekt dient. ffnet man mit einem Texteditor die Datei IPBook.h und sucht in ihr nach der Definition der Klasse POA_IPBook, so findet man unter anderem die Deklaration der pur virtuellen Funktionen

    virtual void addEntry( const char* name, const char* number ) = 0;
    virtual char* searchEntry( const char* name ) = 0;

Dies sind die Methoden, die in der Interfacedefinition spezifiziert wurden. Aus dem IDL Datentyp string ist jetzt nur der C++ Datentyp char* geworden, das IDL Attribut in findet seine Entsprechung in der const Spezifikation des Funktionsparameters.

Die vom Programmierer noch zu leistende Arbeit beschrnkt sich nun darauf, eine neue Klasse zu schreiben, die von POA_IPBook abgeleitet ist und mindestens die beiden pur virtuellen Funktionen addEntry() und searchEntry() implementiert. Es hat sich eingebrgert, diese Klasse mit dem Nachsatz _impl zu benennen, im Beispiel also IPBook_impl. Die Definition der Klasse steht also in der Datei IPBook_impl.h:

#ifndef IPBook_impl_h
#define IPBook_impl_h 1

#include "IPBook.h"

#include <map>
#include <string>

using namespace std;

class IPBook_impl : virtual public POA_IPBook
{
public:
    // implement pure virtual functions from POA_IPBook
    virtual void addEntry( const char* name, const char* number );
    virtual char* searchEntry( const char* name );
    
private:
    map <string, string, less<string> > _numbers;
};

#endif

Neben den notwendigen Funktionen addEntry() und searchEntry() hat die Klasse noch die private Membervariable _numbers, die zur Aufnahme der Daten des Telefonbuches dient. blich sind an dieser Stelle auch die Definitionen eigener Konstruktoren und des Destruktors. Dieses relativ einfache Beispiel kommt jedoch mit den vom C++ Compiler zur Verfgung gestellten Standard-Konstruktor und -Destruktor aus.

Die eigentliche Implementierung der Klasse in der Datei IPBook_impl.cpp sollte nun komplikationslos ber die Bhne gehen:

#include <CORBA.h>
#include "IPBook_impl.h"

using namespace std;

void IPBook_impl::addEntry( const char* name, const char* number)
{
    string nam = name;
    string num = number;

    _numbers[nam] = num;
}

char* IPBook_impl::searchEntry( const char* name )
{
    map <string, string, less<string> >::iterator r;
    r = _numbers.find( name);
    if (r != _numbers.end()) {
        return CORBA::string_dup( (*r).second.c_str());
    }
    else {
        return CORBA::string_dup( "???");
    }
}

Wichtig ist, zweierlei zu bedenken (bzw. zu wissen!): Die bergabeparameter vom Typ char* belegen Speicherplatz auf dem Heap. Dieser wird vom Aufrufer der Funktion, also dem ORB, allokiert. Nach Beendigung der Funktion wird dieser Speicherbereich vom ORB wieder freigegeben. Dann drfen im Serverobjekt keinerlei Referenzen mehr auf diesen Speicher existieren! Deshalb werden die Parameter name und number zunchst in die C++ Strings nam und num kopiert, bevor sie in die Map _numbers eingefgt werden (dabei wird dann zwar nochmal kopiert, jedoch liegen nam und num ja auf dem Stack und werden beim Verlassen der Funktion zerstrt).

Zweitens gibt searchEntry() einen Zeiger auf einen Speicherbereich vom Typ char* an den ORB zurck. Damit hier keine Speicherlecks entstehen knnen, wurde im CORBA Standard festgelegt, dass der ORB fr die Freigabe dieses Speicherbereiches verantwortlich ist. Somit muss der Programmierer beachten, dass er auf dem Heap liegenden Speicher zurckliefert. Dafr stellt CORBA die Funktion CORBA::string_dup() zur Verfgung.

Nun knnen die bisher erstellten Module kompiliert werden:

mico-c++ -c IPBook.cpp
mico-c++ -c IPBook_impl.cpp

erzeugt die Objektmodule IPBook.o und IPBook_impl.o. Um diese zum Leben zur erwecken, bedarf es noch der entsprechenden Server- und Clientapplikationen.

Die Serverapplikation

Zur Erstellung eines kompletten CORBA-Serverprogrammes fehlt nur noch die entsprechende main() Funktion. Diese wird sinnvollerweise in ein eigenes Sourcefile, zum Beispiel mit dem Namen cabsrv_co.cpp geschrieben. Zunchst werden die Headerfiles inkludiert - absolut notwendig sind hier das von MICO gelieferte CORBA.h sowie die Deklaration des Servants in IPBook_impl.h:

#include <CORBA.h>
#include "IPBook_impl.h"

#include <fstream>

using namespace std;

int main( int argc, char **argv)
{
    int rc = 0;

Als nchstes mssen der ORB (Object Request Broker) und ein POA-Manager (POA: Portable Object Adapter) erzeugt und initialisiert werden. Fr dieses einfache Beispiel reicht es, den vom System zur Verfgung gestellten Root-POA-Manager zu verwenden. Alle CORBA Systemaufrufe werden in einem try-catch Block gekapselt:

    try {
        // init ORB and POA Manager
        CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);
        CORBA::Object_var poaobj = orb -> resolve_initial_references("RootPOA");
        PortableServer::POA_var poa = PortableServer::POA::_narrow( poaobj);
        PortableServer::POAManager_var mgr = poa -> the_POAManager();

Nun kann der Servant erzeugt und aktiviert werden. CORBA stellt mehrere Aktivierungsmethoden zur Verfgung, hier wird die implizite Aktivierung verwendet:

        // create a new instance of the servant
        IPBook_impl *impl = new IPBook_impl;
        // activate the servant
        IPBook_var f = impl -> _this();

Jedes CORBA-Serverobjekt bentigt natrlich eine eindeutige Identikation, damit ein Client auch den richtigen Servant finden kann. Diese Identifikation wird als IOR (Interoperable Object Reference) bezeichnet. Um diese IOR vom Server zum Client transportieren zu knnen, gibt es mehrere Mglichkeiten. Das einfachste (und einzig portable) Verfahren ist, die IOR in eine Zeichenkette umzuwandeln und diese in einer Datei zu speichern, auf die Server und Client gleichermaen Zugriff haben (in den nchsten Kapiteln wird noch ausfhrlich auf Alternativen dazu eingegangen):

        // save the stringified object reference to file
        CORBA::String_var s = orb -> object_to_string( f);
        ofstream out( "IPBook.ref");
        out << s << endl;
        out.close();

Nun sind alle notwendigen Vorbereitungen abgeschlossen, jetzt kann der POA Manager aktiviert und der ORB gestartet werden. Damit wird der Servant endgltig zum Leben erweckt:

        // activate POA manager
        mgr -> activate();
        // run the ORB
        orb -> run();

Aus diesem Funktionsaufruf kehrt das Programm normalerweise nicht zurck, es sei denn, dass ein explizites Shutdown des ORBs ausgefhrt wird. In diesem Falle sollten dann noch POA-Manager und Servant korrekt aufgerumt werden:

        poa -> destroy( TRUE, TRUE);
        delete impl;
        rc = 0;
    }

Der Rest der main() Funktion befasst sich nun noch mit dem Auffangen von Exceptions aus dem CORBA System und der Fehlerausgabe:

    catch(CORBA::SystemException_catch& ex)
    {
        ex -> _print(cerr);
        cerr << endl;
        rc = 1;
    }
    return rc;
}

Damit ist unsere erste CORBA-Serverapplikation fertig programmiert, sie kann nun mittels

mico-c++ -c cabsrv_co.cpp

bersetzt werden. Abschlieend mssen die bisher entstandenen Objektmodule zusammen mit der MICO-Library gelinkt werden, um ein ausfhrbares Programm zu erhalten:

mico-ld -o cabsrv_co cabsrv_co.o IPBook.o IPBook_impl.o -lmico2.3.11

Jetzt kann cabsrv_co ber die Kommandozeile gestartet werden. Wenn kein Fehler auftritt, luft das Programm im Vordergrund ohne weitere Ausgabe. Um berhaupt etwas sinnvolles tun zu knnen, bentigt man noch Programme, die zumindest das Neuanlegen von Telefonbucheintrgen und eine Recherche darin erlauben.

CORBA Clients

Als erstes soll ein Programm erstellt werden, welches das Anlegen von Eintrgen im Telefonbuch erlaubt. Eine Eingabe an der Kommandozeile in der Form

cabadd_co name nummer

soll einen Eintrag mit dem Namen name und der Nummer nummer dem Telefonbuch hinzufgen. Dazu wird die Programmdatei cabadd_co.cpp erstellt. Hier muss zunchst auch wieder CORBA.h inkludiert werden. Im Unterschied zum Serverprogramm darf hier aber die Deklaration des Servants nicht inkludiert werden, stattdessen wird das vom IDL-Preprozessor generierte Stubfile IPBook.h verwendet:

#include <CORBA.h>
#include "IPBook.h"

#include <fstream> 

using namespace std;

Im Hauptprogramm wird als erstes der ORB initialisiert und dann ein Test auf das Vorhandensein der Kommandozeilenargumente durchgefhrt. Diese Reihenfolge ist sinnvoll, da es so mglich ist, zustzliche Parameter fr den ORB mit auf der Kommandozeile zu bergeben (einige dieser Parameter werden wir in den nchsten Kapitel noch kennenlernen). Die Funktion CORBA::ORB_init( argc, argv) wertet diese ORB-spezifischen Parameter aus und passt argc und argv entsprechend an. Der darauffolgende Test kann sich dann auf die Abfrage der programmspezifischen Kommandozeilenargumente beschrnken:

int main( int argc, char **argv)
{
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);

    int rc = 0;
    if (argc != 3) {
        cerr << "usage: " << argv[0] << " name number\n";
        exit(1);
    }

Das Clientprogramm bentigt auerdem die Information, welchen CORBA-Server es verwenden soll. Schlielich knnen in einer umfangreichen CORBA-Applikation durchaus mehrere hundert Servants ihren Dienst tun! Wir erinnern uns: Bei der Programmierung des Servers im vorhergehenden Kapitel wurde veranlasst, dass die IOR des Servants in eine Zeichenkette umgewandelt und in die Datei IPBook.ref geschrieben wird. Diese Datei wird nun vom Clientprogramm eingelesen:

    ifstream in( "IPBook.ref");
    char s[1000];
    in >> s;
    in.close(); 

Die stringifizierte IOR steht jetzt also in der Variablen s. Diese kann nun zur Erstellung eines Objektes vom Typ IPBook_var verwendet werden. Zum Abfangen eventueller Fehler bei diesem Vorgang wird ein try-catch Block geffnet:

    try {
        CORBA::Object_var obj = orb -> string_to_object( s);
        IPBook_var f = IPBook::_narrow( obj); 

Mit dem nun vorliegenden Objekt f vom Typ IPBook_var knnen smtliche Operationen durchgefhrt werden, die ursprnglich in der Interfacedefinition IPBook.idl festgelegt worden sind. Das CORBA-Laufzeitsystem und unsere Vorarbeiten bewirken nun, dass alle auf dem Objekt f (dem Clientstub, Stub = Stummel) ausgefhrten Operationen automatisch auf den Servant, das heit auf das im Adressbereich des Serverprogramms cabsrv_co liegende Objekt vom Typ IPBook_impl weitergeleitet werden. Logischerweise muss das Serverprogramm also schon gestartet sein, bevor der Client aufgerufen werden kann (es gibt zwar die Mglichkeit, per Implementation-Repository eine Serveraktivierung on demand auszufhren, jedoch geht dies ber die Thematik dieser Einfhrung in CORBA hinaus).

Wir rufen jetzt also die Methode add mit den beiden Argumenten name und nummer von der Kommandozeile auf:

        f -> addEntry( argv[1], argv[2]); 

Der Rest des Programmmes ist dem Auffangen von Exceptions und der Ausgabe ihrer Ursache gewidmet:

    }
    catch(CORBA::SystemException_catch& ex)
    {
        ex -> _print(cerr);
        cerr << endl;
        rc = 1;
    }

    return rc;
}

Als zweites bentigen wir noch ein Programm, um nach Namen im Telefonbuch suchen zu knnen. Die Eingabe

cabsearch_co name

soll die zu name gehrende Telefonnummer vom Server holen. Der Quelltext des Programmes (cabsearch_co.cpp) sei hier kommentarlos angegeben, mit den bisher gewonnenen Erkenntnissen sollte seine Interpretation problemlos mglich sein:

#include <CORBA.h>
#include "IPBook.h"

#include <iostream>
#include <fstream>

using namespace std;

int main( int argc, char **argv)
{
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);

    int rc = 0;
    if (argc != 2) {
        cerr << "usage: " << argv[0] << " name\n";
        exit(1);
    }

    ifstream in( "IPBook.ref");
    char s[1000];
    in >> s;
    in.close();
    
    try {
        CORBA::Object_var obj = orb -> string_to_object( s);
        IPBook_var f = IPBook::_narrow( obj);

        cout << f -> searchEntry( argv[1]) << endl;
    }
    catch(CORBA::SystemException_catch& ex)
    {
        ex -> _print(cerr);
        cerr << endl;
        rc = 1;
    }

    return rc;
} 

Jetzt knnen die Clientprogramme bersetzt und gelinkt werden:

mico-c++ -c cabadd_co.cpp
mico-c++ -c cabsearch_co.cpp

mico-ld -o cabsearch_co cabsearch_co.o IPBook.o -lmico2.3.11
mico-ld -o cabadd_co cabadd_co.o IPBook.o -lmico2.3.11

Logischerweise braucht das Servant-Objekt IPBook_impl.o im Gegensatz zum Serverprogramm hier nicht dazugelinkt werden.

Nun knnen wir unsere erste vollstndige CORBA-Applikation testen:

$ cabsrv_co &
$ cabadd_co Kurt 123
$ cabadd_co Heinz 445-566
$ cabsearch_co Kurt
123
$ cabsearch_co Moni
???
$ iordump < IPBook.ref
...

Ein Wermutstropfen bleibt: Damit Client und Server sich finden knnen, musste die Objektreferenz (IOR) des Servants in einer Datei gespeichert werden. Sowohl Client- als auch Serverprogramm bentigen Zugriff auf diese Datei. Solange beide Programme auf einem Rechner laufen, sollte dies kein Problem sein. Was aber, wenn dabei Rechnergrenzen zu berwinden sind? Eventuell knnte man sich noch mit der Einrichtung von Netzlaufwerken (z.B. SAMBA- oder NFS-Shares) behelfen, jedoch wird man hier bald an administrative Grenzen stoen. Daher werden in den folgenden zwei Kapiteln Methoden vorgestellt, um Objektreferenzen auf andere Art und Weise zu transportieren: Die Verwendung des properitren MICO-Binders und die Einbeziehung des CORBA Naming Service.

Der MICO-Binder

Die CORBA-Implementierung MICO stellt als Hilfe bei der Lokalisation von Serverobjekten den MICO-Binder zur Verfgung. Diesem liegt folgende berlegung zugrunde: Um einen Servant in einem Netzwerk eindeutig indentifizieren und auffinden zu knnen, bedarf es nicht unbedingt einer IOR. Alternativ ist es mglich, einen Servant durch seine Netzwerkadresse zu bezeichnen. Netzwerkadressen bestehen in einem TCP/IP Netzwerk aus der IP-Adresse des Rechners (oder seinem Namen) und einer Portnummer. Da IP-Adressen und Hostnamen zentral vergeben werden, ist damit die weltweite Eindeutigkeit der Kombination IP-Adresse:Portnummer gewhrleistet. Falls ein CORBA-Serverprogramm mehrere Objekte der Auenwelt zur Verfgung stellt, knnen diese durch den Typ des Servants (genauer gesagt durch die Repository-Id der Interfacedefinition) unterschieden werden. Wenn dann noch mehrere Servants des gleichen Typs existieren, dann muss der Programmierer diesen noch einen (je Serverprogramm) eindeutigen Namen verpassen.

Da unser in im vorletzten Kapitel erstellter Telefonbuchserver cabsrv_co nur ein ein einziges CORBA-Objekt noch auen liefert, kann das Programm ohne Vernderung fr die Benutzung des MICO-Binders verwendet werden. Es muss lediglich sichergestellt werden, dass der Servant beim Starten immer die gleiche Portnummer benutzt. Dies erfolgt durch Angabe der Kommandozeilenoption -ORBIIOPAddr inet:hostname:port. Ohne diese Option wrde sich der Server bei jedem Start an irgendeine freie Portnummer binden. Der Aufruf

cabsrv_co -ORBIIOPAddr inet:munzel:4500

bewirkt also, dass der Server auf Port 4500 des Rechners munzel auf Verbindungen wartet. Wir müssen natürlich munzel mit dem Hostnamen des Rechners ersetzen, auf dem unser Server wirklich läuft. Der Hostname lässt sich mit dem Kommando

hostname

bestimmen.

Um mit unseren beiden Clients den MICO-Binder verwenden zu knnen, sind noch einige nderungen an deren Sourcecode ntig. Im wesentlichen muss der Aufruf von string_to_object(stringified ior) durch bind(repository id, address) ersetzt werden. Die Adresse sollte dabei als Kommandozeilenargument bergeben werden, um einfach zwischen verschiedenen Servern umschalten zu knnen. Exemplarisch sei im folgenden das modifizierte Programm cabadd_co.cpp, welches wir jetzt cabadd_mi.cpp nennen, vorgestellt.

Zunchst erfolgt wieder das Inkludieren der notwendigen Header und der Test auf das Vorhandensein der Kommandozeilenargumente. Es muss jetzt als viertes Argument die Adresse des CORBA-Servers mit bergeben werden:

#include <CORBA.h>
#include "IPBook.h"

#include <iostream>

using namespace std;

int main( int argc, char **argv)
{
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);

    int rc = 0;
    if (argc != 4) {
        cerr << "usage: " << argv[0] << " name number server_address \n";
        exit(1);
    } 

Nun kommt die entscheidende Stelle: Anstelle der Verwendung von string_to_object() wird direkt bind() aufgerufen. Der erste Parameter ist die Repository-ID unseres Servants, der zweite die per Kommandozeile bergebene Netzwerkadresse des Servers:

    try {
        CORBA::Object_var obj = orb -> bind( "IDL:IPBook:1.0", argv[3]);
        if (CORBA::is_nil( obj)) {
            cerr << "no object at " << argv[3] << " found.\n";
            exit(1);
        }
        IPBook_var f = IPBook::_narrow( obj); 

Falls alles gut gegangen ist, haben wir jetzt wieder ein IPBook_var Objekt, mit dem alle Methoden des Interfaces IPBook ausgefhrt werden knnen. Der verbleibende Rest des Programmes bringt daher nicht Neues:

        f -> addEntry( argv[1], argv[2]);
    }
    catch(CORBA::SystemException_catch& ex)
    {
        ex -> _print(cerr);
        cerr << endl;
        rc = 1;
    }

    return rc;
} 

Die nderungen am Programm cabsearch sind analog und daher ohne besondere Beschreibung nachzuvollziehen.

Die Programme werden wie bekannt bersetzt und gelinkt. Falls der Server wie oben angegeben an Port 4500 gebunden wurde, so sollte jetzt der Aufruf von

$ cabadd_mi Bimbo 123 inet:munzel:4500
$ cabsearch_mi Bimbo inet:munzel:4500

die erwartete Ausgabe 123 bringen.

Wer in der glcklichen Lage ist, mehrere ber TCP/IP verbundene Rechner zu besitzen, kann jetzt auch Server und Client auf verschiedenen Rechnern starten. Dabei muss nur gewährleistet sein, dass die Auflösung des Hostnamen munzel auf allen beteiligten Rechnern funktioniert.

Die Verwendung des MICO-Binders ermglicht es dem Anwender, auf den Transport von Objektreferenzen zu verzichten und stattdessen Netzwerkadressen zur Lokalisierung eines CORBA-Servers zu verwenden. Diese Methode drfte fr die meisten Anwendungen vollkommen ausreichend sein; jedoch hat sie noch zwei Nachteile:

In diesen Situationen hilft die Verwendung des CORBA-Naming-Service weiter.

Der CORBA Naming Service

Der Naming Service ist ein Standard CORBA Dienst, der fr die einfache Verwaltung einer Vielzahl von Objektreferenzen geeignet ist. Er drfte in den meisten Implementierungen, wie auch im MICO, zur Verfgung stehen. Der Naming Service ist ein hierachisches Verzeichnis, in dem es Namenskontexte und Namenseintrge gibt. Einen Namenskontext kann man sich als eine Art Unterverzeichnis vorstellen. Ein Namenseintrag enthlt die IOR eines konkreten CORBA-Objektes. Sowohl Namenskontexte als auch Namenseintrge haben einen symbolischen Namen, mittels dessen das Objekt eindeutig lokalisiert werden kann:

Die Clientprogramme brauchen somit nur noch den Namen des Objektes, welches sie benutzen wollen, sowie die Adresse des Naming Service zu kennen. Letztere kann per Kommandozeilenparameter -ORBNamingAddr inet:hostname:port an die Anwendung bergeben werden.

Allerdings muss der Softwareentwickler in Server- und Clientapplikation einige Modifizierungen vornehmen, damit die Objektreferenzen ber den Naming Service aufgelst werden knnen. Schauen wir uns zunchst den Serverquellcode cabserv_ns.cpp an. Die ersten Zeilen sind bis zur Aktivierung des Servants sind fast identisch mit dem bereits erstellten Programm cabserv_co.cpp, es muss nur zustzlich die Headerdatei CosNaming.h inkludiert werden:

#include <CORBA.h>
#include <coss/CosNaming.h>

#include "IPBook_impl.h"

using namespace std;

int main( int argc, char **argv)
{
    int rc = 0;

    try {
        // init ORB and POA Manager
        CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);
        CORBA::Object_var poaobj = orb->resolve_initial_references("RootPOA");
        PortableServer::POA_var poa = PortableServer::POA::_narrow( poaobj);
        PortableServer::POAManager_var mgr = poa->the_POAManager();

        // create a new instance of the servant
        IPBook_impl *impl = new IPBook_impl;
        // activate the servant
        IPBook_var f = impl->_this(); 

Nun muss der Rootkontext des Naming Service aufgelst werden. Dazu wird zuerst mittels resolve_initial_references die per Kommandozeilenoption -ORBNamingAddr bergebene Objektreferenz auf den Naming Service besorgt. Anschliessend wird diese auf einen NamingContext eingeengt:

         // resolve the naming service
        CORBA::Object_var nsobj =
          orb->resolve_initial_references ("NameService");
        if (CORBA::is_nil( nsobj)) {
            cerr << "can't resolve NameService\n";
            exit(1);
        }
  
        // narrow the root naming context
        CosNaming::NamingContext_var nc =
          CosNaming::NamingContext::_narrow (nsobj); 

Ausgehend von diesem NamingContext (der ja der RootNamingContext ist), kann nun die gesamte Hierarchie des Naming Service durchlaufen werden. In diesem einfachen Beispiel wollen wir uns darauf beschrnken, direkt den einfachen Namenseintrag AddressBook anzulegen. Dies erfolgt mittels der Funktion bind(). Diese wirft allerdings eine Exception, falls der Eintrag schon (von einem vorherigen Lauf des Programms) existiert. In diesem Fall muss dann rebind() verwendet werden:

        // create a name entry
        CosNaming::Name name;
        name.length (1);
        name[0].id = CORBA::string_dup ("AddressBook");
        name[0].kind = CORBA::string_dup ("");

        // bind or rebind the servant to the naming service
        try {
            nc->bind (name, f);
        }
        catch (CosNaming::NamingContext::AlreadyBound_catch &ex) {
            nc->rebind (name, f);
        } 

Der Rest des Programmes luft wie gehabt, lediglich der Code zur Behandlung von Exceptions ist erweitert worden, damit Fehler bei der Angabe der Adresse des Naming Service abgefangen werden:

        // activate POA manager
        mgr->activate();
        // run the ORB
        orb->run();

        poa->destroy( TRUE, TRUE);
        delete impl;
    }
    catch(CORBA::ORB::InvalidName_catch& ex)
    {
        ex->_print(cerr);
        cerr << endl;
        cerr << "possible cause: can't locate Naming Service\n";
        rc = 1;
    }
    catch(CORBA::SystemException_catch& ex)
    {
        ex->_print(cerr);
        cerr << endl;
        rc = 1;
    }
    return rc;
} 

Beim Linken des Programmes muss mittels -lmicocoss2.3.11 die Library mit dem Zugriffsroutinen auf den Naming Service dazugebunden werden.

Der Code des Clients cabadd_ns.cpp ist analog an die Verwendung des Naming Service anzupassen, anstelle von bind() oder rebind() wird hier resolve() aufgerufen, um an den vom Server angelegten Namenseintrag heranzukommen:

#include <CORBA.h>
#include <coss/CosNaming.h>
#include "IPBook.h"

#include <iostream>

using namespace std;

int main( int argc, char **argv)
{
    // init ORB
    CORBA::ORB_var orb = CORBA::ORB_init( argc, argv);

    int rc = 0;
    if (argc != 3) {
        cerr << "usage: " << argv[0] << " name number\n";
        exit(1);
    }

    try {
        // resolve the naming service
        CORBA::Object_var nsobj =
            orb->resolve_initial_references ("NameService");
        if (CORBA::is_nil( nsobj)) {
            cerr << "can't resolve NameService\n";
            exit(1);
        }
        // narrow the root naming context
        CosNaming::NamingContext_var nc =
            CosNaming::NamingContext::_narrow (nsobj);

        // create a name component
        CosNaming::Name name;
        name.length (1);
        name[0].id = CORBA::string_dup ("AddressBook");
        name[0].kind = CORBA::string_dup ("");
  
        // resolve the name component with the naming service
        CORBA::Object_var obj = nc->resolve( name);

        // narrow this object to IPBook 
        IPBook_var f = IPBook::_narrow( obj);
        
        // work with IPBook
        f->addEntry( argv[1], argv[2]);
    }
    catch(CORBA::ORB::InvalidName_catch& ex)
    {
        ex->_print(cerr);
        cerr << endl;
        cerr << "possible cause: can't locate Naming Service\n";
        rc = 1;
    }
    catch(CosNaming::NamingContext::NotFound_catch& ex)
    {
        cerr << "Name not found at Naming Service\n";
        rc = 1;
    }
    catch(CORBA::SystemException_catch& ex)
    {
        ex->_print(cerr);
        cerr << endl;
        rc = 1;
    }

    return rc;
} 

Bevor die Programme nun ausprobiert werden knnen, muss als erstes natrlich der CORBA (bzw. MICO) Naming Service nsd gestartet werden. Damit dieser sich an einen bestimmten Netzwerkport bindet, wird dabei die bekannte Option -ORBIIOPAddr verwendet. Als offizielle Portnummer für den CORBA Namingserver hat die IANA 2809 festgelegt.

$ nsd -ORBIIOPAddr inet:munzel:2809 &
# munzel ist wieder mit dem korrekten Hostnamen zu ersetzen!

Nun knnen unsere Server- und Clientprogramme in Aktion treten; sie bentigen jedoch nun immer die Angabe der Adresse des (korrekten) Naming Service:

$ cabsrv_ns -ORBNamingAddr inet:munzel:2809 &
$ cabadd_ns Bimbo 123 -ORBNamingAddr inet:munzel:2809
$ cabsearch_ns Bimbo -ORBNamingAddr inet:munzel:2809
123

Zur Administration des Naming Service gibt es noch das Konsolenprogramm nsadmin:

$ nsadmin -ORBNamingAddr inet:munzel:2809
CosNaming administration tool for MICO(tm)
nsadmin> ls
        AddressBook

nsadmin> iordump AddressBook

    Repo Id:  IDL:IPBook:1.0
    ...

nsadmin> exit
$

Der Vorteil der Verwendung des Naming Service kommt erst dann voll zur Wirkung, wenn eine Vielzahl von CORBA Objekten eines Frameworks verwaltet werden sollen. Statt dann mit einer Unmenge von stringified IORs oder Netzwerkadressen zu arbeiten, reicht es nun aus, allen Anwendungskomponenten lediglich die Adresse des Namingservice mitzuteilen. Bei geschickter Strukturierung unter Verwendung von Namenskontexten kann eine Vielzahl von Objekten verwaltet werden.

Damit sind wir am Ende des CORBA Tutorials angelangt. Selbstverstndlich konnte hier vieles nur angerissen werden oder wurde komplett weggelassen. Zur Vertiefung sei auch auf folgende Bcher verwiesen: