Wednesday, December 14, 2016

Glib Object System: 派生クラスの作成

「そもそも、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.hmyfoo.c が生成されます。生成されたコードは、数百行あるので掲載は割愛します。内容を確認してみると勉強になりますよ。

No comments:

Post a Comment