RubyInline
RubyInlineを使うとCを埋め込めるらしいので試してみた。
$:.unshift File.dirname(__FILE__) require 'rubygems' require 'inline' VERSION = '0.0.1' class KQueue inline do |builder| builder.include "<sys/event.h>" builder.include "<sys/time.h>" builder.include "<unistd.h>" builder.include "<errno.h>" =begin builder.map_c_const({ 'EVFILT_READ' => 'int', 'EVFILT_WRITE' => 'int', 'EVFILT_AIO' => 'int', 'EVFILT_VNODE' => 'int', 'EVFILT_PROC' => 'int', 'EVFILT_SIGNAL' => 'int', 'EVFILT_TIMER' => 'int', 'EV_ADD' => 'int', 'EV_ENABLE' => 'int', 'EV_DISABLE' => 'int', 'EV_DELETE' => 'int', 'EV_ONESHOT' => 'int', 'EV_CLEAR' => 'int', 'EV_EOF' => 'int', 'NOTE_LOWAT' => 'int', 'NOTE_DELETE' => 'int', 'NOTE_WRITE' => 'int', 'NOTE_EXTEND' => 'int', 'NOTE_ATTRIB' => 'int', 'NOTE_LINK' => 'int', 'NOTE_RENAME' => 'int', 'NOTE_REVOKE' => 'int', 'NOTE_EXIT' => 'int', 'NOTE_FORK' => 'int', 'NOTE_EXEC' => 'int', 'NOTE_PCTRLMASK' => 'int', 'NOTE_PDATAMASK' => 'int', 'NOTE_TRACK' => 'int', 'NOTE_CHILD' => 'int', 'NOTE_TRACKERR' => 'int', }) =end builder.prefix <<-"END" #define Mem_New(type, n) \ n == 0 ? NULL : \ (type *)malloc((n) * sizeof(type)) static VALUE rb_cKQueue, rb_cKEvent; struct keventdata { struct kevent e; }; static int get_kqueue_fd(VALUE obj) { VALUE fd; fd = rb_iv_get(obj, "@fileno"); return NUM2INT(fd); }; static void free_kqueue(VALUE obj) { int fd; if (obj) { fd = get_kqueue_fd(obj); close(fd); xfree(obj); } }; static void free_kevent(struct keventdata *kev) { if (kev) { xfree(kev); } }; static VALUE fkqueue_alloc(VALUE klass) { return Data_Wrap_Struct(klass, 0, free_kqueue, 0); }; static VALUE fkevent_alloc(VALUE klass) { return Data_Wrap_Struct(klass, 0, free_kevent, 0); }; static void set_kevent_flags(VALUE obj, struct kevent kev) { rb_iv_set(obj, "@ident", INT2NUM(kev.ident)); rb_iv_set(obj, "@filter", INT2NUM(kev.filter)); rb_iv_set(obj, "@flags", INT2NUM(kev.flags)); rb_iv_set(obj, "@fflags", INT2NUM(kev.fflags)); rb_iv_set(obj, "@data", INT2NUM(kev.data)); }; static VALUE fkqueue_initialize(VALUE obj) { int kq_fd = kqueue(); if(kq_fd == -1){ rb_raise(rb_eStandardError, "kqueue initilization failed"); goto error; } rb_iv_set(obj, "@fileno", INT2NUM(kq_fd)); return obj; error: return Qnil; }; static VALUE fkevent_initialize(int argc, VALUE *argv, VALUE obj) { struct keventdata *kev; struct kevent ev; VALUE vident,vfilter,vflags,vfflags,vdata,vudata; int ident; int filter = EVFILT_READ; int flags = EV_ADD; int fflags = 0; int data = 0; void *udata = NULL; rb_scan_args(argc, argv, "15", &vident, &vfilter, &vflags, &vfflags, &vdata, &vudata ); ident = NUM2INT(vident); if(!NIL_P(vfilter)) filter = NUM2INT(vfilter); if(!NIL_P(vflags)) flags = NUM2INT(vflags); if(!NIL_P(vfflags)) fflags = NUM2INT(vfflags); if(!NIL_P(vdata)) data = NUM2INT(vdata); EV_SET(&ev, ident, filter, flags, fflags, data, udata); kev = ALLOC(struct keventdata); DATA_PTR(obj) = kev; kev->e = ev; set_kevent_flags(obj, ev); return obj; }; static struct kevent get_kevent(VALUE obj) { struct keventdata *data; Data_Get_Struct(obj, struct keventdata, data); return data->e; }; static VALUE fkqueue_control(int argc, VALUE *argv, VALUE obj) { int i, n; int kq = get_kqueue_fd(obj); int nchanges = 0; int nevents = 0; struct kevent *chl = NULL; struct kevent *evl = NULL; struct timespec timeoutspec; struct timespec *ptimeoutspec; VALUE changelist, maxevents, vtimeout, result; rb_scan_args(argc, argv, "21", &changelist, &maxevents, &vtimeout ); if(!NIL_P(vtimeout)){ Check_Type(vtimeout, T_FIXNUM); double timeout; long seconds; timeout = NUM2DBL(vtimeout); seconds = (long)timeout; timeout = timeout - (double)seconds; timeoutspec.tv_sec = seconds; timeoutspec.tv_nsec = (long)(timeout * 1E9); ptimeoutspec = &timeoutspec; }else{ ptimeoutspec = NULL; } if(!NIL_P(changelist)){ Check_Type(changelist, T_ARRAY); nchanges = RARRAY_LEN(changelist); chl = Mem_New(struct kevent, RARRAY_LEN(changelist)); for(i = 0; i < RARRAY_LEN(changelist); i++){ chl[i] = get_kevent(RARRAY_PTR(changelist)[i]); } } nevents = NUM2INT(maxevents); evl = Mem_New(struct kevent, nevents); n = kevent(kq, chl, nchanges, evl, nevents, ptimeoutspec); if(n == -1){ rb_raise(rb_eStandardError, "kevent Bad Descriptor"); goto error; } result = rb_ary_new(); if(result == -1){ goto error; } for(i = 0; i < n; i++){ VALUE kevent_object; struct keventdata *kev; kevent_object = fkevent_alloc(rb_cKEvent); kev = ALLOC(struct keventdata); DATA_PTR(kevent_object) = kev; kev->e = evl[i]; set_kevent_flags(kevent_object, kev->e); rb_ary_push(result, kevent_object); } free(chl); free(evl); return result; error: free(chl); free(evl); return Qnil; }; static VALUE fkevent_to_s(VALUE obj) { int ident, filter, flags, fflags, data; ident = rb_iv_get(obj, "@ident"); filter = rb_iv_get(obj, "@filter"); flags = rb_iv_get(obj, "@flags"); fflags = rb_iv_get(obj, "@fflags"); data = rb_iv_get(obj, "@data"); return rb_sprintf("<KEvent ident=%d filter=%d flags=%d fflags=%d data=%d>", \ ident, filter, flags, fflags, data); }; END builder.add_to_init <<-"END" rb_cKEvent = rb_define_class("KEvent", rb_cObject); rb_define_alloc_func(rb_cKEvent, fkevent_alloc); rb_define_method(rb_cKEvent, "initialize", fkevent_initialize, -1); rb_define_method(rb_cKEvent, "to_s", fkevent_to_s, 0); rb_define_attr(rb_cKEvent, "ident", 1, 1); rb_define_attr(rb_cKEvent, "filter", 1, 1); rb_define_attr(rb_cKEvent, "flags", 1, 1); rb_define_attr(rb_cKEvent, "fflags", 1, 1); rb_define_attr(rb_cKEvent, "data", 1, 1); rb_define_attr(rb_cKEvent, "udata", 1, 1); rb_cKQueue = rb_define_class("KQueue", rb_cObject); rb_define_alloc_func(rb_cKQueue, fkqueue_alloc); rb_define_method(rb_cKQueue, "initialize", fkqueue_initialize, 0); rb_define_method(rb_cKQueue, "control", fkqueue_control, -1); rb_define_attr(rb_cKQueue, "fileno", 1, 0); rb_define_global_const("EVFILT_READ", INT2FIX(EVFILT_READ)); rb_define_global_const("EVFILT_WRITE", INT2FIX(EVFILT_WRITE)); rb_define_global_const("EVFILT_AIO", INT2FIX(EVFILT_AIO)); rb_define_global_const("EVFILT_VNODE", INT2FIX(EVFILT_VNODE)); rb_define_global_const("EVFILT_PROC", INT2FIX(EVFILT_PROC)); rb_define_global_const("EVFILT_SIGNAL", INT2FIX(EVFILT_SIGNAL)); rb_define_global_const("EVFILT_TIMER", INT2FIX(EVFILT_TIMER)); rb_define_global_const("EV_ADD", INT2FIX(EV_ADD)); rb_define_global_const("EV_ENABLE", INT2FIX(EV_ENABLE)); rb_define_global_const("EV_DISABLE", INT2FIX(EV_DISABLE)); rb_define_global_const("EV_DELETE", INT2FIX(EV_DELETE)); rb_define_global_const("EV_ONESHOT", INT2FIX(EV_ONESHOT)); rb_define_global_const("EV_CLEAR", INT2FIX(EV_CLEAR)); rb_define_global_const("EV_EOF", INT2FIX(EV_EOF)); rb_define_global_const("NOTE_LOWAT", INT2FIX(NOTE_LOWAT)); rb_define_global_const("NOTE_DELETE", INT2FIX(NOTE_DELETE)); rb_define_global_const("NOTE_WRITE", INT2FIX(NOTE_WRITE)); rb_define_global_const("NOTE_EXTEND", INT2FIX(NOTE_EXTEND)); rb_define_global_const("NOTE_ATTRIB", INT2FIX(NOTE_ATTRIB)); rb_define_global_const("NOTE_LINK", INT2FIX(NOTE_LINK)); rb_define_global_const("NOTE_RENAME", INT2FIX(NOTE_RENAME)); rb_define_global_const("NOTE_REVOKE", INT2FIX(NOTE_REVOKE)); rb_define_global_const("NOTE_EXIT", INT2FIX(NOTE_EXIT)); rb_define_global_const("NOTE_FORK", INT2FIX(NOTE_FORK)); rb_define_global_const("NOTE_EXEC", INT2FIX(NOTE_EXEC)); rb_define_global_const("NOTE_PCTRLMASK", INT2FIX(NOTE_PCTRLMASK)); rb_define_global_const("NOTE_PDATAMASK", INT2FIX(NOTE_PDATAMASK)); rb_define_global_const("NOTE_TRACK", INT2FIX(NOTE_TRACK)); rb_define_global_const("NOTE_CHILD", INT2FIX(NOTE_CHILD)); rb_define_global_const("NOTE_TRACKERR", INT2FIX(NOTE_TRACKERR)); END end end
初心者なのでkqueueのラッパーから書いてみることにした。
どんどん書いていくうちに普通の拡張コードになってた。。。。
RubyInlineじゃなくてもいいけどビルドが一発なのでそのままにしてる。。。
基本的にpython2.6のkqueueと同じAPIにした。よけいな事は一切しない。
kq = KQueue.new f = File.open("/tmp/test") kevent = KEvent.new(f.to_i, EVFILT_VNODE, EV_ADD|EV_ONESHOT|EV_ENABLE, NOTE_WRITE|NOTE_DELETE) kq.control([kevent], 0) ev = kq.control(nil, 1) ev.each{|item| p item }
ぶっちゃけrubyの拡張の方がpythonに比べて奇麗に書けるし、楽。
(参照カウントの上げ下げ、実行権の許可とかいらないし)
ピンチになったらCで!っていうのが比較的楽なのでrubyに乗り換えてもいいなって気がしてくるよね。