aravis解析 その6 [aravis]
aravisを理解するためにGObejctの使い方を見ている。GObjectではクラス定義からインスタンスを作るのも非常に複雑である。初期化もいろいろなことができるようになっているのでよけいである。マニュアルを読んだぐらいでは、全体像をつかむことはとんでもなく難しい。ごく普通の場合だけを調べることにする....
の形にする。例えばaravisのArvCameraの場合
となっている。この中でg_object_new()関数を呼ぶ。AevCameraではarv_camera_new()の中で
となっている。この関数はクラスの初期化をしてメモリをアロケートしてプロパティを初期化して、オブジェクトを返す。
g_object_new()関数は任意数の引数をとる。まずインスタンスのGType、そのあとはプロパティの名前とその値の組を並べて、NULLで終端することになっているようである。ちゃんと仕様に従えばObjective-Cのように文字列でもプロパティをアクセスできるみたいで、プロパティに対するダイナミックな動作を可能にするメカニズムになっている。aravisでは積極的には使っていないようである。
g_object_new()関数はその中で、
などが呼ばれる(一般名にするのが面倒なのでArvCameraの場合を示した)。
arv_camera_class_init()は名前の通りクラスを初期化する関数で、これは1度しか呼ばれないようである。
arv_camera_constructor()関数はここで親クラスの初期化をするようである。arv_camera_init()は最終的にオブジェクトのプロパティのデフォルト値を設定する、C++で言えばコンストラクタに対応するものだ、と言っている。初期化の段階ではこの_init()関数が一番最後のようである。さらにObjective-Cでのinitializeメソッドやloadメソッドのような機能を果たす関数の*_base_init()なども用意されているようである。
これらの初期化関数は基底クラスのGObjectのクラス構造体がポインタを持っている。「オブジェクト定義」のところで書いたように、どのクラス構造体も先頭にGObjectの構造体を持っているので、そこのポインタをクラス初期化用のarv_camera_class_init()関数の中で設定する。
ではarv_camera_class_init()はどこで設定するのか、というとG_DEFINE_TYPE()マクロが文字列として生成するようである。マクロが入れ子になると、追いかけるのが難しくなってくる。
例えばArvCameraの中でも盛んに使われている、オブジェクトが引数の場合にチェックして、指定した方のインスタンスでなかった場合、エラーを示す返り値(多くの場合NULL)をreturnするというマクロ
がある。これは変数のcameraがArvCameraオブジェクトでなかったらNULLを返り値にしてreturnするというマクロだけど、これが実際にプリプロセサで展開されると
なんてことになっている(if文の条件を書くかっこの中にもif文が埋まっている。普通はまずこんな書き方はしないのでわけわかんなくてみるのが嫌になる)。適当に整形したけど、これが1行に埋まっている。うっかり一文字書き間違った、なんてことがあるとコンパイラからのエラーメッセージが全くトンチンカンなものになってデバグが非常に難しい。
G_BEGIN_DECLSとG_END_DECLSのマクロはCでは何もしない。インスタンス構造体は完全にopaqueではないので、プライベートな変数を別の構造体ArvCameraPrivateに持って、その定義は実装ファイルの方(あるいは専用のヘッダファイル)に記述することで隠蔽している。このスタイルはGNOMEライブラリでは一般的な書き方のようである。
それ以外はGObjectが指定する通り、そのままになっていることがわかる。
GObjectの、どんな環境でもコンパイルできるようにオブジェクト指向風な記述をCでやる、というのは高い志だとは思うけど、膨大な手間が増えてしまって、ヘタをするとプログラミングの目的が、ソフトウェアをオブジェクト間の相互作用として組み立てることより、まずオブジェクトとして振る舞わせることになってしまうようにも思える。
よほど書き慣れた人でないと、GObjectベースの記述は困難を極める、という気がする。少なくとも僕にできる気がしない。
さすがに他の人にとってもGObjectを書くのは大変だと見えて、valaというGOjbectのコードを出力するプリプロセッサが作られているようである。でもなんとなく本末転倒というか、それならC++(か、あるいはあきらかにモデルにしたと思われるObjective-C)にしてもいいじゃん、と思ってしまう。まあ、すでにGObjectのコードが大量にあるのでそれとの互換性をとりながら書きやすくする、ということなんだろうけど。
4.2.7 インスタンスの初期化
新しいインスタンスを作る関数はobject *prefix_class_new (args)
ArvCamera *arv_camera_new (const char *name);
camera = g_object_new (ARV_TYPE_CAMERA, "device", device, NULL);
g_object_new()関数は任意数の引数をとる。まずインスタンスのGType、そのあとはプロパティの名前とその値の組を並べて、NULLで終端することになっているようである。ちゃんと仕様に従えばObjective-Cのように文字列でもプロパティをアクセスできるみたいで、プロパティに対するダイナミックな動作を可能にするメカニズムになっている。aravisでは積極的には使っていないようである。
g_object_new()関数はその中で、
static void arv_camera_class_init (ArvCameraClass *camera_class); static GObject *arv_camera_constructor (GType gtype, guint n_properties, GObjectConstructParam *properties); static void arv_camera_init (ArvCamera *camera);
arv_camera_class_init()は名前の通りクラスを初期化する関数で、これは1度しか呼ばれないようである。
arv_camera_constructor()関数はここで親クラスの初期化をするようである。arv_camera_init()は最終的にオブジェクトのプロパティのデフォルト値を設定する、C++で言えばコンストラクタに対応するものだ、と言っている。初期化の段階ではこの_init()関数が一番最後のようである。さらにObjective-Cでのinitializeメソッドやloadメソッドのような機能を果たす関数の*_base_init()なども用意されているようである。
これらの初期化関数は基底クラスのGObjectのクラス構造体がポインタを持っている。「オブジェクト定義」のところで書いたように、どのクラス構造体も先頭にGObjectの構造体を持っているので、そこのポインタをクラス初期化用のarv_camera_class_init()関数の中で設定する。
ではarv_camera_class_init()はどこで設定するのか、というとG_DEFINE_TYPE()マクロが文字列として生成するようである。マクロが入れ子になると、追いかけるのが難しくなってくる。
4.2.8 マクロ展開によるボイラープレートの軽減
こうやってみてくるとオブジェクト指向言語ではコンパイラが自動生成して意識することはない作業も、Cではすべて書かないといけない。その手間を少しでも軽減するためのマクロがいろいろ定義されている。例えばArvCameraの中でも盛んに使われている、オブジェクトが引数の場合にチェックして、指定した方のインスタンスでなかった場合、エラーを示す返り値(多くの場合NULL)をreturnするというマクロ
g_return_val_if_fail (ARV_IS_CAMERA (camera), NULL);
do{ if ((((__extension__ ({ GTypeInstance *__inst = (GTypeInstance*) ((camera)); GType __t = ((arv_camera_get_type ())); gboolean __r; if (!__inst) __r = (0); else if (__inst->g_class && __inst->g_class->g_type == __t) __r = (!(0)); else __r = g_type_check_instance_is_a (__inst, __t); __r; }))))) { } else { g_return_if_fail_warning (((gchar*) 0), ((const char*) (__func__)), "ARV_IS_CAMERA (camera)"); return (((void*)0)); }; }while (0);
4.2.9 ArvCameraのヘッダファイル
典型的なヘッダとしてArvCameraを見てみる。#ifndef ARV_CAMERA_H #define ARV_CAMERA_H #if !defined (ARV_H_INSIDE) && !defined (ARAVIS_COMPILATION) #error "Only <arv.h> can be included directly." #endif #include <arvtypes.h> #include <arvstream.h> #include <arvgvstream.h> G_BEGIN_DECLS #define ARV_TYPE_CAMERA (arv_camera_get_type ()) #define ARV_CAMERA(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), ARV_TYPE_CAMERA, ArvCamera)) #define ARV_CAMERA_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), ARV_TYPE_CAMERA, ArvCameraClass)) #define ARV_IS_CAMERA(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ARV_TYPE_CAMERA)) #define ARV_IS_CAMERA_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), ARV_TYPE_CAMERA)) #define ARV_CAMERA_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), ARV_TYPE_CAMERA, ArvCameraClass)) typedef struct _ArvCameraPrivate ArvCameraPrivate; typedef struct _ArvCameraClass ArvCameraClass; struct _ArvCamera { GObject object; ArvCameraPrivate *priv; }; struct _ArvCameraClass { GObjectClass parent_class; }; GType arv_camera_get_type (void); ArvCamera * arv_camera_new (const char *name); /* ここにArvCameraのメソッド(関数)のプロトタイプが並ぶ */ G_END_DECLS #endif
それ以外はGObjectが指定する通り、そのままになっていることがわかる。
4.2.10 GObjectによるオブジェクトプログラミングの難しさ
こうやってみていると、GObjectのオブジェクト指向な構造はコンパイラは全く関与していなくて、すべてプログラマの脳内に構築されているだけである。Cで書いているんだから当然と言えば当然なんだけど、今ごく普通にあるSwiftやC++、さらにはObjective-Cでさえ、プログラマがオブジェクトの動作に集中できるようにコンパイラが十分に面倒を見ているということがよくわかる。GObjectの、どんな環境でもコンパイルできるようにオブジェクト指向風な記述をCでやる、というのは高い志だとは思うけど、膨大な手間が増えてしまって、ヘタをするとプログラミングの目的が、ソフトウェアをオブジェクト間の相互作用として組み立てることより、まずオブジェクトとして振る舞わせることになってしまうようにも思える。
よほど書き慣れた人でないと、GObjectベースの記述は困難を極める、という気がする。少なくとも僕にできる気がしない。
さすがに他の人にとってもGObjectを書くのは大変だと見えて、valaというGOjbectのコードを出力するプリプロセッサが作られているようである。でもなんとなく本末転倒というか、それならC++(か、あるいはあきらかにモデルにしたと思われるObjective-C)にしてもいいじゃん、と思ってしまう。まあ、すでにGObjectのコードが大量にあるのでそれとの互換性をとりながら書きやすくする、ということなんだろうけど。
2019-07-06 21:56
nice!(0)
コメント(0)
コメント 0