Table of Contents
「そもそも、Glib / GObject を使ってクラスを作る方法が分らない」という話があったので、書いてみようかと思います。
1 まずは C で書く
40行程度なので、いきなりコードから
/* Declaration Begin */ #include <glib-object.h> #define MY_TYPE_FOO (my_foo_get_type()) G_DECLARE_FINAL_TYPE (MyFoo, my_foo, MY, FOO, GObject) /* Declaration End */ /* Definition Begin */ struct _MyFoo { GObject parent_class; }; G_DEFINE_TYPE(MyFoo, my_foo, G_TYPE_OBJECT) static void my_foo_class_init (MyFooClass * klass) { g_print("class init\n"); } static void my_foo_init (MyFoo * noop) { g_print("instance init\n"); } MyFoo * my_foo_new(void) { return (MyFoo*) g_type_create_instance(MY_TYPE_FOO); } /* Definition End */ int main() { g_print("main\n"); my_foo_new(); my_foo_new(); return 0; }
これで全部です。
では、最初の行から説明していきます。
#include <glib-object.h>
- GObject を使う場合に include するヘッダーです
#define MY_TYPE_FOO (my_foo_get_type())
- これだけは、
G_DECLARE_FINAL_TYPE
が生成できないので手で作る必要があります。my_foo_get_type()
と書けば事足りるので、「そもそも、必要あるのか?」と自分は思ってしまうのですが、 GObject の流儀なので作成しておきましょう。G_DECLARE_FINAL_TYPE
がこのマクロを作成できないのは、C 言語の制限で、C言語のマクロ(#define
)の中では#define
できない為です G_DECLARE_FINAL_TYPE
MyFoo
クラスの宣言をしてくれます。my_foo
という「モジュール_クラス」の名前を宣言します。MY
というモジュールの下でFOO
というクラスを作成することになります。こんな感じに 2種類の宣言を書かなければいけない所が C に無理矢理オブジェクトシステムを入れたイマイチ感を匂わせます。最後の引数が親クラスです。GObject から派生させたクラスにしたいので、GObject
を指定します。GStreamer のエレメントを作る場合は、GstElemet
を指定したりします。
ここまでが宣言になります。もし他のファイルからこのクラスを使う場合は、ここまでをヘッダーファイル .h
に記載します。ここからは実際のクラスの定義に入って行きます。他のコードと分けるには、ここからのコードは .c
に記載してください。
- struct _MyFoo
- ここからがクラス本体です。メソッドがないので、親クラスである GObject を内包しているだけの
struct
です G_DEFINE_TYPE
- GObject タイプシステムとして必要なコードを生成してくれるマクロです。
_get_type()
の生成がメインです。作成するタイプのキャメルケース を第1引数に、スネークケース を第2引数に指定します。最後は親のタイプ名、ここでは GObject のタイプ名、を指定します my_foo_class_init()
- クラスの初期化関数です。 GObject Type System が
MyFoo
クラスを始めて使う場合に呼ばれます。大抵の場合は最初のインスタンスを作成する時でしょうか。 my_foo_init()
- インスタンスの初期化関数です。インスタンスが生成される度に呼び出されます
my_foo_new()
- MyFoo クラスのコンストラクターです。シンプルな場合は、直接 Type System のジェネリックなコンストラクタ
g_type_create_instance()
を呼んでも良いですが、ラップしておいた方が無難です。
main関数で、インスタンスを2つ作成して、どのタイミングでクラスの初期化関数とインスタンスの初期化関数が呼ばれるか確認してみます。
$ meson . build $ ninja -C build $ build/foo main class init instance init instance init
main関数が始まって、「main」と表示されます。次に my_foo_new()
が始めて呼び出された時に GObject Type System は MyFoo タイプを初期化します。これが結果的に my_foo_class_init()
を呼び出します。クラスが初期化されると実際にインスタンスが生成され my_foo_init()
が呼び出されます。
2回目に my_foo_init()
が呼び出された時には Type System は MyFoo
クラスを知っているのでクラスが初期化されることはありません。すぐにインスタンスの初期化関数 my_foo_init()
が呼び出されます。
こんな感じに、シンプルなクラスであれば、それなりの行数で作成することができます。もちろん GObject を継承しているので MyFoo
のオブジェクトも プロパティー を持ったり シグナル を扱うことができます。
とは言え Ruby や C# のように class 生成をサポートしている言語には敵いませんね。
2 次は Vala でも書いてみる
GObject Type System を使った言語で Vala という言語があります。 Hello World はこんな感じです。
class Demo.HelloWorld : GLib.Object { public static int main(string[] args) { stdout.printf("Hello, World\n"); return 0; } }
この Vala、「Vala → C → ネイティブコード」という順番にコンパイルしています。つまり、実行ファイルを生成する前に C へのコンパイルで止めてあげれば、コードジェネレーターとして使うことができます。
先程の C のコードと同じ動きをする Vala のコードは、こんな感じです。
class My.Foo { static construct { stdout.printf("class init\n"); } public Foo() { stdout.printf("instance init\n"); } } public static int main(string[] args) { stdout.printf("main\n"); new My.Foo(); new My.Foo(); return 0; }
$ valac myfoo.vala $ ./myfoo main class init instance init instance init
この Vala のコードから C のコードを生成するには
$ valac -h myfoo.h -C myfoo.vala
とします。 myfoo.h
と myfoo.c
が生成されます。生成されたコードは、数百行あるので掲載は割愛します。内容を確認してみると勉強になりますよ。
No comments:
Post a Comment