[ C++で開発 ]

TAOプログラミング(その1)

オープンソースのTAOを使ったCORBAプログラミングです。

まず最初に

最初のプログラムは、書籍「Advanced CORBA Programming with C++」(Michi Henning、Steve Vinoski著、Addison-Wesley刊)の3章”最小限のCORBAアプリケーション”にある例題を作成していきます。

CORBAプログラム作成の手順

  1. (分散化する)オブジェクトを決定し、そのインタフェースをIDLで定義する
  2. IDL定義を記述したファイルをIDLコンパイルし、C++スタブ&スケルトンファイルを生成する
  3. CORBAオブジェクトを具現化(実装)するサーバントクラスをC++で宣言しコーディングする
  4. サーバのmainプログラムを書く
  5. サーバのソースコードファイルと、2.で生成されたスタブ・スケルトンファイルとをコンパイル・リンクする
  6. クライアントのコードを書く
  7. クライアントのソースコードファイルと、2.で作成されたスタブファイルとをコンパイル・リンクする

1. IDL定義

サンプルプログラムは、時刻(時分秒)の問合せを受けると現在時刻(GMT)を返却するというプログラムです。時刻情報(時分秒)を保持するIDL構造体と、現在時刻のサービスを提供するIDLインタフェースを記述します。

time.idl

// time.idl
struct TimeOfDay {
    short hour;
    short minute;
    short second;
};
interface Time {
    TimeOfDay get_gmt();
};

CORBAでは、データを受け渡す際はCORBA基本型か、CORBA構造体を使います。最近のCORBA仕様になってObject By Value(OBV)が追加され、オブジェクトの値渡しができるようになりました。

2. スタブ・スケルトンの生成(IDLコンパイル)

IDL定義ファイルは、CORBA実装系に付いているIDLコンパイラを使って実装言語のスタブ・スケルトンファイルに変換します。生成されるファイル名とファイルの数はCORBA仕様では規定されていないので、CORBA実装系によって異なります。つまり、CORBA仕様で標準化されているのはファイルに関してはIDLファイルだけです。

TAO 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
サーバスケルトンテンプレートファイル

TAO IDLコンパイラの実行(VC++コマンドライン環境)

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>

TAO IDLコンパイラの実行(VC++統合環境)

IDL用プロジェクトの作成

IDL定義ファイルをIDLコンパイラによってスタブ・スケルトンファイルへ変換するためのプロジェクトファイルを作成します。VC++の[ファイル]メニューから[新規作成]を選択し、新規作成ダイアログの[プロジェクト]タブを選びます。プロジェクト種類として"Utility Project"を選び、プロジェクト名を適宜指定し(例ではtime_idl)、現在のワークスペースに追加します。そして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の実行ファイルパスを追加します。

time_idlプロジェクトのビルド

設定が完了すれば、プロジェクトをビルドすることによって、IDL定義ファイルからスタブ・スケルトンファイルが生成されます。

3. サーバントクラスのコーディング

POA継承によってサーバントクラスを作成するので、POA_Time[*1]を継承したサーバントクラスTime_implを記述します。

[*1] IDL定義でmoduleを使用した場合、クラス名が変わります。module timeapp とした場合、POA_timeapp::Timeクラスを継承します。(POA_timeappはnamespace)

server.h

// 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(Time_implのメンバ関数定義)

// 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;
}

4. サーバのmainを書く

server.cpp(main関数)

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;
}
ORB_var型、Object_var型、POAManager_var型、Time_var型、String_var型などの*_var型
IDLで使われる型名に_varが付いているのは、C++マッピングにおいてIDLの型をラッピングしたものです。IDLコンパイル時に合わせて生成されます。_varはスマートポインタと呼ばれ、メモリ管理をするように拡張されています。C++のauto_ptrに似た役割を果たします。C++マッピングが開発された時にはまだ標準C++とauto_ptrがなかったので、このような仕組みが導入されました。

_var型を使わない場合は、

  CORBA::ORB_ptr orb;
  // ...
  CORBA::release(orb);
};

と自分でreleaseを呼んであげる必要がある。

5. サーバのコンパイル・リンク

サーバのソースコードファイルと、2.で生成されたスタブファイルおよびスケルトンファイルとをコンパイル・リンクします。

コンパイル・リンクの実行(VC++コマンドライン環境)

コンパイル

まずサーバのソースコードファイルをコンパイルします。コンパイル時には、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++統合環境)

サーバプロジェクトの作成

サーバプログラムをビルドするためのプロジェクトを作成します。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のライブラリファイル(リリースビルド版)を記述します。

サーバプロジェクトのビルド

ビルドを実行します。ビルドが成功すれば、出力ディレクトリに実行ファイルが出来ています。

6. クライアントを書く

client.cpp

#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;
}

7. クライアントのコンパイル・リンク

クライアントのソースコードファイルと、2.で生成されたスタブファイルとをコンパイル・リンクします。

コンパイル・リンクの実行(VC++統合環境)

クライアントプロジェクトの作成

クライアントプログラムをビルドするためのプロジェクトを作成します。VC++の[ファイル]メニューから[新規作成]を選択し、新規作成ダイアログの[プロジェクト]タブを選びます。プロジェクト種類はアプリケーションによって違いますので適したものを選びます。今回はコマンドラインから実行するプログラムなので"Win32 Console Application"を選び、プロジェクト名を適宜指定し(例ではclient)、現在のワークスペースに追加します。そして、client.cppとスタブファイルのtimeC.cppをこのプロジェクトに追加します。

プロジェクトに入れるファイル 内容
client.cpp main関数
timeC.cpp IDL定義から生成されたスタブファイル
クライアントプロジェクトの設定

サーバプロジェクトの設定と同様

実行

実行にあたっては、Cygwinのbashシェルを使用します。

サーバの実行(bash)

$ ./server > ior

クライアントの実行(bash)

$ ./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
    :