1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Wed Feb 3 10:12:09 2010 Aaron Patterson <tenderlove@ruby-lang.org>

* ext/dl/function.c: DL::Function now uses libffi

        * ext/dl/cfunc.c (rb_dl_set_last_error): set to non static so errors
          can be exposed.

        * ext/dl/closure.c: DL::Closure will now be used in place of
          ext/dl/callback/*.

        * ext/dl/dl.c: legacy callbacks removed in favor of libffi

        * ext/dl/dl_converions.(c,h): used for converting ruby types to FFI
          types.

        * ext/dl/callback/*: replaced by libffi callbacks.

        * ext/dl/lib/dl/callback.rb: Converting internal callbacks to use
          DL::Closure

        * ext/dl/lib/dl/closure.rb: Ruby parts of the new DL::Closure object

        * ext/dl/lib/dl/import.rb: More conversion to use DL::Closure object

        * ext/dl/lib/dl/value.rb (ruby2ffi): adding private method for
          DL::CPtr to ffi value conversion.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@26545 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
tenderlove 2010-02-03 01:23:48 +00:00
parent b378bda47c
commit b386fe21ec
21 changed files with 802 additions and 378 deletions

View file

@ -1,3 +1,30 @@
Wed Feb 3 10:12:09 2010 Aaron Patterson <tenderlove@ruby-lang.org>
* ext/dl/function.c: DL::Function now uses libffi
* ext/dl/cfunc.c (rb_dl_set_last_error): set to non static so errors
can be exposed.
* ext/dl/closure.c: DL::Closure will now be used in place of
ext/dl/callback/*.
* ext/dl/dl.c: legacy callbacks removed in favor of libffi
* ext/dl/dl_converions.(c,h): used for converting ruby types to FFI
types.
* ext/dl/callback/*: replaced by libffi callbacks.
* ext/dl/lib/dl/callback.rb: Converting internal callbacks to use
DL::Closure
* ext/dl/lib/dl/closure.rb: Ruby parts of the new DL::Closure object
* ext/dl/lib/dl/import.rb: More conversion to use DL::Closure object
* ext/dl/lib/dl/value.rb (ruby2ffi): adding private method for
DL::CPtr to ffi value conversion.
Tue Feb 2 18:15:12 2010 Nobuyoshi Nakada <nobu@ruby-lang.org>
* ext/socket/socket.c: turn on do_not_reverse_lookup by default,

View file

@ -1,15 +0,0 @@
src: callback.c \
callback-0.c callback-1.c callback-2.c \
callback-3.c callback-4.c callback-5.c \
callback-6.c callback-7.c callback-8.c
$(OBJS): $(hdrdir)/ruby.h
callback-0.c callback-1.c callback-2.c \
callback-3.c callback-4.c callback-5.c \
callback-6.c callback-7.c callback-8.c \
: callback.c
callback.c: $(srcdir)/mkcallback.rb $(srcdir)/../dl.h
@echo "generating callback.c"
@$(RUBY) $(srcdir)/mkcallback.rb -output=callback $(srcdir)/../dl.h

View file

@ -1,14 +0,0 @@
require 'mkmf'
if compiled?("dl")
callbacks = (0..8).map{|i| "callback-#{i}"}.unshift("callback")
callback_srcs = callbacks.map{|basename| "#{basename}.c"}
callback_objs = callbacks.map{|basename| "#{basename}.o"}
$distcleanfiles << '$(SRCS)'
$srcs = callback_srcs
$objs = callback_objs
$INCFLAGS << " -I$(srcdir)/.."
create_makefile("dl/callback")
end

View file

@ -1,238 +0,0 @@
#!ruby -s
$output ||= "callback"
$out = open("#{$output}.c", "w")
$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 => '(float)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)
#include "ruby.h"
VALUE rb_DLCdeclCallbackAddrs, rb_DLCdeclCallbackProcs;
#ifdef FUNC_STDCALL
VALUE rb_DLStdcallCallbackAddrs, rb_DLStdcallCallbackProcs;
#endif
/*static void *cdecl_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/
#ifdef FUNC_STDCALL
/*static void *stdcall_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/
#endif
ID rb_dl_cb_call;
EOS
def foreach_proc_entry
for calltype in CALLTYPES
case calltype
when CDECL
proc_entry = "rb_DLCdeclCallbackProcs"
when STDCALL
proc_entry = "rb_DLStdcallCallbackProcs"
else
raise "unknown calltype: #{calltype}"
end
yield calltype, proc_entry
end
end
def gencallback(ty, calltype, proc_entry, argc, n)
<<-EOS
#{calltype == STDCALL ? "\n#ifdef FUNC_STDCALL" : ""}
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, rb_dl_cb_call, #{argc}, #{argc > 0 ? 'args' : 'NULL'});
return #{DLTYPE[ty][:conv] ? DLTYPE[ty][:conv] % "ret" : ""};
}
#{calltype == STDCALL ? "#endif\n" : ""}
EOS
end
def gen_push_proc_ary(ty, aryname)
sprintf(" rb_ary_push(#{aryname}, rb_ary_new3(%d,%s));",
MAX_CALLBACK * DLSTACK_SIZE,
(0...MAX_CALLBACK).collect{
(0...DLSTACK_SIZE).collect{ "Qnil" }.join(",")
}.join(","))
end
def gen_push_addr_ary(ty, aryname, calltype)
sprintf(" rb_ary_push(#{aryname}, 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,calltype)
}.join(",")
}.join(","))
end
def gen_callback_file(ty)
filename = "#{$output}-#{ty}.c"
initname = "rb_dl_init_callbacks_#{ty}"
body = <<-EOS
#include "dl.h"
extern VALUE rb_DLCdeclCallbackAddrs, rb_DLCdeclCallbackProcs;
#ifdef FUNC_STDCALL
extern VALUE rb_DLStdcallCallbackAddrs, rb_DLStdcallCallbackProcs;
#endif
extern ID rb_dl_cb_call;
EOS
yield body
body << <<-EOS
void
#{initname}()
{
#{gen_push_proc_ary(ty, "rb_DLCdeclCallbackProcs")}
#{gen_push_addr_ary(ty, "rb_DLCdeclCallbackAddrs", CDECL)}
#ifdef FUNC_STDCALL
#{gen_push_proc_ary(ty, "rb_DLStdcallCallbackProcs")}
#{gen_push_addr_ary(ty, "rb_DLStdcallCallbackAddrs", STDCALL)}
#endif
}
EOS
[filename, initname, body]
end
callbacks = []
for ty in 0...MAX_DLTYPE
filename, initname, body = gen_callback_file(ty) {|f|
foreach_proc_entry do |calltype, proc_entry|
for argc in 0...DLSTACK_SIZE
for n in 0...MAX_CALLBACK
f << gencallback(ty, calltype, proc_entry, argc, n)
end
end
end
}
$out << "void #{initname}();\n"
callbacks << [filename, body]
end
$out << (<<EOS)
void
Init_callback(void)
{
VALUE tmp;
VALUE rb_mDL = rb_path2class("DL");
rb_dl_cb_call = rb_intern("call");
tmp = rb_DLCdeclCallbackProcs = rb_ary_new();
rb_define_const(rb_mDL, "CdeclCallbackProcs", tmp);
tmp = rb_DLCdeclCallbackAddrs = rb_ary_new();
rb_define_const(rb_mDL, "CdeclCallbackAddrs", tmp);
#ifdef FUNC_STDCALL
tmp = rb_DLStdcallCallbackProcs = rb_ary_new();
rb_define_const(rb_mDL, "StdcallCallbackProcs", tmp);
tmp = rb_DLStdcallCallbackAddrs = rb_ary_new();
rb_define_const(rb_mDL, "StdcallCallbackAddrs", tmp);
#endif
#{
(0...MAX_DLTYPE).collect{|ty|
" rb_dl_init_callbacks_#{ty}();"
}.join("\n")
}
}
EOS
$out.close
for filename, body in callbacks
open(filename, "wb") {|f| f.puts body}
end

View file

@ -16,7 +16,7 @@ rb_dl_get_last_error(VALUE self)
return rb_thread_local_aref(rb_thread_current(), id_last_error);
}
static VALUE
VALUE
rb_dl_set_last_error(VALUE self, VALUE val)
{
rb_thread_local_aset(rb_thread_current(), id_last_error, val);
@ -33,7 +33,7 @@ rb_dl_get_win32_last_error(VALUE self)
return rb_thread_local_aref(rb_thread_current(), id_win32_last_error);
}
static VALUE
VALUE
rb_dl_set_win32_last_error(VALUE self, VALUE val)
{
rb_thread_local_aset(rb_thread_current(), id_win32_last_error, val);

228
ext/dl/closure.c Normal file
View file

@ -0,0 +1,228 @@
/* -*- C -*-
* $Id$
*/
#include <ruby.h>
#include "dl.h"
#include <sys/mman.h>
#include <dl_conversions.h>
VALUE rb_cDLClosure;
typedef struct {
void * code;
ffi_closure *pcl;
ffi_cif * cif;
int argc;
ffi_type **argv;
} dl_closure;
static void
dlclosure_free(void * ptr)
{
dl_closure * cls = (dl_closure *)ptr;
#ifdef USE_NEW_CLOSURE_API
ffi_closure_free(cls->pcl);
#else
munmap(cls->pcl, sizeof(cls->pcl));
#endif
xfree(cls->cif);
if(cls->argv) xfree(cls->argv);
xfree(cls);
}
static size_t
dlclosure_memsize(const void * ptr)
{
dl_closure * cls = (dl_closure *)ptr;
size_t size = 0;
if(ptr) {
size += sizeof(*cls);
size += ffi_raw_size(cls->cif);
size += sizeof(*cls->argv);
size += sizeof(ffi_closure);
}
return size;
}
const rb_data_type_t dlclosure_data_type = {
"dl/closure",
0, dlclosure_free, dlclosure_memsize,
};
void
dlc_callback(ffi_cif *cif, void *resp, void **args, void *ctx)
{
VALUE self = (VALUE)ctx;
VALUE rbargs = rb_iv_get(self, "@args");
VALUE ctype = rb_iv_get(self, "@ctype");
int argc = RARRAY_LEN(rbargs);
VALUE *params = xcalloc(argc, sizeof(VALUE *));
int i;
for(i = 0; i < argc; i++) {
int dl_type = NUM2INT(RARRAY_PTR(rbargs)[i]);
switch(dl_type) {
case DLTYPE_VOID:
argc = 0;
break;
case DLTYPE_INT:
params[i] = INT2NUM(*(int *)args[i]);
break;
case DLTYPE_VOIDP:
params[i] = rb_dlptr_new(*(void **)args[i], 0, NULL);
break;
case DLTYPE_LONG:
params[i] = LONG2NUM(*(long *)args[i]);
break;
case DLTYPE_CHAR:
params[i] = INT2NUM(*(char *)args[i]);
break;
case DLTYPE_DOUBLE:
params[i] = rb_float_new(*(double *)args[i]);
break;
case DLTYPE_FLOAT:
params[i] = rb_float_new(*(float *)args[i]);
break;
#if HAVE_LONG_LONG
case DLTYPE_LONG_LONG:
params[i] = rb_ull2inum(*(unsigned LONG_LONG *)args[i]);
break;
#endif
default:
rb_raise(rb_eRuntimeError, "closure args: %d", dl_type);
}
}
VALUE ret = rb_funcall2(self, rb_intern("call"), argc, params);
int dl_type = NUM2INT(ctype);
switch(dl_type) {
case DLTYPE_VOID:
break;
case DLTYPE_LONG:
*(long *)resp = NUM2LONG(ret);
break;
case DLTYPE_CHAR:
*(char *)resp = NUM2INT(ret);
break;
case DLTYPE_VOIDP:
*(void **)resp = NUM2PTR(ret);
break;
case DLTYPE_INT:
*(int *)resp = NUM2INT(ret);
break;
case DLTYPE_DOUBLE:
*(double *)resp = NUM2DBL(ret);
break;
case DLTYPE_FLOAT:
*(float *)resp = NUM2DBL(ret);
break;
#if HAVE_LONG_LONG
case DLTYPE_LONG_LONG:
*(unsigned LONG_LONG *)resp = rb_big2ull(ret);
break;
#endif
default:
rb_raise(rb_eRuntimeError, "closure retval: %d", dl_type);
}
xfree(params);
}
static VALUE
rb_dlclosure_allocate(VALUE klass)
{
dl_closure * closure;
VALUE i = TypedData_Make_Struct(klass, dl_closure,
&dlclosure_data_type, closure);
#ifdef USE_NEW_CLOSURE_API
closure->pcl = ffi_closure_alloc(sizeof(ffi_closure), &closure->code);
#else
closure->pcl = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
#endif
closure->cif = xmalloc(sizeof(ffi_cif));
return i;
}
static VALUE
rb_dlclosure_init(int rbargc, VALUE argv[], VALUE self)
{
VALUE ret;
VALUE args;
VALUE abi;
dl_closure * cl;
ffi_cif * cif;
ffi_closure *pcl;
if(2 == rb_scan_args(rbargc, argv, "21", &ret, &args, &abi))
abi = INT2NUM(FFI_DEFAULT_ABI);
int i;
int argc = RARRAY_LEN(args);
TypedData_Get_Struct(self, dl_closure, &dlclosure_data_type, cl);
cl->argv = (ffi_type **)xcalloc(argc + 1, sizeof(ffi_type *));
for(i = 0; i < argc; i++) {
int dltype = NUM2INT(RARRAY_PTR(args)[i]);
cl->argv[i] = DL2FFI_TYPE(dltype);
}
cl->argv[argc] = NULL;
rb_iv_set(self, "@ctype", ret);
rb_iv_set(self, "@args", args);
cif = cl->cif;
pcl = cl->pcl;
ffi_status result = ffi_prep_cif(cif, NUM2INT(abi), argc,
DL2FFI_TYPE(NUM2INT(ret)),
cl->argv);
if(FFI_OK != result)
rb_raise(rb_eRuntimeError, "error prepping CIF %d", result);
#ifdef USE_NEW_CLOSURE_API
result = ffi_prep_closure_loc(pcl, cif, dlc_callback,
(void *)self, cl->code);
#else
result = ffi_prep_closure(pcl, cif, dlc_callback, (void *)self);
cl->code = (void *)pcl;
mprotect(pcl, sizeof(pcl), PROT_READ | PROT_EXEC);
#endif
if(FFI_OK != result)
rb_raise(rb_eRuntimeError, "error prepping closure %d", result);
return self;
}
static VALUE
rb_dlclosure_to_i(VALUE self)
{
dl_closure * cl;
TypedData_Get_Struct(self, dl_closure, &dlclosure_data_type, cl);
void * code = cl->code;
return PTR2NUM(code);
}
void
Init_dlclosure(void)
{
rb_cDLClosure = rb_define_class_under(rb_mDL, "Closure", rb_cObject);
rb_define_alloc_func(rb_cDLClosure, rb_dlclosure_allocate);
rb_define_method(rb_cDLClosure, "initialize", rb_dlclosure_init, -1);
rb_define_method(rb_cDLClosure, "to_i", rb_dlclosure_to_i, 0);
}
/* vim: set noet sw=4 sts=4 */

View file

@ -77,19 +77,6 @@ rb_dl_value2ptr(VALUE self, VALUE val)
return PTR2NUM((void*)val);
}
static void
rb_dl_init_callbacks(VALUE dl)
{
static const char cb[] = "dl/callback.so";
rb_autoload(dl, rb_intern_const("CdeclCallbackAddrs"), cb);
rb_autoload(dl, rb_intern_const("CdeclCallbackProcs"), cb);
#ifdef FUNC_STDCALL
rb_autoload(dl, rb_intern_const("StdcallCallbackAddrs"), cb);
rb_autoload(dl, rb_intern_const("StdcallCallbackProcs"), cb);
#endif
}
void
Init_dl(void)
{
@ -107,8 +94,6 @@ Init_dl(void)
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_mDL);
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));
@ -162,4 +147,6 @@ Init_dl(void)
Init_dlhandle();
Init_dlcfunc();
Init_dlptr();
Init_dlfunction();
Init_dlclosure();
}

View file

@ -3,6 +3,12 @@
#include <ruby.h>
#ifdef USE_HEADER_HACKS
#include <ffi/ffi.h>
#else
#include <ffi.h>
#endif
#if !defined(FUNC_CDECL)
# define FUNC_CDECL(x) x
#endif
@ -221,4 +227,9 @@ 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);
VALUE rb_dl_set_last_error(VALUE self, VALUE val);
#if defined(HAVE_WINDOWS_H)
VALUE rb_dl_set_win32_last_error(VALUE self, VALUE val);
#endif
#endif

38
ext/dl/dl_conversions.c Normal file
View file

@ -0,0 +1,38 @@
#include <dl_conversions.h>
ffi_type * rb_dl_type_to_ffi_type(int dl_type)
{
int signed_p = 1;
if(dl_type < 0) {
dl_type = -1 * dl_type;
signed_p = 0;
}
switch(dl_type) {
case DLTYPE_VOID:
return &ffi_type_void;
case DLTYPE_VOIDP:
return &ffi_type_pointer;
case DLTYPE_CHAR:
return signed_p ? &ffi_type_schar : &ffi_type_uchar;
case DLTYPE_SHORT:
return signed_p ? &ffi_type_sshort : &ffi_type_ushort;
case DLTYPE_INT:
return signed_p ? &ffi_type_sint : &ffi_type_uint;
case DLTYPE_LONG:
return signed_p ? &ffi_type_slong : &ffi_type_ulong;
#if HAVE_LONG_LONG
case DLTYPE_LONG_LONG:
return &ffi_type_uint64;
break;
#endif
case DLTYPE_FLOAT:
return &ffi_type_float;
case DLTYPE_DOUBLE:
return &ffi_type_double;
default:
rb_raise(rb_eRuntimeError, "unknown type %d", dl_type);
}
return &ffi_type_pointer;
}

10
ext/dl/dl_conversions.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef DL_CONVERSIONS
#define DL_CONVERSIONS
#include <dl.h>
#define DL2FFI_TYPE(a) rb_dl_type_to_ffi_type(a)
ffi_type * rb_dl_type_to_ffi_type(int dl_type);
#endif

View file

@ -8,8 +8,30 @@ $INSTALLFILES = [
["dl.h", "$(HDRDIR)"],
]
if pkg_config("libffi")
# libffi closure api must be switched depending on the version
if system("pkg-config --atleast-version=3.0.9 libffi")
$defs.push(format('-DUSE_NEW_CLOSURE_API'))
end
else
dir_config('ffi', '/usr/include', '/usr/lib')
end
unless have_header('ffi.h')
if have_header('ffi/ffi.h')
$defs.push(format('-DUSE_HEADER_HACKS'))
else
abort "ffi is missing"
end
end
unless have_library('ffi')
abort "ffi is missing"
end
check = true
if( have_header("dlfcn.h") )
have_library("dl")
check &&= have_func("dlopen")
check &&= have_func("dlclose")

233
ext/dl/function.c Normal file
View file

@ -0,0 +1,233 @@
/* -*- C -*-
* $Id$
*/
#include <ruby.h>
#include <errno.h>
#include "dl.h"
#include <dl_conversions.h>
VALUE rb_cDLFunction;
typedef union
{
unsigned char uchar; // ffi_type_uchar
signed char schar; // ffi_type_schar
unsigned short ushort; // ffi_type_sshort
signed short sshort; // ffi_type_ushort
unsigned int uint; // ffi_type_uint
signed int sint; // ffi_type_sint
unsigned long ulong; // ffi_type_ulong
signed long slong; // ffi_type_slong
float ffloat; // ffi_type_float
double ddouble; // ffi_type_double
#if HAVE_LONG_LONG
unsigned LONG_LONG long_long; // ffi_type_uint64
#endif
void * pointer; // ffi_type_pointer
} dl_generic;
static void
dlfunction_free(ffi_cif *ptr)
{
if(ptr->arg_types) xfree(ptr->arg_types);
xfree(ptr);
}
static size_t
dlfunction_memsize(ffi_cif *ptr)
{
size_t size = 0;
if(ptr) {
size += sizeof(*ptr);
size += ffi_raw_size(ptr);
}
return size;
}
const rb_data_type_t dlfunction_data_type = {
"dl/function",
0, dlfunction_free, dlfunction_memsize,
};
static VALUE
rb_dlfunc_allocate(VALUE klass)
{
ffi_cif * cif;
return TypedData_Make_Struct(klass, ffi_cif, &dlfunction_data_type, cif);
}
static VALUE
rb_dlfunction_native_init(VALUE self, VALUE args, VALUE ret_type, VALUE abi)
{
ffi_cif * cif;
ffi_type **arg_types;
TypedData_Get_Struct(self, ffi_cif, &dlfunction_data_type, cif);
arg_types = xcalloc(RARRAY_LEN(args) + 1, sizeof(ffi_type *));
int i;
for(i = 0; i < RARRAY_LEN(args); i++) {
int type = NUM2INT(RARRAY_PTR(args)[i]);
arg_types[i] = DL2FFI_TYPE(type);
}
arg_types[RARRAY_LEN(args)] = NULL;
ffi_status result = ffi_prep_cif(
cif,
NUM2INT(abi),
RARRAY_LEN(args),
DL2FFI_TYPE(NUM2INT(ret_type)),
arg_types);
if(result)
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
return self;
}
static void
dl2generic(int dl_type, VALUE src, dl_generic * dst)
{
int signed_p = 1;
if(dl_type < 0) {
dl_type = -1 * dl_type;
signed_p = 0;
}
switch(dl_type) {
case DLTYPE_VOID:
break;
case DLTYPE_VOIDP:
dst->pointer = NUM2PTR(rb_Integer(src));
break;
case DLTYPE_CHAR:
case DLTYPE_SHORT:
case DLTYPE_INT:
dst->sint = NUM2INT(src);
break;
case DLTYPE_LONG:
if(signed_p)
dst->slong = NUM2LONG(src);
else
dst->ulong = NUM2LONG(src);
break;
#if HAVE_LONG_LONG
case DLTYPE_LONG_LONG:
dst->long_long = rb_big2ull(src);
break;
#endif
case DLTYPE_FLOAT:
dst->ffloat = NUM2DBL(src);
break;
case DLTYPE_DOUBLE:
dst->ddouble = NUM2DBL(src);
break;
default:
rb_raise(rb_eRuntimeError, "unknown type %d", dl_type);
}
}
static VALUE
unwrap_ffi(VALUE rettype, dl_generic retval)
{
int signed_p = 1;
int dl_type = NUM2INT(rettype);
if(dl_type < 0) {
dl_type = -1 * dl_type;
signed_p = 0;
}
switch(dl_type) {
case DLTYPE_VOID:
return Qnil;
case DLTYPE_VOIDP:
return rb_dlptr_new((void *)retval.pointer, 0, NULL);
case DLTYPE_CHAR:
case DLTYPE_SHORT:
case DLTYPE_INT:
return INT2NUM(retval.sint);
case DLTYPE_LONG:
if(signed_p) return LONG2NUM(retval.slong);
return LONG2NUM(retval.ulong);
#if HAVE_LONG_LONG
case DLTYPE_LONG_LONG:
return rb_ll2inum(retval.long_long);
break;
#endif
case DLTYPE_FLOAT:
return rb_float_new(retval.ffloat);
case DLTYPE_DOUBLE:
return rb_float_new(retval.ddouble);
default:
rb_raise(rb_eRuntimeError, "unknown type %d", dl_type);
}
}
static VALUE
rb_dlfunction_call(int argc, VALUE argv[], VALUE self)
{
ffi_cif * cif;
dl_generic retval;
dl_generic *generic_args;
void **values;
void * fun_ptr;
TypedData_Get_Struct(self, ffi_cif, &dlfunction_data_type, cif);
values = xcalloc((size_t)argc + 1, (size_t)sizeof(void *));
generic_args = xcalloc((size_t)argc, (size_t)sizeof(dl_generic));
VALUE cfunc = rb_iv_get(self, "@cfunc");
VALUE types = rb_iv_get(self, "@args");
int i;
for(i = 0; i < argc; i++) {
VALUE dl_type = RARRAY_PTR(types)[i];
VALUE src = rb_funcall(self,
rb_intern("ruby2ffi"),
2,
argv[i],
dl_type
);
dl2generic(NUM2INT(dl_type), src, &generic_args[i]);
values[i] = (void *)&generic_args[i];
}
values[argc] = NULL;
ffi_call(cif, NUM2PTR(rb_Integer(cfunc)), &retval, values);
rb_dl_set_last_error(self, INT2NUM(errno));
#if defined(HAVE_WINDOWS_H)
rb_dl_set_win32_last_error(self, INT2NUM(GetLastError()));
#endif
xfree(values);
xfree(generic_args);
return unwrap_ffi(rb_funcall(cfunc, rb_intern("ctype"), 0), retval);
}
void
Init_dlfunction(void)
{
rb_cDLFunction = rb_define_class_under(rb_mDL, "Function", rb_cObject);
rb_define_const(rb_cDLFunction, "DEFAULT", INT2NUM(FFI_DEFAULT_ABI));
#ifdef FFI_STDCALL
rb_define_const(rb_cDLFunction, "STDCALL", INT2NUM(FFI_STDCALL));
#endif
rb_define_alloc_func(rb_cDLFunction, rb_dlfunc_allocate);
rb_define_private_method(rb_cDLFunction, "native_call", rb_dlfunction_call, -1);
rb_define_private_method(rb_cDLFunction, "native_init", rb_dlfunction_native_init, 3);
}
/* vim: set noet sw=4 sts=4 */

View file

@ -1,26 +1,21 @@
require 'dl'
require 'dl/closure'
require 'thread'
module DL
SEM = Mutex.new
def set_callback_internal(proc_entry, addr_entry, argc, ty, &cbp)
CdeclCallbackProcs = {}
CdeclCallbackAddrs = {}
def set_callback_internal(proc_entry, addr_entry, argc, ty, abi = DL::Function::DEFAULT, &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
closure = DL::Closure::BlockCaller.new(ty, [TYPE_VOIDP] * argc, abi, &cbp)
proc_entry[closure.to_i] = closure
closure.to_i
end
def set_cdecl_callback(ty, argc, &cbp)
@ -28,32 +23,14 @@ module DL
end
def set_stdcall_callback(ty, argc, &cbp)
set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, &cbp)
set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, DL::Function::STDCALL, &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( index and proc_entry[ctype][index] )
proc_entry[ctype][index] = nil
return true
else
return false
end
addr = addr.to_i
return false unless proc_entry.key?(addr)
proc_entry.delete(addr)
true
end
def remove_cdecl_callback(addr, ctype = nil)

19
ext/dl/lib/dl/closure.rb Normal file
View file

@ -0,0 +1,19 @@
require 'dl'
module DL
class Closure
attr_reader :ctype
attr_reader :args
class BlockCaller < DL::Closure
def initialize ctype, args, abi = DL::Function::DEFAULT, &block
super(ctype, args, abi)
@block = block
end
def call *args
@block.call(*args)
end
end
end
end

View file

@ -1,4 +1,5 @@
require 'dl'
require 'dl/closure'
require 'dl/callback'
require 'dl/stack'
require 'dl/value'
@ -9,18 +10,17 @@ module DL
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
def initialize cfunc, argtypes, abi = DEFAULT, &block
if block_given?
@cfunc = Class.new(DL::Closure) {
define_method(:call, block)
}.new(cfunc.ctype, argtypes)
else
@unsigned = false
end
if( proc )
bind(&proc)
@cfunc = cfunc
end
@args = argtypes
native_init(@args.reject { |x| x == TYPE_VOID }, cfunc.ctype, abi)
end
def to_i()
@ -32,11 +32,10 @@ module DL
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)
if block_given?
args.find { |a| DL::Function === a }.bind_at_call(&block)
end
native_call(*args)
end
def wrap_result(r)
@ -52,33 +51,16 @@ module DL
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}")
@cfunc = Class.new(DL::Closure) {
def initialize ctype, args, block
super(ctype, args)
@block = block
end
if( @cfunc.ptr == 0 )
raise(RuntimeException, "can't bind C function.")
def call *args
@block.call(*args)
end
end
}.new(@cfunc.ctype, @args, block)
end
def unbind()

View file

@ -1,4 +1,5 @@
require 'dl'
require 'dl/closure'
require 'dl/func.rb'
require 'dl/struct.rb'
require 'dl/cparser.rb'
@ -211,9 +212,11 @@ module DL
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
closure = Class.new(DL::Closure) {
define_method(:call, block)
}.new(ctype, argtype)
Function.new(closure, argtype)
end
def create_temp_function(name, ctype, argtype, call_type = nil)

View file

@ -36,16 +36,20 @@ module DL
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
def ruby2ffi arg, type
return arg unless type == TYPE_VOIDP
case arg
when nil
0
when CPtr
arg.to_i
else
CPtr[arg].to_i
end
end
private :ruby2ffi
def wrap_arg(arg, ty, funcs, &block)
def wrap_arg(arg, ty, funcs = [], &block)
funcs ||= []
case arg
when nil

View file

@ -74,7 +74,7 @@ module DL
end
def assert_zero(actual)
assert(actual == 0)
assert_equal(0, actual)
end
def assert_negative(actual)

130
test/dl/test_closure.rb Normal file
View file

@ -0,0 +1,130 @@
require_relative 'test_base'
require 'dl/func'
require 'dl/closure'
module DL
class TestClosure < Test::Unit::TestCase
class Returner < DL::Closure
attr_accessor :called
attr_accessor :called_with
def call *args
@called = true
@called_with = args
a = args.first
DL::CPtr === a ? a.to_i : a
end
end
if defined?(TYPE_LONG_LONG)
def test_long_long
type = TYPE_LONG_LONG
addr = Returner.new(type, [type]) do |num|
called = true
called_with = num
end
func = DL::Function.new(addr, [type])
assert_equal(9223372036854775807, func.call(9223372036854775807))
end
end
def test_with_abi
called = false
addr = DL::Closure::BlockCaller.new(
TYPE_INT,
[TYPE_INT],
DL::Function::DEFAULT
) do |num|
called = true
num
end
func = DL::Function.new(addr, [TYPE_INT])
func.call(50)
assert called
end
def test_block_caller
called = false
called_with = nil
addr = DL::Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |num|
called = true
called_with = num
end
func = DL::Function.new(addr, [TYPE_INT])
func.call(50)
assert called, 'function was called'
assert_equal 50, called_with
end
def test_multival
adder = Class.new(DL::Closure) {
def call a, b
a + b
end
}.new(TYPE_INT, [TYPE_INT, TYPE_INT])
assert_equal [TYPE_INT, TYPE_INT], adder.args
func = DL::Function.new(adder, adder.args)
assert_equal 70, func.call(50, 20)
end
def test_call
closure = Class.new(DL::Closure) {
attr_accessor :called_with
def call num
@called_with = num
end
}.new(TYPE_INT, [TYPE_INT])
func = DL::Function.new(closure, [TYPE_INT])
func.call(50)
assert_equal 50, closure.called_with
end
def test_return_value
closure = Returner.new(TYPE_INT, [TYPE_INT])
func = DL::Function.new(closure, [TYPE_INT])
assert_equal 50, func.call(50)
end
def test_float
closure = Returner.new(TYPE_FLOAT, [TYPE_FLOAT])
func = DL::Function.new(closure, [TYPE_FLOAT])
assert_equal 2.0, func.call(2.0)
end
def test_char
closure = Returner.new(TYPE_CHAR, [TYPE_CHAR])
func = DL::Function.new(closure, [TYPE_CHAR])
assert_equal 60, func.call(60)
end
def test_long
closure = Returner.new(TYPE_LONG, [TYPE_LONG])
func = DL::Function.new(closure, [TYPE_LONG])
assert_equal 60, func.call(60)
end
def test_double
closure = Returner.new(TYPE_DOUBLE, [TYPE_DOUBLE])
func = DL::Function.new(closure, [TYPE_DOUBLE])
assert_equal 60, func.call(60)
end
def test_voidp
closure = Returner.new(TYPE_VOIDP, [TYPE_VOIDP])
func = DL::Function.new(closure, [TYPE_VOIDP])
voidp = CPtr['foo']
assert_equal voidp, func.call(voidp)
end
def test_void
closure = Returner.new(TYPE_VOID, [TYPE_VOID])
func = DL::Function.new(closure, [TYPE_VOID])
func.call()
assert closure.called
end
end
end

View file

@ -88,14 +88,16 @@ class TestDL < TestBase
assert_in_delta(-0.1, x)
end
def test_sin()
def test_sin
pi_2 = Math::PI/2
cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin')
x = cfunc.call([pi_2].pack("d").unpack("l!*"))
cfunc = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'),
[TYPE_DOUBLE])
x = cfunc.call(pi_2)
assert_equal(Math.sin(pi_2), x)
cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin')
x = cfunc.call([-pi_2].pack("d").unpack("l!*"))
cfunc = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'),
[TYPE_DOUBLE])
x = cfunc.call(-pi_2)
assert_equal(Math.sin(-pi_2), x)
end

View file

@ -15,6 +15,24 @@ module DL
assert_equal cfunc.to_i, f.to_i
end
def test_random
f = Function.new(CFunc.new(@libc['srand'], TYPE_VOID, 'srand'),
[-TYPE_LONG])
assert_nil f.call(10)
end
def test_sinf
f = Function.new(CFunc.new(@libm['sinf'], TYPE_FLOAT, 'sinf'),
[TYPE_FLOAT])
assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
end
def test_sin
f = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'),
[TYPE_DOUBLE])
assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
end
def test_strcpy()
f = Function.new(CFunc.new(@libc['strcpy'], TYPE_VOIDP, 'strcpy'),
[TYPE_VOIDP, TYPE_VOIDP])