L'Isola di Niente
L'Isola di Niente » C and GLib Tips » GLib Tips

はじめに

下記コマンドの src.c 部をファイル名にしてビルドしてください
gcc src.c `pkg-config --cflags --libs glib-2.0`
ココでは実用を考慮して扱うデータは文字列で解説しています
int の解説では実用性も応用性も皆無なうえに間違えて覚える人が多いので
(昔の筆者がそうだった)

基本

メモリ領域確保と破棄
glib 自身、又は GTK+ アプリのソースを見ると文字列や構造体は全部関数の戻り値で作成
そのインスタンスは呼び出し側で破棄、という超初心者泣かせで構築されているのが解る
(初心者本ではローカル変数のポインタを関数の引数に入れて値を取得がデフォ)

多言語へのバインド、及びバッファオーバーラン対策の為と思われます
このページではソレになるべく合わせて書いていきます
#include <glib.h>

gchar*
get_string (void) {
    return g_strdup("自作関数もなるべくヒープ上に\n");
}

int
main (int argc, char* argv[]) {

    gchar* s;
    gunichar u[] = {L'エ', L'ロ', L'本'};
    GPtrArray* array;

    /* 領域確保に g_malloc はあまり使われない */
    s = g_strdup("文字列はこんな感じで\n");
    g_printf(s);
    g_free(s);

    /* ゼロ詰めで malloc することもできる */
    /* 下記のように末尾に \0 が付与されない場合に便利 */
    s = g_malloc0(4);
    g_unichar_to_utf8(u[2], s);
    g_printf("%s\n", s);
    g_free(s);

    /* 関数の戻り値は規定の方法で破棄する */
    array = g_ptr_array_new();
    g_ptr_array_add(array, g_strdup("GLib の構造体は"));
    g_printf("%sこう使う\n", g_ptr_array_index(array, 0));
    g_ptr_array_free(array, TRUE);

    /* 自作関数も同様にする */
    s = get_string();
    g_printf(s);
    g_free(s);

    return 0;
}

標準入出力
標準出力 g_printf は標準ライブラリ関数 printf とまったく同等
標準入力関数(fgets, scanf 相当)は用意されていない
下記のような関数を作っておくと便利(Vala のパクり)
#include <glib.h>
#include <stdio.h>

gchar*
iostream_read_line (FILE* p) {

	GString* gstr;
	gchar* result;
    gint n;

	gstr = g_string_new("");
	while (TRUE) {
	    n = fgetc(p);
        if (n == ((gint)'\n') || n == EOF) {
			break;
		}
	    g_string_append_c(gstr, (gchar)n);
    }
    result = g_strdup(gstr->str);
    g_string_free(gstr, TRUE);
    return result;
}

int
main (int argc, char* argv[]) {

    gchar* line;

    g_printf("Hi!\nPlease enter your name: ");
    line = iostream_read_line(stdin);
    g_printf("Welcome, %s!\n", line);
    g_free(line);

    return 0;
}

文字列
文字列については標準 C 言語と同様です
多少便利な関数が追加されているのでいくつか書き出し
#include <glib.h>
#include <stdio.h>

/* 上記の入力関数 */
gchar*
iostream_read_line (FILE* p) {
 
    GString* gstr;
    gchar* result;
    gint n;
 
    gstr = g_string_new("");
    while (TRUE) {
        n = fgetc(p);
        if (n == ((gint)'\n') || n == EOF) {
            break;
        }
        g_string_append_c(gstr, (gchar)n);
    }
    result = g_strdup(gstr->str);
    g_string_free(gstr, TRUE);
    return result;
}

/* gchar** の要素数を戻す関数 */
gint
get_array_length (gchar** array) {
	gint length = 0;
	if (array)
	    while (array[length])
		    length++;
	return length;
}

int
main (int argc, char* argv[]) {

    GString* sb;
    gchar*   s;
    gchar**  env_array;
    gint     count;

    /* g_print("日本語\n"); では文字化けする */
    g_printf("日本語\n");

    /* 漢字も一文字として文字列の長さを得る */
    s = g_strdup("たのしい Linux");
    g_printf ("%s は %ld 文字です\n", s, g_utf8_strlen(s, -1));
    g_free(s);

    /* 書式付きをメモリ上に、g_sprintf も一応使える */
    s = g_strdup_printf("数値(%d 等)を文字列に\n", 256);
    g_printf(s);
    g_free(s);

    /* GString の使い方 */
    sb = g_string_new ("");
    for (;;) {
        g_printf("何か入力してください(空打ちで終了): ");
        s = iostream_read_line(stdin);
        if (g_strcmp0(s, "") == 0) break;
        g_string_append (sb, g_strdup(s));
    }
    g_printf("結果 : %s\n", sb->str);
	g_string_free(sb, TRUE); /* 2番目引数を TRUE で内容物も破棄 */

    /* 単純な文字列結合ならこんな手段がある  */
    s = g_strdup("単純な" "結合\n");
    g_printf(s);
    g_free(s);

    /* gchar** を Python の '\n'.join(list) みたいにできる */
    env_array = g_get_environ();
    g_printf("%d 個を合体します\n", get_array_length(env_array));
    s = g_strjoinv("\n", env_array);
    g_printf(s);
    g_free(s);
    g_strfreev(env_array); /* gchar** 内容物も一括破棄 */

    return 0;
}

配列、動的配列
配列自体は C 言語標準のものを利用します
動的配列は GPtrArray という便利な構造体があります
#include <glib.h>

int
main (int argc, char* argv[]) {
 
    GPtrArray* ptr_array;
    gchar* array[4] = {"YAMAHA", "HONDA", "SUZUKI", "KAWASAKI"};
    gchar*  str;
    glong   count, i;

    /*
     * 静的配列(ローカル変数の配列)
     * この場合の要素数は下記のように得る
    **/
    count = sizeof(array) / sizeof(array[0]);
    i = 0;
    for (i; i<count; i++) {
        g_printf("%s\n", array[i]);
    }

    /*
     * GPtrArray の例
     * g_strdup で新規領域を作成し add していく
    **/
    ptr_array = g_ptr_array_new();
    i = count-1;
    for (i; i>=0; i--) {
        str = g_strdup(array[i]);
        g_ptr_array_add(ptr_array, str);
    }
    /* 確認、[0] のような添字は使えないので下記関数を使う */
    i = 0;
    for (i; i<ptr_array->len; i++) {
        g_printf("逆順 : %s\n", g_ptr_array_index(ptr_array, i));
    }
    /* 破棄、2番目引数を TRUE で内容物も破棄できる */
    g_ptr_array_free(ptr_array, TRUE);

    return 0;
}
GLib は gchar** を戻す関数が結構あります
ループさせたい場合は以下のようにするとよい
#include <glib.h>

gint 
get_array_length (gchar** array) {
	gint length = 0;
	if (array)
	    while (array[length])
		    length++;
	return length;
}

gchar**
create_array(void) {
    gchar** arg;
    arg = (gchar**)g_malloc0(sizeof(gchar*) * 4);
    arg[0] = g_strdup("YAMAHA");
    arg[1] = g_strdup("HONDA");
    arg[2] = g_strdup("KAWASAKI");
    arg[3] = g_strdup("SUZUKI");
    return arg;
}

int
main (int argc, char* argv[]) {

    gchar** array;
    gint i, count;

    array = create_array();
    count = get_array_length(array);
    g_printf("count=%d\n", count);
    for (i=0; i<count; i++) {
        g_printf("%s\n", array[i]);
    }
    g_strfreev(array);

    return 0;
}

現在時刻
GDateTime を使います
#include <glib.h>

int
main (int argc, char* argv[]) {

    GDateTime* date;
    gchar* s;

    date = g_date_time_new_now_local();
    s = g_date_time_format(date, "%Y.%m.%d %H:%M:%S\n");
    g_printf(s);
    g_free(s);
    g_date_time_unref(date);

    return 0;
}

環境変数
関数に const の有り無しがあるのでリファレンスマニュアルで確認
#include <glib.h>
 
int
main (int argc, char* argv[]) {

    const gchar* cc;
    gchar* c;

    /* 環境変数 */
    cc = g_get_home_dir();
    g_printf("ホームディレクトリは %s です\n", cc);
    c = g_get_current_dir();
    g_printf("現在のカレントディレクトリは %s です\n", c);
    g_free(c);

    /* $USER のような手段もある */
    cc = g_getenv("USER");
    g_printf("ユーザー名は %s です\n", cc);

    /* 環境変数を追加 */
    g_setenv("YAMAHA", "Jog", FALSE);
    cc = g_getenv("YAMAHA");
    g_printf("ヤマハで一番有名なバイクは %s です\n", cc);

    /* ユーザーディレクトリ */
    cc = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
    g_printf("デスクトップは %s です\n", cc);
 
    return 0;
}

コマンドの実行
アプリケーション内でコマンドを実行したい場合に
standard_error 指定は GError があるので不要のような...
#include <glib.h>
 
#define OK_CMD "echo 水色のセーラー服"
#define ERR_CMD "echooo グレーのセーラー服"
  
int
main (int argc, char* argv[]) {
 
    GError*  error = NULL;
    gboolean result;
    gchar*   output = NULL;
    gint     status;
 
    /* ランチャ等の出力結果が不要の場合 */
    g_spawn_command_line_async("nautilus .", &error);
 
    /* 成功すると出力結果が output に入る */
    result = g_spawn_command_line_sync(OK_CMD, &output, NULL, &status, &error);
    if (!result) {
        g_printf("%s\n", error->message);
        g_error_free(error);
        g_free(output);
        return status;
    }
    g_printf(output);
    g_free(output);
 
    /* 失敗すると GError が作成される */
    result = g_spawn_command_line_sync(ERR_CMD, &output, NULL, &status, &error);
    if (!result) {
        g_printf("%s\n", error->message);
        g_error_free(error);
        g_free(output);
        return status;
    }
    g_printf(output);
    g_free(output);
  
    return 0;
}

ファイル名変換
フルパスからパス名のみを抜き出したり URI に変換など
#include <glib.h>

#define FULLPATH "/home/imouto/小野 妹子/ぱん チラ.jpeg"
 
int
main (int argc, char* argv[]) {

    gchar* s = NULL;
    gchar* t = NULL;

    /* 半角空白や日本語も問題無し */
    s = g_path_get_basename(FULLPATH);
    g_printf("%s\n", s);
    g_free(s);
    s = g_path_get_dirname(FULLPATH);
    g_printf("%s\n", s);
    g_free(s);
    /* URI 変換、ローカルなら hostname は不要 */
    s = g_filename_to_uri(FULLPATH, NULL, NULL);
    g_printf("%s\n", s);
    /* キチンとフルパスに戻るか確認 */
    t = g_filename_from_uri(s, NULL, NULL);
    g_printf("%s\n", t);
    g_free(t);
    g_free(s);
 
    return 0;
}

ファイルのチェック
test コマンド
#include <glib.h>

/* 同一ディレクトリに実在するファイル名に書き換えてください */
#define FILENAME "test.c"
 
int
main (int argc, char* argv[]) {

    if (g_file_test(FILENAME, G_FILE_TEST_EXISTS)) {
        if (g_file_test(FILENAME, G_FILE_TEST_IS_SYMLINK))
            g_printf("シンボリックリンクです\n");
        else if (g_file_test(FILENAME, G_FILE_TEST_IS_DIR))
            g_printf("ディレクトリです\n");
        else if (g_file_test(FILENAME, G_FILE_TEST_IS_REGULAR))
            g_printf("普通のファイルです\n");
        else
            g_printf("何コレ...\n");
    } else {
        g_printf("File Not Found.\n");
    }
 
    return 0;
}

ファイルの読み書き
細かい読み書きは Gio を使ったほうが便利です
g_fopen() という関数がありますが無視したほうが無難なようです
#include <glib.h>

#define STRINGS "これが書き込まれます\n存在する場合は上書きされます"
 
int
main (int argc, char* argv[]) {

    GError*  error = NULL;
    gboolean result;
    gchar*   s;

    /* 書き込み */
    result = g_file_set_contents("output.txt", STRINGS, -1, &error);
    if (!result) {
        g_printf("%s\n", error->message);
        gint retval = error->code;
        g_error_free(error);
        g_free(s);
        return retval;
    }
    /* 読み込み */
    result = g_file_get_contents("output.txt", &s, NULL, &error);
    if (!result) {
        g_printf("%s\n", error->message);
        gint retval = error->code;
        g_error_free(error);
        g_free(s);
        return retval;
    }
    g_printf("%s\n", s);
    g_free(s);

    return 0;
}

コードポイントの取得
local が UTF-8 以外の御仁は変換処理を入れてください
#include <glib.h>

#define HOGE "ほげ"

int main (int argc, char* argv[]) {

    gint      i;
    glong     length;
    gunichar* ucharp;

    length = g_utf8_strlen(HOGE, -1);
    ucharp = g_utf8_to_ucs4_fast(HOGE, -1, NULL); 
    for (i=0; i<length; i++) {
        g_printf("[U+%04X] ", ucharp[i]);
    }
    g_printf("\n");
    g_free(ucharp);
    
    return 0;
}

文字コード(cp932, utf8)変換
iconv で変換できる文字コードは全部同様にできます
#include <glib.h>

int main (int argc, char* argv[]) {

    GError*  error = NULL;
    gboolean result;
    gchar*   cp932;
    gchar*   utf8;
 
    /* Windows で普通に作成されたテキストファイルの読み込み */
    result = g_file_get_contents("cp932.txt", &cp932, NULL, &error);
    if (!result) {
        g_printf("%s\n", error->message);
        gint retval = error->code;
        g_error_free(error);
        g_free(cp932);
        return retval;
    }
    /* UTF-8 に変換 */
    utf8 = g_convert(cp932, -1, "utf-8", "cp932", NULL, NULL, &error);
    if (utf8 == NULL) {
        g_printf("%s\n", error->message);
        gint retval = error->code;
        g_error_free(error);
        g_free(cp932);
        g_free(utf8);
        return retval;
    }
    g_printf("%s\n", utf8);
    g_free(cp932);
    g_free(utf8);
    
    return 0;
}

タイマーを使う
g_timeout_add に渡すハンドラは TRUE を戻すと継続される
#include <glib.h>

static int count = 0;

gboolean
timer_cb(gpointer mainloop) {
    if (count == 9) {
        /* メインループ停止 */
        g_main_loop_quit(mainloop);
        /* FALSE を戻すとタイマー停止 */
        return FALSE;
    }
    count++;
    g_printf("count %02d\n", count);
    return TRUE;
}

int
main (int argc, char* argv[]) {

    GMainLoop* mainloop;

    /* 二番目引数 TRUE なら即ループ開始 */
    mainloop = g_main_loop_new(NULL, FALSE);

    /* 1/1000 ミリ秒単位で指定、下記は一秒 */
    g_timeout_add(1000, timer_cb, mainloop);

    /* メインループ開始、停止するまでこの行で待機 */
    g_main_loop_run(mainloop);

    g_printf("done\n");
    g_main_loop_unref(mainloop);
    return 0;
}

コマンドラインオプション
GOptionContext は物凄く便利
#include <glib.h>
#include <stdio.h> /* stderr */

static gboolean _bool = 0;     /* 値を受ける必要が無いとき */
static gint     _num  = 0;     /* getopt と違い数値として受け取り */
static gchar*   _str  = NULL;  /* 文字列は破棄が必要、日本語はダメだった */

static GOptionEntry entries[] = {
    {"bool", 'b', 0, G_OPTION_ARG_NONE,   &_bool, "Show", NULL},
    {"num",  'n', 0, G_OPTION_ARG_INT,    &_num,  "Size", "<num>"},
    {"str",  's', 0, G_OPTION_ARG_STRING, &_str,  "Text", "<string>"},
    {NULL}
};

int
main (int argc, char *argv[]) {

    GError* error = NULL;
    GOptionContext* context;

    context = g_option_context_new ("After GApplication ?");
    g_option_context_add_main_entries (context, entries, NULL);
    if (!g_option_context_parse (context, &argc, &argv, &error)) {
        g_fprintf(stderr, "Error parsing options, try --help.\n");
        return 1;
    }
    /* 値を確認 */
    g_printf("boolean=%d, int=%d, string=%s\n", _bool, _num, _str);

    /* 後は GApplication で */

    g_free(_str);
    g_option_context_free(context);
    return 0;
}
/* output

$ ./a.out
boolean=0, int=0, string=(null)

$ ./a.out -b --num 675 -s Daytona
boolean=1, int=675, string=Daytona

$ ./a.out -h
Usage:
  a.out [OPTION...] After GApplication ?

Help Options:
  -h, --help             Show help options

Application Options:
  -b, --bool             Show
  -n, --num=<num>        Size
  -s, --str=<string>     Text

*/
Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.