funini.com 自由研究 C/C++ GObjectデバッグ

GObjectデバッグ

はじめに注意

GObject は C 言語でオブジェクト指向の

GType, GObject は、Glib に含まれる C 言語で実装された型・オブジェクト 記述フレームワークである。 他のオブジェクト指向言語へのバインディングが無理なく行えるような  記述ルール・システムを提供する。

GObject を用いることにより、オブジェクトを指すポインタから メンバ関数 (メソッド) を呼び出したり、メンバ変数にアクセスしたりできる。 オブジェクト定義は継承を用いることができる。 多重継承を用いることはできないが、インターフェース・仮想関数を提供する。

  1. 型とクラス

2.1. 型とクラスの概要

GType では、ユーザは型 (type) を定義する。 型はコンパイル時に埋め込まれるのではなく、GTypeInfo という構造体で 定義される。ここには、構造体に型のサイズ・コンストラクタ/デストラクタ関数 などが代入されている。

GClass を用いると、型にメンバ関数を持たせることができる。(Classed type) 呼ばれた関数は、型として代入されている値に応じて 行われる処理を変え、メソッドとして登録されている関数ポインタにより 実行される処理を決定する。

2.2. 型の種類

GObject では、型を以下のように分けることが出来る。

GValue はあらゆる型のコンテナとして使える g_value_copy でコピーできる。

2.3. 型変換マクロ

型から対応する関数を探したり、継承されたメンバの場所を得るために 以下の型変換マクロを用いる。

  1. 型の定義

型の実装は、以下の部分から成る。

3.1. 型の定義

GType として型を定義する。ここには、初期化関数・終了関数・サイズなどを 定義する。XXX_TYPE_YYY (XXXがパッケージ名・YYYが型名) として、この GType を返すマクロを定義する。

以下に、GType を返すマクロの例を示す。


#define MAMAN_TYPE_BAR (maman_bar_get_type ())

GType maman_bar_get_type (void){ static GType type = 0; if (type == 0) { // type は static 変数なので、以下の代入は一度だけ行われる static const GTypeInfo info = { sizeof (MamanBarClass), NULL, /* コンストラクタ / NULL, / デストラクタ / (GClassInitFunc) foo_class_init, / クラス初期化*/ NULL, /* class_finalize / NULL, / class_data / sizeof (MamanBar), 0, / n_preallocs / (GInstanceInitFunc) NULL / instance_init */ }; type = g_type_register_static (G_TYPE_OBJECT, "BarType", &info, 0); } return type; }

☆ この型定義のソースコード中に出現する G_GNUC_CONST は、関数が 常に同じ値を返すことをコンパイラに最適化ヒントとして与えているもので、 意味的な影響は無い。

3.2. 構造体の定義

実装としては、型とクラスは別々の構造体で定義される。 型は GObject を継承し、メンバ変数を定義する。次に例を示す。

typedef struct { GObject parent; int field_a; } MamanBar;

ここで Mamanbar が型の名前、field_a がメンバ変数名である。

クラスは GObjectClass を継承し、メンバ関数を定義する。次に例を示す。

typedef struct { GObjectClass parent; /* class members */ void (*do_action_public_virtual) (MamanBar *self, guint8 i); void (*do_action_public_pure_virtual) (MamanBar *self, guint8 i); } MamanBarClass;

do_action_public_virtual と do_action_public_pure_virtual が関数になる。

3.3. 型・クラスの継承

継承における呼び出しが働く仕組みは、以下のようになっている。 例えば、A という型に a0, a1 というメンバ (int) があったとする。 この場合、A のインスタンスの構造は例えば以下のようになる。

struct A { int a0; // 0-3 byte int a1; // 4-7 byte };

ここで、A を継承した B が、b0 と b1 をメンバとして持つとする。 B は以下のように定義される。

struct B { struct A parent; // 0-7 byte int b0; // 8-11 byte int b1; // 8-11 byte };

ここで、parent の中身は A と全く同じ offset になっている。

struct B { struct A { int a0; // 0-3 byte int a1; // 4-7 byte }; int b0; // 8-11 byte int b1; // 8-11 byte };

つまり、B のインスタンスを作って、これを A にキャストして a1 を呼び出しても、正しくメンバにアクセスできる。


struct B *my_b = (struct B *)malloc(sizeof(struct B)); struct A casted_a = (struct A)my_b;

my_b->a0 = ... // OK

クラスの継承も基本的に同じだが、インターフェースの扱いは少し特殊になる。

3.3. private なフィールド

private なメンバ Type 構造体のメンバにある変数は、C コンパイラ的に保護はかからない。 コメントに /* Private */ と書かれているフィールドは、ユーザが 意識して private として扱う。

また、GObject には、プライベートな領域を宣言するための仕組みがある。 構造体には priv 構造体としてまとめて1つの構造体に宣言し、GType の宣言では 単にこの priv 構造体のみを宣言しておく。

struct _ClutterActor { GInitiallyUnowned parent_instance; guint32 flags; // <-- public なフィールド ... ClutterActorPrivate *priv; // <-- priv 構造体 };

priv 構造体は別に定義しておく。 また、g_type_class_add_private() という関数を用いることで、初期化/終了時に priv フィールドの確保/解放も自動的に行われるようになる。

3.4. インターフェース

インターフェースを実装するときは、構造体のメンバに何かを加えるのではなく、  型を登録する G_DEFINE_TYPE 関数で指定する。 インターフェース自体は、GTypeInterface を継承して作る。

  1. 関数

4.1. 関数のネーミング

関数や構造体の名前には、モジュール名を prefix として付ける。 モジュール名は C++ の namespace に対応する。 このモジュールの中にオブジェクト名を定義する。 例えば foo というモジュール内に hoge というクラスを作るときは、 メソッド名は以下のようにつける。

foo_hoge_method();

4.2. 関数の呼び出し

クラスのインスタンスが与えられたとき、そのメンバ関数を呼ぶには、  二つの方法がある。

1 つは、クラス定義のメンバ変数に入っている関数ポインタを利用する方法である。  pointer が HogeClass のインスタンスで、func() が実装されているものとすると、  以下のように呼ぶことができる。

HOGE_CLASS(pointer)->func()


もう 1 つは、関数名を直接用いる方法である。 これはクラス以外の型に対しても使うことができる。

hoge_func(HOGE(pointer));


この二つの動作は、共通になるように実装する。

4.3. 仮想関数

virtual 関数が実現できる。 つまり、クラス A を継承した クラス B があるとき、 B のインスタンスに対し A の仮想関数を呼び出すと、B の関数が呼び出される。

これは、以下の C++ のコードに相当する。

class A { virtual void f()=0; };

class B : public A { void f(){ printf("b\n"); } };

main(){ B *b = new B(); A *a = (A *)b; a->f(); // <--- ここで b が print される }

ここでは A::f() の中身は完全に空で、純粋仮想関数が実現されている。  しかし、A::f() に何かを実装し、B::f() が実装されなければ A::f() が 呼び出されるような仕組みにすることもできる。

  1. メモリ管理

5.1. コンストラクタ・デストラクタの定義

クラスの場合、インスタンス全部で共通な初期化処理とインスタンスごとに 必要な処理がある場合があるので、各種関数が定義されている。

a) 型/クラスに対して一回しか呼ばれないもの - base_init (type) - class_init (class) - interface_init (interface)

b) インスタンスごとに呼ばれるもの - instance_init (instance の初期化)

5.2. 外部からの使い方 (確保・解放)

オブジェクトの確保には、GType ごとの別々の初期化関数を呼ぶ。

クラスの場合、インスタンス全部で共通な初期化処理とインスタンスごとに 必要な処理がある場合があるが、ユーザは単純にインスタンスごとの 初期化関数を呼べばよい。

オブジェクトを解放するときには、g_object_unref() が呼ぶ。  GObject が使う資源は内部にリファレンスカウントを持っていて、  この関数は、リファレンスカウントを 1 少なくする。  もし参照数が 0 になったら、その資源を解放する。

  1. 他の言語へのバインディング

例えば Python から C の関数を呼ぶとき、バインディングライブラリを利用する。 バインディングライブラリは C のオブジェクトファイルをロードし、引数を C の関数と互換性があるよう変換してセットし、関数に制御を渡す。 関数の実行が終了すると、ライブラリは返り値を取得し、Python コードに処理を戻す。

GObject を用いて実装されたコードに対しては、このバインディング関数を コードごとに別々に実装する必要は無い。