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

* ext/fiddle/*: Adding fiddle library to wrap libffi

* test/fiddle/*: testing fiddle extension
* ext/dl/lib/dl.rb: Requiring fiddle if it is available
* ext/dl/lib/dl/callback.rb: using Fiddle if it is available
* ext/dl/lib/dl/func.rb: ditto

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27640 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
tenderlove 2010-05-06 06:59:24 +00:00
parent ca3c007f05
commit 4bada8b864
22 changed files with 1084 additions and 71 deletions

View file

@ -1,3 +1,11 @@
Thu May 6 15:56:12 2010 Aaron Patterson <aaron@tenderlovemaking.com>
* ext/fiddle/*: Adding fiddle library to wrap libffi
* test/fiddle/*: testing fiddle extension
* ext/dl/lib/dl.rb: Requiring fiddle if it is available
* ext/dl/lib/dl/callback.rb: using Fiddle if it is available
* ext/dl/lib/dl/func.rb: ditto
Thu May 6 15:04:37 2010 NARUSE, Yui <naruse@ruby-lang.org>
* string.c (rb_str_match_m): add description about optional

12
ext/dl/lib/dl.rb Normal file
View file

@ -0,0 +1,12 @@
require 'dl.so'
begin
require 'fiddle'
rescue LoadError
end
module DL
def self.fiddle?
Object.const_defined?(:Fiddle)
end
end

View file

@ -4,22 +4,38 @@ require 'thread'
module DL
SEM = Mutex.new
def set_callback_internal(proc_entry, addr_entry, argc, ty, &cbp)
if DL.fiddle?
CdeclCallbackProcs = {}
CdeclCallbackAddrs = {}
StdcallCallbackProcs = {}
StdcallCallbackAddrs = {}
end
def set_callback_internal(proc_entry, addr_entry, argc, ty, abi = nil, &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
if DL.fiddle?
abi ||= Fiddle::Function::DEFAULT
closure = Fiddle::Closure::BlockCaller.new(ty, [TYPE_VOIDP] * argc, abi, &cbp)
proc_entry[closure.to_i] = closure
addr = closure.to_i
else
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
}
}
}
end
addr
end
@ -28,31 +44,42 @@ module DL
end
def set_stdcall_callback(ty, argc, &cbp)
set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, &cbp)
if DL.fiddle?
set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, Fiddle::Function::STDCALL, &cbp)
else
set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, &cbp)
end
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
}
if DL.fiddle?
addr = addr.to_i
return false unless proc_entry.key?(addr)
proc_entry.delete(addr)
true
else
addr_entry.each{|ty,entry|
entry.each_with_index{|xaddr, idx|
index = nil
if( ctype )
addr_entry[ctype].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
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
end
end

View file

@ -5,21 +5,37 @@ require 'dl/value'
require 'thread'
module DL
class Function
parent = DL.fiddle? ? Fiddle::Function : Object
class Function < parent
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 = nil, &block
if DL.fiddle?
abi ||= Fiddle::Function::DEFAULT
if block_given?
@cfunc = Class.new(Fiddle::Closure) {
define_method(:call, block)
}.new(cfunc.ctype, argtypes)
else
@cfunc = cfunc
end
@args = argtypes
super(@cfunc, @args.reject { |x| x == TYPE_VOID }, cfunc.ctype, abi)
else
@unsigned = false
end
if( proc )
bind(&proc)
@cfunc = cfunc
@stack = Stack.new(argtypes.collect{|ty| ty.abs})
if( @cfunc.ctype < 0 )
@cfunc.ctype = @cfunc.ctype.abs
@unsigned = true
else
@unsigned = false
end
if block_given?
bind(&block)
end
end
end
@ -32,11 +48,18 @@ 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 DL.fiddle?
if block_given?
args.find { |a| DL::Function === a }.bind_at_call(&block)
end
super
else
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
end
def wrap_result(r)
@ -52,31 +75,44 @@ 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}")
if DL.fiddle?
@cfunc = Class.new(Fiddle::Closure) {
def initialize ctype, args, block
super(ctype, args)
@block = block
end
def call *args
@block.call(*args)
end
}.new(@cfunc.ctype, @args, block)
else
if( !block )
raise(RuntimeError, "block must be given.")
end
if( @cfunc.ptr == 0 )
raise(RuntimeException, "can't bind C function.")
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
end

View file

@ -211,9 +211,17 @@ 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
if DL.fiddle?
closure = Class.new(Fiddle::Closure) {
define_method(:call, block)
}.new(ctype, argtype)
Function.new(closure, argtype)
else
f = Function.new(CFunc.new(0, ctype, name, call_type || :cdecl), argtype)
f.bind(&block)
f
end
end
def create_temp_function(name, ctype, argtype, call_type = nil)

View file

@ -45,7 +45,7 @@ module DL
result
end
def wrap_arg(arg, ty, funcs, &block)
def wrap_arg(arg, ty, funcs = [], &block)
funcs ||= []
case arg
when nil

232
ext/fiddle/closure.c Normal file
View file

@ -0,0 +1,232 @@
#include <fiddle.h>
VALUE cFiddleClosure;
typedef struct {
void * code;
ffi_closure *pcl;
ffi_cif * cif;
int argc;
ffi_type **argv;
} fiddle_closure;
static void
dealloc(void * ptr)
{
fiddle_closure * cls = (fiddle_closure *)ptr;
#ifndef MACOSX
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
closure_memsize(const void * ptr)
{
fiddle_closure * cls = (fiddle_closure *)ptr;
size_t size = 0;
if (ptr) {
size += sizeof(*cls);
#if !defined(FFI_NO_RAW_API) || !FFI_NO_RAW_API
size += ffi_raw_size(cls->cif);
#endif
size += sizeof(*cls->argv);
size += sizeof(ffi_closure);
}
return size;
}
const rb_data_type_t closure_data_type = {
"fiddle/closure",
0, dealloc, closure_memsize,
};
void
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_LENINT(rbargs);
VALUE *params = xcalloc(argc, sizeof(VALUE *));
VALUE ret;
VALUE cPointer;
int i, type;
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
for (i = 0; i < argc; i++) {
type = NUM2INT(RARRAY_PTR(rbargs)[i]);
switch (type) {
case TYPE_VOID:
argc = 0;
break;
case TYPE_INT:
params[i] = INT2NUM(*(int *)args[i]);
break;
case TYPE_VOIDP:
params[i] = rb_funcall(cPointer, rb_intern("[]"), 1,
PTR2NUM(*(void **)args[i]));
break;
case TYPE_LONG:
params[i] = LONG2NUM(*(long *)args[i]);
break;
case TYPE_CHAR:
params[i] = INT2NUM(*(char *)args[i]);
break;
case TYPE_DOUBLE:
params[i] = rb_float_new(*(double *)args[i]);
break;
case TYPE_FLOAT:
params[i] = rb_float_new(*(float *)args[i]);
break;
#if HAVE_LONG_LONG
case TYPE_LONG_LONG:
params[i] = rb_ull2inum(*(unsigned LONG_LONG *)args[i]);
break;
#endif
default:
rb_raise(rb_eRuntimeError, "closure args: %d", type);
}
}
ret = rb_funcall2(self, rb_intern("call"), argc, params);
type = NUM2INT(ctype);
switch (type) {
case TYPE_VOID:
break;
case TYPE_LONG:
*(long *)resp = NUM2LONG(ret);
break;
case TYPE_CHAR:
*(char *)resp = NUM2INT(ret);
break;
case TYPE_VOIDP:
*(void **)resp = NUM2PTR(ret);
break;
case TYPE_INT:
*(int *)resp = NUM2INT(ret);
break;
case TYPE_DOUBLE:
*(double *)resp = NUM2DBL(ret);
break;
case TYPE_FLOAT:
*(float *)resp = (float)NUM2DBL(ret);
break;
#if HAVE_LONG_LONG
case TYPE_LONG_LONG:
*(unsigned LONG_LONG *)resp = rb_big2ull(ret);
break;
#endif
default:
rb_raise(rb_eRuntimeError, "closure retval: %d", type);
}
xfree(params);
}
static VALUE
allocate(VALUE klass)
{
fiddle_closure * closure;
VALUE i = TypedData_Make_Struct(klass, fiddle_closure,
&closure_data_type, closure);
#ifndef MACOSX
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
initialize(int rbargc, VALUE argv[], VALUE self)
{
VALUE ret;
VALUE args;
VALUE abi;
fiddle_closure * cl;
ffi_cif * cif;
ffi_closure *pcl;
ffi_status result;
int i, argc;
if (2 == rb_scan_args(rbargc, argv, "21", &ret, &args, &abi))
abi = INT2NUM(FFI_DEFAULT_ABI);
Check_Type(args, T_ARRAY);
argc = RARRAY_LENINT(args);
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl);
cl->argv = (ffi_type **)xcalloc(argc + 1, sizeof(ffi_type *));
for (i = 0; i < argc; i++) {
int type = NUM2INT(RARRAY_PTR(args)[i]);
cl->argv[i] = INT2FFI_TYPE(type);
}
cl->argv[argc] = NULL;
rb_iv_set(self, "@ctype", ret);
rb_iv_set(self, "@args", args);
cif = cl->cif;
pcl = cl->pcl;
result = ffi_prep_cif(cif, NUM2INT(abi), argc,
INT2FFI_TYPE(NUM2INT(ret)),
cl->argv);
if (FFI_OK != result)
rb_raise(rb_eRuntimeError, "error prepping CIF %d", result);
#ifndef MACOSX
result = ffi_prep_closure_loc(pcl, cif, callback,
(void *)self, cl->code);
#else
result = ffi_prep_closure(pcl, cif, 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
to_i(VALUE self)
{
fiddle_closure * cl;
void *code;
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl);
code = cl->code;
return PTR2NUM(code);
}
void
Init_fiddle_closure()
{
cFiddleClosure = rb_define_class_under(mFiddle, "Closure", rb_cObject);
rb_define_alloc_func(cFiddleClosure, allocate);
rb_define_method(cFiddleClosure, "initialize", initialize, -1);
rb_define_method(cFiddleClosure, "to_i", to_i, 0);
}
/* vim: set noet sw=4 sts=4 */

8
ext/fiddle/closure.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef FIDDLE_CLOSURE_H
#define FIDDLE_CLOSURE_H
#include <fiddle.h>
void Init_fiddle_closure();
#endif

126
ext/fiddle/conversions.c Normal file
View file

@ -0,0 +1,126 @@
#include <fiddle.h>
ffi_type *
int_to_ffi_type(int type)
{
int signed_p = 1;
if (type < 0) {
type = -1 * type;
signed_p = 0;
}
#define rb_ffi_type_of(t) (signed_p ? &ffi_type_s##t : &ffi_type_u##t)
switch (type) {
case TYPE_VOID:
return &ffi_type_void;
case TYPE_VOIDP:
return &ffi_type_pointer;
case TYPE_CHAR:
return rb_ffi_type_of(char);
case TYPE_SHORT:
return rb_ffi_type_of(short);
case TYPE_INT:
return rb_ffi_type_of(int);
case TYPE_LONG:
return rb_ffi_type_of(long);
#if HAVE_LONG_LONG
case TYPE_LONG_LONG:
return rb_ffi_type_of(int64);
#endif
case TYPE_FLOAT:
return &ffi_type_float;
case TYPE_DOUBLE:
return &ffi_type_double;
default:
rb_raise(rb_eRuntimeError, "unknown type %d", type);
}
return &ffi_type_pointer;
}
void
value_to_generic(int type, VALUE src, fiddle_generic * dst)
{
int signed_p = 1;
if (type < 0) {
type = -1 * type;
signed_p = 0;
}
switch (type) {
case TYPE_VOID:
break;
case TYPE_VOIDP:
dst->pointer = NUM2PTR(rb_Integer(src));
break;
case TYPE_CHAR:
case TYPE_SHORT:
case TYPE_INT:
dst->sint = NUM2INT(src);
break;
case TYPE_LONG:
if (signed_p)
dst->slong = NUM2LONG(src);
else
dst->ulong = NUM2LONG(src);
break;
#if HAVE_LONG_LONG
case TYPE_LONG_LONG:
dst->long_long = rb_big2ull(src);
break;
#endif
case TYPE_FLOAT:
dst->ffloat = (float)NUM2DBL(src);
break;
case TYPE_DOUBLE:
dst->ddouble = NUM2DBL(src);
break;
default:
rb_raise(rb_eRuntimeError, "unknown type %d", type);
}
}
VALUE
generic_to_value(VALUE rettype, fiddle_generic retval)
{
int signed_p = 1;
int type = NUM2INT(rettype);
VALUE cPointer;
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
if (type < 0) {
type = -1 * type;
signed_p = 0;
}
switch (type) {
case TYPE_VOID:
return Qnil;
case TYPE_VOIDP:
return rb_funcall(cPointer, rb_intern("[]"), 1,
PTR2NUM((void *)retval.pointer));
case TYPE_CHAR:
case TYPE_SHORT:
case TYPE_INT:
return INT2NUM(retval.sint);
case TYPE_LONG:
if (signed_p) return LONG2NUM(retval.slong);
return ULONG2NUM(retval.ulong);
#if HAVE_LONG_LONG
case TYPE_LONG_LONG:
return rb_ll2inum(retval.long_long);
break;
#endif
case TYPE_FLOAT:
return rb_float_new(retval.ffloat);
case TYPE_DOUBLE:
return rb_float_new(retval.ddouble);
default:
rb_raise(rb_eRuntimeError, "unknown type %d", type);
}
}
/* vim: set noet sw=4 sts=4 */

41
ext/fiddle/conversions.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef FIDDLE_CONVERSIONS_H
#define FIDDLE_CONVERSIONS_H
#include <fiddle.h>
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 */
} fiddle_generic;
ffi_type * int_to_ffi_type(int type);
void value_to_generic(int type, VALUE src, fiddle_generic * dst);
VALUE generic_to_value(VALUE rettype, fiddle_generic retval);
#define VALUE2GENERIC(_type, _src, _dst) value_to_generic(_type, _src, _dst)
#define INT2FFI_TYPE(_type) int_to_ffi_type(_type)
#define GENERIC2VALUE(_type, _retval) generic_to_value(_type, _retval)
#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
#endif

23
ext/fiddle/extconf.rb Normal file
View file

@ -0,0 +1,23 @@
require 'mkmf'
# :stopdoc:
dir_config 'libffi'
unless have_header('ffi.h')
if have_header('ffi/ffi.h')
$defs.push(format('-DUSE_HEADER_HACKS'))
else
abort "ffi.h is missing. Please install libffi."
end
end
unless have_library('ffi')
abort "libffi is missing. Please install libffi."
end
have_header 'sys/mman.h'
create_makefile 'fiddle'
# :startdoc:

30
ext/fiddle/fiddle.c Normal file
View file

@ -0,0 +1,30 @@
#include <fiddle.h>
VALUE mFiddle;
void Init_fiddle()
{
mFiddle = rb_define_module("Fiddle");
rb_define_const(mFiddle, "TYPE_VOID", INT2NUM(TYPE_VOID));
rb_define_const(mFiddle, "TYPE_VOIDP", INT2NUM(TYPE_VOIDP));
rb_define_const(mFiddle, "TYPE_CHAR", INT2NUM(TYPE_CHAR));
rb_define_const(mFiddle, "TYPE_SHORT", INT2NUM(TYPE_SHORT));
rb_define_const(mFiddle, "TYPE_INT", INT2NUM(TYPE_INT));
rb_define_const(mFiddle, "TYPE_LONG", INT2NUM(TYPE_LONG));
#if HAVE_LONG_LONG
rb_define_const(mFiddle, "TYPE_LONG_LONG", INT2NUM(TYPE_LONG_LONG));
#endif
rb_define_const(mFiddle, "TYPE_FLOAT", INT2NUM(TYPE_FLOAT));
rb_define_const(mFiddle, "TYPE_DOUBLE", INT2NUM(TYPE_DOUBLE));
#if defined(HAVE_WINDOWS_H)
rb_define_const(mFiddle, "WINDOWS", Qtrue);
#else
rb_define_const(mFiddle, "WINDOWS", Qfalse);
#endif
Init_fiddle_function();
Init_fiddle_closure();
}
/* vim: set noet sws=4 sw=4: */

45
ext/fiddle/fiddle.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef FIDDLE_H
#define FIDDLE_H
#include <ruby.h>
#include <errno.h>
#if defined(HAVE_WINDOWS_H)
#include <windows.h>
#endif
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#ifdef USE_HEADER_HACKS
#include <ffi/ffi.h>
#else
#include <ffi.h>
#endif
#include <closure.h>
#include <conversions.h>
#include <function.h>
/* FIXME
* These constants need to match up with DL. We need to refactor this to use
* the DL header files or vice versa.
*/
#define TYPE_VOID 0
#define TYPE_VOIDP 1
#define TYPE_CHAR 2
#define TYPE_SHORT 3
#define TYPE_INT 4
#define TYPE_LONG 5
#if HAVE_LONG_LONG
#define TYPE_LONG_LONG 6
#endif
#define TYPE_FLOAT 7
#define TYPE_DOUBLE 8
extern VALUE mFiddle;
#endif
/* vim: set noet sws=4 sw=4: */

155
ext/fiddle/function.c Normal file
View file

@ -0,0 +1,155 @@
#include <fiddle.h>
VALUE cFiddleFunction;
static void
deallocate(void *p)
{
ffi_cif *ptr = p;
if (ptr->arg_types) xfree(ptr->arg_types);
xfree(ptr);
}
static size_t
function_memsize(const void *p)
{
/* const */ffi_cif *ptr = (ffi_cif *)p;
size_t size = 0;
if (ptr) {
size += sizeof(*ptr);
#if !defined(FFI_NO_RAW_API) || !FFI_NO_RAW_API
size += ffi_raw_size(ptr);
#endif
}
return size;
}
const rb_data_type_t function_data_type = {
"fiddle/function",
0, deallocate, function_memsize,
};
static VALUE
allocate(VALUE klass)
{
ffi_cif * cif;
return TypedData_Make_Struct(klass, ffi_cif, &function_data_type, cif);
}
static VALUE
initialize(int argc, VALUE argv[], VALUE self)
{
ffi_cif * cif;
ffi_type **arg_types;
ffi_status result;
VALUE ptr, args, ret_type, abi;
int i;
rb_scan_args(argc, argv, "31", &ptr, &args, &ret_type, &abi);
if(NIL_P(abi)) abi = INT2NUM(FFI_DEFAULT_ABI);
Check_Type(args, T_ARRAY);
rb_iv_set(self, "@ptr", ptr);
rb_iv_set(self, "@args", args);
rb_iv_set(self, "@return_type", ret_type);
rb_iv_set(self, "@abi", abi);
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
arg_types = xcalloc(RARRAY_LEN(args) + 1, sizeof(ffi_type *));
for (i = 0; i < RARRAY_LEN(args); i++) {
int type = NUM2INT(RARRAY_PTR(args)[i]);
arg_types[i] = INT2FFI_TYPE(type);
}
arg_types[RARRAY_LEN(args)] = NULL;
result = ffi_prep_cif (
cif,
NUM2INT(abi),
RARRAY_LENINT(args),
INT2FFI_TYPE(NUM2INT(ret_type)),
arg_types);
if (result)
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
return self;
}
static VALUE
function_call(int argc, VALUE argv[], VALUE self)
{
ffi_cif * cif;
fiddle_generic retval;
fiddle_generic *generic_args;
void **values;
void * fun_ptr;
VALUE cfunc, types, cPointer;
int i;
cfunc = rb_iv_get(self, "@ptr");
types = rb_iv_get(self, "@args");
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
if(argc != RARRAY_LENINT(types)) {
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, RARRAY_LENINT(types));
}
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
values = xcalloc((size_t)argc + 1, (size_t)sizeof(void *));
generic_args = xcalloc((size_t)argc, (size_t)sizeof(fiddle_generic));
for (i = 0; i < argc; i++) {
VALUE type = RARRAY_PTR(types)[i];
VALUE src = argv[i];
if(NUM2INT(type) == TYPE_VOIDP) {
if(NIL_P(src)) {
src = INT2NUM(0);
} else if(cPointer != CLASS_OF(src)) {
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
}
src = rb_Integer(src);
}
VALUE2GENERIC(NUM2INT(type), src, &generic_args[i]);
values[i] = (void *)&generic_args[i];
}
values[argc] = NULL;
ffi_call(cif, NUM2PTR(rb_Integer(cfunc)), &retval, values);
rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno));
#if defined(HAVE_WINDOWS_H)
rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, INT2NUM(errno));
#endif
xfree(values);
xfree(generic_args);
return GENERIC2VALUE(rb_iv_get(self, "@return_type"), retval);
}
void
Init_fiddle_function(void)
{
cFiddleFunction = rb_define_class_under(mFiddle, "Function", rb_cObject);
rb_define_const(cFiddleFunction, "DEFAULT", INT2NUM(FFI_DEFAULT_ABI));
#ifdef FFI_STDCALL
rb_define_const(cFiddleFunction, "STDCALL", INT2NUM(FFI_STDCALL));
#endif
rb_define_alloc_func(cFiddleFunction, allocate);
rb_define_method(cFiddleFunction, "call", function_call, -1);
rb_define_method(cFiddleFunction, "initialize", initialize, -1);
}
/* vim: set noet sws=4 sw=4: */

8
ext/fiddle/function.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef FIDDLE_FUNCTION_H
#define FIDDLE_FUNCTION_H
#include <fiddle.h>
void Init_fiddle_function();
#endif

27
ext/fiddle/lib/fiddle.rb Normal file
View file

@ -0,0 +1,27 @@
require 'fiddle.so'
require 'fiddle/function'
require 'fiddle/closure'
require 'dl'
module Fiddle
Pointer = DL::CPtr
if WINDOWS
def self.win32_last_error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__]
end
def self.win32_last_error= error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error
end
end
def self.last_error
Thread.current[:__FIDDLE_LAST_ERROR__]
end
def self.last_error= error
Thread.current[:__DL2_LAST_ERROR__] = error
Thread.current[:__FIDDLE_LAST_ERROR__] = error
end
end

View file

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

View file

@ -0,0 +1,5 @@
module Fiddle
class Function
attr_reader :abi
end
end

71
test/fiddle/helper.rb Normal file
View file

@ -0,0 +1,71 @@
require 'minitest/autorun'
require 'dl'
require 'fiddle'
# FIXME: this is stolen from DL and needs to be refactored.
require_relative '../ruby/envutil'
libc_so = libm_so = nil
case RUBY_PLATFORM
when /cygwin/
libc_so = "cygwin1.dll"
libm_so = "cygwin1.dll"
when /x86_64-linux/
libc_so = "/lib64/libc.so.6"
libm_so = "/lib64/libm.so.6"
when /linux/
libdir = '/lib'
case [0].pack('L!').size
when 4
# 32-bit ruby
libdir = '/lib32' if File.directory? '/lib32'
when 8
# 64-bit ruby
libdir = '/lib64' if File.directory? '/lib64'
end
libc_so = File.join(libdir, "libc.so.6")
libm_so = File.join(libdir, "libm.so.6")
when /mingw/, /mswin/
require "rbconfig"
libc_so = libm_so = RbConfig::CONFIG["RUBY_SO_NAME"].split(/-/, 2)[0] + ".dll"
when /darwin/
libc_so = "/usr/lib/libc.dylib"
libm_so = "/usr/lib/libm.dylib"
when /kfreebsd/
libc_so = "/lib/libc.so.0.1"
libm_so = "/lib/libm.so.1"
when /bsd|dragonfly/
libc_so = "/usr/lib/libc.so"
libm_so = "/usr/lib/libm.so"
else
libc_so = ARGV[0] if ARGV[0] && ARGV[0][0] == ?/
libm_so = ARGV[1] if ARGV[1] && ARGV[1][0] == ?/
if( !(libc_so && libm_so) )
$stderr.puts("libc and libm not found: #{$0} <libc> <libm>")
end
end
libc_so = nil if !libc_so || (libc_so[0] == ?/ && !File.file?(libc_so))
libm_so = nil if !libm_so || (libm_so[0] == ?/ && !File.file?(libm_so))
if !libc_so || !libm_so
ruby = EnvUtil.rubybin
ldd = `ldd #{ruby}`
#puts ldd
libc_so = $& if !libc_so && %r{/\S*/libc\.so\S*} =~ ldd
libm_so = $& if !libm_so && %r{/\S*/libm\.so\S*} =~ ldd
#p [libc_so, libm_so]
end
Fiddle::LIBC_SO = libc_so
Fiddle::LIBM_SO = libm_so
module Fiddle
class TestCase < MiniTest::Unit::TestCase
def setup
@libc = DL.dlopen(LIBC_SO)
@libm = DL.dlopen(LIBM_SO)
end
end
end

View file

@ -0,0 +1,49 @@
require_relative 'helper'
module Fiddle
class TestClosure < Fiddle::TestCase
def test_argument_errors
assert_raises(TypeError) do
Closure.new(TYPE_INT, TYPE_INT)
end
assert_raises(TypeError) do
Closure.new('foo', [TYPE_INT])
end
assert_raises(TypeError) do
Closure.new(TYPE_INT, ['meow!'])
end
end
def test_call
closure = Class.new(Closure) {
def call
10
end
}.new(TYPE_INT, [])
func = Function.new(closure, [], TYPE_INT)
assert_equal 10, func.call
end
def test_returner
closure = Class.new(Closure) {
def call thing
thing
end
}.new(TYPE_INT, [TYPE_INT])
func = Function.new(closure, [TYPE_INT], TYPE_INT)
assert_equal 10, func.call(10)
end
def test_block_caller
cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
one
end
func = Function.new(cb, [TYPE_INT], TYPE_INT)
assert_equal 11, func.call(11)
end
end
end

View file

@ -0,0 +1,19 @@
require_relative 'helper'
class TestFiddle < Fiddle::TestCase
def test_constants_match
[
:TYPE_VOID,
:TYPE_VOIDP,
:TYPE_CHAR,
:TYPE_SHORT,
:TYPE_INT,
:TYPE_LONG,
:TYPE_LONG_LONG,
:TYPE_FLOAT,
:TYPE_DOUBLE,
].each do |name|
assert_equal(DL.const_get(name), Fiddle.const_get(name))
end
end
end

View file

@ -0,0 +1,66 @@
require_relative 'helper'
module Fiddle
class TestFunction < Fiddle::TestCase
def setup
super
Fiddle.last_error = nil
end
def test_default_abi
func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
assert_equal Function::DEFAULT, func.abi
end
def test_argument_errors
assert_raises(TypeError) do
Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE)
end
assert_raises(TypeError) do
Function.new(@libm['sin'], ['foo'], TYPE_DOUBLE)
end
assert_raises(TypeError) do
Function.new(@libm['sin'], [TYPE_DOUBLE], 'foo')
end
end
def test_call
func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
assert_in_delta 1.0, func.call(90 * Math::PI / 180), 0.0001
end
def test_argument_count
closure = Class.new(Closure) {
def call one
10 + one
end
}.new(TYPE_INT, [TYPE_INT])
func = Function.new(closure, [TYPE_INT], TYPE_INT)
assert_raises(ArgumentError) do
func.call(1,2,3)
end
assert_raises(ArgumentError) do
func.call
end
end
def test_last_error
func = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
assert_nil Fiddle.last_error
str = func.call("000", "123")
refute_nil Fiddle.last_error
end
def test_strcpy
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
buff = "000"
str = f.call(buff, "123")
assert_equal("123", buff)
assert_equal("123", str.to_s)
end
end
end