diff options
Diffstat (limited to 'trunk/ext/dl')
29 files changed, 5007 insertions, 0 deletions
diff --git a/trunk/ext/dl/.cvsignore b/trunk/ext/dl/.cvsignore new file mode 100644 index 0000000000..6c0b25a1c5 --- /dev/null +++ b/trunk/ext/dl/.cvsignore @@ -0,0 +1,10 @@ +Makefile +mkmf.log +dlconfig.h +dlconfig.rb +callback.h +*.func +*.o +*~ +*.def +extconf.h diff --git a/trunk/ext/dl/cfunc.c b/trunk/ext/dl/cfunc.c new file mode 100644 index 0000000000..724c290f2f --- /dev/null +++ b/trunk/ext/dl/cfunc.c @@ -0,0 +1,516 @@ +/* -*- C -*- + * $Id$ + */ + +#include <ruby.h> +#include <errno.h> +#include "dl.h" + +VALUE rb_cDLCFunc; + +static ID id_last_error; + +static VALUE +rb_dl_get_last_error(VALUE self) +{ + return rb_thread_local_aref(rb_thread_current(), id_last_error); +} + +static VALUE +rb_dl_set_last_error(VALUE self, VALUE val) +{ + rb_thread_local_aset(rb_thread_current(), id_last_error, val); + return Qnil; +} + +#if defined(HAVE_WINDOWS_H) +#include <windows.h> +static ID id_win32_last_error; + +static VALUE +rb_dl_get_win32_last_error(VALUE self) +{ + return rb_thread_local_aref(rb_thread_current(), id_win32_last_error); +} + +static VALUE +rb_dl_set_win32_last_error(VALUE self, VALUE val) +{ + rb_thread_local_aset(rb_thread_current(), id_win32_last_error, val); + return Qnil; +} +#endif + + +void +dlcfunc_free(struct cfunc_data *data) +{ + if( data->name ){ + xfree(data->name); + } + xfree(data); +} + +VALUE +rb_dlcfunc_new(void (*func)(), int type, const char *name, ID calltype) +{ + VALUE val; + struct cfunc_data *data; + + rb_secure(4); + if( func ){ + val = Data_Make_Struct(rb_cDLCFunc, struct cfunc_data, 0, dlcfunc_free, data); + data->ptr = func; + data->name = name ? strdup(name) : NULL; + data->type = type; + data->calltype = calltype; + } + else{ + val = Qnil; + } + + return val; +} + +void * +rb_dlcfunc2ptr(VALUE val) +{ + struct cfunc_data *data; + void * func; + + if( rb_obj_is_kind_of(val, rb_cDLCFunc) ){ + Data_Get_Struct(val, struct cfunc_data, data); + func = data->ptr; + } + else if( val == Qnil ){ + func = NULL; + } + else{ + rb_raise(rb_eTypeError, "DL::CFunc was expected"); + } + + return func; +} + +VALUE +rb_dlcfunc_s_allocate(VALUE klass) +{ + VALUE obj; + struct cfunc_data *data; + + obj = Data_Make_Struct(klass, struct cfunc_data, 0, dlcfunc_free, data); + data->ptr = 0; + data->name = 0; + data->type = 0; + data->calltype = CFUNC_CDECL; + + return obj; +} + +VALUE +rb_dlcfunc_initialize(int argc, VALUE argv[], VALUE self) +{ + VALUE addr, name, type, calltype; + struct cfunc_data *data; + void *saddr; + const char *sname; + + rb_scan_args(argc, argv, "13", &addr, &type, &name, &calltype); + + saddr = (void*)(NUM2PTR(rb_Integer(addr))); + sname = NIL_P(name) ? NULL : StringValuePtr(name); + + Data_Get_Struct(self, struct cfunc_data, data); + if( data->name ) xfree(data->name); + data->ptr = saddr; + data->name = sname ? strdup(sname) : 0; + data->type = (type == Qnil) ? DLTYPE_VOID : NUM2INT(type); + data->calltype = (calltype == Qnil) ? CFUNC_CDECL : SYM2ID(calltype); + + return Qnil; +} + +VALUE +rb_dlcfunc_name(VALUE self) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + return cfunc->name ? rb_tainted_str_new2(cfunc->name) : Qnil; +} + +VALUE +rb_dlcfunc_ctype(VALUE self) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + return INT2NUM(cfunc->type); +} + +VALUE +rb_dlcfunc_set_ctype(VALUE self, VALUE ctype) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + cfunc->type = NUM2INT(ctype); + return ctype; +} + +VALUE +rb_dlcfunc_calltype(VALUE self) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + return ID2SYM(cfunc->calltype); +} + +VALUE +rb_dlcfunc_set_calltype(VALUE self, VALUE sym) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + cfunc->calltype = SYM2ID(sym); + return sym; +} + + +VALUE +rb_dlcfunc_ptr(VALUE self) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + return PTR2NUM(cfunc->ptr); +} + +VALUE +rb_dlcfunc_set_ptr(VALUE self, VALUE addr) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + cfunc->ptr = NUM2PTR(addr); + + return Qnil; +} + +VALUE +rb_dlcfunc_inspect(VALUE self) +{ + VALUE val; + char *str; + int str_size; + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + + str_size = (cfunc->name ? strlen(cfunc->name) : 0) + 100; + str = ruby_xmalloc(str_size); + snprintf(str, str_size - 1, + "#<DL::CFunc:%p ptr=%p type=%d name='%s'>", + cfunc, + cfunc->ptr, + cfunc->type, + cfunc->name ? cfunc->name : ""); + val = rb_tainted_str_new2(str); + ruby_xfree(str); + + return val; +} + + +# define DECL_FUNC_CDECL(f,ret,args) ret (FUNC_CDECL(*f))(args) +# define DECL_FUNC_STDCALL(f,ret,args) ret (FUNC_STDCALL(*f))(args) + +#define CALL_CASE switch( RARRAY_LEN(ary) ){ \ + CASE(0); break; \ + CASE(1); break; CASE(2); break; CASE(3); break; CASE(4); break; CASE(5); break; \ + CASE(6); break; CASE(7); break; CASE(8); break; CASE(9); break; CASE(10);break; \ + CASE(11);break; CASE(12);break; CASE(13);break; CASE(14);break; CASE(15);break; \ + CASE(16);break; CASE(17);break; CASE(18);break; CASE(19);break; CASE(20);break; \ + default: rb_raise(rb_eArgError, "too many arguments"); \ +} + + +VALUE +rb_dlcfunc_call(VALUE self, VALUE ary) +{ + struct cfunc_data *cfunc; + int i; + DLSTACK_TYPE stack[DLSTACK_SIZE]; + VALUE result = Qnil; + + rb_secure_update(self); + + memset(stack, 0, sizeof(DLSTACK_TYPE) * DLSTACK_SIZE); + Check_Type(ary, T_ARRAY); + + Data_Get_Struct(self, struct cfunc_data, cfunc); + + if( cfunc->ptr == 0 ){ + rb_raise(rb_eDLError, "can't call null-function"); + return Qnil; + } + + for( i = 0; i < RARRAY_LEN(ary); i++ ){ + if( i >= DLSTACK_SIZE ){ + rb_raise(rb_eDLError, "too many arguments (stack overflow)"); + } + rb_check_safe_obj(RARRAY_PTR(ary)[i]); + stack[i] = NUM2LONG(RARRAY_PTR(ary)[i]); + } + + /* calltype == CFUNC_CDECL */ + if( cfunc->calltype == CFUNC_CDECL ){ + switch( cfunc->type ){ + case DLTYPE_VOID: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,void,DLSTACK_PROTO##n) = cfunc->ptr; \ + f(DLSTACK_ARGS##n(stack)); \ + result = Qnil; \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_VOIDP: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,void*,DLSTACK_PROTO##n) = cfunc->ptr; \ + void * ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = PTR2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_CHAR: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,char,DLSTACK_PROTO##n) = cfunc->ptr; \ + char ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = CHR2FIX(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_SHORT: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,short,DLSTACK_PROTO##n) = cfunc->ptr; \ + short ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = INT2NUM((int)ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_INT: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,int,DLSTACK_PROTO##n) = cfunc->ptr; \ + int ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = INT2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_LONG: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,long,DLSTACK_PROTO##n) = cfunc->ptr; \ + long ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = LONG2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; +#if HAVE_LONG_LONG /* used in ruby.h */ + case DLTYPE_LONG_LONG: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,LONG_LONG,DLSTACK_PROTO) = cfunc->ptr; \ + LONG_LONG ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = LL2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; +#endif + case DLTYPE_FLOAT: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,float,DLSTACK_PROTO) = cfunc->ptr; \ + float ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = rb_float_new(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_DOUBLE: +#define CASE(n) case n: { \ + DECL_FUNC_CDECL(f,double,DLSTACK_PROTO) = cfunc->ptr; \ + double ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = rb_float_new(ret); \ +} + CALL_CASE; +#undef CASE + break; + default: + rb_raise(rb_eDLTypeError, "unknown type %d", cfunc->type); + } + } + else if( cfunc->calltype == CFUNC_STDCALL ){ + /* calltype == CFUNC_STDCALL */ + switch( cfunc->type ){ + case DLTYPE_VOID: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,void,DLSTACK_PROTO##n) = cfunc->ptr; \ + f(DLSTACK_ARGS##n(stack)); \ + result = Qnil; \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_VOIDP: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,void*,DLSTACK_PROTO##n) = cfunc->ptr; \ + void * ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = PTR2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_CHAR: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,char,DLSTACK_PROTO##n) = cfunc->ptr; \ + char ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = CHR2FIX(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_SHORT: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,short,DLSTACK_PROTO##n) = cfunc->ptr; \ + short ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = INT2NUM((int)ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_INT: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,int,DLSTACK_PROTO##n) = cfunc->ptr; \ + int ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = INT2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_LONG: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,long,DLSTACK_PROTO##n) = cfunc->ptr; \ + long ret; \ + ret = f(DLSTACK_ARGS##n(stack)); \ + result = LONG2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; +#if HAVE_LONG_LONG /* used in ruby.h */ + case DLTYPE_LONG_LONG: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,LONG_LONG,DLSTACK_PROTO) = cfunc->ptr; \ + LONG_LONG ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = LL2NUM(ret); \ +} + CALL_CASE; +#undef CASE + break; +#endif + case DLTYPE_FLOAT: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,float,DLSTACK_PROTO) = cfunc->ptr; \ + float ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = rb_float_new(ret); \ +} + CALL_CASE; +#undef CASE + break; + case DLTYPE_DOUBLE: +#define CASE(n) case n: { \ + DECL_FUNC_STDCALL(f,double,DLSTACK_PROTO) = cfunc->ptr; \ + double ret; \ + ret = f(DLSTACK_ARGS(stack)); \ + result = rb_float_new(ret); \ +} + CALL_CASE; +#undef CASE + break; + default: + rb_raise(rb_eDLTypeError, "unknown type %d", cfunc->type); + } + } + else{ + rb_raise(rb_eDLError, +#ifndef LONG_LONG_VALUE + "unsupported call type: %lx", +#else + "unsupported call type: %llx", +#endif + cfunc->calltype); + } + + rb_dl_set_last_error(self, INT2NUM(errno)); +#if defined(HAVE_WINDOWS_H) + rb_dl_set_win32_last_error(self, INT2NUM(GetLastError())); +#endif + + return result; +} + +VALUE +rb_dlcfunc_to_i(VALUE self) +{ + struct cfunc_data *cfunc; + + Data_Get_Struct(self, struct cfunc_data, cfunc); + return PTR2NUM(cfunc->ptr); +} + +void +Init_dlcfunc() +{ + id_last_error = rb_intern("__DL2_LAST_ERROR__"); +#if defined(HAVE_WINDOWS_H) + id_win32_last_error = rb_intern("__DL2_WIN32_LAST_ERROR__"); +#endif + rb_cDLCFunc = rb_define_class_under(rb_mDL, "CFunc", rb_cObject); + rb_define_alloc_func(rb_cDLCFunc, rb_dlcfunc_s_allocate); + rb_define_module_function(rb_cDLCFunc, "last_error", rb_dl_get_last_error, 0); +#if defined(HAVE_WINDOWS_H) + rb_define_module_function(rb_cDLCFunc, "win32_last_error", rb_dl_get_win32_last_error, 0); +#endif + rb_define_method(rb_cDLCFunc, "initialize", rb_dlcfunc_initialize, -1); + rb_define_method(rb_cDLCFunc, "call", rb_dlcfunc_call, 1); + rb_define_method(rb_cDLCFunc, "[]", rb_dlcfunc_call, 1); + rb_define_method(rb_cDLCFunc, "name", rb_dlcfunc_name, 0); + rb_define_method(rb_cDLCFunc, "ctype", rb_dlcfunc_ctype, 0); + rb_define_method(rb_cDLCFunc, "ctype=", rb_dlcfunc_set_ctype, 1); + rb_define_method(rb_cDLCFunc, "calltype", rb_dlcfunc_calltype, 0); + rb_define_method(rb_cDLCFunc, "calltype=", rb_dlcfunc_set_calltype, 1); + rb_define_method(rb_cDLCFunc, "ptr", rb_dlcfunc_ptr, 0); + rb_define_method(rb_cDLCFunc, "ptr=", rb_dlcfunc_set_ptr, 1); + rb_define_method(rb_cDLCFunc, "inspect", rb_dlcfunc_inspect, 0); + rb_define_method(rb_cDLCFunc, "to_s", rb_dlcfunc_inspect, 0); + rb_define_method(rb_cDLCFunc, "to_i", rb_dlcfunc_to_i, 0); +} diff --git a/trunk/ext/dl/cptr.c b/trunk/ext/dl/cptr.c new file mode 100644 index 0000000000..7487b0cf55 --- /dev/null +++ b/trunk/ext/dl/cptr.c @@ -0,0 +1,482 @@ +/* -*- C -*- + * $Id$ + */ + +#include <ruby/ruby.h> +#include <ruby/io.h> +#include <ctype.h> +#include "dl.h" + +VALUE rb_cDLCPtr; + +static ID id_to_ptr; + +static void +dlptr_free(struct ptr_data *data) +{ + if (data->ptr) { + if (data->free) { + (*(data->free))(data->ptr); + } + } +} + +static void +dlptr_mark(struct ptr_data *data) +{ +} + +void +dlptr_init(VALUE val) +{ + struct ptr_data *data; + + Data_Get_Struct(val, struct ptr_data, data); + OBJ_TAINT(val); +} + +VALUE +rb_dlptr_new2(VALUE klass, void *ptr, long size, freefunc_t func) +{ + struct ptr_data *data; + VALUE val; + + rb_secure(4); + val = Data_Make_Struct(klass, struct ptr_data, + 0, dlptr_free, data); + data->ptr = ptr; + data->free = func; + data->size = size; + dlptr_init(val); + + return val; +} + +VALUE +rb_dlptr_new(void *ptr, long size, freefunc_t func) +{ + return rb_dlptr_new2(rb_cDLCPtr, ptr, size, func); +} + +VALUE +rb_dlptr_malloc(long size, freefunc_t func) +{ + void *ptr; + + rb_secure(4); + ptr = ruby_xmalloc((size_t)size); + memset(ptr,0,(size_t)size); + return rb_dlptr_new(ptr, size, func); +} + +void * +rb_dlptr2cptr(VALUE val) +{ + struct ptr_data *data; + void *ptr; + + if (rb_obj_is_kind_of(val, rb_cDLCPtr)) { + Data_Get_Struct(val, struct ptr_data, data); + ptr = data->ptr; + } + else if (val == Qnil) { + ptr = NULL; + } + else{ + rb_raise(rb_eTypeError, "DL::PtrData was expected"); + } + + return ptr; +} + +static VALUE +rb_dlptr_s_allocate(VALUE klass) +{ + VALUE obj; + struct ptr_data *data; + + rb_secure(4); + obj = Data_Make_Struct(klass, struct ptr_data, dlptr_mark, dlptr_free, data); + data->ptr = 0; + data->size = 0; + data->free = 0; + + return obj; +} + +static VALUE +rb_dlptr_initialize(int argc, VALUE argv[], VALUE self) +{ + VALUE ptr, sym, size; + struct ptr_data *data; + void *p = NULL; + freefunc_t f = NULL; + long s = 0; + + switch (rb_scan_args(argc, argv, "12", &ptr, &size, &sym)) { + case 1: + p = (void*)(NUM2PTR(rb_Integer(ptr))); + break; + case 2: + p = (void*)(NUM2PTR(rb_Integer(ptr))); + s = NUM2LONG(size); + break; + case 3: + p = (void*)(NUM2PTR(rb_Integer(ptr))); + s = NUM2LONG(size); + f = NIL_P(sym) ? NULL : RCFUNC_DATA(sym)->ptr; + break; + default: + rb_bug("rb_dlptr_initialize"); + } + + if (p) { + Data_Get_Struct(self, struct ptr_data, data); + if (data->ptr && data->free) { + /* Free previous memory. Use of inappropriate initialize may cause SEGV. */ + (*(data->free))(data->ptr); + } + data->ptr = p; + data->size = s; + data->free = f; + } + + return Qnil; +} + +static VALUE +rb_dlptr_s_malloc(int argc, VALUE argv[], VALUE klass) +{ + VALUE size, sym, obj; + int s; + freefunc_t f; + + switch (rb_scan_args(argc, argv, "11", &size, &sym)) { + case 1: + s = NUM2LONG(size); + f = NULL; + break; + case 2: + s = NUM2LONG(size); + f = RCFUNC_DATA(sym)->ptr; + break; + default: + rb_bug("rb_dlptr_s_malloc"); + } + + obj = rb_dlptr_malloc(s,f); + + return obj; +} + +VALUE +rb_dlptr_to_i(VALUE self) +{ + struct ptr_data *data; + + Data_Get_Struct(self, struct ptr_data, data); + return PTR2NUM(data->ptr); +} + +VALUE +rb_dlptr_to_value(VALUE self) +{ + struct ptr_data *data; + Data_Get_Struct(self, struct ptr_data, data); + return (VALUE)(data->ptr); +} + +VALUE +rb_dlptr_ptr(VALUE self) +{ + struct ptr_data *data; + + Data_Get_Struct(self, struct ptr_data, data); + return rb_dlptr_new(*((void**)(data->ptr)),0,0); +} + +VALUE +rb_dlptr_ref(VALUE self) +{ + struct ptr_data *data; + + Data_Get_Struct(self, struct ptr_data, data); + return rb_dlptr_new(&(data->ptr),0,0); +} + +VALUE +rb_dlptr_null_p(VALUE self) +{ + struct ptr_data *data; + + Data_Get_Struct(self, struct ptr_data, data); + return data->ptr ? Qfalse : Qtrue; +} + +VALUE +rb_dlptr_free_set(VALUE self, VALUE val) +{ + struct ptr_data *data; + extern VALUE rb_cDLCFunc; + + Data_Get_Struct(self, struct ptr_data, data); + if( rb_obj_is_kind_of(val, rb_cDLCFunc) == Qtrue ){ + data->free = RCFUNC_DATA(val)->ptr; + } + else{ + data->free = NUM2PTR(rb_Integer(val)); + } + + return Qnil; +} + +VALUE +rb_dlptr_free_get(VALUE self) +{ + struct ptr_data *pdata; + + Data_Get_Struct(self, struct ptr_data, pdata); + + return rb_dlcfunc_new(pdata->free, DLTYPE_VOID, "free<anonymous>", CFUNC_CDECL); +} + +VALUE +rb_dlptr_to_s(int argc, VALUE argv[], VALUE self) +{ + struct ptr_data *data; + VALUE arg1, val; + int len; + + Data_Get_Struct(self, struct ptr_data, data); + switch (rb_scan_args(argc, argv, "01", &arg1)) { + case 0: + val = rb_tainted_str_new2((char*)(data->ptr)); + break; + case 1: + len = NUM2INT(arg1); + val = rb_tainted_str_new((char*)(data->ptr), len); + break; + default: + rb_bug("rb_dlptr_to_s"); + } + + return val; +} + +VALUE +rb_dlptr_to_str(int argc, VALUE argv[], VALUE self) +{ + struct ptr_data *data; + VALUE arg1, val; + int len; + + Data_Get_Struct(self, struct ptr_data, data); + switch (rb_scan_args(argc, argv, "01", &arg1)) { + case 0: + val = rb_tainted_str_new((char*)(data->ptr),data->size); + break; + case 1: + len = NUM2INT(arg1); + val = rb_tainted_str_new((char*)(data->ptr), len); + break; + default: + rb_bug("rb_dlptr_to_str"); + } + + return val; +} + +VALUE +rb_dlptr_inspect(VALUE self) +{ + struct ptr_data *data; + char str[1024]; + + Data_Get_Struct(self, struct ptr_data, data); + snprintf(str, 1023, "#<%s:%p ptr=%p size=%ld free=%p>", + rb_class2name(CLASS_OF(self)), data, data->ptr, data->size, data->free); + return rb_str_new2(str); +} + +VALUE +rb_dlptr_eql(VALUE self, VALUE other) +{ + void *ptr1, *ptr2; + ptr1 = rb_dlptr2cptr(self); + ptr2 = rb_dlptr2cptr(other); + + return ptr1 == ptr2 ? Qtrue : Qfalse; +} + +VALUE +rb_dlptr_cmp(VALUE self, VALUE other) +{ + void *ptr1, *ptr2; + ptr1 = rb_dlptr2cptr(self); + ptr2 = rb_dlptr2cptr(other); + return PTR2NUM((long)ptr1 - (long)ptr2); +} + +VALUE +rb_dlptr_plus(VALUE self, VALUE other) +{ + void *ptr; + long num, size; + + ptr = rb_dlptr2cptr(self); + size = RPTR_DATA(self)->size; + num = NUM2LONG(other); + return rb_dlptr_new((char *)ptr + num, size - num, 0); +} + +VALUE +rb_dlptr_minus(VALUE self, VALUE other) +{ + void *ptr; + long num, size; + + ptr = rb_dlptr2cptr(self); + size = RPTR_DATA(self)->size; + num = NUM2LONG(other); + return rb_dlptr_new((char *)ptr - num, size + num, 0); +} + +VALUE +rb_dlptr_aref(int argc, VALUE argv[], VALUE self) +{ + VALUE arg0, arg1; + VALUE retval = Qnil; + size_t offset, len; + + switch( rb_scan_args(argc, argv, "11", &arg0, &arg1) ){ + case 1: + offset = NUM2ULONG(arg0); + retval = INT2NUM(*((char*)RPTR_DATA(self)->ptr + offset)); + break; + case 2: + offset = NUM2ULONG(arg0); + len = NUM2ULONG(arg1); + retval = rb_tainted_str_new((char *)RPTR_DATA(self)->ptr + offset, len); + break; + default: + rb_bug("rb_dlptr_aref()"); + } + return retval; +} + +VALUE +rb_dlptr_aset(int argc, VALUE argv[], VALUE self) +{ + VALUE arg0, arg1, arg2; + VALUE retval = Qnil; + size_t offset, len; + void *mem; + + switch( rb_scan_args(argc, argv, "21", &arg0, &arg1, &arg2) ){ + case 2: + offset = NUM2ULONG(arg0); + ((char*)RPTR_DATA(self)->ptr)[offset] = NUM2UINT(arg1); + retval = arg1; + break; + case 3: + offset = NUM2ULONG(arg0); + len = NUM2ULONG(arg1); + if( TYPE(arg2) == T_STRING ){ + mem = StringValuePtr(arg2); + } + else if( rb_obj_is_kind_of(arg2, rb_cDLCPtr) ){ + mem = rb_dlptr2cptr(arg2); + } + else{ + mem = NUM2PTR(arg2); + } + memcpy((char *)RPTR_DATA(self)->ptr + offset, mem, len); + retval = arg2; + break; + default: + rb_bug("rb_dlptr_aset()"); + } + return retval; +} + +VALUE +rb_dlptr_size(int argc, VALUE argv[], VALUE self) +{ + VALUE size; + + if (rb_scan_args(argc, argv, "01", &size) == 0){ + return LONG2NUM(RPTR_DATA(self)->size); + } + else{ + RPTR_DATA(self)->size = NUM2LONG(size); + return size; + } +} + +VALUE +rb_dlptr_s_to_ptr(VALUE self, VALUE val) +{ + VALUE ptr; + + if (rb_obj_is_kind_of(val, rb_cIO) == Qtrue){ + rb_io_t *fptr; + FILE *fp; + GetOpenFile(val, fptr); + fp = rb_io_stdio_file(fptr); + ptr = rb_dlptr_new(fp, 0, NULL); + } + else if (rb_obj_is_kind_of(val, rb_cString) == Qtrue){ + char *str = StringValuePtr(val); + ptr = rb_dlptr_new(str, RSTRING_LEN(val), NULL); + } + else if (rb_respond_to(val, id_to_ptr)){ + VALUE vptr = rb_funcall(val, id_to_ptr, 0); + if (rb_obj_is_kind_of(vptr, rb_cDLCPtr)){ + ptr = vptr; + } + else{ + rb_raise(rb_eDLError, "to_ptr should return a CPtr object"); + } + } + else{ + ptr = rb_dlptr_new(NUM2PTR(rb_Integer(val)), 0, NULL); + } + OBJ_INFECT(ptr, val); + return ptr; +} + +void +Init_dlptr() +{ + id_to_ptr = rb_intern("to_ptr"); + + rb_cDLCPtr = rb_define_class_under(rb_mDL, "CPtr", rb_cObject); + rb_define_alloc_func(rb_cDLCPtr, rb_dlptr_s_allocate); + rb_define_singleton_method(rb_cDLCPtr, "malloc", rb_dlptr_s_malloc, -1); + rb_define_singleton_method(rb_cDLCPtr, "to_ptr", rb_dlptr_s_to_ptr, 1); + rb_define_singleton_method(rb_cDLCPtr, "[]", rb_dlptr_s_to_ptr, 1); + rb_define_method(rb_cDLCPtr, "initialize", rb_dlptr_initialize, -1); + rb_define_method(rb_cDLCPtr, "free=", rb_dlptr_free_set, 1); + rb_define_method(rb_cDLCPtr, "free", rb_dlptr_free_get, 0); + rb_define_method(rb_cDLCPtr, "to_i", rb_dlptr_to_i, 0); + rb_define_method(rb_cDLCPtr, "to_value", rb_dlptr_to_value, 0); + rb_define_method(rb_cDLCPtr, "ptr", rb_dlptr_ptr, 0); + rb_define_method(rb_cDLCPtr, "+@", rb_dlptr_ptr, 0); + rb_define_method(rb_cDLCPtr, "ref", rb_dlptr_ref, 0); + rb_define_method(rb_cDLCPtr, "-@", rb_dlptr_ref, 0); + rb_define_method(rb_cDLCPtr, "null?", rb_dlptr_null_p, 0); + rb_define_method(rb_cDLCPtr, "to_s", rb_dlptr_to_s, -1); + rb_define_method(rb_cDLCPtr, "to_str", rb_dlptr_to_str, -1); + rb_define_method(rb_cDLCPtr, "inspect", rb_dlptr_inspect, 0); + rb_define_method(rb_cDLCPtr, "<=>", rb_dlptr_cmp, 1); + rb_define_method(rb_cDLCPtr, "==", rb_dlptr_eql, 1); + rb_define_method(rb_cDLCPtr, "eql?", rb_dlptr_eql, 1); + rb_define_method(rb_cDLCPtr, "+", rb_dlptr_plus, 1); + rb_define_method(rb_cDLCPtr, "-", rb_dlptr_minus, 1); + rb_define_method(rb_cDLCPtr, "[]", rb_dlptr_aref, -1); + rb_define_method(rb_cDLCPtr, "[]=", rb_dlptr_aset, -1); + rb_define_method(rb_cDLCPtr, "size", rb_dlptr_size, -1); + rb_define_method(rb_cDLCPtr, "size=", rb_dlptr_size, -1); + + rb_define_const(rb_mDL, "NULL", rb_dlptr_new(0, 0, 0)); +} diff --git a/trunk/ext/dl/depend b/trunk/ext/dl/depend new file mode 100644 index 0000000000..fe89a9fae3 --- /dev/null +++ b/trunk/ext/dl/depend @@ -0,0 +1,11 @@ +cfunc.o: cfunc.c dl.h $(hdrdir)/ruby.h + +cptr.o: cptr.c dl.h $(hdrdir)/ruby.h $(hdrdir)/io.h + +handle.o: handle.c dl.h $(hdrdir)/ruby.h + +dl.o: dl.c dl.h callback.h $(hdrdir)/ruby.h $(hdrdir)/io.h + +callback.h: $(srcdir)/mkcallback.rb dl.h + @echo "generating callback.h" + @$(RUBY) $(srcdir)/mkcallback.rb $(srcdir)/dl.h > $@ diff --git a/trunk/ext/dl/dl.c b/trunk/ext/dl/dl.c new file mode 100644 index 0000000000..0427dfb3f3 --- /dev/null +++ b/trunk/ext/dl/dl.c @@ -0,0 +1,137 @@ +#include <ruby/ruby.h> +#include <ruby/io.h> +#include <ctype.h> +#include "dl.h" + +VALUE rb_mDL; +VALUE rb_eDLError; +VALUE rb_eDLTypeError; + +ID rbdl_id_cdecl; +ID rbdl_id_stdcall; + +VALUE +rb_dl_dlopen(int argc, VALUE argv[], VALUE self) +{ + rb_secure(2); + return rb_class_new_instance(argc, argv, rb_cDLHandle); +} + +VALUE +rb_dl_malloc(VALUE self, VALUE size) +{ + void *ptr; + + rb_secure(4); + ptr = (void*)ruby_xmalloc(NUM2INT(size)); + return PTR2NUM(ptr); +} + +VALUE +rb_dl_realloc(VALUE self, VALUE addr, VALUE size) +{ + void *ptr = NUM2PTR(addr); + + rb_secure(4); + ptr = (void*)ruby_xrealloc(ptr, NUM2INT(size)); + return PTR2NUM(ptr); +} + +VALUE +rb_dl_free(VALUE self, VALUE addr) +{ + void *ptr = NUM2PTR(addr); + + rb_secure(4); + ruby_xfree(ptr); + return Qnil; +} + +VALUE +rb_dl_ptr2value(VALUE self, VALUE addr) +{ + rb_secure(4); + return (VALUE)NUM2PTR(addr); +} + +VALUE +rb_dl_value2ptr(VALUE self, VALUE val) +{ + return PTR2NUM((void*)val); +} + +#include "callback.h" + +void +Init_dl() +{ + void Init_dlhandle(); + void Init_dlcfunc(); + void Init_dlptr(); + + rbdl_id_cdecl = rb_intern("cdecl"); + rbdl_id_stdcall = rb_intern("stdcall"); + + rb_mDL = rb_define_module("DL"); + rb_eDLError = rb_define_class_under(rb_mDL, "DLError", rb_eStandardError); + rb_eDLTypeError = rb_define_class_under(rb_mDL, "DLTypeError", rb_eDLError); + + rb_define_const(rb_mDL, "MAX_CALLBACK", INT2NUM(MAX_CALLBACK)); + rb_define_const(rb_mDL, "DLSTACK_SIZE", INT2NUM(DLSTACK_SIZE)); + + rb_dl_init_callbacks(); + + rb_define_const(rb_mDL, "RTLD_GLOBAL", INT2NUM(RTLD_GLOBAL)); + rb_define_const(rb_mDL, "RTLD_LAZY", INT2NUM(RTLD_LAZY)); + rb_define_const(rb_mDL, "RTLD_NOW", INT2NUM(RTLD_NOW)); + + rb_define_const(rb_mDL, "TYPE_VOID", INT2NUM(DLTYPE_VOID)); + rb_define_const(rb_mDL, "TYPE_VOIDP", INT2NUM(DLTYPE_VOIDP)); + rb_define_const(rb_mDL, "TYPE_CHAR", INT2NUM(DLTYPE_CHAR)); + rb_define_const(rb_mDL, "TYPE_SHORT", INT2NUM(DLTYPE_SHORT)); + rb_define_const(rb_mDL, "TYPE_INT", INT2NUM(DLTYPE_INT)); + rb_define_const(rb_mDL, "TYPE_LONG", INT2NUM(DLTYPE_LONG)); +#if HAVE_LONG_LONG + rb_define_const(rb_mDL, "TYPE_LONG_LONG", INT2NUM(DLTYPE_LONG_LONG)); +#endif + rb_define_const(rb_mDL, "TYPE_FLOAT", INT2NUM(DLTYPE_FLOAT)); + rb_define_const(rb_mDL, "TYPE_DOUBLE", INT2NUM(DLTYPE_DOUBLE)); + + rb_define_const(rb_mDL, "ALIGN_VOIDP", INT2NUM(ALIGN_VOIDP)); + rb_define_const(rb_mDL, "ALIGN_CHAR", INT2NUM(ALIGN_CHAR)); + rb_define_const(rb_mDL, "ALIGN_SHORT", INT2NUM(ALIGN_SHORT)); + rb_define_const(rb_mDL, "ALIGN_INT", INT2NUM(ALIGN_INT)); + rb_define_const(rb_mDL, "ALIGN_LONG", INT2NUM(ALIGN_LONG)); +#if HAVE_LONG_LONG + rb_define_const(rb_mDL, "ALIGN_LONG_LONG", INT2NUM(ALIGN_LONG_LONG)); +#endif + rb_define_const(rb_mDL, "ALIGN_FLOAT", INT2NUM(ALIGN_FLOAT)); + rb_define_const(rb_mDL, "ALIGN_DOUBLE",INT2NUM(ALIGN_DOUBLE)); + + rb_define_const(rb_mDL, "SIZEOF_VOIDP", INT2NUM(sizeof(void*))); + rb_define_const(rb_mDL, "SIZEOF_CHAR", INT2NUM(sizeof(char))); + rb_define_const(rb_mDL, "SIZEOF_SHORT", INT2NUM(sizeof(short))); + rb_define_const(rb_mDL, "SIZEOF_INT", INT2NUM(sizeof(int))); + rb_define_const(rb_mDL, "SIZEOF_LONG", INT2NUM(sizeof(long))); +#if HAVE_LONG_LONG + rb_define_const(rb_mDL, "SIZEOF_LONG_LONG", INT2NUM(sizeof(LONG_LONG))); +#endif + rb_define_const(rb_mDL, "SIZEOF_FLOAT", INT2NUM(sizeof(float))); + rb_define_const(rb_mDL, "SIZEOF_DOUBLE",INT2NUM(sizeof(double))); + + rb_define_module_function(rb_mDL, "dlwrap", rb_dl_value2ptr, 1); + rb_define_module_function(rb_mDL, "dlunwrap", rb_dl_ptr2value, 1); + + rb_define_module_function(rb_mDL, "dlopen", rb_dl_dlopen, -1); + rb_define_module_function(rb_mDL, "malloc", rb_dl_malloc, 1); + rb_define_module_function(rb_mDL, "realloc", rb_dl_realloc, 2); + rb_define_module_function(rb_mDL, "free", rb_dl_free, 1); + + rb_define_const(rb_mDL, "RUBY_FREE", PTR2NUM(ruby_xfree)); + rb_define_const(rb_mDL, "BUILD_RUBY_PLATFORM", rb_str_new2(RUBY_PLATFORM)); + rb_define_const(rb_mDL, "BUILD_RUBY_VERSION", rb_str_new2(RUBY_VERSION)); + + Init_dlhandle(); + Init_dlcfunc(); + Init_dlptr(); +} diff --git a/trunk/ext/dl/dl.h b/trunk/ext/dl/dl.h new file mode 100644 index 0000000000..7b8ad077ae --- /dev/null +++ b/trunk/ext/dl/dl.h @@ -0,0 +1,190 @@ +#ifndef RUBY_DL_H +#define RUBY_DL_H + +#include <ruby.h> + +#if !defined(FUNC_CDECL) +# define FUNC_CDECL(x) x +#endif +#if !defined(FUNC_STDCALL) +# define FUNC_STDCALL(x) x +#endif + +#if defined(HAVE_DLFCN_H) +# include <dlfcn.h> +# /* some stranger systems may not define all of these */ +#ifndef RTLD_LAZY +#define RTLD_LAZY 0 +#endif +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif +#else +# if defined(HAVE_WINDOWS_H) +# include <windows.h> +# define dlclose(ptr) FreeLibrary((HINSTANCE)ptr) +# define dlopen(name,flag) ((void*)LoadLibrary(name)) +# define dlerror() "unknown error" +# define dlsym(handle,name) ((void*)GetProcAddress(handle,name)) +# define RTLD_LAZY -1 +# define RTLD_NOW -1 +# define RTLD_GLOBAL -1 +# endif +#endif + +#define MAX_CALLBACK 5 +#define DLSTACK_TYPE long +#define DLSTACK_SIZE (20) +#define DLSTACK_PROTO \ + DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,\ + DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,\ + DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,\ + DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE,DLSTACK_TYPE +#define DLSTACK_ARGS(stack) \ + stack[0],stack[1],stack[2],stack[3],stack[4],\ + stack[5],stack[6],stack[7],stack[8],stack[9],\ + stack[10],stack[11],stack[12],stack[13],stack[14],\ + stack[15],stack[16],stack[17],stack[18],stack[19] + +#define DLSTACK_PROTO0 +#define DLSTACK_PROTO1 DLSTACK_TYPE +#define DLSTACK_PROTO2 DLSTACK_PROTO1, DLSTACK_TYPE +#define DLSTACK_PROTO3 DLSTACK_PROTO2, DLSTACK_TYPE +#define DLSTACK_PROTO4 DLSTACK_PROTO3, DLSTACK_TYPE +#define DLSTACK_PROTO4 DLSTACK_PROTO3, DLSTACK_TYPE +#define DLSTACK_PROTO5 DLSTACK_PROTO4, DLSTACK_TYPE +#define DLSTACK_PROTO6 DLSTACK_PROTO5, DLSTACK_TYPE +#define DLSTACK_PROTO7 DLSTACK_PROTO6, DLSTACK_TYPE +#define DLSTACK_PROTO8 DLSTACK_PROTO7, DLSTACK_TYPE +#define DLSTACK_PROTO9 DLSTACK_PROTO8, DLSTACK_TYPE +#define DLSTACK_PROTO10 DLSTACK_PROTO9, DLSTACK_TYPE +#define DLSTACK_PROTO11 DLSTACK_PROTO10, DLSTACK_TYPE +#define DLSTACK_PROTO12 DLSTACK_PROTO11, DLSTACK_TYPE +#define DLSTACK_PROTO13 DLSTACK_PROTO12, DLSTACK_TYPE +#define DLSTACK_PROTO14 DLSTACK_PROTO13, DLSTACK_TYPE +#define DLSTACK_PROTO14 DLSTACK_PROTO13, DLSTACK_TYPE +#define DLSTACK_PROTO15 DLSTACK_PROTO14, DLSTACK_TYPE +#define DLSTACK_PROTO16 DLSTACK_PROTO15, DLSTACK_TYPE +#define DLSTACK_PROTO17 DLSTACK_PROTO16, DLSTACK_TYPE +#define DLSTACK_PROTO18 DLSTACK_PROTO17, DLSTACK_TYPE +#define DLSTACK_PROTO19 DLSTACK_PROTO18, DLSTACK_TYPE +#define DLSTACK_PROTO20 DLSTACK_PROTO19, DLSTACK_TYPE + +#define DLSTACK_ARGS0(stack) +#define DLSTACK_ARGS1(stack) stack[0] +#define DLSTACK_ARGS2(stack) DLSTACK_ARGS1(stack), stack[1] +#define DLSTACK_ARGS3(stack) DLSTACK_ARGS2(stack), stack[2] +#define DLSTACK_ARGS4(stack) DLSTACK_ARGS3(stack), stack[3] +#define DLSTACK_ARGS5(stack) DLSTACK_ARGS4(stack), stack[4] +#define DLSTACK_ARGS6(stack) DLSTACK_ARGS5(stack), stack[5] +#define DLSTACK_ARGS7(stack) DLSTACK_ARGS6(stack), stack[6] +#define DLSTACK_ARGS8(stack) DLSTACK_ARGS7(stack), stack[7] +#define DLSTACK_ARGS9(stack) DLSTACK_ARGS8(stack), stack[8] +#define DLSTACK_ARGS10(stack) DLSTACK_ARGS9(stack), stack[9] +#define DLSTACK_ARGS11(stack) DLSTACK_ARGS10(stack), stack[10] +#define DLSTACK_ARGS12(stack) DLSTACK_ARGS11(stack), stack[11] +#define DLSTACK_ARGS13(stack) DLSTACK_ARGS12(stack), stack[12] +#define DLSTACK_ARGS14(stack) DLSTACK_ARGS13(stack), stack[13] +#define DLSTACK_ARGS15(stack) DLSTACK_ARGS14(stack), stack[14] +#define DLSTACK_ARGS16(stack) DLSTACK_ARGS15(stack), stack[15] +#define DLSTACK_ARGS17(stack) DLSTACK_ARGS16(stack), stack[16] +#define DLSTACK_ARGS18(stack) DLSTACK_ARGS17(stack), stack[17] +#define DLSTACK_ARGS19(stack) DLSTACK_ARGS18(stack), stack[18] +#define DLSTACK_ARGS20(stack) DLSTACK_ARGS19(stack), stack[19] + +extern VALUE rb_mDL; +extern VALUE rb_cDLHandle; +extern VALUE rb_cDLSymbol; +extern VALUE rb_eDLError; +extern VALUE rb_eDLTypeError; + +typedef struct { char c; void *x; } s_voidp; +typedef struct { char c; short x; } s_short; +typedef struct { char c; int x; } s_int; +typedef struct { char c; long x; } s_long; +typedef struct { char c; float x; } s_float; +typedef struct { char c; double x; } s_double; +#if HAVE_LONG_LONG +typedef struct { char c; LONG_LONG x; } s_long_long; +#endif + +#define ALIGN_VOIDP (sizeof(s_voidp) - sizeof(void *)) +#define ALIGN_SHORT (sizeof(s_short) - sizeof(short)) +#define ALIGN_CHAR (1) +#define ALIGN_INT (sizeof(s_int) - sizeof(int)) +#define ALIGN_LONG (sizeof(s_long) - sizeof(long)) +#if HAVE_LONG_LONG +#define ALIGN_LONG_LONG (sizeof(s_long_long) - sizeof(LONG_LONG)) +#endif +#define ALIGN_FLOAT (sizeof(s_float) - sizeof(float)) +#define ALIGN_DOUBLE (sizeof(s_double) - sizeof(double)) + +#define DLALIGN(ptr,offset,align) {\ + while( (((unsigned long)((char *)ptr + offset)) % align) != 0 ) offset++;\ +} + + +#define DLTYPE_VOID 0 +#define DLTYPE_VOIDP 1 +#define DLTYPE_CHAR 2 +#define DLTYPE_SHORT 3 +#define DLTYPE_INT 4 +#define DLTYPE_LONG 5 +#if HAVE_LONG_LONG +#define DLTYPE_LONG_LONG 6 +#endif +#define DLTYPE_FLOAT 7 +#define DLTYPE_DOUBLE 8 +#define MAX_DLTYPE 9 + +#if SIZEOF_VOIDP == SIZEOF_LONG +# define PTR2NUM(x) (ULONG2NUM((unsigned long)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULONG(x))) +#else +/* # error --->> Ruby/DL2 requires sizeof(void*) == sizeof(long) to be compiled. <<--- */ +# define PTR2NUM(x) (ULL2NUM((unsigned long long)(x))) +# define NUM2PTR(x) ((void*)(NUM2ULL(x))) +#endif + +#define BOOL2INT(x) ((x == Qtrue)?1:0) +#define INT2BOOL(x) (x?Qtrue:Qfalse) + +typedef void (*freefunc_t)(void*); + +struct dl_handle { + void *ptr; + int open; + int enable_close; +}; + + +struct cfunc_data { + void *ptr; + char *name; + int type; + ID calltype; +}; +extern ID rbdl_id_cdecl; +extern ID rbdl_id_stdcall; +#define CFUNC_CDECL (rbdl_id_cdecl) +#define CFUNC_STDCALL (rbdl_id_stdcall) + +struct ptr_data { + void *ptr; + long size; + freefunc_t free; +}; + +#define RDL_HANDLE(obj) ((struct dl_handle *)(DATA_PTR(obj))) +#define RCFUNC_DATA(obj) ((struct cfunc_data *)(DATA_PTR(obj))) +#define RPTR_DATA(obj) ((struct ptr_data *)(DATA_PTR(obj))) + +VALUE rb_dlcfunc_new(void (*func)(), int dltype, const char * name, ID calltype); +VALUE rb_dlptr_new(void *ptr, long size, freefunc_t func); +VALUE rb_dlptr_new2(VALUE klass, void *ptr, long size, freefunc_t func); +VALUE rb_dlptr_malloc(long size, freefunc_t func); + +#endif diff --git a/trunk/ext/dl/extconf.rb b/trunk/ext/dl/extconf.rb new file mode 100644 index 0000000000..abd8d8f781 --- /dev/null +++ b/trunk/ext/dl/extconf.rb @@ -0,0 +1,31 @@ +require 'mkmf' + +if( RbConfig::CONFIG['CC'] =~ /gcc/ ) + $CFLAGS << " -fno-defer-pop -fno-omit-frame-pointer" +end + +$INSTALLFILES = [ + ["dl.h", "$(HDRDIR)"], +] +$distcleanfiles << "callback.h" + + +check = true +if( have_header("dlfcn.h") ) + have_library("dl") + check &&= have_func("dlopen") + check &&= have_func("dlclose") + check &&= have_func("dlsym") + have_func("dlerror") +elsif( have_header("windows.h") ) + check &&= have_func("LoadLibrary") + check &&= have_func("FreeLibrary") + check &&= have_func("GetProcAddress") +else + check = false +end + +if check + $defs << %[-DRUBY_VERSION=\\"#{RUBY_VERSION}\\"] + create_makefile("dl") +end diff --git a/trunk/ext/dl/handle.c b/trunk/ext/dl/handle.c new file mode 100644 index 0000000000..24269dfd78 --- /dev/null +++ b/trunk/ext/dl/handle.c @@ -0,0 +1,227 @@ +/* -*- C -*- + * $Id$ + */ + +#include <ruby.h> +#include "dl.h" + +VALUE rb_cDLHandle; + +void +dlhandle_free(struct dl_handle *dlhandle) +{ + if( dlhandle->ptr && dlhandle->open && dlhandle->enable_close ){ + dlclose(dlhandle->ptr); + } +} + +VALUE +rb_dlhandle_close(VALUE self) +{ + struct dl_handle *dlhandle; + + Data_Get_Struct(self, struct dl_handle, dlhandle); + dlhandle->open = 0; + return INT2NUM(dlclose(dlhandle->ptr)); +} + +VALUE +rb_dlhandle_s_allocate(VALUE klass) +{ + VALUE obj; + struct dl_handle *dlhandle; + + obj = Data_Make_Struct(rb_cDLHandle, struct dl_handle, 0, + dlhandle_free, dlhandle); + dlhandle->ptr = 0; + dlhandle->open = 0; + dlhandle->enable_close = 0; + + return obj; +} + +VALUE +rb_dlhandle_initialize(int argc, VALUE argv[], VALUE self) +{ + void *ptr; + struct dl_handle *dlhandle; + VALUE lib, flag; + char *clib; + int cflag; + const char *err; + + switch( rb_scan_args(argc, argv, "02", &lib, &flag) ){ + case 0: + clib = NULL; + cflag = RTLD_LAZY | RTLD_GLOBAL; + break; + case 1: + clib = NIL_P(lib) ? NULL : StringValuePtr(lib); + cflag = RTLD_LAZY | RTLD_GLOBAL; + break; + case 2: + clib = NIL_P(lib) ? NULL : StringValuePtr(lib); + cflag = NUM2INT(flag); + break; + default: + rb_bug("rb_dlhandle_new"); + } + + ptr = dlopen(clib, cflag); +#if defined(HAVE_DLERROR) + if( !ptr && (err = dlerror()) ){ + rb_raise(rb_eDLError, "%s", err); + } +#else + if( !ptr ){ + err = dlerror(); + rb_raise(rb_eDLError, "%s", err); + } +#endif + Data_Get_Struct(self, struct dl_handle, dlhandle); + if( dlhandle->ptr && dlhandle->open && dlhandle->enable_close ){ + dlclose(dlhandle->ptr); + } + dlhandle->ptr = ptr; + dlhandle->open = 1; + dlhandle->enable_close = 0; + + if( rb_block_given_p() ){ + rb_ensure(rb_yield, self, rb_dlhandle_close, self); + } + + return Qnil; +} + +VALUE +rb_dlhandle_enable_close(VALUE self) +{ + struct dl_handle *dlhandle; + + Data_Get_Struct(self, struct dl_handle, dlhandle); + dlhandle->enable_close = 1; + return Qnil; +} + +VALUE +rb_dlhandle_disable_close(VALUE self) +{ + struct dl_handle *dlhandle; + + Data_Get_Struct(self, struct dl_handle, dlhandle); + dlhandle->enable_close = 0; + return Qnil; +} + +VALUE +rb_dlhandle_to_i(VALUE self) +{ + struct dl_handle *dlhandle; + + Data_Get_Struct(self, struct dl_handle, dlhandle); + return PTR2NUM(dlhandle); +} + +VALUE +rb_dlhandle_sym(VALUE self, VALUE sym) +{ + void (*func)(); + struct dl_handle *dlhandle; + void *handle; + const char *name; + const char *err; + int i; + +#if defined(HAVE_DLERROR) +# define CHECK_DLERROR if( err = dlerror() ){ func = 0; } +#else +# define CHECK_DLERROR +#endif + + rb_secure(2); + + if( sym == Qnil ){ +#if defined(RTLD_NEXT) + name = RTLD_NEXT; +#else + name = NULL; +#endif + } + else{ + name = StringValuePtr(sym); + } + + + Data_Get_Struct(self, struct dl_handle, dlhandle); + if( ! dlhandle->open ){ + rb_raise(rb_eDLError, "closed handle"); + } + handle = dlhandle->ptr; + + func = dlsym(handle, name); + CHECK_DLERROR; + if( !func ){ +#if defined(__CYGWIN__) || defined(WIN32) || defined(__MINGW32__) + { + int len = strlen(name); + char *name_a = (char*)xmalloc(len+2); + strcpy(name_a, name); + name_a[len] = 'A'; + name_a[len+1] = '\0'; + func = dlsym(handle, name_a); + xfree(name_a); + CHECK_DLERROR; + if( !func ){ + for( i = 0; i < 256; i += 4 ){ + int len = strlen(name); + char *name_n = (char*)xmalloc(len+5); + sprintf(name_n, "%s@%d%c", name, i, 0); + func = dlsym(handle, name_n); + xfree(name_n); + CHECK_DLERROR; + if( func ) + { + break; + } + } + CHECK_DLERROR; + if( !func ){ + rb_raise(rb_eDLError, "unknown symbol \"%s\"", name); + } + } + } +#else + for( i = 0; i < 256; i += 4 ){ + int len = strlen(name); + char *name_n = (char*)xmalloc(len+4); + sprintf(name_n, "%s@%d", name, i); + func = dlsym(handle, name_n); + xfree(name_n); + CHECK_DLERROR; + if( func ){ + break; + } + } + CHECK_DLERROR; + if( !func ){ + rb_raise(rb_eDLError, "unknown symbol \"%s\"", name); + } +#endif + } + + return PTR2NUM(func); +} + +void +Init_dlhandle() +{ + rb_cDLHandle = rb_define_class_under(rb_mDL, "Handle", rb_cObject); + rb_define_alloc_func(rb_cDLHandle, rb_dlhandle_s_allocate); + rb_define_method(rb_cDLHandle, "initialize", rb_dlhandle_initialize, -1); + rb_define_method(rb_cDLHandle, "to_i", rb_dlhandle_to_i, 0); + rb_define_method(rb_cDLHandle, "close", rb_dlhandle_close, 0); + rb_define_method(rb_cDLHandle, "sym", rb_dlhandle_sym, 1); + rb_define_method(rb_cDLHandle, "[]", rb_dlhandle_sym, 1); + rb_define_method(rb_cDLHandle, "disable_close", rb_dlhandle_disable_close, 0); + rb_define_method(rb_cDLHandle, "enable_close", rb_dlhandle_enable_close, 0); +} diff --git a/trunk/ext/dl/lib/dl/callback.rb b/trunk/ext/dl/lib/dl/callback.rb new file mode 100644 index 0000000000..d0b2c7a819 --- /dev/null +++ b/trunk/ext/dl/lib/dl/callback.rb @@ -0,0 +1,69 @@ +require 'dl' +require 'thread' + +module DL + SEM = Mutex.new + + def set_callback_internal(proc_entry, addr_entry, argc, ty, &cbp) + if( argc < 0 ) + raise(ArgumentError, "arity should not be less than 0.") + end + addr = nil + SEM.synchronize{ + ary = proc_entry[ty] + (0...MAX_CALLBACK).each{|n| + idx = (n * DLSTACK_SIZE) + argc + if( ary[idx].nil? ) + ary[idx] = cbp + addr = addr_entry[ty][idx] + break + end + } + } + addr + end + + def set_cdecl_callback(ty, argc, &cbp) + set_callback_internal(CdeclCallbackProcs, CdeclCallbackAddrs, argc, ty, &cbp) + end + + def set_stdcall_callback(ty, argc, &cbp) + set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, &cbp) + end + + def remove_callback_internal(proc_entry, addr_entry, addr, ctype = nil) + index = nil + if( ctype ) + addr_entry[ctype].each_with_index{|xaddr, idx| + if( xaddr == addr ) + index = idx + end + } + else + addr_entry.each{|ty,entry| + entry.each_with_index{|xaddr, idx| + if( xaddr == addr ) + index = idx + end + } + } + end + if( proc_entry[ctype][index] ) + proc_entry[ctype][index] = nil + return true + else + return false + end + end + + def remove_cdecl_callback(addr, ctype = nil) + remove_callback_internal(CdeclCallbackProcs, CdeclCallbackAddrs, addr, ctype) + end + + def remove_stdcall_callback(addr, ctype = nil) + remove_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, addr, ctype) + end + + alias set_callback set_cdecl_callback + alias remove_callback remove_cdecl_callback +end diff --git a/trunk/ext/dl/lib/dl/cparser.rb b/trunk/ext/dl/lib/dl/cparser.rb new file mode 100644 index 0000000000..c897d1b69f --- /dev/null +++ b/trunk/ext/dl/lib/dl/cparser.rb @@ -0,0 +1,109 @@ +module DL + module CParser + def parse_struct_signature(signature, tymap=nil) + if( signature.is_a?(String) ) + signature = signature.split("\s*,\s*") + end + mems = [] + tys = [] + signature.each{|msig| + tks = msig.split(/\s+(\*)?/) + ty = tks[0..-2].join(" ") + member = tks[-1] + + case ty + when /\[(\d+)\]/ + n = $1.to_i + ty.gsub!(/\s*\[\d+\]/,"") + ty = [ty, n] + when /\[\]/ + ty.gsub!(/\s*\[\]/, "*") + end + + case member + when /\[(\d+)\]/ + ty = [ty, $1.to_i] + member.gsub!(/\s*\[\d+\]/,"") + when /\[\]/ + ty = ty + "*" + member.gsub!(/\s*\[\]/, "") + end + + mems.push(member) + tys.push(parse_ctype(ty,tymap)) + } + return tys, mems + end + + def parse_signature(signature, tymap=nil) + tymap ||= {} + signature = signature.gsub(/\s+/, " ").strip + case signature + when /^([\d\w@\*_\s]+)\(([\d\w\*_\s\,\[\]]*)\)$/ + ret = $1 + args = $2 + ret = ret.split(/\s+/) + args = args.split(/\s*,\s*/) + func = ret.pop + if( func =~ /^\*/ ) + func.gsub!(/^\*+/,"") + ret.push("*") + end + ret = ret.join(" ") + return [func, parse_ctype(ret, tymap), args.collect{|arg| parse_ctype(arg, tymap)}] + else + raise(RuntimeError,"can't parse the function prototype: #{proto}") + end + end + + def parse_ctype(ty, tymap=nil) + tymap ||= {} + case ty + when Array + return [parse_ctype(ty[0], tymap), ty[1]] + when "void" + return TYPE_VOID + when "char" + return TYPE_CHAR + when "unsigned char" + return -TYPE_CHAR + when "short" + return TYPE_SHORT + when "unsigned short" + return -TYPE_SHORT + when "int" + return TYPE_INT + when "unsigned int" + return -TYPE_INT + when "long" + return TYPE_LONG + when "unsigned long" + return -TYPE_LONG + when "long long" + if( defined?(TYPE_LONG_LONG) ) + return TYPE_LONG_LONG + else + raise(RuntimeError, "unsupported type: #{ty}") + end + when "unsigned long long" + if( defined?(TYPE_LONG_LONG) ) + return -TYPE_LONG_LONG + else + raise(RuntimeError, "unsupported type: #{ty}") + end + when "float" + return TYPE_FLOAT + when "double" + return TYPE_DOUBLE + when /\*/, /\[\s*\]/ + return TYPE_VOIDP + else + if( tymap[ty] ) + return parse_ctype(tymap[ty], tymap) + else + raise(DLError, "unknown type: #{ty}") + end + end + end + end +end diff --git a/trunk/ext/dl/lib/dl/func.rb b/trunk/ext/dl/lib/dl/func.rb new file mode 100644 index 0000000000..b29aebcc8b --- /dev/null +++ b/trunk/ext/dl/lib/dl/func.rb @@ -0,0 +1,141 @@ +require 'dl' +require 'dl/callback' +require 'dl/stack' +require 'dl/value' +require 'thread' + +module DL + class Function + include DL + include ValueUtil + + def initialize(cfunc, argtypes, &proc) + @cfunc = cfunc + @stack = Stack.new(argtypes.collect{|ty| ty.abs}) + if( @cfunc.ctype < 0 ) + @cfunc.ctype = @cfunc.ctype.abs + @unsigned = true + end + if( proc ) + bind(&proc) + end + end + + def to_i() + @cfunc.to_i + end + + def call(*args, &block) + funcs = [] + args = wrap_args(args, @stack.types, funcs, &block) + r = @cfunc.call(@stack.pack(args)) + funcs.each{|f| f.unbind_at_call()} + return wrap_result(r) + end + + def wrap_result(r) + case @cfunc.ctype + when TYPE_VOIDP + r = CPtr.new(r) + else + if( @unsigned ) + r = unsigned_value(r, @cfunc.ctype) + end + end + r + end + + def bind(&block) + if( !block ) + raise(RuntimeError, "block must be given.") + end + if( @cfunc.ptr == 0 ) + cb = Proc.new{|*args| + ary = @stack.unpack(args) + @stack.types.each_with_index{|ty, idx| + case ty + when TYPE_VOIDP + ary[idx] = CPtr.new(ary[idx]) + end + } + r = block.call(*ary) + wrap_arg(r, @cfunc.ctype, []) + } + case @cfunc.calltype + when :cdecl + @cfunc.ptr = set_cdecl_callback(@cfunc.ctype, @stack.size, &cb) + when :stdcall + @cfunc.ptr = set_stdcall_callback(@cfunc.ctype, @stack.size, &cb) + else + raise(RuntimeError, "unsupported calltype: #{@cfunc.calltype}") + end + if( @cfunc.ptr == 0 ) + raise(RuntimeException, "can't bind C function.") + end + end + end + + def unbind() + if( @cfunc.ptr != 0 ) + case @cfunc.calltype + when :cdecl + remove_cdecl_callback(@cfunc.ptr, @cfunc.ctype) + when :stdcall + remove_stdcall_callback(@cfunc.ptr, @cfunc.ctype) + else + raise(RuntimeError, "unsupported calltype: #{@cfunc.calltype}") + end + @cfunc.ptr = 0 + end + end + + def bind_at_call(&block) + bind(&block) + end + + def unbind_at_call() + end + end + + class TempFunction < Function + def bind_at_call(&block) + bind(&block) + end + + def unbind_at_call() + unbind() + end + end + + class CarriedFunction < Function + def initialize(cfunc, argtypes, n) + super(cfunc, argtypes) + @carrier = [] + @index = n + @mutex = Mutex.new + end + + def create_carrier(data) + ary = [] + userdata = [ary, data] + @mutex.lock() + @carrier.push(userdata) + return dlwrap(userdata) + end + + def bind_at_call(&block) + userdata = @carrier[-1] + userdata[0].push(block) + bind{|*args| + ptr = args[@index] + if( !ptr ) + raise(RuntimeError, "The index of userdata should be lower than #{args.size}.") + end + userdata = dlunwrap(Integer(ptr)) + args[@index] = userdata[1] + userdata[0][0].call(*args) + } + @mutex.unlock() + end + end +end diff --git a/trunk/ext/dl/lib/dl/import.rb b/trunk/ext/dl/lib/dl/import.rb new file mode 100644 index 0000000000..f6fb35944d --- /dev/null +++ b/trunk/ext/dl/lib/dl/import.rb @@ -0,0 +1,215 @@ +require 'dl' +require 'dl/func.rb' +require 'dl/struct.rb' +require 'dl/cparser.rb' + +module DL + class CompositeHandler + def initialize(handlers) + @handlers = handlers + end + + def handlers() + @handlers + end + + def sym(symbol) + @handlers.each{|handle| + if( handle ) + begin + addr = handle.sym(symbol) + return addr + rescue DLError + end + end + } + return nil + end + + def [](symbol) + sym(symbol) + end + end + + module Importer + include DL + include CParser + extend Importer + + def dlload(*libs) + handles = libs.collect{|lib| + case lib + when nil + nil + when Handle + lib + when Importer + lib.handlers + else + begin + DL.dlopen(lib) + rescue DLError + raise(DLError, "can't load #{lib}") + end + end + }.flatten() + @handler = CompositeHandler.new(handles) + @func_map = {} + @type_alias = {} + end + + def typealias(alias_type, orig_type) + @type_alias[alias_type] = orig_type + end + + def sizeof(ty) + case ty + when String + ty = parse_ctype(ty, @type_alias).abs() + case ty + when TYPE_CHAR + return SIZEOF_CHAR + when TYPE_SHORT + return SIZEOF_SHORT + when TYPE_INT + return SIZEOF_INT + when TYPE_LONG + return SIZEOF_LONG + when TYPE_LONG_LONG + return SIZEOF_LONG_LON + when TYPE_FLOAT + return SIZEOF_FLOAT + when TYPE_DOUBLE + return SIZEOF_DOUBLE + when TYPE_VOIDP + return SIZEOF_VOIDP + else + raise(DLError, "unknown type: #{ty}") + end + when Class + if( ty.instance_methods().include?("to_ptr") ) + return ty.size() + end + end + return CPtr[ty].size() + end + + def parse_bind_options(opts) + h = {} + prekey = nil + while( opt = opts.shift() ) + case opt + when :stdcall, :cdecl + h[:call_type] = opt + when :carried, :temp, :temporal, :bind + h[:callback_type] = opt + h[:carrier] = opts.shift() + else + h[opt] = true + end + end + h + end + private :parse_bind_options + + def extern(signature, *opts) + symname, ctype, argtype = parse_signature(signature, @type_alias) + opt = parse_bind_options(opts) + f = import_function(symname, ctype, argtype, opt[:call_type]) + name = symname.gsub(/@.+/,'') + @func_map[name] = f + # define_method(name){|*args,&block| f.call(*args,&block)} + module_eval(<<-EOS) + def #{name}(*args, &block) + @func_map['#{name}'].call(*args,&block) + end + EOS + module_function(name) + f + end + + def bind(signature, *opts, &blk) + name, ctype, argtype = parse_signature(signature, @type_alias) + h = parse_bind_options(opts) + case h[:callback_type] + when :bind, nil + f = bind_function(name, ctype, argtype, h[:call_type], &blk) + when :temp, :temporal + f = create_temp_function(name, ctype, argtype, h[:call_type]) + when :carried + f = create_carried_function(name, ctype, argtype, h[:call_type], h[:carrier]) + else + raise(RuntimeError, "unknown callback type: #{h[:callback_type]}") + end + @func_map[name] = f + #define_method(name){|*args,&block| f.call(*args,&block)} + module_eval(<<-EOS) + def #{name}(*args,&block) + @func_map['#{name}'].call(*args,&block) + end + EOS + module_function(name) + f + end + + def struct(signature) + tys, mems = parse_struct_signature(signature, @type_alias) + DL::CStructBuilder.create(CStruct, tys, mems) + end + + def union(signature) + tys, mems = parse_struct_signature(signature, @type_alias) + DL::CStructBuilder.create(CUnion, tys, mems) + end + + def [](name) + @func_map[name] + end + + def create_value(ty, val=nil) + s = struct([ty + " value"]) + ptr = s.malloc() + if( val ) + ptr.value = val + end + return ptr + end + alias value create_value + + def import_value(ty, addr) + s = struct([ty + " value"]) + ptr = s.new(addr) + return ptr + end + + def import_symbol(name) + addr = @handler.sym(name) + if( !addr ) + raise(DLError, "cannot find the symbol: #{name}") + end + CPtr.new(addr) + end + + def import_function(name, ctype, argtype, call_type = nil) + addr = @handler.sym(name) + if( !addr ) + raise(DLError, "cannot find the function: #{name}()") + end + Function.new(CFunc.new(addr, ctype, name, call_type || :cdecl), argtype) + end + + def bind_function(name, ctype, argtype, call_type = nil, &block) + f = Function.new(CFunc.new(0, ctype, name, call_type || :cdecl), argtype) + f.bind(&block) + f + end + + def create_temp_function(name, ctype, argtype, call_type = nil) + TempFunction.new(CFunc.new(0, ctype, name, call_type || :cdecl), argtype) + end + + def create_carried_function(name, ctype, argtype, call_type = nil, n = 0) + CarriedFunction.new(CFunc.new(0, ctype, name, call_type || :cdecl), argtype, n) + end + end +end diff --git a/trunk/ext/dl/lib/dl/pack.rb b/trunk/ext/dl/lib/dl/pack.rb new file mode 100644 index 0000000000..ad91833b3a --- /dev/null +++ b/trunk/ext/dl/lib/dl/pack.rb @@ -0,0 +1,173 @@ +require 'dl' + +module DL + module PackInfo + if( defined?(TYPE_LONG_LONG) ) + ALIGN_MAP = { + TYPE_VOIDP => ALIGN_VOIDP, + TYPE_CHAR => ALIGN_CHAR, + TYPE_SHORT => ALIGN_SHORT, + TYPE_INT => ALIGN_INT, + TYPE_LONG => ALIGN_LONG, + TYPE_LONG_LONG => ALIGN_LONG_LONG, + TYPE_FLOAT => ALIGN_FLOAT, + TYPE_DOUBLE => ALIGN_DOUBLE, + -TYPE_CHAR => ALIGN_CHAR, + -TYPE_SHORT => ALIGN_SHORT, + -TYPE_INT => ALIGN_INT, + -TYPE_LONG => ALIGN_LONG, + -TYPE_LONG_LONG => ALIGN_LONG_LONG, + } + + PACK_MAP = { + TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG) ? "q" : "l!"), + TYPE_CHAR => "c", + TYPE_SHORT => "s!", + TYPE_INT => "i!", + TYPE_LONG => "l!", + TYPE_LONG_LONG => "q", + TYPE_FLOAT => "f", + TYPE_DOUBLE => "d", + -TYPE_CHAR => "c", + -TYPE_SHORT => "s!", + -TYPE_INT => "i!", + -TYPE_LONG => "l!", + -TYPE_LONG_LONG => "q", + } + + SIZE_MAP = { + TYPE_VOIDP => SIZEOF_VOIDP, + TYPE_CHAR => SIZEOF_CHAR, + TYPE_SHORT => SIZEOF_SHORT, + TYPE_INT => SIZEOF_INT, + TYPE_LONG => SIZEOF_LONG, + TYPE_LONG_LONG => SIZEOF_LONG_LONG, + TYPE_FLOAT => SIZEOF_FLOAT, + TYPE_DOUBLE => SIZEOF_DOUBLE, + -TYPE_CHAR => SIZEOF_CHAR, + -TYPE_SHORT => SIZEOF_SHORT, + -TYPE_INT => SIZEOF_INT, + -TYPE_LONG => SIZEOF_LONG, + -TYPE_LONG_LONG => SIZEOF_LONG_LONG, + } + else + ALIGN_MAP = { + TYPE_VOIDP => ALIGN_VOIDP, + TYPE_CHAR => ALIGN_CHAR, + TYPE_SHORT => ALIGN_SHORT, + TYPE_INT => ALIGN_INT, + TYPE_LONG => ALIGN_LONG, + TYPE_FLOAT => ALIGN_FLOAT, + TYPE_DOUBLE => ALIGN_DOUBLE, + -TYPE_CHAR => ALIGN_CHAR, + -TYPE_SHORT => ALIGN_SHORT, + -TYPE_INT => ALIGN_INT, + -TYPE_LONG => ALIGN_LONG, + } + + PACK_MAP = { + TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG) ? "q" : "l!"), + TYPE_CHAR => "c", + TYPE_SHORT => "s!", + TYPE_INT => "i!", + TYPE_LONG => "l!", + TYPE_FLOAT => "f", + TYPE_DOUBLE => "d", + -TYPE_CHAR => "c", + -TYPE_SHORT => "s!", + -TYPE_INT => "i!", + -TYPE_LONG => "l!", + } + + SIZE_MAP = { + TYPE_VOIDP => SIZEOF_VOIDP, + TYPE_CHAR => SIZEOF_CHAR, + TYPE_SHORT => SIZEOF_SHORT, + TYPE_INT => SIZEOF_INT, + TYPE_LONG => SIZEOF_LONG, + TYPE_FLOAT => SIZEOF_FLOAT, + TYPE_DOUBLE => SIZEOF_DOUBLE, + -TYPE_CHAR => SIZEOF_CHAR, + -TYPE_SHORT => SIZEOF_SHORT, + -TYPE_INT => SIZEOF_INT, + -TYPE_LONG => SIZEOF_LONG, + } + end + + def align(addr, align) + d = addr % align + if( d == 0 ) + addr + else + addr + (align - d) + end + end + module_function :align + end + + class Packer + include PackInfo + + def Packer.[](*types) + Packer.new(types) + end + + def initialize(types) + parse_types(types) + end + + def size() + @size + end + + def pack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.pack(@template) + when SIZEOF_LONG + ary.pack(@template) + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + def unpack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.join().unpack(@template) + when SIZEOF_LONG_LONG + ary.join().unpack(@template) + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + private + + def parse_types(types) + @template = "" + addr = 0 + types.each{|t| + orig_addr = addr + if( t.is_a?(Array) ) + addr = align(orig_addr, ALIGN_MAP[TYPE_VOIDP]) + else + addr = align(orig_addr, ALIGN_MAP[t]) + end + d = addr - orig_addr + if( d > 0 ) + @template << "x#{d}" + end + if( t.is_a?(Array) ) + @template << (PACK_MAP[t[0]] * t[1]) + addr += (SIZE_MAP[t[0]] * t[1]) + else + @template << PACK_MAP[t] + addr += SIZE_MAP[t] + end + } + addr = align(addr, ALIGN_MAP[TYPE_VOIDP]) + @size = addr + end + end +end diff --git a/trunk/ext/dl/lib/dl/stack.rb b/trunk/ext/dl/lib/dl/stack.rb new file mode 100644 index 0000000000..9daf089775 --- /dev/null +++ b/trunk/ext/dl/lib/dl/stack.rb @@ -0,0 +1,140 @@ +require 'dl' + +module DL + class Stack + def Stack.[](*types) + Stack.new(types) + end + + def initialize(types) + parse_types(types) + end + + def size() + @size + end + + def types() + @types + end + + def pack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.pack(@template).unpack('l!*') + when SIZEOF_LONG_LONG + ary.pack(@template).unpack('q*') + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + def unpack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.pack('l!*').unpack(@template) + when SIZEOF_LONG_LONG + ary.pack('q*').unpack(@template) + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + private + + def align(addr, align) + d = addr % align + if( d == 0 ) + addr + else + addr + (align - d) + end + end + +if( defined?(TYPE_LONG_LONG) ) + ALIGN_MAP = { + TYPE_VOIDP => ALIGN_VOIDP, + TYPE_CHAR => ALIGN_VOIDP, + TYPE_SHORT => ALIGN_VOIDP, + TYPE_INT => ALIGN_VOIDP, + TYPE_LONG => ALIGN_VOIDP, + TYPE_LONG_LONG => ALIGN_LONG_LONG, + TYPE_FLOAT => ALIGN_FLOAT, + TYPE_DOUBLE => ALIGN_DOUBLE, + } + + PACK_MAP = { + TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG)? "q" : "l!"), + TYPE_CHAR => "c", + TYPE_SHORT => "s!", + TYPE_INT => "i!", + TYPE_LONG => "l!", + TYPE_LONG_LONG => "q", + TYPE_FLOAT => "f", + TYPE_DOUBLE => "d", + } + + SIZE_MAP = { + TYPE_VOIDP => SIZEOF_VOIDP, + TYPE_CHAR => SIZEOF_CHAR, + TYPE_SHORT => SIZEOF_SHORT, + TYPE_INT => SIZEOF_INT, + TYPE_LONG => SIZEOF_LONG, + TYPE_LONG_LONG => SIZEOF_LONG_LONG, + TYPE_FLOAT => SIZEOF_FLOAT, + TYPE_DOUBLE => SIZEOF_DOUBLE, + } +else + ALIGN_MAP = { + TYPE_VOIDP => ALIGN_VOIDP, + TYPE_CHAR => ALIGN_VOIDP, + TYPE_SHORT => ALIGN_VOIDP, + TYPE_INT => ALIGN_VOIDP, + TYPE_LONG => ALIGN_VOIDP, + TYPE_FLOAT => ALIGN_FLOAT, + TYPE_DOUBLE => ALIGN_DOUBLE, + } + + PACK_MAP = { + TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG)? "q" : "l!"), + TYPE_CHAR => "c", + TYPE_SHORT => "s!", + TYPE_INT => "i!", + TYPE_LONG => "l!", + TYPE_FLOAT => "f", + TYPE_DOUBLE => "d", + } + + SIZE_MAP = { + TYPE_VOIDP => SIZEOF_VOIDP, + TYPE_CHAR => SIZEOF_CHAR, + TYPE_SHORT => SIZEOF_SHORT, + TYPE_INT => SIZEOF_INT, + TYPE_LONG => SIZEOF_LONG, + TYPE_FLOAT => SIZEOF_FLOAT, + TYPE_DOUBLE => SIZEOF_DOUBLE, + } +end + + def parse_types(types) + @types = types + @template = "" + addr = 0 + types.each{|t| + orig_addr = addr + addr = align(orig_addr, ALIGN_MAP[t]) + d = addr - orig_addr + if( d > 0 ) + @template << "x#{d}" + end + @template << PACK_MAP[t] + addr += SIZE_MAP[t] + } + if( addr % SIZEOF_VOIDP == 0 ) + @size = addr / SIZEOF_VOIDP + else + @size = (addr / SIZEOF_VOIDP) + 1 + end + end + end +end diff --git a/trunk/ext/dl/lib/dl/struct.rb b/trunk/ext/dl/lib/dl/struct.rb new file mode 100644 index 0000000000..4272b3960c --- /dev/null +++ b/trunk/ext/dl/lib/dl/struct.rb @@ -0,0 +1,213 @@ +require 'dl' +require 'dl/pack.rb' + +module DL + class CStruct + def CStruct.entity_class() + CStructEntity + end + end + + class CUnion + def CUnion.entity_class() + CUnionEntity + end + end + + module CStructBuilder + def create(klass, types, members) + new_class = Class.new(klass){ + define_method(:initialize){|addr| + @entity = klass.entity_class.new(addr, types) + @entity.assign_names(members) + } + define_method(:to_ptr){ @entity } + define_method(:to_i){ @entity.to_i } + members.each{|name| + define_method(name){ @entity[name] } + define_method(name + "="){|val| @entity[name] = val } + } + } + size = klass.entity_class.size(types) + new_class.module_eval(<<-EOS) + def new_class.size() + #{size} + end + def new_class.malloc() + addr = DL.malloc(#{size}) + new(addr) + end + EOS + return new_class + end + module_function :create + end + + class CStructEntity < CPtr + include PackInfo + include ValueUtil + + def CStructEntity.malloc(types, func = nil) + addr = DL.malloc(CStructEntity.size(types)) + CStructEntity.new(addr, types, func) + end + + def CStructEntity.size(types) + offset = 0 + max_align = 0 + types.each_with_index{|t,i| + orig_offset = offset + if( t.is_a?(Array) ) + align = PackInfo::ALIGN_MAP[t[0]] + offset = PackInfo.align(orig_offset, align) + size = offset - orig_offset + offset += (PackInfo::SIZE_MAP[t[0]] * t[1]) + else + align = PackInfo::ALIGN_MAP[t] + offset = PackInfo.align(orig_offset, align) + size = offset - orig_offset + offset += PackInfo::SIZE_MAP[t] + end + if (max_align < align) + max_align = align + end + } + offset = PackInfo.align(offset, max_align) + offset + end + + def initialize(addr, types, func = nil) + set_ctypes(types) + super(addr, @size, func) + end + + def assign_names(members) + @members = members + end + + def set_ctypes(types) + @ctypes = types + @offset = [] + offset = 0 + max_align = 0 + types.each_with_index{|t,i| + orig_offset = offset + if( t.is_a?(Array) ) + align = ALIGN_MAP[t[0]] + else + align = ALIGN_MAP[t] + end + offset = PackInfo.align(orig_offset, align) + size = offset - orig_offset + @offset[i] = offset + if( t.is_a?(Array) ) + offset += (SIZE_MAP[t[0]] * t[1]) + else + offset += SIZE_MAP[t] + end + if (max_align < align) + max_align = align + end + } + offset = PackInfo.align(offset, max_align) + @size = offset + end + + def [](name) + idx = @members.index(name) + if( idx.nil? ) + raise(ArgumentError, "no such member: #{name}") + end + ty = @ctypes[idx] + if( ty.is_a?(Array) ) + r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + else + r = super(@offset[idx], SIZE_MAP[ty.abs]) + end + packer = Packer.new([ty]) + val = packer.unpack([r]) + case ty + when Array + case ty[0] + when TYPE_VOIDP + val = val.collect{|v| CPtr.new(v)} + end + when TYPE_VOIDP + val = CPtr.new(val[0]) + else + val = val[0] + end + if( ty.is_a?(Integer) && (ty < 0) ) + return unsigned_value(val, ty) + elsif( ty.is_a?(Array) && (ty[0] < 0) ) + return val.collect{|v| unsigned_value(v,ty[0])} + else + return val + end + end + + def []=(name, val) + idx = @members.index(name) + if( idx.nil? ) + raise(ArgumentError, "no such member: #{name}") + end + ty = @ctypes[idx] + packer = Packer.new([ty]) + val = wrap_arg(val, ty, []) + buff = packer.pack([val].flatten()) + super(@offset[idx], buff.size, buff) + if( ty.is_a?(Integer) && (ty < 0) ) + return unsigned_value(val, ty) + elsif( ty.is_a?(Array) && (ty[0] < 0) ) + return val.collect{|v| unsigned_value(v,ty[0])} + else + return val + end + end + + def to_s() + super(@size) + end + end + + class CUnionEntity < CStructEntity + include PackInfo + + def CUnionEntity.malloc(types, func=nil) + addr = DL.malloc(CUnionEntity.size(types)) + CUnionEntity.new(addr, types, func) + end + + def CUnionEntity.size(types) + size = 0 + types.each_with_index{|t,i| + if( t.is_a?(Array) ) + tsize = PackInfo::SIZE_MAP[t[0]] * t[1] + else + tsize = PackInfo::SIZE_MAP[t] + end + if( tsize > size ) + size = tsize + end + } + end + + def set_ctypes(types) + @ctypes = types + @offset = [] + @size = 0 + types.each_with_index{|t,i| + @offset[i] = 0 + if( t.is_a?(Array) ) + size = SIZE_MAP[t[0]] * t[1] + else + size = SIZE_MAP[t] + end + if( size > @size ) + @size = size + end + } + end + end +end + diff --git a/trunk/ext/dl/lib/dl/types.rb b/trunk/ext/dl/lib/dl/types.rb new file mode 100644 index 0000000000..b85ac890cd --- /dev/null +++ b/trunk/ext/dl/lib/dl/types.rb @@ -0,0 +1,40 @@ +module DL + module Win32Types + def included(m) + m.module_eval{ + typealias "DWORD", "unsigned long" + typealias "PDWORD", "unsigned long *" + typealias "WORD", "unsigned short" + typealias "PWORD", "unsigned short *" + typealias "BOOL", "int" + typealias "ATOM", "int" + typealias "BYTE", "unsigned char" + typealias "PBYTE", "unsigned char *" + typealias "UINT", "unsigned int" + typealias "ULONG", "unsigned long" + typealias "UCHAR", "unsigned char" + typealias "HANDLE", "unsigned long" + typealias "PHANDLE", "void*" + typealias "PVOID", "void*" + typealias "LPCSTR", "char*" + typealias "LPSTR", "char*" + typealias "HINSTANCE", "unsigned int" + typealias "HDC", "unsigned int" + typealias "HWND", "unsigned int" + } + end + module_function :included + end + + module BasicTypes + def included(m) + m.module_eval{ + typealias "uint", "unsigned int" + typealias "u_int", "unsigned int" + typealias "ulong", "unsigned long" + typealias "u_long", "unsigned long" + } + end + module_function :included + end +end diff --git a/trunk/ext/dl/lib/dl/value.rb b/trunk/ext/dl/lib/dl/value.rb new file mode 100644 index 0000000000..aa7e0dd325 --- /dev/null +++ b/trunk/ext/dl/lib/dl/value.rb @@ -0,0 +1,108 @@ +require 'dl' + +module DL + module ValueUtil + def unsigned_value(val, ty) + case ty.abs + when TYPE_CHAR + [val].pack("c").unpack("C")[0] + when TYPE_SHORT + [val].pack("s!").unpack("S!")[0] + when TYPE_INT + [val].pack("i!").unpack("I!")[0] + when TYPE_LONG + [val].pack("l!").unpack("L!")[0] + when TYPE_LONG_LONG + [val].pack("q!").unpack("Q!")[0] + else + val + end + end + + def signed_value(val, ty) + case ty.abs + when TYPE_CHAR + [val].pack("C").unpack("c")[0] + when TYPE_SHORT + [val].pack("S!").unpack("s!")[0] + when TYPE_INT + [val].pack("I!").unpack("i!")[0] + when TYPE_LONG + [val].pack("L!").unpack("l!")[0] + when TYPE_LONG_LONG + [val].pack("Q!").unpack("q!")[0] + else + val + end + end + + def wrap_args(args, tys, funcs, &block) + result = [] + tys ||= [] + args.each_with_index{|arg, idx| + result.push(wrap_arg(arg, tys[idx], funcs, &block)) + } + result + end + + def wrap_arg(arg, ty, funcs, &block) + funcs ||= [] + case arg + when CPtr + return arg.to_i + when IO + case ty + when TYPE_VOIDP + return CPtr[arg].to_i + else + return arg.to_i + end + when Function + if( block ) + arg.bind_at_call(&block) + funcs.push(arg) + end + return arg.to_i + when String + if( ty.is_a?(Array) ) + return arg.unpack('C*') + else + case SIZEOF_VOIDP + when SIZEOF_LONG + return [arg].pack("p").unpack("l!")[0] + when SIZEOF_LONG_LONG + return [arg].pack("p").unpack("q")[0] + else + raise(RuntimeError, "sizeof(void*)?") + end + end + when Float, Integer + return arg + when Array + if( ty.is_a?(Array) ) # used only by struct + case ty[0] + when TYPE_VOIDP + return arg.collect{|v| Integer(v)} + when TYPE_CHAR + if( arg.is_a?(String) ) + return val.unpack('C*') + end + end + return arg + else + return arg + end + else + if( arg.respond_to?(:to_ptr) ) + return arg.to_ptr.to_i + else + begin + return Integer(arg) + rescue + raise(ArgumentError, "unknown argument type: #{arg.class}") + end + end + end + end + end +end diff --git a/trunk/ext/dl/mkcallback.rb b/trunk/ext/dl/mkcallback.rb new file mode 100644 index 0000000000..21fb177f8b --- /dev/null +++ b/trunk/ext/dl/mkcallback.rb @@ -0,0 +1,189 @@ +$out ||= $stdout +$dl_h = ARGV[0] || "dl.h" + +# import DLSTACK_SIZE, DLSTACK_ARGS and so on +File.open($dl_h){|f| + pre = "" + f.each{|line| + line.chop! + if( line[-1] == ?\ ) + line.chop! + line.concat(" ") + pre += line + next + end + if( pre.size > 0 ) + line = pre + line + pre = "" + end + case line + when /#define\s+DLSTACK_SIZE\s+\(?(\d+)\)?/ + DLSTACK_SIZE = $1.to_i + when /#define\s+DLSTACK_ARGS\s+(.+)/ + DLSTACK_ARGS = $1.to_i + when /#define\s+DLTYPE_([A-Z_]+)\s+\(?(\d+)\)?/ + eval("#{$1} = #{$2}") + when /#define\s+MAX_DLTYPE\s+\(?(\d+)\)?/ + MAX_DLTYPE = $1.to_i + when /#define\s+MAX_CALLBACK\s+\(?(\d+)\)?/ + MAX_CALLBACK = $1.to_i + end + } +} + +CDECL = "cdecl" +STDCALL = "stdcall" + +CALLTYPES = [CDECL, STDCALL] + +DLTYPE = { + VOID => { + :name => 'void', + :type => 'void', + :conv => nil, + }, + CHAR => { + :name => 'char', + :type => 'char', + :conv => 'NUM2CHR(%s)' + }, + SHORT => { + :name => 'short', + :type => 'short', + :conv => 'NUM2INT(%s)', + }, + INT => { + :name => 'int', + :type => 'int', + :conv => 'NUM2INT(%s)', + }, + LONG => { + :name => 'long', + :type => 'long', + :conv => 'NUM2LONG(%s)', + }, + LONG_LONG => { + :name => 'long_long', + :type => 'LONG_LONG', + :conv => 'NUM2LL(%s)', + }, + FLOAT => { + :name => 'float', + :type => 'float', + :conv => 'RFLOAT_VALUE(%s)', + }, + DOUBLE => { + :name => 'double', + :type => 'double', + :conv => 'RFLOAT_VALUE(%s)', + }, + VOIDP => { + :name => 'ptr', + :type => 'void *', + :conv => 'NUM2PTR(%s)', + }, +} + + +def func_name(ty, argc, n, calltype) + "rb_dl_callback_#{DLTYPE[ty][:name]}_#{argc}_#{n}_#{calltype}" +end + +$out << (<<EOS) +VALUE rb_DLCdeclCallbackAddrs, rb_DLCdeclCallbackProcs; +VALUE rb_DLStdcallCallbackAddrs, rb_DLStdcallCallbackProcs; +/*static void *cdecl_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/ +/*static void *stdcall_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/ +static ID cb_call; +EOS + +for calltype in CALLTYPES + case calltype + when CDECL + proc_entry = "rb_DLCdeclCallbackProcs" + when STDCALL + proc_entry = "rb_DLStdcallCallbackProcs" + else + raise "unknown calltype: #{calltype}" + end + for ty in 0..(MAX_DLTYPE-1) + for argc in 0..(DLSTACK_SIZE-1) + for n in 0..(MAX_CALLBACK-1) + $out << (<<-EOS) + +static #{DLTYPE[ty][:type]} +FUNC_#{calltype.upcase}(#{func_name(ty,argc,n,calltype)})(#{(0...argc).collect{|i| "DLSTACK_TYPE stack" + i.to_s}.join(", ")}) +{ + VALUE ret, cb#{argc > 0 ? ", args[#{argc}]" : ""}; +#{ + (0...argc).collect{|i| + " args[%d] = LONG2NUM(stack%d);" % [i,i] + }.join("\n") +} + cb = rb_ary_entry(rb_ary_entry(#{proc_entry}, #{ty}), #{(n * DLSTACK_SIZE) + argc}); + ret = rb_funcall2(cb, cb_call, #{argc}, #{argc > 0 ? 'args' : 'NULL'}); + return #{DLTYPE[ty][:conv] ? DLTYPE[ty][:conv] % "ret" : ""}; +} + + EOS + end + end + end +end + +$out << (<<EOS) +static void +rb_dl_init_callbacks() +{ + cb_call = rb_intern("call"); + + rb_DLCdeclCallbackProcs = rb_ary_new(); + rb_DLCdeclCallbackAddrs = rb_ary_new(); + rb_DLStdcallCallbackProcs = rb_ary_new(); + rb_DLStdcallCallbackAddrs = rb_ary_new(); + rb_define_const(rb_mDL, "CdeclCallbackProcs", rb_DLCdeclCallbackProcs); + rb_define_const(rb_mDL, "CdeclCallbackAddrs", rb_DLCdeclCallbackAddrs); + rb_define_const(rb_mDL, "StdcallCallbackProcs", rb_DLStdcallCallbackProcs); + rb_define_const(rb_mDL, "StdcallCallbackAddrs", rb_DLStdcallCallbackAddrs); +#{ + (0...MAX_DLTYPE).collect{|ty| + sprintf(" rb_ary_push(rb_DLCdeclCallbackProcs, rb_ary_new3(%d,%s));", + MAX_CALLBACK * DLSTACK_SIZE, + (0...MAX_CALLBACK).collect{ + (0...DLSTACK_SIZE).collect{ "Qnil" }.join(",") + }.join(",")) + }.join("\n") +} +#{ + (0...MAX_DLTYPE).collect{|ty| + sprintf(" rb_ary_push(rb_DLCdeclCallbackAddrs, rb_ary_new3(%d,%s));", + MAX_CALLBACK * DLSTACK_SIZE, + (0...MAX_CALLBACK).collect{|i| + (0...DLSTACK_SIZE).collect{|argc| + "PTR2NUM(%s)" % func_name(ty,argc,i,CDECL) + }.join(",") + }.join(",")) + }.join("\n") +} +#{ + (0...MAX_DLTYPE).collect{|ty| + sprintf(" rb_ary_push(rb_DLStdcallCallbackProcs, rb_ary_new3(%d,%s));", + MAX_CALLBACK * DLSTACK_SIZE, + (0...MAX_CALLBACK).collect{ + (0...DLSTACK_SIZE).collect{ "Qnil" }.join(",") + }.join(",")) + }.join("\n") +} +#{ + (0...MAX_DLTYPE).collect{|ty| + sprintf(" rb_ary_push(rb_DLStdcallCallbackAddrs, rb_ary_new3(%d,%s));", + MAX_CALLBACK * DLSTACK_SIZE, + (0...MAX_CALLBACK).collect{|i| + (0...DLSTACK_SIZE).collect{|argc| + "PTR2NUM(%s)" % func_name(ty,argc,i,STDCALL) + }.join(",") + }.join(",")) + }.join("\n") +} +} +EOS diff --git a/trunk/ext/dl/test/test_all.rb b/trunk/ext/dl/test/test_all.rb new file mode 100644 index 0000000000..fb1f4939f9 --- /dev/null +++ b/trunk/ext/dl/test/test_all.rb @@ -0,0 +1,11 @@ +require 'test_base' +require 'dl/import' + +require 'test_dl2' +require 'test_func' +require 'test_import' + +case RUBY_PLATFORM +when /cygwin/, /mingw32/, /mswin32/, /bccwin32/ + require 'test_win32' +end diff --git a/trunk/ext/dl/test/test_base.rb b/trunk/ext/dl/test/test_base.rb new file mode 100644 index 0000000000..fc72db4e5e --- /dev/null +++ b/trunk/ext/dl/test/test_base.rb @@ -0,0 +1,52 @@ +require 'test/unit' +require 'dl' + +case RUBY_PLATFORM +when /cygwin/ + LIBC_SO = "cygwin1.dll" + LIBM_SO = "cygwin1.dll" +when /linux/ + LIBC_SO = "/lib/libc.so.6" + LIBM_SO = "/lib/libm.so.6" +when /mingw/, /mswin32/ + LIBC_SO = "msvcrt.dll" + LIBM_SO = "msvcrt.dll" +else + LIBC_SO = ARGV[0] + LIBM_SO = ARGV[1] + if( !(LIBC_SO && LIBM_SO) ) + $stderr.puts("#{$0} <libc> <libm>") + exit + end +end + +module DL + class TestBase < Test::Unit::TestCase + include Math + include DL + + def setup + @libc = dlopen(LIBC_SO) + @libm = dlopen(LIBM_SO) + end + + def assert_match(expected, actual, message="") + assert(expected === actual, message) + end + + def assert_positive(actual) + assert(actual > 0) + end + + def assert_zero(actual) + assert(actual == 0) + end + + def assert_negative(actual) + assert(actual < 0) + end + + def test_empty() + end + end +end diff --git a/trunk/ext/dl/test/test_dl2.rb b/trunk/ext/dl/test/test_dl2.rb new file mode 100644 index 0000000000..8c1f3e71b4 --- /dev/null +++ b/trunk/ext/dl/test/test_dl2.rb @@ -0,0 +1,111 @@ +require 'test_base.rb' +require 'dl/callback' + +module DL +class TestDL < TestBase + def test_call_int() + cfunc = CFunc.new(@libc['atoi'], TYPE_INT, 'atoi') + x = cfunc.call(["100"].pack("p").unpack("l!*")) + assert_equal(100, x) + + cfunc = CFunc.new(@libc['atoi'], TYPE_INT, 'atoi') + x = cfunc.call(["-100"].pack("p").unpack("l!*")) + assert_equal(-100, x) + end + + def test_call_long() + cfunc = CFunc.new(@libc['atol'], TYPE_LONG, 'atol') + x = cfunc.call(["100"].pack("p").unpack("l!*")) + assert_equal(100, x) + cfunc = CFunc.new(@libc['atol'], TYPE_LONG, 'atol') + x = cfunc.call(["-100"].pack("p").unpack("l!*")) + assert_equal(-100, x) + end + + def test_call_double() + cfunc = CFunc.new(@libc['atof'], TYPE_DOUBLE, 'atof') + x = cfunc.call(["0.1"].pack("p").unpack("l!*")) + assert_match(0.09..0.11, x) + + cfunc = CFunc.new(@libc['atof'], TYPE_DOUBLE, 'atof') + x = cfunc.call(["-0.1"].pack("p").unpack("l!*")) + assert_match(-0.11 .. -0.09, x) + end + + def test_sin() + cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin') + x = cfunc.call([3.14/2].pack("d").unpack("l!*")) + assert_equal(x, Math.sin(3.14/2)) + + cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin') + x = cfunc.call([-3.14/2].pack("d").unpack("l!*")) + assert_equal(Math.sin(-3.14/2), x) + end + + def test_strlen() + cfunc = CFunc.new(@libc['strlen'], TYPE_INT, 'strlen') + x = cfunc.call(["abc"].pack("p").unpack("l!*")) + assert_equal("abc".size, x) + end + + def test_strcpy() + buff = "xxxx" + str = "abc" + cfunc = CFunc.new(@libc['strcpy'], TYPE_VOIDP, 'strcpy') + x = cfunc.call([buff,str].pack("pp").unpack("l!*")) + assert_equal("abc\0", buff) + assert_equal("abc\0", CPtr.new(x).to_s(4)) + + buff = "xxxx" + str = "abc" + cfunc = CFunc.new(@libc['strncpy'], TYPE_VOIDP, 'strncpy') + x = cfunc.call([buff,str,3].pack("ppi").unpack("l!*")) + assert_equal("abcx", buff) + assert_equal("abcx", CPtr.new(x).to_s(4)) + + ptr = CPtr.malloc(4) + str = "abc" + cfunc = CFunc.new(@libc['strcpy'], TYPE_VOIDP, 'strcpy') + x = cfunc.call([ptr.to_i,str].pack("lp").unpack("l!*")) + assert_equal("abc\0", ptr[0,4]) + assert_equal("abc\0", CPtr.new(x).to_s(4)) + end + + def test_callback() + buff = "foobarbaz" + cb = set_callback(TYPE_INT,2){|x,y| CPtr.new(x)[0] <=> CPtr.new(y)[0]} + cfunc = CFunc.new(@libc['qsort'], TYPE_VOID, 'qsort') + cfunc.call([buff, buff.size, 1, cb].pack("pI!I!L!").unpack("l!*")) + assert_equal('aabbfoorz', buff) + end + + def test_dlwrap() + ary = [0,1,2,4,5] + addr = dlwrap(ary) + ary2 = dlunwrap(addr) + assert_equal(ary, ary2) + end + + def test_cptr() + check = Proc.new{|str,ptr| + assert_equal(str.size(), ptr.size()) + assert_equal(str, ptr.to_s()) + assert_equal(str[0,2], ptr.to_s(2)) + assert_equal(str[0,2], ptr[0,2]) + assert_equal(str[1,2], ptr[1,2]) + assert_equal(str[1,0], ptr[1,0]) + assert_equal(str[0].ord, ptr[0]) + assert_equal(str[1].ord, ptr[1]) + } + str = 'abc' + ptr = CPtr[str] + check.call(str, ptr) + str[0] = "c" + ptr[0] = "c".ord + check.call(str, ptr) + str[0,2] = "aa" + ptr[0,2] = "aa" + check.call(str, ptr) + end +end +end # module DL diff --git a/trunk/ext/dl/test/test_func.rb b/trunk/ext/dl/test/test_func.rb new file mode 100644 index 0000000000..64a32d9565 --- /dev/null +++ b/trunk/ext/dl/test/test_func.rb @@ -0,0 +1,62 @@ +require 'test_base' +require 'dl/func' + +module DL + class TestFunc < TestBase + def test_strcpy() + f = Function.new(CFunc.new(@libc['strcpy'], TYPE_VOIDP, 'strcpy'), + [TYPE_VOIDP, TYPE_VOIDP]) + buff = "000" + str = f.call(buff, "123") + assert_equal("123", buff) + assert_equal("123", str.to_s) + end + + def test_isdigit() + f = Function.new(CFunc.new(@libc['isdigit'], TYPE_INT, 'isdigit'), + [TYPE_INT]) + r1 = f.call(?1) + r2 = f.call(?2) + rr = f.call(?r) + assert_positive(r1) + assert_positive(r2) + assert_zero(rr) + end + + def test_atof() + f = Function.new(CFunc.new(@libc['atof'], TYPE_FLOAT, 'atof'), + [TYPE_VOIDP]) + r = f.call("12.34") + assert_match(12.00..13.00, r) + end + + def test_strtod() + f = Function.new(CFunc.new(@libc['strtod'], TYPE_DOUBLE, 'strtod'), + [TYPE_VOIDP, TYPE_VOIDP]) + buff1 = "12.34" + buff2 = " " + r = f.call(buff1, buff2) + assert_match(12.00..13.00, r) + end + + def test_qsort1() + cb = Function.new(CFunc.new(0, TYPE_INT, '<callback>qsort'), + [TYPE_VOIDP, TYPE_VOIDP]){|x,y| CPtr.new(x)[0] <=> CPtr.new(y)[0]} + qsort = Function.new(CFunc.new(@libc['qsort'], TYPE_VOID, 'qsort'), + [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP]) + buff = "9341" + qsort.call(buff, buff.size, 1, cb) + assert_equal("1349", buff) + end + + def test_qsort2() + cb = TempFunction.new(CFunc.new(0, TYPE_INT, '<callback>qsort'), + [TYPE_VOIDP, TYPE_VOIDP]) + qsort = Function.new(CFunc.new(@libc['qsort'], TYPE_VOID, 'qsort'), + [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP]) + buff = "9341" + qsort.call(buff, buff.size, 1, cb){|x,y| CPtr.new(x)[0] <=> CPtr.new(y)[0]} + assert_equal("1349", buff) + end + end +end diff --git a/trunk/ext/dl/test/test_import.rb b/trunk/ext/dl/test/test_import.rb new file mode 100644 index 0000000000..f0694637eb --- /dev/null +++ b/trunk/ext/dl/test/test_import.rb @@ -0,0 +1,154 @@ +require 'test_base' +require 'dl/import' + +module DL + module LIBC + extend Importer + dlload LIBC_SO, LIBM_SO + + typealias 'string', 'char*' + typealias 'FILE*', 'void*' + + extern "void *strcpy(char*, char*)" + extern "int isdigit(int)" + extern "float atof(string)" + extern "unsigned long strtoul(char*, char **, int)" + extern "int qsort(void*, int, int, void*)" + extern "void fprintf(FILE*, char*)" + extern "int gettimeofday(timeval*, timezone*)" rescue nil + + QsortCallback = bind("void *qsort_callback(void*, void*)", :temp) + BoundQsortCallback = bind("void *qsort_callback(void*, void*)"){|ptr1,ptr2| ptr1[0] <=> ptr2[0]} + Timeval = struct [ + "long tv_sec", + "long tv_usec", + ] + Timezone = struct [ + "int tz_minuteswest", + "int tz_dsttime", + ] + MyStruct = struct [ + "short num[5]", + "char c", + "unsigned char buff[7]", + ] + + CallCallback = bind("void call_callback(void*, void*)"){|ptr1, ptr2| + f = Function.new(CFunc.new(ptr1.to_i, DL::TYPE_VOID, "<anonymous>"), [TYPE_VOIDP]) + f.call(ptr2) + } + CarriedFunction = bind("void callback_function(void*)", :carried, 0) + end + + class TestImport < TestBase + def test_malloc() + s1 = LIBC::Timeval.malloc() + s2 = LIBC::Timeval.malloc() + assert_not_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) + end + + def test_sizeof() + assert_equal(DL::SIZEOF_VOIDP, LIBC.sizeof("FILE*")) + assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) + end + + def test_unsigned_result() + d = (2 ** 31) + 1 + + r = LIBC.strtoul(d.to_s, 0, 0) + assert_equal(d, r) + end + + def test_io() + if( RUBY_PLATFORM != DL::BUILD_RUBY_PLATFORM ) + return + end + io_in,io_out = IO.pipe() + LIBC.fprintf(io_out, "hello") + io_out.flush() + io_out.close() + str = io_in.read() + io_in.close() + assert_equal("hello", str) + end + + def test_value() + i = LIBC.value('int', 2) + assert_equal(2, i.value) + + d = LIBC.value('double', 2.0) + assert_equal(2.0, d.value) + + ary = LIBC.value('int[3]', [0,1,2]) + assert_equal([0,1,2], ary.value) + end + + def test_carried_function() + data1 = "data" + data2 = nil + LIBC.call_callback(LIBC::CarriedFunction, LIBC::CarriedFunction.create_carrier(data1)){|d| + data2 = d + } + assert_equal(data1, data2) + end + + def test_struct() + s = LIBC::MyStruct.malloc() + s.num = [0,1,2,3,4] + s.c = ?a + s.buff = "012345\377" + assert_equal([0,1,2,3,4], s.num) + assert_equal(?a, s.c) + assert_equal([?0,?1,?2,?3,?4,?5,?\377], s.buff) + end + + def test_gettimeofday() + if( defined?(LIBC.gettimeofday) ) + timeval = LIBC::Timeval.malloc() + timezone = LIBC::Timezone.malloc() + LIBC.gettimeofday(timeval, timezone) + cur = Time.now() + assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) + end + end + + def test_strcpy() + buff = "000" + str = LIBC.strcpy(buff, "123") + assert_equal("123", buff) + assert_equal("123", str.to_s) + end + + def test_isdigit() + r1 = LIBC.isdigit(?1) + r2 = LIBC.isdigit(?2) + rr = LIBC.isdigit(?r) + assert_positive(r1) + assert_positive(r2) + assert_zero(rr) + end + + def test_atof() + r = LIBC.atof("12.34") + assert_match(12.00..13.00, r) + end + + def test_strtod() + f = Function.new(CFunc.new(@libc['strtod'], TYPE_DOUBLE, 'strtod'), + [TYPE_VOIDP, TYPE_VOIDP]) + buff1 = "12.34" + buff2 = " " + r = f.call(buff1, buff2) + assert_match(12.00..13.00, r) + end + + def test_qsort() + buff = "9341" + LIBC.qsort(buff, buff.size, 1, LIBC::QsortCallback){|ptr1,ptr2| ptr1[0] <=> ptr2[0]} + assert_equal("1349", buff) + buff = "9341" + LIBC.qsort(buff, buff.size, 1, LIBC::BoundQsortCallback) + assert_equal("1349", buff) + end + end +end diff --git a/trunk/ext/dl/test/test_win32.rb b/trunk/ext/dl/test/test_win32.rb new file mode 100644 index 0000000000..b2210287a1 --- /dev/null +++ b/trunk/ext/dl/test/test_win32.rb @@ -0,0 +1,53 @@ +require 'test_base' +require 'dl/import' +require 'dl/types' + +module Win32API + extend DL::Importer + + dlload "kernel32.dll" + + include DL::Win32Types + + OSVERSIONINFO = struct [ + "DWORD dwOSVersionInfoSize", + "DWORD dwMajorVersion", + "DWORD dwMinorVersion", + "DWORD dwBuildNumber", + "DWORD dwPlatformId", + "UCHAR szCSDVersion[128]", + ] + + typealias "POSVERSIONINFO", "OSVERSIONINFO*" + + extern "BOOL GetVersionEx(POSVERSIONINFO)", :stdcall + + def get_version_ex() + ptr = OSVERSIONINFO.malloc() + ptr.dwOSVersionInfoSize = OSVERSIONINFO.size + ret = GetVersionEx(ptr) + if( ret ) + ptr + else + nil + end + end + module_function :get_version_ex +end + +module DL +class TestWin32 < TestBase + def test_version() + platform = Win32API.get_version_ex().dwPlatformId + case ENV['OS'] + when 'Windows_NT' + expect = 2 + when /Windows.+/ + expect = 1 + else + expect = 0 + end + assert_equal(expect, platform) + end +end +end diff --git a/trunk/ext/dl/win32/extconf.rb b/trunk/ext/dl/win32/extconf.rb new file mode 100644 index 0000000000..a72ca49c06 --- /dev/null +++ b/trunk/ext/dl/win32/extconf.rb @@ -0,0 +1,3 @@ +if compiled?('dl') and $mswin||$bccwin||$mingw||$cygwin + create_makefile('win32') +end diff --git a/trunk/ext/dl/win32/lib/Win32API.rb b/trunk/ext/dl/win32/lib/Win32API.rb new file mode 100644 index 0000000000..b642e0c8fe --- /dev/null +++ b/trunk/ext/dl/win32/lib/Win32API.rb @@ -0,0 +1,28 @@ +# -*- ruby -*- +# for backward compatibility +warn "Warning:#{caller[0].sub(/:in `.*'\z/, '')}: Win32API is deprecated after Ruby 1.9.1; use dl directly instead" if $VERBOSE + +require 'dl' + +class Win32API + DLL = {} + TYPEMAP = {"0" => DL::TYPE_VOID, "S" => DL::TYPE_VOIDP, "I" => DL::TYPE_LONG} + + def initialize(dllname, func, import, export = "0") + @proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1') + handle = DLL[dllname] ||= DL.dlopen(dllname) + @func = DL::CFunc.new(handle[func], TYPEMAP[export.tr("VPpNnLlIi", "0SSI")], func) + end + + def call(*args) + import = @proto.split("") + args.each_with_index do |x, i| + args[i], = [x == 0 ? nil : x].pack("p").unpack("l!*") if import[i] == "S" + args[i], = [x].pack("I").unpack("i") if import[i] == "I" + end + ret, = @func.call(args) + return ret || 0 + end + + alias Call call +end diff --git a/trunk/ext/dl/win32/lib/win32/registry.rb b/trunk/ext/dl/win32/lib/win32/registry.rb new file mode 100644 index 0000000000..ccdd721818 --- /dev/null +++ b/trunk/ext/dl/win32/lib/win32/registry.rb @@ -0,0 +1,832 @@ +=begin += Win32 Registry I/F +win32/registry is registry accessor library for Win32 platform. +It uses Win32API to call Win32 Registry APIs. + +== example + Win32::Registry::HKEY_CURRENT_USER.open('SOFTWARE\foo') do |reg| + value = reg['foo'] # read a value + value = reg['foo', Win32::Registry::REG_SZ] # read a value with type + type, value = reg.read('foo') # read a value + reg['foo'] = 'bar' # write a value + reg['foo', Win32::Registry::REG_SZ] = 'bar' # write a value with type + reg.write('foo', Win32::Registry::REG_SZ, 'bar') # write a value + + reg.each_value { |name, type, data| ... } # Enumerate values + reg.each_key { |key, wtime| ... } # Enumerate subkeys + + reg.delete_value(name) # Delete a value + reg.delete_key(name) # Delete a subkey + reg.delete_key(name, true) # Delete a subkey recursively + end + += Reference + +== Win32::Registry class + +=== including modules + +* Enumerable +* Registry::Constants + +=== class methods +--- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) +--- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) { |reg| ... } + Open the registry key ((|subkey|)) under ((|key|)). + ((|key|)) is Win32::Registry object of parent key. + You can use predefined key HKEY_* (see ((<constants>))) + + ((|desired|)) and ((|opt|)) is access mask and key option. + For detail, see ((<MSDN Library|URL:http://msdn.microsoft.com/library/en-us/sysinfo/base/regopenkeyex.asp>)). + + If block is given, the key is closed automatically. + +--- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) +--- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) { |reg| ... } + Create or open the registry key ((|subkey|)) under ((|key|)). + You can use predefined key HKEY_* (see ((<constants>))) + + If subkey is already exists, key is opened and Registry#((<created?>)) + method will return false. + + If block is given, the key is closed automatically. + +--- Registry.expand_environ(str) + Replace (({%\w+%})) into the environment value of ((|str|)). + This method is used for REG_EXPAND_SZ. + + For detail, see ((<ExpandEnvironmentStrings|URL:http://msdn.microsoft.com/library/en-us/sysinfo/base/expandenvironmentstrings.asp>)) Win32 API. + +--- Registry.type2name(type) + Convert registry type value to readable string. + +--- Registry.wtime2time(wtime) + Convert 64-bit FILETIME integer into Time object. + +--- Registry.time2wtime(time) + Convert Time object or Integer object into 64-bit FILETIME. + +=== instance methods +--- open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) + Same as (({Win32::((<Registry.open>))(self, subkey, desired, opt)})) + +--- create(subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) + Same as (({Win32::((<Registry.create>))(self, subkey, desired, opt)})) + +--- close + Close key. + + After closed, most method raises error. + +--- read(name, *rtype) + Read a registry value named ((|name|)) and return array of + [ ((|type|)), ((|data|)) ]. + When name is nil, the `default' value is read. + + ((|type|)) is value type. (see ((<Win32::Registry::Constants module>))) + ((|data|)) is value data, its class is: + :REG_SZ, REG_EXPAND_SZ + String + :REG_MULTI_SZ + Array of String + :REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD + Integer + :REG_BINARY + String (contains binary data) + + When ((|rtype|)) is specified, the value type must be included by + ((|rtype|)) array, or TypeError is raised. + +--- self[name, *rtype] + Read a registry value named ((|name|)) and return its value data. + The class of value is same as ((<read>)) method returns. + + If the value type is REG_EXPAND_SZ, returns value data whose environment + variables are replaced. + If the value type is neither REG_SZ, REG_MULTI_SZ, REG_DWORD, + REG_DWORD_BIG_ENDIAN, nor REG_QWORD, TypeError is raised. + + The meaning of ((|rtype|)) is same as ((<read>)) method. + +--- read_s(name) +--- read_i(name) +--- read_bin(name) + Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) + registry value named ((|name|)). + + If the values type does not match, TypeError is raised. + +--- read_s_expand(name) + Read a REG_SZ or REG_EXPAND_SZ registry value named ((|name|)). + + If the value type is REG_EXPAND_SZ, environment variables are replaced. + Unless the value type is REG_SZ or REG_EXPAND_SZ, TypeError is raised. + +--- write(name, type, data) + Write ((|data|)) to a registry value named ((|name|)). + When name is nil, write to the `default' value. + + ((|type|)) is type value. (see ((<Registry::Constants module>))) + Class of ((|data|)) must be same as which ((<read>)) + method returns. + +--- self[name, wtype = nil] = value + Write ((|value|)) to a registry value named ((|name|)). + + If ((|wtype|)) is specified, the value type is it. + Otherwise, the value type is depend on class of ((|value|)): + :Integer + REG_DWORD + :String + REG_SZ + :Array + REG_MULTI_SZ + +--- write_s(name, value) +--- write_i(name, value) +--- write_bin(name, value) + Write ((|value|)) to a registry value named ((|name|)). + + The value type is REG_SZ(write_s), REG_DWORD(write_i), or + REG_BINARY(write_bin). + +--- each { |name, type, value| ... } +--- each_value { |name, type, value| ... } + Enumerate values. + +--- each_key { |subkey, wtime| ... } + Enumerate subkeys. + + ((|subkey|)) is String which contains name of subkey. + ((|wtime|)) is last write time as FILETIME (64-bit integer). + (see ((<Registry.wtime2time>))) + +--- delete(name) +--- delete_value(name) + Delete a registry value named ((|name|)). + We can not delete the `default' value. + +--- delete_key(name, recursive = false) + Delete a subkey named ((|name|)) and all its values. + + If ((|recursive|)) is false, the subkey must not have subkeys. + Otherwise, this method deletes all subkeys and values recursively. + +--- flush + Write all the attributes into the registry file. + +--- created? + Returns if key is created ((*newly*)). + (see ((<Registry.create>))) + +--- open? + Returns if key is not closed. + +--- hkey + Returns key handle value. + +--- parent + Win32::Registry object of parent key, or nil if predefeined key. + +--- keyname + Same as ((|subkey|)) value of ((<Registry.open>)) or + ((<Registry.create>)) method. + +--- disposition + Disposition value (REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY). + +--- name +--- to_s + Full path of key such as (({'HKEY_CURRENT_USER\SOFTWARE\foo\bar'})). + +--- info + Returns key information as Array of: + :num_keys + The number of subkeys. + :max_key_length + Maximum length of name of subkeys. + :num_values + The number of values. + :max_value_name_length + Maximum length of name of values. + :max_value_length + Maximum length of value of values. + :descriptor_length + Length of security descriptor. + :wtime + Last write time as FILETIME(64-bit integer) + + For detail, see ((<RegQueryInfoKey|URL:http://msdn.microsoft.com/library/en-us/sysinfo/base/regqueryinfokey.asp>)) Win32 API. + +--- num_keys +--- max_key_length +--- num_values +--- max_value_name_length +--- max_value_length +--- descriptor_length +--- wtime + Returns an item of key information. + +=== constants +--- HKEY_CLASSES_ROOT +--- HKEY_CURRENT_USER +--- HKEY_LOCAL_MACHINE +--- HKEY_PERFORMANCE_DATA +--- HKEY_CURRENT_CONFIG +--- HKEY_DYN_DATA + Win32::Registry object whose key is predefined key. + For detail, see ((<MSDN Library|URL:http://msdn.microsoft.com/library/en-us/sysinfo/base/predefined_keys.asp>)). + +== Win32::Registry::Constants module + +For detail, see ((<MSDN Library|URL:http://msdn.microsoft.com/library/en-us/sysinfo/base/registry.asp>)). + +--- HKEY_* + Predefined key ((*handle*)). + These are Integer, not Win32::Registry. + +--- REG_* + Registry value type. + +--- KEY_* + Security access mask. + +--- KEY_OPTIONS_* + Key options. + +--- REG_CREATED_NEW_KEY +--- REG_OPENED_EXISTING_KEY + If the key is created newly or opened existing key. + See also Registry#((<disposition>)) method. + +=end + +require 'Win32API' + +module Win32 + class Registry + module Constants + HKEY_CLASSES_ROOT = 0x80000000 + HKEY_CURRENT_USER = 0x80000001 + HKEY_LOCAL_MACHINE = 0x80000002 + HKEY_USERS = 0x80000003 + HKEY_PERFORMANCE_DATA = 0x80000004 + HKEY_PERFORMANCE_TEXT = 0x80000050 + HKEY_PERFORMANCE_NLSTEXT = 0x80000060 + HKEY_CURRENT_CONFIG = 0x80000005 + HKEY_DYN_DATA = 0x80000006 + + REG_NONE = 0 + REG_SZ = 1 + REG_EXPAND_SZ = 2 + REG_BINARY = 3 + REG_DWORD = 4 + REG_DWORD_LITTLE_ENDIAN = 4 + REG_DWORD_BIG_ENDIAN = 5 + REG_LINK = 6 + REG_MULTI_SZ = 7 + REG_RESOURCE_LIST = 8 + REG_FULL_RESOURCE_DESCRIPTOR = 9 + REG_RESOURCE_REQUIREMENTS_LIST = 10 + REG_QWORD = 11 + REG_QWORD_LITTLE_ENDIAN = 11 + + STANDARD_RIGHTS_READ = 0x00020000 + STANDARD_RIGHTS_WRITE = 0x00020000 + KEY_QUERY_VALUE = 0x0001 + KEY_SET_VALUE = 0x0002 + KEY_CREATE_SUB_KEY = 0x0004 + KEY_ENUMERATE_SUB_KEYS = 0x0008 + KEY_NOTIFY = 0x0010 + KEY_CREATE_LINK = 0x0020 + KEY_READ = STANDARD_RIGHTS_READ | + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY + KEY_WRITE = STANDARD_RIGHTS_WRITE | + KEY_SET_VALUE | KEY_CREATE_SUB_KEY + KEY_EXECUTE = KEY_READ + KEY_ALL_ACCESS = KEY_READ | KEY_WRITE | KEY_CREATE_LINK + + REG_OPTION_RESERVED = 0x0000 + REG_OPTION_NON_VOLATILE = 0x0000 + REG_OPTION_VOLATILE = 0x0001 + REG_OPTION_CREATE_LINK = 0x0002 + REG_OPTION_BACKUP_RESTORE = 0x0004 + REG_OPTION_OPEN_LINK = 0x0008 + REG_LEGAL_OPTION = REG_OPTION_RESERVED | + REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK | + REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK + + REG_CREATED_NEW_KEY = 1 + REG_OPENED_EXISTING_KEY = 2 + + REG_WHOLE_HIVE_VOLATILE = 0x0001 + REG_REFRESH_HIVE = 0x0002 + REG_NO_LAZY_FLUSH = 0x0004 + REG_FORCE_RESTORE = 0x0008 + + MAX_KEY_LENGTH = 514 + MAX_VALUE_LENGTH = 32768 + end + include Constants + include Enumerable + + # + # Error + # + class Error < ::StandardError + FormatMessageA = Win32API.new('kernel32.dll', 'FormatMessageA', 'LPLLPLP', 'L') + def initialize(code) + @code = code + msg = "\0".force_encoding(Encoding::ASCII_8BIT) * 1024 + len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0) + msg = msg[0, len].force_encoding(Encoding.find(Encoding.locale_charmap)) + super msg.tr("\r", '').chomp + end + attr_reader :code + end + + # + # Predefined Keys + # + class PredefinedKey < Registry + def initialize(hkey, keyname) + @hkey = hkey + @parent = nil + @keyname = keyname + @disposition = REG_OPENED_EXISTING_KEY + end + + # Predefined keys cannot be closed + def close + raise Error.new(5) ## ERROR_ACCESS_DENIED + end + + # Fake class for Registry#open, Registry#create + def class + Registry + end + + # Make all + Constants.constants.grep(/^HKEY_/) do |c| + Registry.const_set c, new(Constants.const_get(c), c) + end + end + + # + # Win32 APIs + # + module API + [ + %w/RegOpenKeyExA LPLLP L/, + %w/RegCreateKeyExA LPLLLLPPP L/, + %w/RegEnumValueA LLPPPPPP L/, + %w/RegEnumKeyExA LLPPLLLP L/, + %w/RegQueryValueExA LPLPPP L/, + %w/RegSetValueExA LPLLPL L/, + %w/RegDeleteValue LP L/, + %w/RegDeleteKey LP L/, + %w/RegFlushKey L L/, + %w/RegCloseKey L L/, + %w/RegQueryInfoKey LPPPPPPPPPPP L/, + ].each do |fn| + const_set fn[0].intern, Win32API.new('advapi32.dll', *fn) + end + + module_function + + def check(result) + raise Error, result, caller(2) if result != 0 + end + + def packdw(dw) + [dw].pack('V') + end + + def unpackdw(dw) + dw += [0].pack('V') + dw.unpack('V')[0] + end + + def packqw(qw) + [ qw & 0xFFFFFFFF, qw >> 32 ].pack('VV') + end + + def unpackqw(qw) + qw = qw.unpack('VV') + (qw[1] << 32) | qw[0] + end + + def OpenKey(hkey, name, opt, desired) + result = packdw(0) + check RegOpenKeyExA.call(hkey, name, opt, desired, result) + unpackdw(result) + end + + def CreateKey(hkey, name, opt, desired) + result = packdw(0) + disp = packdw(0) + check RegCreateKeyExA.call(hkey, name, 0, 0, opt, desired, + 0, result, disp) + [ unpackdw(result), unpackdw(disp) ] + end + + def EnumValue(hkey, index) + name = ' ' * Constants::MAX_KEY_LENGTH + size = packdw(Constants::MAX_KEY_LENGTH) + check RegEnumValueA.call(hkey, index, name, size, 0, 0, 0, 0) + name[0, unpackdw(size)] + end + + def EnumKey(hkey, index) + name = ' ' * Constants::MAX_KEY_LENGTH + size = packdw(Constants::MAX_KEY_LENGTH) + wtime = ' ' * 8 + check RegEnumKeyExA.call(hkey, index, name, size, 0, 0, 0, wtime) + [ name[0, unpackdw(size)], unpackqw(wtime) ] + end + + def QueryValue(hkey, name) + type = packdw(0) + size = packdw(0) + check RegQueryValueExA.call(hkey, name, 0, type, 0, size) + data = ' ' * unpackdw(size) + check RegQueryValueExA.call(hkey, name, 0, type, data, size) + [ unpackdw(type), data[0, unpackdw(size)] ] + end + + def SetValue(hkey, name, type, data, size) + check RegSetValueExA.call(hkey, name, 0, type, data, size) + end + + def DeleteValue(hkey, name) + check RegDeleteValue.call(hkey, name) + end + + def DeleteKey(hkey, name) + check RegDeleteKey.call(hkey, name) + end + + def FlushKey(hkey) + check RegFlushKey.call(hkey) + end + + def CloseKey(hkey) + check RegCloseKey.call(hkey) + end + + def QueryInfoKey(hkey) + subkeys = packdw(0) + maxsubkeylen = packdw(0) + values = packdw(0) + maxvaluenamelen = packdw(0) + maxvaluelen = packdw(0) + secdescs = packdw(0) + wtime = ' ' * 8 + check RegQueryInfoKey.call(hkey, 0, 0, 0, subkeys, maxsubkeylen, 0, + values, maxvaluenamelen, maxvaluelen, secdescs, wtime) + [ unpackdw(subkeys), unpackdw(maxsubkeylen), unpackdw(values), + unpackdw(maxvaluenamelen), unpackdw(maxvaluelen), + unpackdw(secdescs), unpackqw(wtime) ] + end + end + + # + # utility functions + # + def self.expand_environ(str) + str.gsub(/%([^%]+)%/) { ENV[$1] || ENV[$1.upcase] || $& } + end + + @@type2name = { } + %w[ + REG_NONE REG_SZ REG_EXPAND_SZ REG_BINARY REG_DWORD + REG_DWORD_BIG_ENDIAN REG_LINK REG_MULTI_SZ + REG_RESOURCE_LIST REG_FULL_RESOURCE_DESCRIPTOR + REG_RESOURCE_REQUIREMENTS_LIST REG_QWORD + ].each do |type| + @@type2name[Constants.const_get(type)] = type + end + + def self.type2name(type) + @@type2name[type] || type.to_s + end + + def self.wtime2time(wtime) + Time.at((wtime - 116444736000000000) / 10000000) + end + + def self.time2wtime(time) + time.to_i * 10000000 + 116444736000000000 + end + + # + # constructors + # + private_class_method :new + + def self.open(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) + subkey = subkey.chomp('\\') + newkey = API.OpenKey(hkey.hkey, subkey, opt, desired) + obj = new(newkey, hkey, subkey, REG_OPENED_EXISTING_KEY) + if block_given? + begin + yield obj + ensure + obj.close + end + else + obj + end + end + + def self.create(hkey, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) + newkey, disp = API.CreateKey(hkey.hkey, subkey, opt, desired) + obj = new(newkey, hkey, subkey, disp) + if block_given? + begin + yield obj + ensure + obj.close + end + else + obj + end + end + + # + # finalizer + # + @@final = proc { |hkey| proc { API.CloseKey(hkey[0]) if hkey[0] } } + + # + # initialize + # + def initialize(hkey, parent, keyname, disposition) + @hkey = hkey + @parent = parent + @keyname = keyname + @disposition = disposition + @hkeyfinal = [ hkey ] + ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal) + end + attr_reader :hkey, :parent, :keyname, :disposition + + # + # attributes + # + def created? + @disposition == REG_CREATED_NEW_KEY + end + + def open? + !@hkey.nil? + end + + def name + parent = self + name = @keyname + while parent = parent.parent + name = parent.keyname + '\\' + name + end + name + end + + def inspect + "\#<Win32::Registry key=#{name.inspect}>" + end + + # + # marshalling + # + def _dump(depth) + raise TypeError, "can't dump Win32::Registry" + end + + # + # open/close + # + def open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED, &blk) + self.class.open(self, subkey, desired, opt, &blk) + end + + def create(subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED, &blk) + self.class.create(self, subkey, desired, opt, &blk) + end + + def close + API.CloseKey(@hkey) + @hkey = @parent = @keyname = nil + @hkeyfinal[0] = nil + end + + # + # iterator + # + def each_value + index = 0 + while true + begin + subkey = API.EnumValue(@hkey, index) + rescue Error + break + end + begin + type, data = read(subkey) + rescue Error + next + end + yield subkey, type, data + index += 1 + end + index + end + alias each each_value + + def each_key + index = 0 + while true + begin + subkey, wtime = API.EnumKey(@hkey, index) + rescue Error + break + end + yield subkey, wtime + index += 1 + end + index + end + + def keys + keys_ary = [] + each_key { |key,| keys_ary << key } + keys_ary + end + + # + # reader + # + def read(name, *rtype) + type, data = API.QueryValue(@hkey, name) + unless rtype.empty? or rtype.include?(type) + raise TypeError, "Type mismatch (expect #{rtype.inspect} but #{type} present)" + end + case type + when REG_SZ, REG_EXPAND_SZ + [ type, data.chop ] + when REG_MULTI_SZ + [ type, data.split(/\0/) ] + when REG_BINARY + [ type, data ] + when REG_DWORD + [ type, API.unpackdw(data) ] + when REG_DWORD_BIG_ENDIAN + [ type, data.unpack('N')[0] ] + when REG_QWORD + [ type, API.unpackqw(data) ] + else + raise TypeError, "Type #{type} is not supported." + end + end + + def [](name, *rtype) + type, data = read(name, *rtype) + case type + when REG_SZ, REG_DWORD, REG_QWORD, REG_MULTI_SZ + data + when REG_EXPAND_SZ + Registry.expand_environ(data) + else + raise TypeError, "Type #{type} is not supported." + end + end + + def read_s(name) + read(name, REG_SZ)[1] + end + + def read_s_expand(name) + type, data = read(name, REG_SZ, REG_EXPAND_SZ) + if type == REG_EXPAND_SZ + Registry.expand_environ(data) + else + data + end + end + + def read_i(name) + read(name, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD)[1] + end + + def read_bin(name) + read(name, REG_BINARY)[1] + end + + # + # writer + # + def write(name, type, data) + case type + when REG_SZ, REG_EXPAND_SZ + data = data.to_s + "\0" + when REG_MULTI_SZ + data = data.to_a.join("\0") + "\0\0" + when REG_BINARY + data = data.to_s + when REG_DWORD + data = API.packdw(data.to_i) + when REG_DWORD_BIG_ENDIAN + data = [data.to_i].pack('N') + when REG_QWORD + data = API.packqw(data.to_i) + else + raise TypeError, "Unsupported type #{type}" + end + API.SetValue(@hkey, name, type, data, data.length) + end + + def []=(name, rtype, value = nil) + if value + write name, rtype, value + else + case value = rtype + when Integer + write name, REG_DWORD, value + when String + write name, REG_SZ, value + when Array + write name, REG_MULTI_SZ, value + else + raise TypeError, "Unexpected type #{value.class}" + end + end + value + end + + def write_s(name, value) + write name, REG_SZ, value.to_s + end + + def write_i(name, value) + write name, REG_DWORD, value.to_i + end + + def write_bin(name, value) + write name, REG_BINARY, value.to_s + end + + # + # delete + # + def delete_value(name) + API.DeleteValue(@hkey, name) + end + alias delete delete_value + + def delete_key(name, recursive = false) + if recursive + open(name, KEY_ALL_ACCESS) do |reg| + reg.keys.each do |key| + begin + reg.delete_key(key, true) + rescue Error + # + end + end + end + API.DeleteKey(@hkey, name) + else + begin + API.EnumKey @hkey, 0 + rescue Error + return API.DeleteKey(@hkey, name) + end + raise Error.new(5) ## ERROR_ACCESS_DENIED + end + end + + # + # flush + # + def flush + API.FlushKey @hkey + end + + # + # key information + # + def info + API.QueryInfoKey(@hkey) + end + %w[ + num_keys max_key_length + num_values max_value_name_length max_value_length + descriptor_length wtime + ].each_with_index do |s, i| + eval <<-__END__ + def #{s} + info[#{i}] + end + __END__ + end + end +end diff --git a/trunk/ext/dl/win32/lib/win32/resolv.rb b/trunk/ext/dl/win32/lib/win32/resolv.rb new file mode 100644 index 0000000000..4e69f58085 --- /dev/null +++ b/trunk/ext/dl/win32/lib/win32/resolv.rb @@ -0,0 +1,370 @@ +=begin += Win32 DNS and DHCP I/F + +=end + +require 'win32/registry' + +module Win32 + module Resolv + API = Registry::API + + def self.get_hosts_path + path = get_hosts_dir + path = File.expand_path('hosts', path) + File.exist?(path) ? path : nil + end + + def self.get_resolv_info + search, nameserver = get_info + if search.empty? + search = nil + else + search.delete("") + search.uniq! + end + if nameserver.empty? + nameserver = nil + else + nameserver.delete("") + nameserver.delete("0.0.0.0") + nameserver.uniq! + end + [ search, nameserver ] + end + +getv = Win32API.new('kernel32.dll', 'GetVersionExA', 'P', 'L') +info = [ 148, 0, 0, 0, 0 ].pack('V5') + "\0" * 128 +getv.call(info) +if info.unpack('V5')[4] == 2 # VER_PLATFORM_WIN32_NT +#==================================================================== +# Windows NT +#==================================================================== + module_eval <<-'__EOS__', __FILE__, __LINE__+1 + TCPIP_NT = 'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' + + class << self + private + def get_hosts_dir + Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg| + reg.read_s_expand('DataBasePath') + end + end + + def get_info + search = nil + nameserver = [] + Registry::HKEY_LOCAL_MACHINE.open(TCPIP_NT) do |reg| + begin + slist = reg.read_s('SearchList') + search = slist.split(/,\s*/) unless slist.empty? + rescue Registry::Error + end + + if add_search = search.nil? + search = [] + begin + nvdom = reg.read_s('NV Domain') + unless nvdom.empty? + @search = [ nvdom ] + if reg.read_i('UseDomainNameDevolution') != 0 + if /^[\w\d]+\./ =~ nvdom + devo = $' + end + end + end + rescue Registry::Error + end + end + + reg.open('Interfaces') do |reg| + reg.each_key do |iface,| + reg.open(iface) do |regif| + begin + [ 'NameServer', 'DhcpNameServer' ].each do |key| + begin + ns = regif.read_s(key) + rescue + else + unless ns.empty? + nameserver.concat(ns.split(/[,\s]\s*/)) + break + end + end + end + rescue Registry::Error + end + + if add_search + begin + [ 'Domain', 'DhcpDomain' ].each do |key| + dom = regif.read_s(key) + unless dom.empty? + search.concat(dom.split(/,\s*/)) + break + end + end + rescue Registry::Error + end + end + end + end + end + search << devo if add_search and devo + end + [ search.uniq, nameserver.uniq ] + end + end + __EOS__ +else +#==================================================================== +# Windows 9x +#==================================================================== + module_eval <<-'__EOS__', __FILE__, __LINE__+1 + TCPIP_9X = 'SYSTEM\CurrentControlSet\Services\VxD\MSTCP' + DHCP_9X = 'SYSTEM\CurrentControlSet\Services\VxD\DHCP' + WINDOWS = 'Software\Microsoft\Windows\CurrentVersion' + + class << self + # private + + def get_hosts_dir + Registry::HKEY_LOCAL_MACHINE.open(WINDOWS) do |reg| + reg.read_s_expand('SystemRoot') + end + end + + def get_info + search = [] + nameserver = [] + begin + Registry::HKEY_LOCAL_MACHINE.open(TCPIP_9X) do |reg| + if reg.read_s("EnableDNS") == "1" + domain = reg.read_s("Domain") + ns = reg.read_s("NameServer") + slist = reg.read_s("SearchList") + search << domain unless domain.empty? + search.concat(slist.split(/,\s*/)) + nameserver.concat(ns.split(/[,\s]\s*/)) + end + end + rescue Registry::Error + end + + dhcpinfo = get_dhcpinfo + search.concat(dhcpinfo[0]) + nameserver.concat(dhcpinfo[1]) + [ search, nameserver ] + end + + def get_dhcpinfo + macaddrs = {} + ipaddrs = {} + WsControl.get_iflist.each do |index, macaddr, *ipaddr| + macaddrs[macaddr] = 1 + ipaddr.each { |ipaddr| ipaddrs[ipaddr] = 1 } + end + iflist = [ macaddrs, ipaddrs ] + + search = [] + nameserver = [] + version = -1 + Registry::HKEY_LOCAL_MACHINE.open(DHCP_9X) do |reg| + begin + version = API.unpackdw(reg.read_bin("Version")) + rescue Registry::Error + end + + reg.each_key do |key,| + catch(:not_used) do + reg.open(key) do |regdi| + dom, ns = get_dhcpinfo_key(version, regdi, iflist) + search << dom if dom + nameserver.concat(ns) if ns + end + end + end + end + [ search, nameserver ] + end + + def get_dhcpinfo_95(reg) + dhcp = reg.read_bin("DhcpInfo") + [ + API.unpackdw(dhcp[4..7]), + API.unpackdw(dhcp[8..11]), + 1, + dhcp[45..50], + reg.read_bin("OptionInfo"), + ] + end + + def get_dhcpinfo_98(reg) + [ + API.unpackdw(reg.read_bin("DhcpIPAddress")), + API.unpackdw(reg.read_bin("DhcpSubnetMask")), + API.unpackdw(reg.read_bin("HardwareType")), + reg.read_bin("HardwareAddress"), + reg.read_bin("OptionInfo"), + ] + end + + def get_dhcpinfo_key(version, reg, iflist) + info = case version + when 1 + get_dhcpinfo_95(reg) + when 2 + get_dhcpinfo_98(reg) + else + begin + get_dhcpinfo_98(reg) + rescue Registry::Error + get_dhcpinfo_95(reg) + end + end + ipaddr, netmask, hwtype, macaddr, opt = info + throw :not_used unless + ipaddr and ipaddr != 0 and + netmask and netmask != 0 and + macaddr and macaddr.size == 6 and + hwtype == 1 and + iflist[0][macaddr] and iflist[1][ipaddr] + + size = opt.size + idx = 0 + while idx <= size + opttype = opt[idx] + optsize = opt[idx + 1] + optval = opt[idx + 2, optsize] + case opttype + when 0xFF ## term + break + when 0x0F ## domain + domain = optval.chomp("\0") + when 0x06 ## dns + nameserver = optval.scan(/..../).collect { |addr| + "%d.%d.%d.%d" % addr.unpack('C4') + } + end + idx += optsize + 2 + end + [ domain, nameserver ] + rescue Registry::Error + throw :not_used + end + end + + module WsControl + WsControl = Win32API.new('wsock32.dll', 'WsControl', 'LLPPPP', 'L') + WSAGetLastError = Win32API.new('wsock32.dll', 'WSAGetLastError', 'V', 'L') + + MAX_TDI_ENTITIES = 512 + IPPROTO_TCP = 6 + WSCTL_TCP_QUERY_INFORMATION = 0 + INFO_CLASS_GENERIC = 0x100 + INFO_CLASS_PROTOCOL = 0x200 + INFO_TYPE_PROVIDER = 0x100 + ENTITY_LIST_ID = 0 + GENERIC_ENTITY = 0 + CL_NL_ENTITY = 0x301 + IF_ENTITY = 0x200 + ENTITY_TYPE_ID = 1 + CL_NL_IP = 0x303 + IF_MIB = 0x202 + IF_MIB_STATS_ID = 1 + IP_MIB_ADDRTABLE_ENTRY_ID = 0x102 + + def self.wsctl(tei_entity, tei_instance, + toi_class, toi_type, toi_id, + buffsize) + reqinfo = [ + ## TDIEntityID + tei_entity, tei_instance, + ## TDIObjectID + toi_class, toi_type, toi_id, + ## TCP_REQUEST_INFORMATION_EX + "" + ].pack('VVVVVa16') + reqsize = API.packdw(reqinfo.size) + buff = "\0" * buffsize + buffsize = API.packdw(buffsize) + result = WsControl.call( + IPPROTO_TCP, + WSCTL_TCP_QUERY_INFORMATION, + reqinfo, reqsize, + buff, buffsize) + if result != 0 + raise RuntimeError, "WsControl failed.(#{result})" + end + [ buff, API.unpackdw(buffsize) ] + end + private_class_method :wsctl + + def self.get_iflist + # Get TDI Entity List + entities, size = + wsctl(GENERIC_ENTITY, 0, + INFO_CLASS_GENERIC, + INFO_TYPE_PROVIDER, + ENTITY_LIST_ID, + MAX_TDI_ENTITIES * 8) # sizeof(TDIEntityID) + entities = entities[0, size]. + scan(/.{8}/). + collect { |e| e.unpack('VV') } + + # Get MIB Interface List + iflist = [] + ifcount = 0 + entities.each do |entity, instance| + if( (entity & IF_ENTITY)>0 ) + ifcount += 1 + etype, = wsctl(entity, instance, + INFO_CLASS_GENERIC, + INFO_TYPE_PROVIDER, + ENTITY_TYPE_ID, + 4) + if( (API.unpackdw(etype) & IF_MIB)==IF_MIB ) + ifentry, = wsctl(entity, instance, + INFO_CLASS_PROTOCOL, + INFO_TYPE_PROVIDER, + IF_MIB_STATS_ID, + 21 * 4 + 8 + 130) # sizeof(IFEntry) + iflist << [ + API.unpackdw(ifentry[0,4]), + ifentry[20, 6] + ] + end + end + end + + # Get IP Addresses + entities.each do |entity, instance| + if entity == CL_NL_ENTITY + etype, = wsctl(entity, instance, + INFO_CLASS_GENERIC, + INFO_TYPE_PROVIDER, + ENTITY_TYPE_ID, + 4) + if API.unpackdw(etype) == CL_NL_IP + ipentries, = wsctl(entity, instance, + INFO_CLASS_PROTOCOL, + INFO_TYPE_PROVIDER, + IP_MIB_ADDRTABLE_ENTRY_ID, + 24 * (ifcount+1)) # sizeof(IPAddrEntry) + ipentries.scan(/.{24}/) do |ipentry| + ipaddr, index = ipentry.unpack('VV') + if ifitem = iflist.assoc(index) + ifitem << ipaddr + end + end + end + end + end + iflist + end + end + __EOS__ +end +#==================================================================== + end +end diff --git a/trunk/ext/dl/win32/lib/win32/sspi.rb b/trunk/ext/dl/win32/lib/win32/sspi.rb new file mode 100644 index 0000000000..ef90ae35a7 --- /dev/null +++ b/trunk/ext/dl/win32/lib/win32/sspi.rb @@ -0,0 +1,330 @@ +# +# = win32/sspi.rb +# +# Copyright (c) 2006-2007 Justin Bailey +# +# Written and maintained by Justin Bailey <jgbailey@gmail.com>. +# +# This program is free software. You can re-distribute and/or +# modify this program under the same terms of ruby itself --- +# Ruby Distribution License or GNU General Public License. +# + +require 'Win32API' + +# Implements bindings to Win32 SSPI functions, focused on authentication to a proxy server over HTTP. +module Win32 + module SSPI + # Specifies how credential structure requested will be used. Only SECPKG_CRED_OUTBOUND is used + # here. + SECPKG_CRED_INBOUND = 0x00000001 + SECPKG_CRED_OUTBOUND = 0x00000002 + SECPKG_CRED_BOTH = 0x00000003 + + # Format of token. NETWORK format is used here. + SECURITY_NATIVE_DREP = 0x00000010 + SECURITY_NETWORK_DREP = 0x00000000 + + # InitializeSecurityContext Requirement flags + ISC_REQ_REPLAY_DETECT = 0x00000004 + ISC_REQ_SEQUENCE_DETECT = 0x00000008 + ISC_REQ_CONFIDENTIALITY = 0x00000010 + ISC_REQ_USE_SESSION_KEY = 0x00000020 + ISC_REQ_PROMPT_FOR_CREDS = 0x00000040 + ISC_REQ_CONNECTION = 0x00000800 + + # Win32 API Functions. Uses Win32API to bind methods to constants contained in class. + module API + # Can be called with AcquireCredentialsHandle.call() + AcquireCredentialsHandle = Win32API.new("secur32", "AcquireCredentialsHandle", 'ppLpppppp', 'L') + # Can be called with InitializeSecurityContext.call() + InitializeSecurityContext = Win32API.new("secur32", "InitializeSecurityContext", 'pppLLLpLpppp', 'L') + # Can be called with DeleteSecurityContext.call() + DeleteSecurityContext = Win32API.new("secur32", "DeleteSecurityContext", 'P', 'L') + # Can be called with FreeCredentialsHandle.call() + FreeCredentialsHandle = Win32API.new("secur32", "FreeCredentialsHandle", 'P', 'L') + end + + # SecHandle struct + class SecurityHandle + def upper + @struct.unpack("LL")[1] + end + + def lower + @struct.unpack("LL")[0] + end + + def to_p + @struct ||= "\0" * 8 + end + end + + # Some familiar aliases for the SecHandle structure + CredHandle = CtxtHandle = SecurityHandle + + # TimeStamp struct + class TimeStamp + attr_reader :struct + + def to_p + @struct ||= "\0" * 8 + end + end + + # Creates binary representaiton of a SecBufferDesc structure, + # including the SecBuffer contained inside. + class SecurityBuffer + + SECBUFFER_TOKEN = 2 # Security token + + TOKENBUFSIZE = 12288 + SECBUFFER_VERSION = 0 + + def initialize(buffer = nil) + @buffer = buffer || "\0" * TOKENBUFSIZE + @bufferSize = @buffer.length + @type = SECBUFFER_TOKEN + end + + def bufferSize + unpack + @bufferSize + end + + def bufferType + unpack + @type + end + + def token + unpack + @buffer + end + + def to_p + # Assumption is that when to_p is called we are going to get a packed structure. Therefore, + # set @unpacked back to nil so we know to unpack when accessors are next accessed. + @unpacked = nil + # Assignment of inner structure to variable is very important here. Without it, + # will not be able to unpack changes to the structure. Alternative, nested unpacks, + # does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer") + @sec_buffer ||= [@bufferSize, @type, @buffer].pack("LLP") + @struct ||= [SECBUFFER_VERSION, 1, @sec_buffer].pack("LLP") + end + + private + + # Unpacks the SecurityBufferDesc structure into member variables. We + # only want to do this once per struct, so the struct is deleted + # after unpacking. + def unpack + if ! @unpacked && @sec_buffer && @struct + @bufferSize, @type = @sec_buffer.unpack("LL") + @buffer = @sec_buffer.unpack("LLP#{@bufferSize}")[2] + @struct = nil + @sec_buffer = nil + @unpacked = true + end + end + end + + # SEC_WINNT_AUTH_IDENTITY structure + class Identity + SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1 + + attr_accessor :user, :domain, :password + + def initialize(user = nil, domain = nil, password = nil) + @user = user + @domain = domain + @password = password + @flags = SEC_WINNT_AUTH_IDENTITY_ANSI + end + + def to_p + [@user, @user ? @user.length : 0, + @domain, @domain ? @domain.length : 0, + @password, @password ? @password.length : 0, + @flags].pack("PLPLPLL") + end + end + + # Takes a return result from an SSPI function and interprets the value. + class SSPIResult + # Good results + SEC_E_OK = 0x00000000 + SEC_I_CONTINUE_NEEDED = 0x00090312 + + # These are generally returned by InitializeSecurityContext + SEC_E_INSUFFICIENT_MEMORY = 0x80090300 + SEC_E_INTERNAL_ERROR = 0x80090304 + SEC_E_INVALID_HANDLE = 0x80090301 + SEC_E_INVALID_TOKEN = 0x80090308 + SEC_E_LOGON_DENIED = 0x8009030C + SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311 + SEC_E_NO_CREDENTIALS = 0x8009030E + SEC_E_TARGET_UNKNOWN = 0x80090303 + SEC_E_UNSUPPORTED_FUNCTION = 0x80090302 + SEC_E_WRONG_PRINCIPAL = 0x80090322 + + # These are generally returned by AcquireCredentialsHandle + SEC_E_NOT_OWNER = 0x80090306 + SEC_E_SECPKG_NOT_FOUND = 0x80090305 + SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D + + @@map = {} + constants.each { |v| @@map[self.const_get(v.to_s)] = v } + + attr_reader :value + + def initialize(value) + # convert to unsigned long + value = [value].pack("L").unpack("L").first + raise "#{value.to_s(16)} is not a recognized result" unless @@map.has_key? value + @value = value + end + + def to_s + @@map[@value].to_s + end + + def ok? + @value == SEC_I_CONTINUE_NEEDED || @value == SEC_E_OK + end + + def ==(other) + if other.is_a?(SSPIResult) + @value == other.value + elsif other.is_a?(Fixnum) + @value == @@map[other] + else + false + end + end + end + + # Handles "Negotiate" type authentication. Geared towards authenticating with a proxy server over HTTP + class NegotiateAuth + attr_accessor :credentials, :context, :contextAttributes, :user, :domain + + # Default request flags for SSPI functions + REQUEST_FLAGS = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION + + # NTLM tokens start with this header always. Encoding alone adds "==" and newline, so remove those + B64_TOKEN_PREFIX = ["NTLMSSP"].pack("m").delete("=\n") + + # Given a connection and a request path, performs authentication as the current user and returns + # the response from a GET request. The connnection should be a Net::HTTP object, and it should + # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work. + # If a user and domain are given, will authenticate as the given user. + # Returns the response received from the get method (usually Net::HTTPResponse) + def NegotiateAuth.proxy_auth_get(http, path, user = nil, domain = nil) + raise "http must respond to :get" unless http.respond_to?(:get) + nego_auth = self.new user, domain + + resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token } + if resp["Proxy-Authenticate"] + resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(resp["Proxy-Authenticate"].split(" ").last.strip) } + end + + resp + end + + # Creates a new instance ready for authentication as the given user in the given domain. + # Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if + # no arguments are supplied. + def initialize(user = nil, domain = nil) + if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil? + raise "A username or domain must be supplied since they cannot be retrieved from the environment" + end + + @user = user || ENV["USERNAME"] + @domain = domain || ENV["USERDOMAIN"] + end + + # Gets the initial Negotiate token. Returns it as a base64 encoded string suitable for use in HTTP. Can + # be easily decoded, however. + def get_initial_token + raise "This object is no longer usable because its resources have been freed." if @cleaned_up + get_credentials + + outputBuffer = SecurityBuffer.new + @context = CtxtHandle.new + @contextAttributes = "\0" * 4 + + result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, nil, nil, + REQUEST_FLAGS,0, SECURITY_NETWORK_DREP, nil, 0, @context.to_p, outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p)) + + if result.ok? then + return encode_token(outputBuffer.token) + else + raise "Error: #{result.to_s}" + end + end + + # Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not. + # The token can include the "Negotiate" header and it will be stripped. + # Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned. + # Token returned is Base64 encoded w/ all new lines removed. + def complete_authentication(token) + raise "This object is no longer usable because its resources have been freed." if @cleaned_up + + # Nil token OK, just set it to empty string + token = "" if token.nil? + + if token.include? "Negotiate" + # If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token. + token = token.split(" ").last + end + + if token.include? B64_TOKEN_PREFIX + # indicates base64 encoded token + token = token.strip.unpack("m")[0] + end + + outputBuffer = SecurityBuffer.new + result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil, + REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0, + @context.to_p, + outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p)) + + if result.ok? then + return encode_token(outputBuffer.token) + else + raise "Error: #{result.to_s}" + end + ensure + # need to make sure we don't clean up if we've already cleaned up. + clean_up unless @cleaned_up + end + + private + + def clean_up + # free structures allocated + @cleaned_up = true + API::FreeCredentialsHandle.call(@credentials.to_p) + API::DeleteSecurityContext.call(@context.to_p) + @context = nil + @credentials = nil + @contextAttributes = nil + end + + # Gets credentials based on user, domain or both. If both are nil, an error occurs + def get_credentials + @credentials = CredHandle.new + ts = TimeStamp.new + @identity = Identity.new @user, @domain + result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p, + nil, nil, @credentials.to_p, ts.to_p)) + raise "Error acquire credentials: #{result}" unless result.ok? + end + + def encode_token(t) + # encode64 will add newlines every 60 characters so we need to remove those. + [t].pack("m").delete("\n") + end + end + end +end
\ No newline at end of file |