Bonobo components

(2005.10.23更新)

GNOME 2.xのlibbonobo / libbonoboui について。

Bonoboとは?

(この節、2005.10.23更新)

Bonoboに関するドキュメントは非常に少ない。しかも、Bonobo自体も何か一つのことをするのにやり方がいろいろあって、何が妥当なやり方か非常に分かりにくい。

Bonoboは、GNOMEで使われているコンポーネント技術。プロセスをまたいでオブジェクトのメソッドを呼び出したり、イベントをやり取りできる。内部ではORBit2を利用してプロセス間通信を行っている。ORBit2については、ORBit2

基本的な考え方や用語は、WindowsのCOM (Component Object Model) から流用している。Unknownインターフェイス、オートメーション、モニカ (Moniker)、などなど。

Bonoboは、libbonoboとlibbonobouiライブラリからなる。GUIのコンテナとそこに埋め込まれるコントロールを作りたいときはlibbonoboとlibbonobouiの両方を使い、そうでない場合はlibbonoboだけ使う。

Bonoboオブジェクトサーバ

(この節、2005.10.23更新)

手始めに、GUIのコントロールではないBonoboオブジェクトサーバを作ってみよう。

オブジェクトサーバを作るときは、次のことをこなさないといけない。

  1. インターフェイスをIDLファイルに書く
  2. IDLコンパイラ (orbit-idl-2) でC言語に変換する
  3. 実装するコードを書く
  4. bonobo-activationの設定ファイルを書き、指定のパスに保存する

まず、IDL (Interface Definition Language) ファイルだが、名前のとおり、インターフェイスを定義する。ここではごく簡単なカウンタを定義してみる。ファイル名はcounter.idl。なお、IDLの書き方については、ORBit2 を参照。

  5| #include <Bonobo.idl>
  6| 
  7| interface Counter: Bonobo::Unknown {  --- (1)
  8|   readonly attribute short value;     --+
  9|   void incr();                          | (2)
 10|   void decr();                          |
 11|   string name();                      --+
 12| };

(1) Bonoboオブジェクトにするインターフェイスは、Bonobo::Unknownから派生するようにする。

(2) Counterインターフェイスは、メソッドincr(), decr(), name()を持ち,読み取り専用の属性valueを持つようにしてみる。

それから、逆順カウンタも定義してみる。ファイル名はReverseCounter.idl。インターフェイスは同じにすることにして、追加のメソッドなどはない。

  4| #include "counter.idl"
  5| interface ReverseCounter: Counter { };

これをIDLコンパイラでコンパイルして、実装する言語(ORBitならplain C)に変換する。(Makefileは後述。)

実装クラスの宣言

IDLファイルをIDLコンパイラに掛けただけでは、サーバー側のスケルトンしか生成されない。インターフェイスの実装は、自分でしないといけない。(インターフェイスだけではどんな動作か決まらないから当たり前。)

Bonoboでは、CORBAインターフェイスの実装をglibオブジェクトとして作る。

まず、Counterインターフェイスのための実装クラスを宣言する。ヘッダファイル CounterImpl.h として、次のようにクラスを定義する。CounterImplクラスはBonoboObjectクラスから (glib的に) 派生し、CounterImplClassはBonoboObjectClassから派生させる。

BonoboObjectクラス、BonoboObjectClassクラスは、<bonobo/bonobo-object.h>で定義される。BonoboObject / BonoboObjectClassクラスは、Bonobo::Unknownインターフェイスを実装している。

  7| #ifndef COUNTER_IMPL_H
  8| #define COUNTER_IMPL_H
  9| 
 10| G_BEGIN_DECLS
 11| 
 12| ////////////////////////////////////////////////////////////////////////
 13| // Counter
 14| 
 15| #define COUNTERIMPL(o) (G_TYPE_CHECK_INSTANCE_CAST((o), CounterImpl_get_type(), CounterImpl))
 16| 
 17| typedef struct {
 18|     BonoboObject base;                            --- (1)
 19|     short value;                                  --- (2)
 20| } CounterImpl;
 21| 
 22| typedef struct {
 23|     BonoboObjectClass parent_class;
 24|     POA_Counter__epv epv;  // メソッドへのポインタ
 25| } CounterImplClass;                              --- (3)
 26| 
 27| GType CounterImpl_get_type();
 28| 
 29| G_END_DECLS
 30| 
 31| #endif

(1) glibはplain Cでの枠組みなので、たとえ実際にはC++で書くとしても、クラスの派生は合成 (composition) として実装しなければならない。すなわち、構造体はPOD structでなければならない。

(2) CounterImplクラスでvalueを定義しておく。

(3) 実装クラスのクラスの名前は、'実装クラス名'Class でなければならない。Bonoboのマクロで決めうちになっている。

それから、ReverseCounterImpl.h ファイルで、ReverseCounterインターフェイスの実装クラスであるReverseCounterImplクラスを定義する。

  7| #include "CounterImpl.h"
  8| 
  9| G_BEGIN_DECLS
 10| 
 11| ////////////////////////////////////////////////////////////////////////
 12| // ReverseCounter
 13| 
 14| #define REVERSECOUNTERIMPL(o) (G_TYPE_CHECK_INSTANCE_CAST((o), ReverseCounterImpl_get_type(), ReverseCounterImpl))
 15| 
 16| typedef struct {
 17|     CounterImpl base;
 18| } ReverseCounterImpl;
 19| 
 20| typedef struct {
 21|     CounterImplClass parent_class;
 22|     POA_ReverseCounter__epv epv;
 23| } ReverseCounterImplClass;
 24| 
 25| GType ReverseCounterImpl_get_type();
 26| 
 27| G_END_DECLS

実装の制作

Bonoboでは、オブジェクトのlife cycle管理は、リファレンスカウント方式で行っている。Bonobo::Unknownインターフェイスで参照、解放メソッドが宣言されている。

module Bonobo {
  interface Unknown {
    void ref();
    void unref();
    Unknown queryInterface(in string repoid);
  };
};

Note.

Bonobo::Unknownインターフェイスは、COMのIUnknownインターフェイスと同じ意図のメソッドを持つ。引数と戻り値が違う。

クライアントからの参照が増えるときにref()し、参照が減るときにunref()する。カウントが0になると、そのオブジェクトは破棄される。

Bonoboでは、BonoboObjectから派生する実装クラスと、Unknownインターフェイスから派生するインターフェイス・クラスが入り乱れる。

リファレンスカウントを操作するためにUnknown::ref(), unref()を直接呼び出してもいいが、インターフェイスクラスとBonoboObject(派生)クラスの変換をしなければならない。めんどくさくないようにヘルパ関数が用意されている。プロトタイプ宣言は次のとおり。この関数にはBonoboObjectインスタンスを引数として渡す。

gpointer bonobo_object_ref(gpointer obj);
gpointer bonobo_object_unref(gpointer obj);

また、インターフェイス参照を引数として渡すヘルパ関数もある。

Bonobo_Unknown bonobo_object_dup_ref(Bonobo_Unknown object, 
                                     CORBA_Environment* opt_ev);
Bonobo_Unknown bonobo_object_release_unref(Bonobo_Unknown object,
                                           CORBA_Environment* opt_ev);

では、実装に入ることにしよう。

 21| #include <libbonobo.h>
 22| #include "counter.h"     // orbit-idlが生成したヘッダファイル
 23| #include "CounterImpl.h" // 実装クラスヘッダ
 24| 
 25| ////////////////////////////////////////////////////////////////////////
 26| // CounterImpl
 27| 
 28| // スーパークラス
 29| static GObjectClass* super_class;
 30| 
 31| static void counter_object_finalize(GObject* object) {
 32|     printf("%s\n", __func__); // DEBUG
 33|     CounterImpl* this_ = COUNTERIMPL(object);
 34|     // 必要があれば、ここでメモリの解放などを行う。
 35| 
 36|     super_class->finalize(object); // 親クラスのfinalize()を必ず呼ぶこと
 37| }
 38| 
 39| static CORBA_short impl_counter_get_value(PortableServer_Servant servant,
 40|                                           CORBA_Environment* ev) {
 41|     CounterImpl* this_ = COUNTERIMPL(bonobo_object(servant));
 42|     return this_->value;
 43| }
 44| 
 45| static void impl_counter_incr(PortableServer_Servant servant,
 46|                               CORBA_Environment* ev) {
 47|     CounterImpl* this_ = COUNTERIMPL(bonobo_object(servant));
 48|     this_->value++;
 49| }
 50| 
 51| static void impl_counter_decr(PortableServer_Servant servant,
 52|                               CORBA_Environment* ev) {
 53|     CounterImpl* this_ = COUNTERIMPL(bonobo_object(servant));
 54|     this_->value--;
 55| }
 56| 
 57| static CORBA_string impl_counter_name(PortableServer_Servant servant,
 58|                                       CORBA_Environment* ev) {
 59|     return CORBA_string_dup("NormalCounter");
 60| }
 61| 
 62| static void CounterImpl_class_init(CounterImplClass* klass, gpointer class_data) {
 63|     printf("%s\n", __func__); // DEBUG
 64| 
 65|     // スーパークラスへのポインタを保存しておく
 66|     super_class = static_cast<GObjectClass*>(g_type_class_peek_parent(klass));
 67| 
 68|     // メソッドの設定
 69|     POA_Counter__epv* epv = &klass->epv;
 70|     ((GObjectClass*) klass)->finalize = counter_object_finalize;
 71|     epv->_get_value = impl_counter_get_value;
 72|     epv->incr = impl_counter_incr;
 73|     epv->decr = impl_counter_decr;
 74|     epv->name = impl_counter_name;
 75| }
 76| 
 77| static void CounterImpl_init(CounterImpl* instance, gpointer g_class) {
 78|     printf("%s: g_class = %s\n", __func__, g_class); // DEBUG
 79|     instance->value = 0;
 80| }
 81| 
 82| BONOBO_TYPE_FUNC_FULL(
 83|     CounterImpl,        // class_name: glib class_name
 84|     Counter,            // corba_name: CORBA interface name
 85|     BONOBO_TYPE_OBJECT, // parent: parent type
 86|     CounterImpl         // prefix: local prefix  -> class_nameと同じにする。
 87| )

サーバ側でインスタンスを生成、破壊するために次の関数が必要になる。

  1. 実装クラスの初期化 -- CounterImpl_class_init
  2. インスタンスの初期化(コンストラクタ) -- CounterImpl_init
  3. インスタンスの破棄(デストラクタ) -- counter_object_finalize

finalizeのほうは名前を自由に決められるが、クラスの初期化は'prefix'_class_init、インスタンスの初期化は'prefix'_init と名前が決まっている。

CounterImpl_class_init()は、最初のインスタンスの生成の前に呼び出される。スーパークラスのほうからクラス初期化関数が順番に呼び出されるので、自分でスーパークラスを呼び出す必要はない。ここでは、スーパークラスへのポインタの保存、メソッドの設定をしなければならない。

CounterImpl_init()は、インスタンスが生成されるたびに呼び出される。この関数も、スーパークラスのほうから順番に呼び出されるので、自分でスーパークラスを呼び出す必要はない。ここでは、プロパティの初期化などを行う。

counter_object_finalize()は、インスタンスのリファレンスカウントが0になって、実際に廃棄されるときに呼び出される。メモリなどインスタンスに関連する資源の解放をここで行う。スーパークラスのfinalize()は自動では呼び出されないので、自分で親クラスのfinalize()を呼び出すようにしなければならない。

最後に、BONOBO_TYPE_FUNC_FULL()マクロでCORBAインターフェイスとBonoboObjectクラスを結びつける。このマクロは、bonobo-object.h で定義されていて、'prefix'_get_type()関数を生成する。

ReverseCounterのほうも同じように実装する。

 14| #include <libbonobo.h>
 15| 
 16| #include "ReverseCounter.h"
 17| #include "ReverseCounterImpl.h"
 18| 
 19| ////////////////////////////////////////////////////////////////////////
 20| // ReverseCounterImpl
 21| 
 22| // スーパークラス
 23| static GObjectClass* super_class;
 24| 
 25| static void rev_object_finalize(GObject* object) {
 26|     printf("%s\n", __func__); // DEBUG
 27|     super_class->finalize(object);
 28| }
 29| 
 30| static void impl_rev_incr(PortableServer_Servant servant,
 31|                           CORBA_Environment* ev) {
 32|     ReverseCounterImpl* this_ = REVERSECOUNTERIMPL(bonobo_object(servant));
 33|     this_->base.value--;
 34| }
 35| 
 36| static void impl_rev_decr(PortableServer_Servant servant,
 37|                           CORBA_Environment* ev) {
 38|     ReverseCounterImpl* this_ = REVERSECOUNTERIMPL(bonobo_object(servant));
 39|     this_->base.value++;
 40| }
 41| 
 42| static CORBA_string impl_rev_name(PortableServer_Servant servant,
 43|                                   CORBA_Environment* ev) {
 44|     bonobo_object_dump_interfaces(bonobo_object(servant)); // DEBUG
 45|     return CORBA_string_dup("ReverseCounter");
 46| }
 47| 
 48| static void ReverseCounterImpl_class_init(ReverseCounterImplClass* klass,
 49|                                           gpointer class_data) {
 50|     printf("%s\n", __func__); // DEBUG
 51|     super_class = static_cast<GObjectClass*>(g_type_class_peek_parent(klass));
 52| 
 53|     ((GObjectClass*) klass)->finalize = rev_object_finalize;
 54|     klass->parent_class.epv.incr = impl_rev_incr;
 55|     klass->parent_class.epv.decr = impl_rev_decr;
 56|     klass->parent_class.epv.name = impl_rev_name;
 57| }
 58| 
 59| static void ReverseCounterImpl_init(ReverseCounterImpl* instance,
 60|                                     gpointer g_class) {
 61|     printf("%s: g_class = %s\n", __func__, g_class); // DEBUG
 62| }
 63| 
 64| BONOBO_TYPE_FUNC_FULL(
 65|     ReverseCounterImpl,        // class_name
 66|     ReverseCounter,            // corba_name
 67|     CounterImpl_get_type(),    // parent
 68|     ReverseCounterImpl         // prefix
 69| )

サーバ(ファクトリ)

Bonoboオブジェクトサーバは、Bonobo::GenericFactoryインターフェイスを実装する。ファクトリ実装は、真の目的のオブジェクトを生成する。このようになっているのは、ひとつのサーバプロセスで複数のオブジェクトを生成できるようにし、また、場合によってはオブジェクトを共有できるようにするためだ。

 クライアントはファクトリに対して本当の目的のオブジェクトを生成するよう要求する。ファクトリオブジェクトはオブジェクトを生成し、その参照を返す。

Bonobo::GenericFactoryインターフェイスは、ただひとつのメソッドcreateObject()を持つ。

module Bonobo {
  interface GenericFactory: Bonobo::Unknown {
    exception CannotActivate { };
    Object createObject(in string iid) raises(CannotActivate);
  };
};

Note.

COMではIClassFactoryインターフェイスが相当する。このインターフェイスはCreateInstance()とLockServer()メソッドを持つ。

実際には、BONOBO_ACTIVATION_FACTORY()マクロを使うことで、実装の手間は少なくなっている。やらないといけないのは、クライアントから要求されたIIDを確認し、適切なオブジェクトを生成し、それを返すこと。

 11| #include <stdio.h>
 12| #include <assert.h>
 13| #include <string.h>
 14| #include <libbonobo.h>
 15| 
 16| #include "ReverseCounter.h"
 17| #include "ReverseCounterImpl.h"
 18| #include "counter_iid.h"
 19| 
 20| //////////////////////////////////////////////////////////////////////
 21| 
 22| static BonoboObject* counterObj = NULL;
 23| static BonoboObject* revCntrObj = NULL;
 24| 
 25| // bonobo_generic_factory_new_generic()から呼び出される
 26| static BonoboObject* counter_factory(BonoboGenericFactory* factory,
 27|                                      const char* component_id, gpointer )
 28| {
 29|     printf("%s: component_id = '%s'\n", __func__, component_id); // DEBUG
 30| 
 31|     if (!strcmp(component_id, IID_Counter)) {                    --- (1)
 32|         if (counterObj) {
 33|             // オブジェクトを再利用してみる
 34|             bonobo_object_ref(counterObj);                       --- (2)
 35|             return counterObj;
 36|         }
 37|         return counterObj = static_cast<BonoboObject*>(
 38|                                  g_object_new(CounterImpl_get_type(), NULL));
 39|     }
 40|     else if (!strcmp(component_id, IID_ReverseCounter)) {
 41|         if (revCntrObj) {
 42|             bonobo_object_ref(revCntrObj);
 43|             return revCntrObj;
 44|         }
 45|         return revCntrObj = static_cast<BonoboObject*>(
 46|                             g_object_new(ReverseCounterImpl_get_type(), NULL));
 47|     }
 48|     assert(0);
 49|     return NULL;
 50| }
 51| 
 52| BONOBO_ACTIVATION_FACTORY(                              --- (3)
 53|     "OAFIID:CounterFactory:20001114",   // oafiid
 54|     "Sample Counter component factory", // descr
 55|     "1.0",                              // version
 56|     counter_factory,                    // callback
 57|     NULL
 58| )

(1) IIDを確認するは、単純に文字列比較すればいい。

(2) オブジェクトを共有しようとするときは、リファレンスカウンタを上げておくことを忘れないように。

(3) BONOBO_ACTIVATION_FACTORY マクロは bonobo-generic-factory.h で定義されている。main()関数を生成し、main()関数のなかではBONOBO_FACTORY_INIT()マクロ、bonobo_generic_factory_main()関数を呼び出す。

サーバの活性化

(2000.11.06の日記を加筆。)
(2005.11.12 この節を更新。)

オブジェクトサーバは出来たが、クライアントはどのようにして自動的にオブジェクトを提供するサーバプログラムを探し、起動するのか。

そのための仕組みがbonobo-activation。頻繁に名前が変わっていて混乱する。一番最初はgnorba (libgnorba) という名前で、GOADがオブジェクト活性化に関わっていた、らしい。私は使ったことがない。bonobo 1.xのときに新たにOAF (liboaf) が登場した。bonobo 1.xはOAFに依存していた。今でも設定ファイルにOAFの名残がある。bonobo 2.xになって、OAFはbonobo-activationに名前が変わった。その後、bonobo-activationは2003年8月のバージョン2.4.0を最後に、その後はlibbonoboに含まれるようになった。

bonobo-activationは、サーバ情報を格納したファイルを調べることでサーバを探す。Fedora Core 4では、/usr/lib/bonobo/servers ディレクトリにそのファイルを保存する。

例えば、gtkhtml3パッケージがインストールされているなら、GNOME_GtkHTML_Editor-3.6.server ファイルを見てみよう。

<oaf_info>
  <oaf_server iid="OAFIID:GNOME_GtkHTML_Editor_Factory:3.6" type="shlib"
            location="/usr/lib/gtkhtml/libgnome-gtkhtml-editor-3.6.so">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:GNOME/GenericFactory:1.0"/>
    </oaf_attribute>
    <oaf_attribute name="name" type="string" value="GNOME HTML Editor Factory"/>

(中略)

  <oaf_server iid="OAFIID:GNOME_GtkHTML_Editor:3.6" type="factory"
               location="OAFIID:GNOME_GtkHTML_Editor_Factory:3.6">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Bonobo/Control:1.0"/>
      <item value="IDL:Bonobo/Unknown:1.0"/>
      <item value="IDL:Bonobo/PersistStream:1.0"/>
      <item value="IDL:Bonobo/PersistFile:1.0"/>
      <item value="IDL:Bonobo/Persist:1.0"/>
    </oaf_attribute>
    <oaf_attribute name="name" type="string" value="GNOME HTML Editor"/>

設定ファイルは、XMLフォーマットで、oaf_infoがルート要素になる。文字コードはUTF-8。oaf_server要素がサーバの設定で、oaf_info内に複数のサーバ設定を書ける。oaf_serverタグは、iid, type, location属性を持つ。iidはサーバを識別する文字列。typeはサーバプログラムをどのように起動するか。factory、exe、shlibのいずれか。

type属性値location
factory ファクトリオブジェクトサーバのiid
shlib サーバプログラム(shared object)のパス
exe サーバプログラム(実行ファイル)のパス

クライアントが OAFIID:GNOME_GtkHTML_Editor:3.6 のオブジェクトを要求すると、bonobo-activation はまずこのサーバのtypeを確認する。ファクトリなので、次に OAFIID:GNOME_GtkHTML_Editor_Factory:3.6 を探す。このサーバはshlib なので、動的にロードし、ファクトリオブジェクトを生成し、さらにOAFIID:GNOME_GtkHTML_Editor:3.6オブジェクトを生成するよう、ファクトリオブジェクトに要求する。

oaf_attribute要素でオブジェクトサーバの属性を設定する。値が複数のときは、oaf_attribute要素のname属性で名前、type属性で型を決め、item要素でサーバの設定値を決める。値がひとつだけのときは、oaf_attribute要素のvalue属性値で設定値を定める。

name type 内容
repo_ids stringv 提供するインターフェイス(複数)
name string サーバの名前
name-言語string サーバの名前。指定の言語で書かれたもの。
description string サーバの説明
description-言語string サーバの説明。指定の言語で書かれたもの。

一つのオブジェクトは複数のインターフェイスを実装できる。クライアントがサーバを探す場合、(1) サーバのIIDを指定して呼び出す。(2) インターフェイスを指定して、そのインターフェイスを実装しているサーバを呼び出す方法がある。通常は、前者の方法を採る。クライアントが汎用的なオブジェクト挿入コマンドを実装するにしても、特定のインターフェイス(IDL:Bonobo/Control:1.0 など)を持つサーバをリストアップして、ユーザに選ばせてからサーバを呼び出すので、やはり前者の方法になるだろう。

Note.

IIDを介してサーバを探すというやり方はWindowsのCOMと同じ。COMではCLSIDといい、その設定はレジストリに保存される。

このようにすることで、インターフェイスさえ変えなければ、コンポーネントをバージョンアップなどで交換することができる。shared objectとの違いだが、インターフェイスをクラス指向で書けるのが大きいように思う。C++のABIを維持するのは至難の業で、現実は不可能に近い。

今回のサンプルの設定ファイルは次のようになる。ファイル名はcounter.server。ファイル名はサーバ名と一致していなくてもいい。

<oaf_info>
  <oaf_server iid="OAFIID:CounterFactory:20001114" type="exe"        --- (1)
              location="/home/hori/src/test/gtk/counter-bonobo/server">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:GNOME/ObjectFactory:1.0"/>
    </oaf_attribute>
    <oaf_attribute name="name" type="string" value="counter automation factory" />
    <oaf_attribute name="name-ja" type="string" 
                   value="counterオートメーション・ファクトリ" />
  </oaf_server>

  <oaf_server iid="OAFIID:Counter:5863719e-143e-4d6d-a161-ae70e0a4895a"
              type="factory" 
              location="OAFIID:CounterFactory:20001114">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Bonobo/Unknown:1.0" />
      <item value="IDL:Counter:1.0" />
    </oaf_attribute>
    <oaf_attribute name="name" type="string" value="counter object" />
    <oaf_attribute name="name-ja" type="string" value="counterオブジェクト" />
  </oaf_server>

  <oaf_server iid="OAFIID:ReverseCounter:6389fbcb-9fe7-47ba-903e-6b7226f5e587"
              type="factory" 
              location="OAFIID:CounterFactory:20001114">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Bonobo/Unknown:1.0" />
      <item value="IDL:ReverseCounter:1.0"/>
    </oaf_attribute>
  </oaf_server>
</oaf_info>

IIDは一意 (unique) でなければならないので、uuidgenコマンドで生成したuuidを使うのが望ましい。しかし、実際の運用では、単純に名前で区別しているみたい。

クライアント

Warning.

以下の解説は、内容が古く、整理されていません。TODO: この節を更新すること。

クライアントのコードを示す。

client.cc

int main(int argc, char* argv[])
{
    CORBA_exception_init(&ev);
    CORBA_ORB orb = oaf_init(argc, argv);

生のORBitではCORBA_ORB_init()を最初に呼び出すが,OAFではそれに代えてoaf_init()を呼ぶ。中は見ていないが,おそらく裏でoafdデーモンの起動などを行うのだろう。

サーバーに接続する部分は次のようになる。

client.cc

    counter = oaf_activate("repo_ids.has('IDL:Counter:1.0')", 
                           NULL, 0, NULL, &ev);
    catchException(ev);
    rc = oaf_activate("repo_ids.has('IDL:ReverseCounter:1.0')", 
                      NULL, 0, NULL, &ev);
    catchException(ev);

repo_ids.has()とは見るからに式のようだが,深く調べていないので,何が書けるかは分からない。差し当たりサーバーを呼び出すだけなら,これで十分。かなり簡単。

サーバーのコード。

server.cc

    oaf_active_server_register(NC_IID, counter_ref);
    oaf_active_server_register(RC_IID, reverse_ref);

counter_refは,PortableServer_POA_servant_to_reference()で取得した参照。oaf_active_server_register()にIDLを渡すと例外が発生する。

では実行。

$ ./client 
Trying dir /usr/share/oaf
ready.
name = Counter
incr():  1 2 3 4 5 6 7 8 9 10.
decr():  9 8 7.
name = ReverseCounter
ready.
incr():  999 998 997 996 995 994 993 992 991 990.
decr():  991 992 993.

$ ps x
 1743 ?        S      0:00 oafd --ac-activate --ior-output-fd=7
 1747 ?        S      0:00 ./server --oaf-activate-iid=OAFIID:Counter:20
 1750 ?        S      0:00 ./server --oaf-activate-iid=OAFIID:ReverseCou

ふむ動く。でもサーバーが二つ動いているのは上手くない。要検討。

ところでOAFはマシンを跨いでサーバーを起動できるの? せっかくCORBAを利用しているのだから,できてほしいところ。

gtkクライアント

TODO:

Bonoboによるリモートgtk+オブジェクト

2001.01.04 随感。

Bonoboは直感的に言ってプロセスを跨いでgtk+オブジェクトを扱うことはすでに述べた。

今日はサーバープロセスで動作するgtk+オブジェクトをクライアントプロセスで表示させよう。

クライアントから。

static GtkWindow* createContainerWindow()
{
    GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    // BonoboUIContainer < BonoboObject < GtkObject
    BonoboUIContainer* uic = bonobo_ui_container_new();
    assert(uic);

    GtkWidget* control = bonobo_widget_new_control(
        "OAFIID:MyClock:20001119",
        bonobo_object_corba_objref(BONOBO_OBJECT(uic)));
    assert(control);
    
    GtkWidget* box = gtk_vbox_new(FALSE, 2);
    gtk_container_add(GTK_CONTAINER(window), box);

    gtk_box_pack_start(GTK_BOX(box), control, TRUE, TRUE, 0);

    GtkWidget* button = gtk_button_new_with_label("close");
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
    gtk_signal_connect(GTK_OBJECT(button), "clicked",
                       GTK_SIGNAL_FUNC(on_clicked), NULL);

    return GTK_WINDOW(window);
}

これだけ。スタンドアローンの場合と異なるのは次の二つの関数だけ。

GtkWidget* bonobo_widget_new_control(const char* moniker,
                                     Bonobo_UIContainer uic);
BonoboUIContainer* bonobo_ui_container_new();

BonoboUIContainerインスタンスが何をするものかはまだ調べてない。ソースにもあるように,IIDをbonobo_widget_new_control()に与えると,内部でサーバーに接続し,サーバーgtk+オブジェクトを取得できる。クライアントはこのオブジェクトがどのようなメソッド,シグナルを持っているかコンパイル時には何も知らない。もちろん,このサーバーに特有のメソッドを呼びたい場合はコンパイル時に知る必要がある。

いったんサーバーgtk+オブジェクトを取得すると,ローカルなオブジェクトと区別することなく扱える。サーバーでの描画はネットワークを介してクライアントプロセスのウィンドウに表示される。

サーバー。

static BonoboObject* my_clock_factory(BonoboGenericFactory* factory, void* )
{
    MyClock* clockWidget = my_clock_new();
    assert(clockWidget);
    gtk_widget_show(clockWidget); // important!!!
    
    BonoboControl* control = bonobo_control_new(clockWidget);
    assert(control);
    return BONOBO_OBJECT(control);
}

これだけ。widgetを生成し,show()してからbonobo_control_new()に渡すだけ。クライアント側で表示するだけならwidget自体に特別な仕掛けは何も必要ない。

Bonobo::Unknownインターフェイスと,サーバーにおけるオブジェクトの破壊

各種CORBA ORBの評価
  • Java RMI-IIOP
  • Java HORB-IIOP

クライアントでは次のようなコードになる。

    printf("incr(): ");
    for (i = 0; i < 10; i++) {
        Counter_incr(counter, &ev);
        printf(" %d", Counter__get_value(counter, &ev));
    }
    printf(".\n");

単純に関数を呼び出すだけのようだが,内部でORBitがメソッド,引数をバイト列に変換してサーバーへ送り,サーバーで再び関数呼び出しに戻す。

サーバーでは次のようになる。

static CORBA_short CounterImpl_get_value(PortableServer_Servant servant,
                                  CORBA_Environment* ev) {
    return Counter_DATA(servant)->value;
}

static void CounterImpl_incr(PortableServer_Servant servant, 
                             CORBA_Environment* ev) {
    Counter_DATA(servant)->value++;
}

サーバーの戻り値は,やはりORBitによってネットワークを介してクライアントに届けられる。

ORBitによるCORBAサーバーの実装

CORBAではオブジェクトを識別するのにIORを用いる。また,ネームサーバーを用いて活性化しているサーバーへ接続できる。しかしサーバープロセスをクライアントが起動することができない(よねぇ?)ので,あらかじめサーバープロセスを起動しておく必要がある。これでは不便なので,GNOMEではliboafが名前からサーバーを検索し,必要があればサーバープロセスを起動する。

liboaf ライブラリ

liboafでは.oafinfoファイルにオブジェクトを識別するID, サーバープロセスへのパスを記述する。.oafinfoファイルはXMLで記述する。

<oaf_info>
  <oaf_server iid="OAFIID:ReverseCounter:20010103" type="factory"
              location="OAFIID:CounterFactory:20001114">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:ReverseCounter:1.0"/>
    </oaf_attribute>
  </oaf_server>

  <oaf_server iid="OAFIID:CounterFactory:20001114" type="exe" 
              location="./server">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:GNOME/ObjectFactory:1.0"/>
    </oaf_attribute>
  </oaf_server>
</oaf_info>

IID属性値がCORBAオブジェクトを識別する文字列。本来は"OAFIID:プログラム名:UUID"という形式だが,手を抜いている。念のため。あるサーバープロセスが異なるインターフェイスを持つ複数のオブジェクトを提供したい場合,type属性値が"factory"のoaf_server要素を並べることになる。

オブジェクトの提供するインターフェイスを変更(追加を含む)した場合は,以前のIIDによるサーバーを登録したまま,異なるIIDを持つサーバーを追加して登録するか,提供するインターフェイスを追加し(.oafinfoファイルではrepo_ids以下にitem要素を加える),動的に新しいインターフェイスを提供することで互換性を維持する。同じIIDのままインターフェイスを変更してはならない。

  • OAFによるサーバーの検索
  • Bonobo/OAF

OAFではスクリプトから利用しやすい別名はない。たとえば次のようなスクリプトがある。

require 'win32ole'

application = WIN32OLE.new('Excel.Application')
application.visible = TRUE
workbook = application.Workbooks.Add()
worksheet = workbook.Worksheets(1)
worksheet.Range("A1:D1").value = ["North", "South", "East", "West"]
worksheet.Range("A2:B2").value = [5.2, 10]

機械としてはCLSIDでも変わらないだろうが,スクリプトに書くときは多少難儀か。

まぁ何にしても,クライアントからはIIDを与えれば後はOAFがよきに計らってくれる。すなわち,OAF/ORBitを用いれば,IIDによってCORBAサーバープロセスを起動し,CORBAサービスを受けることができる。

GNOMEではこの仕組みの上にBonoboを設けている。単純に言えば,Bonoboはプロセスを跨いでgtk+オブジェクトを動作させる。Bonoboを用いると,クライアントを修正することなく,扱えるコンポーネントを追加することができる。

今日はを多少補足する。

オブジェクトを使い終わったら,クライアントでは次の関数を呼び出す。

void bonobo_object_unref(BonoboObject* object);

サーバーがBonobo::Unknown派生の場合,サーバーに通知され,サーバー側でオブジェクトを破棄することができる。Bonobo::Unknown派生でない場合もBonoboObject化することはできるが,この場合はunrefしてもサーバーには何も通知されない。

サーバーでは,生成したオブジェクトに"destroy"シグナルを接続しておく。オブジェクトを生成するところを示す。

static BonoboObject* counter_factory(BonoboGenericFactory* factory,
                                     const char* component_id, void* )
    ...

    if (!strcmp(component_id, REVERSE_IID)) {
        POA_ReverseCounter* poaCounter = ReverseCounterImpl_new(&ev);
        catchException(ev);

        // BonoboObjectインスタンスを生成し,poaCounterを活性化
        revCntrObj = obj = bonobo_object_new_from_servant(poaCounter);
    }
    assert(obj);

    gtk_signal_connect(GTK_OBJECT(obj), "destroy",
                       GTK_SIGNAL_FUNC(on_destroy), NULL);

こうしておくと,クライアントでオブジェクトが不要になったときに"destroy"シグナルハンドラで対処することができる。

    POA_Counter* poaCounter
        = (POA_Counter*) bonobo_object_get_servant(BONOBO_OBJECT(obj));

    // オブジェクトの不活性化
    PortableServer_ObjectId* oid
        = PortableServer_POA_servant_to_id(bonobo_poa(), poaCounter, &ev);
    catchException(ev);
    PortableServer_POA_deactivate_object(bonobo_poa(), oid, &ev);
    catchException(ev);
    CORBA_free(oid);

    // オブジェクトの破棄
    CounterImpl_delete(poaCounter, &ev);
    catchException(ev);

    // bonobo_object_finalize_real()で再解放されないようにする
    BONOBO_OBJECT(obj)->servant = NULL;

    gtk_main_quit();

サーバーでは,提供するサービスによるが,オブジェクトが全て破棄されたときにプロセスを終了するようにしてもよい。クライアントが再びサービスを必要としたときにOAFがサーバープロセスを起動してくれる。

COMオブジェクトとBonoboオブジェクトとの実装からの比較

2000.11.14 随感。

えーっと,昨日の表現は正しくない。COMコントロール版のMyClockでもCMyClockCtrlクラスは具象クラスであるCOleControlから派生している。でもってCOleControlはCWndから派生している。MFCではCOleControlクラスがIOleControl実装クラスのインスタンスを所有するようになってる。Bonoboも考え方としては同じデザイン。

Bonoboサーバーの実装では,BonoboObject(GtkObjectのサブクラス)またはそのサブクラスから派生させてサーバークラスを生成し,CORBAインターフェイスの実装はそのサーバークラスに所有させるようにする。

Bonoboでは,サーバーは独立したプロセスで,機械を跨がないシステム内でもWindowsのようにDLLではない。サーバー・プロセスはリモートオブジェクトの工場(factory)として動作する。

oafinfoファイルを次のように記述する。

<oaf_info>
  <oaf_server iid="OAFIID:Counter:20001114" type="factory" 
              location="OAFIID:CounterFactory:20001114">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:Counter:1.0"/>
    </oaf_attribute>
  </oaf_server>
  <oaf_server iid="OAFIID:CounterFactory:20001114" type="exe" 
              location="./server">
    <oaf_attribute name="repo_ids" type="stringv">
      <item value="IDL:GNOME/ObjectFactory:1.0"/>
    </oaf_attribute>
  </oaf_server>
</oaf_info>

クライアントからIIDとして"OAFIID:Counter:20001114"を与えると,OAFは恐らく次のように動作するのだろう。

  1. oafinfoファイルを調べ,与えられたIIDのOAFサーバーを探す。
  2. type属性の属性値がfactoryなので,location属性で与えられるIIDを持つOAFサーバーを探す
  3. CounterFactoryサーバーのtypeはexeなので,locationを実行。
  4. CounterFactoryリモートオブジェクトから目的のリモートオブジェクトを取得する

クライアントで,サーバーオブジェクトを取得する関数は次;

BonoboObjectClient* bonobo_object_activate(
    const char* iid,
    gint oaf_flags);

クライアントのコードを示す。

client.cc

int main(int argc, char* argv[])
{
    CORBA_Environment ev;
    CORBA_exception_init(&ev);

    CORBA_ORB orb = oaf_init(argc, argv);

    gtk_set_locale();
    gtk_init(&argc, &argv);

    bonobo_init(orb, NULL, NULL);

    // サーバーへ接続し,BonoboObjectの形でリモートオブジェクトを取得する
    BonoboObjectClient* obj = bonobo_object_activate("OAFIID:Counter:20001114", 0);
    assert(obj);

    // BonoboオブジェクトからCORBAオブジェクトを取り出す
    Counter counter = bonobo_object_corba_objref(BONOBO_OBJECT(obj));
    testCounter(counter);

    bonobo_object_unref(BONOBO_OBJECT(obj));

    CORBA_exception_free(&ev);
    return 0;
}

BonoboはGtk+を内部で使うので,ウィンドウを開かなくてもgtk+の初期化が必要なことに注意しよう。それ以外はクライアント側はかなり単純。

次にサーバー。

server.cc

int main(int argc, char* argv[])
{
    CORBA_Environment ev;
    CORBA_exception_init(&ev);

    CORBA_ORB orb = oaf_init(argc, argv);

    gtk_set_locale();
    gtk_init(&argc, &argv);

    // bonobo-main.c: RootPOAの取得,POAManagerの取得を行う
    if (bonobo_init(orb, NULL, NULL) == FALSE) {
        printf("initialize bonobo failed.\n");
        return 1;
    }

    // Bonoboではfactoryクラスを介してサーバーを呼び出す。
    BonoboGenericFactory* factory = bonobo_generic_factory_new(
        "OAFIID:CounterFactory:20001114",
        counter_factory,
        NULL);
    assert(factory);

    printf("Counter server ready.\n");

    // bonobo-main.c: POAマネージャを活性化し,メインループに入る
    bonobo_main();

    CORBA_exception_free(&ev);
    return 0;
}

サーバーを実装する場合に,いちいちGNOME::ObjectFactoryインターフェイスを実装する必要はない。その辺はBonoboがやってくれる。GNOME::ObjectFactoryはGNOME::GenericFactoryの発展したものともいえる。GNOME::GenericFactoryはlibgnorbaで用いるが,今では使われない,ように思う。

次の関数にFactoryクラスのIIDとコールバック関数を与えればいい。

BonoboGenericFactory* bonobo_generic_factory_new(
    const char* goad_id,
    BonoboGenericFactoryFn factory,
    void* data);

クライアント(直接的にはOAF)から新しいリモートオブジェクトの生成するよう呼ばれた場合は,コールバック関数が呼ばれる。

server.cc

BonoboObject* counter_factory(BonoboGenericFactory* factory, void* )
{
    CORBA_Environment ev;
    CORBA_exception_init(&ev);

    // サーバーオブジェクトの生成
    POA_Counter* counter = CounterImpl_new(&ev);
    catchException(ev);

    // BonoboObjectインスタンスを生成し,counterを活性化
    BonoboObject* obj = bonobo_object_new_from_servant(counter);

    CORBA_exception_free(&ev);
    return obj;
}

恐らく,クライアントの目的のオブジェクトの選択だとかがここに入ることになるはずだが,今回は一つしかないので盲目的にCounterオブジェクトを生成している。BonoboObjectオブジェクトもbonobo_object_new_from_servant()関数でBonoboに生成してもらえる。

Bonoboのインターフェイスモデル

2000.11.13 随感。

の続き。<bonobo/bonobo-object.h>ヘッダーファイルを見れば,BonoboObjectクラスが何か分かる。

typedef struct {
    GtkObject            base;
    Bonobo_Unknown       corba_objref;
    gpointer             servant;
    BonoboObjectPrivate *priv;
} BonoboObject;

見ての通り,(gtk+的に)BonoboObjectクラスは,GtkObjectクラスのサブクラスになっている。では,BonoboObjectクラスから派生するクラスにはどういったものがあるか。例えば次のようなものがある。

  • BonoboCanvasComponent
  • BonoboClientSite
  • BonoboControl
  • BonoboMoniker
  • BonoboUIComponent
  • BonoboPersist

BonoboObjectクラスがgtk+的なクラスなので,上記のいずれもCORBAクラスではなくgtk+クラスになっている。この辺は例えばIOleClientSiteがIUnknownから派生している(他のインターフェイスも同様にIDLから生成)のWindows COMの方が徹底しているように見える。

follow-up(s):

BonoboObjectインスタンスを生成するためには,Bonobo::Unknownインターフェイスを実装したオブジェクトへの参照とオブジェクトそのもの(かskeletonオブジェクト。Bonoboではこの区別は明らかではない)が必要になる。

すでにCORBAオブジェクトを生成している場合,新しいBonoboObjectインスタンスの生成には,次の関数を用いる。

BonoboObject* bonobo_object_new_from_servant(void* servant);

この関数では,引数に渡したCORBAオブジェクトの活性化も行われる。

リンク

インサイド Bonobo
CORBA, IDL, Bonoboについて書いている