2011/12/13

node.js の実装を司る libuv を使ってC言語のアプリケーションを書いていると、コールバック関数が増えていく。まぁこれは致し方ない事なんだけど、これで面倒なのがループを止める方法。libuv は作ったハンドルが全て閉じられると勝手に uv_run が終了する仕組みになっているんだけど、例えば処理内に2つハンドルがあって、かつ閉じようとする箇所が別のコールバック処理内にあるとハンドルをグローバル変数にするか uv_timer_t timer1;
uv_timer_t timer2;

void exit_cb(void* data) {
  uv_close(timer1);
  uv_close(timer2);
}
構造体に格納して引きずり回さなければならない。
typedef struct {
  uv_timer_t timer1;
  uv_timer_t timer2;
} THE_TIMERS;

void exit_cb(void* data) {
  THE_TIMERS* timers = (THE_TIMERS*) data;
  uv_close(timers->timer1);
  uv_close(timers->timer2);
}
どんな時に起きるかというと、libuv を GUI アプリと併用する場合。libuv はメインループを uv_run() で実行する為、GUI のメインループを回す為にアイドルを作る必要がある。

アイドルを作るだってぇ!!!

と思ったあなた、ぜひクリスマスは libuv とお過ごし下さい。
例えば、GTK だと以下の様になります。
#include <uv/uv.h>
#include <gtk/gtk.h>

void
idle_cb(uv_idle_t* idle, int status) {
  gtk_main_iteration_do(FALSE);
}

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

  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_show_all(window);

  uv_idle_t idle;
  uv_idle_init(uv_default_loop(), &idle);
  uv_idle_start(&idle, idle_cb);

  uv_run(uv_default_loop());
}
この uv_run() のループを止めるには idle を uv_close() する必要があるのですが、ループ自身もハンドルで、かつ uv_run() はそのハンドルのリファレンスがある内しか回らないという仕様なので、こう書く事が出来ます。
#include <uv/uv.h>
#include <gtk/gtk.h>

void
timer_cb(uv_timer_t* timer, int status) {
  GtkWidget* label = (GtkWidget*) timer->data;
  char buf[64];
  time_t t;
  time(&t);
  strftime(buf, sizeof(buf), "%c", localtime(&t));
  gtk_label_set_text(GTK_LABEL(label), buf);
}

void
idle_cb(uv_idle_t* idle, int status) {
  gtk_main_iteration_do(FALSE);
}

void
destroy_cb(GtkWidget* w, gpointer data) {
  uv_loop_t* loop = (uv_loop_t*) data;
  uv_unref(loop); // stop idle
  uv_unref(loop); // stop timer
}

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

  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "clock");

  GtkWidget* vbox = gtk_vbox_new(FALSE, 1);
  GtkWidget* label = gtk_label_new("");
  gtk_container_add(GTK_CONTAINER(vbox), label);
  gtk_container_add(GTK_CONTAINER(window), vbox);
  gtk_window_set_default_size(GTK_WINDOW(window), 30020);
  gtk_widget_show_all(window);

  uv_timer_t timer;
  uv_timer_init(uv_default_loop(), &timer);
  timer.data = (void*) label;
  uv_timer_start(&timer, timer_cb, 10001000);

  uv_idle_t idle;
  uv_idle_init(uv_default_loop(), &idle);
  uv_idle_start(&idle, idle_cb);

  g_signal_connect(G_OBJECT(window), "destroy",
          G_CALLBACK(destroy_cb), uv_default_loop());

  uv_run(uv_default_loop());
}
もちろん、uv_close() する回数が少なければループは止まらないので、あまりちゃんとした方法では無い気もしますが。

GTKの様に iteration 動作出来るツールキットならばいいのですが、iteration 動作出来ないライブラリと併用する場合、libuv 側が回される事になる訳です。つまり libuv に1回だけループを回す手続きが必要な訳で、探しても無かったのでさっきパッチ書いて投げた。取り込まれるかどうかは知らない。
ちなみに、上記の例は libuv と gtk で作る時計なんだけど、これを perl で書くとこうなる
use strict;
use warnings;

use Encode;
use Encode::Locale;
use UV;
use Glib;
use Gtk2 -init;
use Time::Piece;

my $w = Gtk2::Window->new('toplevel');
$w->set_title("timer");
my $v = Gtk2::VBox->new(01);
my $l = Gtk2::Label->new();
$v->add($l);
$w->add($v);
$w->set_default_size(30020);
$w->show_all;

my $t = UV::timer_init();
UV::timer_start($t10001000sub {
  $l->set_label(decode(locale => localtime->strftime));
});

my $i = UV::idle_init();
UV::idle_start($isub {
  Gtk2->main_iteration_do(0);
});

$w->signal_connect(destroy => sub {
  UV::close($t);
  UV::close($i);
});

UV::run;
UV は typester さんが書いた奴。
typester/p5-UV - GitHub
https://github.com/typester/p5-UV
あと、最近書いた go言語の libuv バインディング go-uv で書くとこうなる
package main

import (
    "fmt"
    "github.com/mattn/go-uv"
    "github.com/mattn/go-gtk/gtk"
    "time"
)

func main() {
    gtk.Init(nil)
    window := gtk.Window(gtk.GTK_WINDOW_TOPLEVEL)
    window.SetTitle("Clock")
    vbox := gtk.VBox(false1)
    label := gtk.Label("")
    vbox.Add(label)
    window.Add(vbox)
    window.SetDefaultSize(30020)
    window.ShowAll()

    timer, _ := uv.TimerInit(nil)
    timer.Start(func(h *uv.Handle, status int10001000) {
        label.SetLabel(fmt.Sprintf("%v", time.Now()))
    })

    idle, _ := uv.IdleInit(nil)
    idle.Start(func(h *uv.Handle, status int) {
        gtk.MainIterationDo(false)
    })

    window.Connect("destroy"func() {
        timer.Close(nil)
        idle.Close(nil)
    })

    uv.DefaultLoop().Run()
}
mattn/go-uv - GitHub

Go binding for libuv

https://github.com/mattn/go-uv
Posted at 14:12 | WriteBacks () | Edit
Edit this entry...

wikieditish message: Ready to edit this entry.






















A quick preview will be rendered here when you click "Preview" button.