summaryrefslogtreecommitdiff
path: root/ext/dl/method.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/dl/method.c')
-rw-r--r--ext/dl/method.c251
1 files changed, 251 insertions, 0 deletions
diff --git a/ext/dl/method.c b/ext/dl/method.c
new file mode 100644
index 00000000000..8462292d98f
--- /dev/null
+++ b/ext/dl/method.c
@@ -0,0 +1,251 @@
+/* -*- C -*-
+ * $Id$
+ */
+
+#include <ruby.h>
+#include <errno.h>
+#include "dl.h"
+#include <dl_conversions.h>
+
+VALUE rb_cDLMethod;
+
+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(void *p)
+{
+ ffi_cif *ptr = p;
+ if (ptr->arg_types) xfree(ptr->arg_types);
+ xfree(ptr);
+}
+
+static size_t
+dlfunction_memsize(const void *p)
+{
+ /* const */ffi_cif *ptr = (ffi_cif *)p;
+ 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/method",
+ 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_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);
+
+ 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, &dlfunction_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] = DL2FFI_TYPE(type);
+ }
+ arg_types[RARRAY_LEN(args)] = NULL;
+
+ 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 = (float)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 ULONG2NUM(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;
+ VALUE cfunc, types;
+ int i;
+
+ 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));
+
+ cfunc = rb_iv_get(self, "@ptr");
+ types = rb_iv_get(self, "@args");
+
+ for (i = 0; i < argc; i++) {
+ VALUE dl_type = RARRAY_PTR(types)[i];
+ VALUE src = argv[i];
+
+ if(NUM2INT(dl_type) == DLTYPE_VOIDP) {
+ if(NIL_P(src)) {
+ src = INT2NUM(0);
+ } else if(rb_cDLCPtr != CLASS_OF(src)) {
+ src = rb_funcall(rb_cDLCPtr, rb_intern("[]"), 1, src);
+ }
+ src = rb_Integer(src);
+ }
+
+ 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_iv_get(self, "@return_type"), retval);
+}
+
+void
+Init_dlfunction(void)
+{
+ rb_cDLMethod = rb_define_class_under(rb_mDL, "Method", rb_cObject);
+
+ rb_define_const(rb_cDLMethod, "DEFAULT", INT2NUM(FFI_DEFAULT_ABI));
+
+#ifdef FFI_STDCALL
+ rb_define_const(rb_cDLMethod, "STDCALL", INT2NUM(FFI_STDCALL));
+#endif
+
+ rb_define_alloc_func(rb_cDLMethod, rb_dlfunc_allocate);
+
+ rb_define_method(rb_cDLMethod, "call", rb_dlfunction_call, -1);
+
+ rb_define_method(rb_cDLMethod, "initialize", rb_dlfunction_initialize, -1);
+}
+/* vim: set noet sw=4 sts=4 */