「GNOMEフレームワーク」の版間の差分

削除された内容 追加された内容
関連項目 OSS開発ツール/GUIツールキット
glibについて、OSS開発ツール/GUIツールキット 2019年9月28日 (土) 13:36‎ から移動。gnome専門の話題なので、このGnomeフレームワークのページのほうが適切。
 
87 行
主にGTKというアプリケーションを使う。詳しくは[[GTKプログラミング]](wikibooks内)を参照せよ。
 
 
== glib ==
w:Gtk+はCで書かれたツールキットです。Gtk+では、w:en:glibを利用してCの枠組の中で、いわゆる「オブジェクト指向型」のプログラミングを実行しています。一見すると不思議ですが、w:継承もサポートされています。
 
ここでは、最初にglibについて述べ、その後にglibを使ったGUIについて述べます。
GTK+のオブジェクト指向
Cのオブジェクト指向
 
(このあたりの事柄はglibソースのglibx.x.x/docs/refrence/gobject 以下の文書によっています)
 
w:オブジェクト指向にはカプセル化、w:継承などいくつかの側面があります。単純にデータに動作を付け加えるという考え方をするなら、Cの範囲でも構造体を利用することでオブジェクト指向を実現することができます。
 
例えば、IDと名前を持ったmemberというオブジェクトを考えるなら、(IDに対するsetとgetも付け加えることにします)
 
struct member {
int id;
char * name;
int getID(struct member *);
void setID(struct member *, int);
}
 
とすることで、memberの振舞いを記述できます。
 
次に、memberの働きを継承するemployeeを定義することを考えてみます。ここで、employeeは、memberの性質に加えて、賃金(wage)と、仕事をする(work)の2つを付け加えることにします。
 
この振舞いを付け加えるには、Cのキャストを利用します。まず、employeeを、
 
struct employee {
struct member parent;
double wage;
void work(employee *);
}
 
として定義します。parentとしてstruct memberを取っているところが重要です。
 
あるemployeeであるemployee1を定義して、そのIDを表示したいとしましょう。この場合には、
 
struct employee employee1;
printf("%d",((struct member *)&employee1)->getID());
 
とすることで、employee1のIDを取得できます。
 
この振舞いを見るためには、構造体とポインタについていくつかの事柄を知る必要があります。まず、member構造体のポインタもemployee構造体のポインタも単なる数値であり、互いにキャストを行なうことで移り変われることに注目します。このことから、employee1のアドレスをstruct member * にキャストできることがわかります。
 
更に、構造体の要素を取りだす演算が、ポインタに指し示されたメモリアドレスより構造体内での取りだす要素の分だけずれたアドレスを計算することに着目します。->はアドレスをデリファレンスした上で構造体の要素を取りだすので、(... )->getID の部分はemployee1のアドレスからstruct member 内でのgetID の位置までずらしたアドレスを取りだします。おそらくこのアドレスは&employee1 より sizeof(int) + sizeof(char *) だけずれた値と予想されます 。(実際にはこのことは保証されていないようです。)
 
しかし、struct employee はstruct member から始まる構造体なので、上で示した位置にあるのは、struct member 内にあるgetID 関数です。結局ここまでの手順で、struct employee からstruct member の関数が使えたことになります。
glibとは
 
ここまでの手順でCを用いてオブジェクト指向プログラミングが出来ることがわかりました。しかし、上の例にはいくつか不足した部分があります。
 
id, name, wage はそれぞれのインスタンス間で異なる値を取るので、インスタンスごとに領域を確保する必要があります。しかし、getID, setID, work などの関数は、全てのemployeeについて共通の関数であり、それぞれのインスタンスにこの関数を与える必要はありません。これらの関数は別の場所にまとめることが望ましいやり方です。
 
次に、CPlusPlusで存在したnew 演算子やコンストラクタがここでは定義されていません。これらを提供しないとメモリの扱いが難しくなり不便です。
 
w:glibは、Cの枠組みでこれらの問題を解決するために作られたライブラリです。gtk+はglibの機能をフルに使って書かれています。
 
実際にはglibの機能はこれだけではなく、C++でいうw:STLに属するw:リスト、w:連想配列などの汎用的なデータ構造もglibの一部として与えられています。また、glib はgtk+ が利用できる環境では必ずインストールされているため、移植性が高いこともメリットです。
 
ここからはglibの使い方について述べます。glibはCを使ってオブジェクト指向プログラミングを行なうときに有効です。
 
glibでは、インスタンス間で共有される関数や変数を、class という枠組みで管理します。また、関数や変数を持った型のことを、オブジェクトと呼びます。例えば、aaa というオブジェクトを定義するときには、struct aaa は、aaaのインスタンスであり、struct aaa_class は、aaaのクラスです。
 
実際の定義では更にいくつかの慣用的記法を利用しますが、これ以上の詳細はglibのリファレンスを参照して下さい。実際にaaaの定義をするには、かなり多くのコードを書く必要があります。
 
/* aaa.h */
#define TYPE_AAA (aaa_get_type())
#define AAA(self) ((aaa*) self)
#define AAA_CLASS(self) ((aaa_class*) self)
#define AAA_GET_CLASS(self) (G_TYPE_INSTANCE_GET_CLASS((self),TYPE_AAA, aaa_class))
typedef struct _aaa aaa;
typedef struct _aaa_class aaa_class;
struct _aaa{
GObject parent;
/* フィールド */
};
struct _aaa_class {
GObjectClass parent;
/* オブジェクト間で共有される関数など */
};
void aaa_init (GTypeInstance *self, gpointer data);
void aaa_class_init (gpointer *self, gpointer data);
GType aaa_get_type ();
 
/* aaa.c */
#include <glib.h>
#include <glib-object.h>
#include <aaa.h>
GType aaa_get_type () {
static GType type = 0;
if (type == 0) {
static GTypeInfo info ={
sizeof(aaa_class),0,0,
aaa_class_init,0,0,
sizeof(aaa),0,
aaa_init
}
type = g_type_register_static (G_TYPE_OBJECT, "aaa", &info, 0);
return type;
}
/* aaa_init(), aaa_class_init()の定義 ... 。*/
 
上の例は非常に長くなるようですが、実際にはaaa_get_type() やマクロの定義は全てのglibオブジェクトで共通なので、他の例からコピーすればよいでしょう。
 
C++の例でいえば、コンストラクタはaaa_initに対応します。ただし、コンストラクタのオーバーロードには対応していないため、実際にいくつかの引数を与えて初期化を行ないたいときには、別のset関数を利用する必要があるでしょう。
 
aaa_class_init ではインスタンスから使用できるメソッドを与えます。例えば、aaa_class 内にwork() という関数が与えられているなら、
 
AAA_CLASS(self)->work = aaa_work;
 
という代入によってaaaのメンバ関数を定義することが出来ます。もちろんaaa_work は別のところで定義する必要があります。
 
ここからは実際のプログラム中でクラスを利用する方法を述べます。
 
まず、glibを用いて定義されたクラスを利用する前に、g_type_init()を利用する必要があります。この関数は型システムの基本的な初期化を行ないます。(gobjectリファレンスマニュアル API Reference より)
 
クラス定義以外の、実際のプログラム中では、C++でいうnew演算子でaaaのインスタンスを作成します。new演算子の機能はこれらの定義を与えた後で、g_object_new() 関数によって提供されます。例えば、aaaのインスタンスを作成したいときには、
 
aaa * aaa1 = g_object_new(TYPE_AAA, 0);
 
によってaaa_initで値が初期化されたインスタンスを得ることが出来ます。aaa1からメンバ関数を利用するには
 
AAA_GET_CLASS(aaa1)-> work(aaa1);
 
としてworkを利用することが出来ます。
 
ここまででnew 演算子とメンバ関数の呼び出し方を説明しました。最後にC++でいうdelete 演算子の説明をします。g_objct_newで確保された領域はC++のnewの場合と同様、手動で解放しないとw:メモリリークを起こします。これを解放するには、C++ではdelete演算子を利用します。glibでは、deleteではなくg_object_unref を利用します。この関数の名前は、メモリの解放がw:参照カウントを用いて行なわれていることによります。参照カウントに関する説明はw:参照カウントを参照して下さい。
 
g_object_unrefは、g_objectを継承したインスタンスの参照カウントを1減らします。参照カウントはg_object_newでオブジェクトが作成されたときには1に設定されており、0になったときに確保したメモリが解放されます。
 
== 関連項目 ==