[ C++で開発 ]
オープンソースのTAOを使ったCORBAプログラミングです。
最初のプログラムは、書籍「Advanced CORBA Programming with C++」(Michi Henning、Steve Vinoski著、Addison-Wesley刊)の3章”最小限のCORBAアプリケーション”にある例題を作成していきます。
サンプルプログラムは、時刻(時分秒)の問合せを受けると現在時刻(GMT)を返却するというプログラムです。時刻情報(時分秒)を保持するIDL構造体と、現在時刻のサービスを提供するIDLインタフェースを記述します。
// time.idl
struct TimeOfDay {
    short hour;
    short minute;
    short second;
};
interface Time {
    TimeOfDay get_gmt();
};
CORBAでは、データを受け渡す際はCORBA基本型か、CORBA構造体を使います。最近のCORBA仕様になってObject By Value(OBV)が追加され、オブジェクトの値渡しができるようになりました。
IDL定義ファイルは、CORBA実装系に付いているIDLコンパイラを使って実装言語のスタブ・スケルトンファイルに変換します。生成されるファイル名とファイルの数はCORBA仕様では規定されていないので、CORBA実装系によって異なります。つまり、CORBA仕様で標準化されているのはファイルに関してはIDLファイルだけです。
time.idlをIDLコンパイルすると、次のファイルが生成されます。一般的な他のCORBA実装に比べて生成されるファイル種類が増えています。この*.iと*S_T.*ファイルは、主にパフォーマンス上の目的で作られています。
| ファイル名 | 種類 | |
|---|---|---|
| timeC.h timeC.i timeC.cpp | クライアントスタブファイル | |
| timeS.h timeS.i timeS.cpp | サーバスケルトンファイル | |
| timeS_T.h timeS_T.i timeS_T.cpp | サーバスケルトンテンプレートファイル | 
IDLコンパイラをスタティックリンク版でビルドした場合、コマンドファイル名は次のとおりです。
tao_idl_static.exe
また、gperf.exeとVC++のcl.exeを内部で呼び出しているので、これらのファイルガある場所を環境変数PATHに追加して置きます。
gperf.exeについてはACEとともにインストールしているので、%ACE_ROOT%\binになります。
cl.exeについてはVC++の環境設定用バッチファイルVCVARS32.BATを実行します。
D:\work\chap3>tao_idl_static time.idl idli_a02428.cc D:\work\chap3>dir /w time.idl timeC.cpp timeC.h timeC.i timeS.cpp timeS.h timeS.i timeS_T.cpp timeS_T.h timeS_T.i D:\work\chap3>
IDL定義ファイルをIDLコンパイラによってスタブ・スケルトンファイルへ変換するためのプロジェクトファイルを作成します。VC++の[ファイル]メニューから[新規作成]を選択し、新規作成ダイアログの[プロジェクト]タブを選びます。プロジェクト種類として"Utility Project"を選び、プロジェクト名を適宜指定し(例ではtime_idl)、現在のワークスペースに追加します。そしてIDLファイルをこのプロジェクトに追加します。
IDLコンパイル用プロジェクト設定を開き、[設定の対象]は"すべての構成"としておいて、IDLファイルを選択します。右側[一般]タブを指定し、[常にカスタムビルドステップを使用]にチェックを付け、[このファイルをビルドしない]のチェックは外します。
次に右側[カスタムビルドタブ]を指定し、[コマンド]欄と[出力]欄に記述します。
tao_idl_static $(InputPath) -o $(InputDir) -I $(InputDir)
$(InputDir)\$(InputName)C.h $(InputDir)\$(InputName)C.i $(InputDir)\$(InputName)C.cpp $(InputDir)\$(InputName)S.h $(InputDir)\$(InputName)S.i $(InputDir)\$(InputName)S.cpp $(InputDir)\$(InputName)S_T.h $(InputDir)\$(InputName)S_T.i $(InputDir)\$(InputName)S_T.cpp
TAOのIDLコンパイラのコマンドをVC++の中で実行できるようにするためには、VC++の実行可能ディレクトリ設定にIDLコンパイラコマンドのある場所を追加する必要があります。(環境変数PATHは見ていない)
[ツール]メニューの[オプション]を選択し、オプションダイアログの[ディレクトリ]タブを指定します。[表示するディレクトリ]リストボックスは"実行可能ファイル"を選択して、ディレクトリ欄にACEとTAOの実行ファイルパスを追加します。
設定が完了すれば、プロジェクトをビルドすることによって、IDL定義ファイルからスタブ・スケルトンファイルが生成されます。
POA継承によってサーバントクラスを作成するので、POA_Time[*1]を継承したサーバントクラスTime_implを記述します。
[*1] IDL定義でmoduleを使用した場合、クラス名が変わります。module timeapp とした場合、POA_timeapp::Timeクラスを継承します。(POA_timeappはnamespace)
// server.h
#include "timeS.h"
class Time_impl : public virtual POA_Time 
{ 
public:
  virtual TimeOfDay get_gmt (void) throw (CORBA::SystemException);
};
ここでは例外仕様 throw (CORBA::SystemException)を追加していますが、C++では例外仕様には是非があります。例外仕様は限定的に使う必要があります。
// server.cpp
#include "server.h"
TimeOfDay Time_impl::get_gmt (void) throw (CORBA::SystemException)
{
  time_t time_now = time (0);
  struct tm *time_p = gmtime (&time_now);
  TimeOfDay tod;
  tod.hour = time_p->tm_hour;
  tod.minute = time_p->tm_min;
  tod.second = time_p->tm_sec;
  return tod;
}
int main (int argc, char *argv[])
{
    try {
        // CORBA初期化
        CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
        // Root POAの参照を取得
        CORBA::Object_var obj
            = orb->resolve_initial_references ("RootPOA");
        PortableServer::POA_var poa
            = PortableServer::POA::_narrow (obj.in());
        // POAマネージャの活性化
        PortableServer::POAManager_var mgr
            = poa->the_POAManager();
        mgr->activate();
        // サーバントクラスのインスタンス化
        Time_impl time_servant;
        // CORBAオブジェクト参照を文字列化して標準出力へ
        Time_var tm = time_servant._this();
        CORBA::String_var str = orb->object_to_string(tm.in());
        cout << str.in() << endl;
        // イベントループ
        orb->run();
    } catch (const CORBA::Exception &) {
        cerr << "Uncaught CORBA exception" << endl;
        return 1;
    }
    return 0;
}
_var型を使わない場合は、
CORBA::ORB_ptr orb; // ... CORBA::release(orb); };
と自分でreleaseを呼んであげる必要がある。
サーバのソースコードファイルと、2.で生成されたスタブファイルおよびスケルトンファイルとをコンパイル・リンクします。
まずサーバのソースコードファイルをコンパイルします。コンパイル時には、ACEとTAOのヘッダファイルがある場所をコマンドラインオプション(/I)で指定します。また、Windows用のプログラミングではWIN32を定義しておく必要があります(/D)。ACE/TAOライブラリはマルチスレッド対応なので、マルチスレッドオプション(/MTか/MD、この例では/MT)を指定しています。例外を使用するので/GXを指定しています。
D:\work\chap3>cl /I%TAO_ROOT%/include /I%ACE_ROOT%/include /DWIN32 /MT /GX /GR /c server.cpp timeS.cpp timeC.cpp Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved. server.cpp timeS.cpp timeC.cpp コードを生成中... D:\work\chap3>
サーバプログラムをビルドするためのプロジェクトを作成します。VC++の[ファイル]メニューから[新規作成]を選択し、新規作成ダイアログの[プロジェクト]タブを選びます。プロジェクト種類はアプリケーションによって違いますので適したものを選びます。今回はコマンドラインから実行するプログラムなので"Win32 Console Application"を選び、プロジェクト名を適宜指定し(例ではserver)、現在のワークスペースに追加します。そして、server.cppとスタブファイルのtimeC.cppおよびスケルトンファイルのtimeS.cppをこのプロジェクトに追加します。
| プロジェクトに入れるファイル | 内容 | 
|---|---|
| server.cpp | main関数およびサーバントの実装 | 
| timeC.cpp | IDL定義から生成されたスタブファイル | 
| timeS.cpp | IDL定義から生成されたスケルトンファイル | 
まず、ACE、TAOのインクルードファイル、ライブラリファイルが置かれている場所(パス)を設定する必要があります。一般にVC++ではこうしたライブラリのインクルードパスやライブラリパスを設定するのに2つの方法があります。
一度インストールすればほとんど変更がないようなライブラリは前者で行い、開発するアプリケーションごとに異なるバージョンを使うことがありえるライブラリは後者で行うとよいでしょう。また、複数の開発者でプロジェクトを共有する場合、前者の設定内容はプロジェクトファイルには反映されないので、後者で設定する方が望ましいことが多いでしょう。
TAOの場合、TAOはCORBA実装系の1つに過ぎず、ある日別なCORBA実装系(MICOやOmniORB、VisiBrokerなど)を使いはじめるかもしれません。そこで後者の設定を使用します。
ここでは、後者の設定によってACEおよびTAOのインクルードファイル、ライブラリファイルのパス指定、リンクするライブラリの指定を行います。プロジェクトの設定で、[設定対象]欄は"すべての構成"を選び、[C/C++]タブを指定して[カテゴリ]欄は"プリプロセッサ"を選択します。
[インクルードファイルのパス]にACEとTAOのインクルードパスを記述します。環境変数の内容を展開する場合には、$(環境変数名)を使用します。ここでは、環境変数ACE_ROOTと環境変数TAO_ROOTが設定されているものとして記述しています。
続いて[リンク]タブを指定して[カテゴリ]欄は"インプット"を選択します。[追加ライブラリのパス]にACEとTAOのライブラリパスを記述します。環境変数の内容を展開する場合には、$(環境変数名)を使用します。ここでは、環境変数ACE_ROOTと環境変数TAO_ROOTが設定されているものとして記述しています。
リンクするライブラリファイル名は、同じ画面上の[オブジェクト/ライブラリモジュール]欄に記述しますが、ACE,TAOはリリースビルドとデバッグビルドでファイル名が異なります。そこで、設定の対象を個別に選択してライブラリファイル名を記述します。
Win32 Debugビルドの時は、[設定の対象]欄を"Win32 Debug"に選択して、[リンク]タブの[カテゴリ]欄は"インプット"を指定しておいて、[オブジェクト/ライブラリモジュール]欄にACEとTAOのライブラリファイル(デバッグビルド版)を記述します。
Win32 Releaseビルドについても同様に設定していきます。[設定の対象]欄を"Win32 Release"に選択して、[リンク]タブの[カテゴリ]欄は"インプット"を指定しておいて、[オブジェクト/ライブラリモジュール]欄にACEとTAOのライブラリファイル(リリースビルド版)を記述します。
ビルドを実行します。ビルドが成功すれば、出力ディレクトリに実行ファイルが出来ています。
#include "timeC.h"
#include <iomanip>
#include <fstream>
#include <string>
int main(int argc, char *argv[])
{
  try {
      if (argc != 2) { 
          cerr << "Usage: client IOR_string" << endl;
          throw 0;
      }
      // CORBAの初期化
      CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
      // IOR文字列を第一引数で受け取る
      CORBA::Object_var obj = orb->string_to_object(argv[1]);
      if (CORBA::is_nil(obj.in())) {
          cerr << "Nil Time reference" << endl;
          throw 0;
      }
      // Narrow
      Time_var tm = Time::_narrow(obj.in ());
      if (CORBA::is_nil(tm.in())) {
          cerr << "Argument is not a Time reference" << endl;
          throw 0;
      }
      {
       cout << "Validating connection"<<endl;
       CORBA::PolicyList_var pols;
       tm->_validate_connection(pols.out());
       cout << "Succesfull " <<endl;
      }
      // Get time
      TimeOfDay tod = tm->get_gmt();
      cout << "Time in Greenwich is "
           << setw(2) << setfill('0') << tod.hour << ":"
           << setw(2) << setfill('0') << tod.minute << ":"
           << setw(2) << setfill('0') << tod.second << endl;
    } catch(const CORBA::Exception &x) {
      ACE_PRINT_EXCEPTION (x,
                           "Who is the culprit \n");
      cerr << "Uncaught CORBA exception" << endl;
      return 1;
    } catch(...) {
      return 1;
    }
    return 0;
}
クライアントのソースコードファイルと、2.で生成されたスタブファイルとをコンパイル・リンクします。
クライアントプログラムをビルドするためのプロジェクトを作成します。VC++の[ファイル]メニューから[新規作成]を選択し、新規作成ダイアログの[プロジェクト]タブを選びます。プロジェクト種類はアプリケーションによって違いますので適したものを選びます。今回はコマンドラインから実行するプログラムなので"Win32 Console Application"を選び、プロジェクト名を適宜指定し(例ではclient)、現在のワークスペースに追加します。そして、client.cppとスタブファイルのtimeC.cppをこのプロジェクトに追加します。
| プロジェクトに入れるファイル | 内容 | 
|---|---|
| client.cpp | main関数 | 
| timeC.cpp | IDL定義から生成されたスタブファイル | 
サーバプロジェクトの設定と同様
実行にあたっては、Cygwinのbashシェルを使用します。
$ ./server > ior
$ ./client `cat ior` Validating connection Successfull Time in Greenwich is 23:11:44 $
MS-DOSシェルではクライアントの実行が難しいです。サーバを実行してIORファイルを作成した後、クライアントの実行時にIORファイルの中身をコマンドラインオプションにそのまま記述します。かなり長いので、IORファイルを少々修正してバッチファイルとするとよいかもしれません。
通常CORBAサーバは空いているポート番号を使用してCORBAクライアントからのリクエストを待ちます。ポート番号を固定したい場合は、-ORBListenEndpoints オプションを指定します。
$ ./server -ORBListenEndpoints iiop://myhost:1374 > ior
ホスト名を省略した書き方 iiop://:1374 というのもあります。
どのポートで実行されるかを確認する方法の一つとして、IORファイルの内容を解析することがあります。TAOでは、catiorコマンドが提供されています。
$ catior -f ior
    :
     Port Number:       1374
    :