C MAGAZINE Linux programming Tips
第9回 GLib プログラミング
■はじめに
今回は、Linuxでしばしば利用されるGLibと呼ばれるライブラリーを紹介します。 GLibは、アプリケーションを作成する際によく使用されるストリング処理、線形リスト、ツリー、ログ機能やメモリ管理などの基本的な機能をはじめ、 字句解析(Lexical Scanner)機能や、イベントドリブン機構などのちょっと複雑な機能などが詰まった便利なC言語用の(ユーティリティー)ライブラリーです。Glibは、 GNOME関連のアプリケーションやその他多くのアプリケーションで利用されているため、現在のほとんどのディストリビューションで最初からインストールされています。
GLibは、(紛らわしいですが、標準Cライブラリーの)glibcでは提供していない機能及びglibcで提供されていても利便性や移植性にかける機能を提供します。例えば、 メモリー管理や日時や時間の処理はglibcでも提供されていますが、GLibではより利便性や移植性を向上されるためにそれらのラッパー関数を提供しています。またリスト、 ツリーやハッシュテーブルなどのデータ構造は、glibcでは提供されていませんが、ある程度のアプリケーションを開発する場合、 データ構造は必要な場合が多いでしょう。しかしC言語では、PerlやPythonには使いやすく一貫性のとれたデータ構造や、 C++言語ではSTL(Standard Template Library)や処理系が持つ便利なクラスライブラリーのようなものがあまり存在していません。 読者の皆さんもC言語でアプリケーションを開発する時に、リスト、ハッシュテーブルや何らかの文字列処理をそのアプリケーション用に実装した経験もあると思います。 GLibは、リスト、ハッシュテーブルなどのよく使われる汎用的なデータ構造やいくつかのglibcのラッパー関数を提供するため、アプリケーションの開発において、 開発コストが低減したり、移植性が向上することでしょう。
今回は、このGLibの機能の中から特徴的なものをピックアップし、そのプログラミング例を紹介します。
■プログラミング/ハックを始める前に
今回はGLibの機能を全て紹介できないためGLibが持つ関数のリファレンスが載っているURLを示しておきます。
http://developer.gnome.org/doc/API/glib/index.html
このリファレンスには、サンプルプログラムや使用例はあまり載っていませんが、GLibが提供する関数の情報を得ることができますので、 今回紹介することができなかった機能を使用したい場合は参考になると思います。
■環境整備
GLibは、ほとんどのディストリビューションでインストールされていると思いますが、 GLibのヘッダーファイルやスタティックライブラリーなどの開発環境がディフォルトでインストールされていない場合があります。RPMベースのディストリビューションであれば、 以下のようにして確認することができます。
$ rpm -q glib-devel
glib-devel-1.2.8-5 |
もしインストールされていないようであればCDROMやFTPサイト等から glib-develパッケージを取得し、 インストールしてください。あるいはオリジナルの配布サイト(
ftp://ftp.gtk.org/pub/gtk/v1.2/など)からダウンロードしインストールしてみてください。
もしRPMパッケージでなくtar.gz形式のファイルからインストールする場合、以下のような手順で行ってみてください。
$ tar xvfz glib-1.2.8.tar.gz
# cd glib-1.2.8
$ ./configure --prefix=/usr
$ make
# make install |
GLibは、GNOME関連のその他のライブラリー同様に、コンフィギュレーションを出力するコマンド glib-configを持っています。以下に、 コマンド glib-config を使った Makefileのサンプルと最も単純なGLibを使ったプログラム例を示します。
リスト1 : Makefile
PROGS=hello
ifeq (on,$(DEBUG))
CFLAGS=-Wall `/usr/local/bin/glib-config --cflags` -g
LDLIBS=`/usr/local/bin/glib-config --libs` -static
else
CFLAGS=-Wall `glib-config --cflags`
LDLIBS=`glib-config --libs`
endif
all: $(PROGS)
clean:
rm -f $(PROGS) |
リスト2: hello.c
#include <glib.h>
int main(int argc, char* argv[])
{
g_print("Hello GLib World!\n");
g_mem_profile(); /* configureで--enable-mem-profile指定時のみ有効 */
return 0;
}
|
上記のMakefileには、GLibデバッグモードをつけておきました。標準パスにインストールしたGLibと /usr/local以下にインストールしたGLibを使い分けることができます。 上記のtar.gz形式のファイルからのインストール時にconfigureに--prefixを付けないか、 もしくは--prefix=/usr/localと指定することで/usr/local以下に GLibをインストールすることができます。特に、 configureにオプション --enable-mem-profile を付けてコンパイルしインストールしておくと関数 g_mem_profile()によりGLibのメモリー利用状況を出力でき非常にデバッグには有効です。 オプション --enable-mem-profile以外にも、GLibの動作を変更するオプションがあるので、configureのヘルプを参考にしてください。
デバッグオプション付きGLib使用例
...
GLibの/usr/localへのインストール
...
$ ./configure --enable-mem-profile
$ make
# make install
...
プログラムのコンパイル & 実行
...
$ make DEBUG=on
$ ./hello
Hello GLib World!
GLib-INFO: 1 allocations of 19 bytes
GLib-INFO: 19 bytes allocated
GLib-INFO: 19 bytes freed
GLib-INFO: 0 bytes in use |
■便利な小物たち
では開発環境の準備ができた所で、まずGLibの便利な小物たちを紹介します。
| |
型定義
最初に触れておきたい型として、GLibは、プログラムの可読性を高めるために、 プログラムでは欠かせない真(TRUE)/偽(FALSE)の値に対してgbooleanや、voidポインターに対して gpointerを提供します。
またGLibは、移植性が考慮され一貫性がある(ときにはタイプ入力を簡単化する)型が定義されています。符号付きの場合、 8,16,32および64ビットの整数の型に対して、gint8, gint16,gint32,gint64を使用することができます。符号無しの整数の場合、 guint8, guint16,guint32,guint64のようになります。64ビットに関しては、プラットフォームによってサポートされていない場合あるので、 プログラム中で以下のようなチェックをすることをお勧めします。
64ビットサポートチェック
#include <glib.h>
...
#ifdef G_HAVE_GINT64
g_print("You machine has 64 bits support.\n");
#endif
... |
GLibは、これら基本型に対して、上限値、特定の型の変換を行うマクロやバイトオーダーに関するマクロなどを提供します。今回は紹介しませんが、 非常に強力なものもありますので、リファレンスを参考にしてみてください。
ストリング処理のたぐい
GLibには、いくつかストリング処理に関する便利な関数があります。C言語(ライブラリーも含めて)は、 お世辞にも文字列処理は得意とは言えないでしょう。
GLibのストリング関数は、それをちょっとだけ穴埋めしてくれるでしょう。文字列を繋げたり、大文字/小文字変換を行ったり、 perlの関数やshellコマンドで見掛けられるような便利な関数が実装されています。ここでは、その中のいくつかユニークなものを紹介します。
まずは、文字列の前後にある空白を削除する関数 g_strchug(), g_strchomp()です(perlに似たような関数があります)。 ファイルから一行ずつバッファを読み込んで、不必要な前後の空白をとる場合に便利です。この関数は、 文字列の中身を書き換えてしまう点に注意して使用してください。
GLib関数 その1
[関数宣言]
gchar* g_strchug (gchar *string);
gchar* g_strchomp (gchar *string);
#define g_strstrip ( string )
[使用例]
#include <glib.h>
static void gets_from_stdin(void)
{
gchar buf[1024];
while( fgets(buf,1024, stdin)!=NULL) {
g_strstrip(buf);
g_print("'%s'\n", buf);
}
} |
つぎは、文字列の中のトークンを取り出す関数とトークンを結合する関数です(こちらもperlではおなじみの関数です)。トークン処理に関しては、 標準のCライブラリーには、strtok()が用意されていますがスレッドセーフでないなどの理由であまりお勧めできないのが現状です。
以下に、これらの関数の使用例を紹介します。この例は、スペースでトークンセパレートした後に、アンダースコア(_)で、 トークンを結合させる例です。若干注意が必要な点は(全般に言えますが)、 関数の戻り値が新たにアロケートされたものかいなかを正しく把握しながらコーディングしないと行けない点で、 メモリーの解放を正しく行う必要があるところです。特に、g_strsplit()などは、NULLターミネートのポインター配列を当てにします(図1のイメージ)。

GLib関数 その2
[関数宣言]
gchar** g_strsplit (const gchar *string,
const gchar *delimiter,
gint max_tokens);
void g_strfreev (gchar **str_array);
gchar* g_strjoinv (const gchar *separator,
gchar **str_array);
[使用例]
#include <glib.h>
static void split_join(void)
{
gchar** tokens;
gchar* str = "192.168.0.1 router.localdomain router";
gchar* joined;
tokens = g_strsplit( str, " ", 3 );
joined = g_strjoinv( "_", tokens);
g_print("'%s'\n", joined);
g_strfreev( tokens );
g_free( joined);
} |
g_strdup_printf()について紹介します。この関数は、標準Cライブラリーのsprintf()に動作は似ていますが、 新たに確保したメモリーエリアに結果をいれます。これによって不用意なバッファーオーバーフローを妨げることができます。
ちょっと強引ですが、エラー番号、シグナル番号に対する(定数)文字列を返す関数 g_strerror(), g_strsignal()と組み合わせた例を以下に示します。
GLib関数 その3
[関数宣言]
gchar* g_strdup_printf (const gchar *format,...);
gchar* g_strerror (gint errnum);
gchar* g_strsignal (gint signum);
[使用例]
#include <glib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
static void sigh(int signum)
{
g_print("%s\n",g_strsignal(signum));
}
static void error_signal(void)
{
int fd;
signal( 2, sigh );
fd = open("non_exist_file", O_RDONLY);
if( fd==-1 ) {
gchar* str;
str = g_strdup_printf("Error: %s\n", g_strerror(errno));
g_print("%s\n",str);
g_free(str);
}
sleep(1000);
} |
その他、便利なものたち
一種の文字列操作ですが、shellでおなじみのコマンド basenameとdirname と同様の動きをするその名も g_basename(), g_dirname()です。原理は簡単ですが、 毎回実装するのは手間なので、こうやって最初からあると便利に感じます。ちなみにこの関数は、WIN32環境でGLibを使用した場合も正しく動作するはずです。
前述したように、g_dirname()のみメモリーを確保するのでメモリーの解放を忘れないようにしなければなりません。 なぜg_dirname()だけメモリー解放が必要かは、図2を見ると分かることでしょう。また以下の使用例では、 メモリー配置を分かりやすくポインターの値も表示するようなっています。

GLib関数 その4
[関数宣言]
gchar* g_basename (const gchar *file_name);
gchar* g_dirname (const gchar *file_name);
[使用例]
static void split_path(gchar* path)
{
gchar* dirname;
gchar* basename;
dirname = g_dirname(path);
basename = g_basename(path);
g_print("%s(%p) -> %s(%p) %s(%p)\n",path, path, dirname, dirname,
basename, basename );
g_free( dirname );
} |
便利なストップウォッチ機能を提供してくれる関数群を紹介します。使用例を見て頂ければ、 一目瞭然だと思います。new, destroyなんていかにもオブジェクト指向的で、C++言語で記述するとすっきりしそうな感じのするがしますね。 ちなみに、sleepは3秒ですが、実際に動作させてみると標準出力などの関係で若干の誤差がでると思います。
GLib関数 その4
[関数宣言]
GTimer* g_timer_new (void);
void g_timer_start (GTimer *timer);
void g_timer_stop (GTimer *timer);
gdouble g_timer_elapsed (GTimer *timer,
gulong *microseconds);
void g_timer_reset (GTimer *timer);
void g_timer_destroy (GTimer *timer);
[使用例]
#include <glib.h>
static void timer(void)
{
GTimer* gt;
gt = g_timer_new();
g_timer_start(gt);
sleep(3);
g_print("%f\n", g_timer_elapsed(gt,NULL));
g_timer_destroy(gt);
} |
|