/* * Copyright (c) 2008-2010 Wayne Meissner * All rights reserved. * * This file is part of ruby-ffi. * * This code is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see . */ #include #include #include #include #include #include #include #include "rbffi.h" #include "compat.h" #include "AbstractMemory.h" #include "Pointer.h" #include "Types.h" #include "Type.h" #include "LastError.h" #include "MethodHandle.h" #include "Call.h" #include "Thread.h" typedef struct VariadicInvoker_ { VALUE rbAddress; VALUE rbReturnType; VALUE rbEnums; Type* returnType; ffi_abi abi; void* function; int paramCount; } VariadicInvoker; static VALUE variadic_allocate(VALUE klass); static VALUE variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE rbReturnType, VALUE options); static void variadic_mark(VariadicInvoker *); static VALUE classVariadicInvoker = Qnil; static VALUE variadic_allocate(VALUE klass) { VariadicInvoker *invoker; VALUE obj = Data_Make_Struct(klass, VariadicInvoker, variadic_mark, -1, invoker); invoker->rbAddress = Qnil; invoker->rbEnums = Qnil; invoker->rbReturnType = Qnil; return obj; } static void variadic_mark(VariadicInvoker *invoker) { rb_gc_mark(invoker->rbEnums); rb_gc_mark(invoker->rbAddress); rb_gc_mark(invoker->rbReturnType); } static VALUE variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE rbReturnType, VALUE options) { VariadicInvoker* invoker = NULL; VALUE retval = Qnil; VALUE convention = Qnil; VALUE fixed = Qnil; VALUE rbConventionStr; int i; Check_Type(options, T_HASH); convention = rb_hash_aref(options, ID2SYM(rb_intern("convention"))); Data_Get_Struct(self, VariadicInvoker, invoker); invoker->rbEnums = rb_hash_aref(options, ID2SYM(rb_intern("enums"))); invoker->rbAddress = rbFunction; invoker->function = rbffi_AbstractMemory_Cast(rbFunction, rbffi_PointerClass)->address; #if defined(_WIN32) || defined(__WIN32__) rbConventionStr = rb_funcall2(convention, rb_intern("to_s"), 0, NULL); invoker->abi = (RTEST(convention) && strcmp(StringValueCStr(rbConventionStr), "stdcall") == 0) ? FFI_STDCALL : FFI_DEFAULT_ABI; #else invoker->abi = FFI_DEFAULT_ABI; #endif invoker->rbReturnType = rbffi_Type_Lookup(rbReturnType); if (!RTEST(invoker->rbReturnType)) { VALUE typeName = rb_funcall2(rbReturnType, rb_intern("inspect"), 0, NULL); rb_raise(rb_eTypeError, "Invalid return type (%s)", RSTRING_PTR(typeName)); } Data_Get_Struct(rbReturnType, Type, invoker->returnType); invoker->paramCount = -1; fixed = rb_ary_new2(RARRAY_LEN(rbParameterTypes) - 1); for (i = 0; i < RARRAY_LEN(rbParameterTypes); ++i) { VALUE entry = rb_ary_entry(rbParameterTypes, i); VALUE rbType = rbffi_Type_Lookup(entry); Type* type; if (!RTEST(rbType)) { VALUE typeName = rb_funcall2(entry, rb_intern("inspect"), 0, NULL); rb_raise(rb_eTypeError, "Invalid parameter type (%s)", RSTRING_PTR(typeName)); } Data_Get_Struct(rbType, Type, type); if (type->nativeType != NATIVE_VARARGS) { rb_ary_push(fixed, entry); } } /* * @fixed and @type_map are used by the parameter mangling ruby code */ rb_iv_set(self, "@fixed", fixed); rb_iv_set(self, "@type_map", rb_hash_aref(options, ID2SYM(rb_intern("type_map")))); return retval; } static VALUE variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues) { VariadicInvoker* invoker; FFIStorage* params; void* retval; ffi_cif cif; void** ffiValues; ffi_type** ffiParamTypes; ffi_type* ffiReturnType; Type** paramTypes; VALUE* argv; int paramCount = 0, i; ffi_status ffiStatus; #ifndef HAVE_RUBY_THREAD_HAS_GVL_P rbffi_thread_t oldThread; #endif Check_Type(parameterTypes, T_ARRAY); Check_Type(parameterValues, T_ARRAY); Data_Get_Struct(self, VariadicInvoker, invoker); paramCount = (int) RARRAY_LEN(parameterTypes); paramTypes = ALLOCA_N(Type *, paramCount); ffiParamTypes = ALLOCA_N(ffi_type *, paramCount); params = ALLOCA_N(FFIStorage, paramCount); ffiValues = ALLOCA_N(void*, paramCount); argv = ALLOCA_N(VALUE, paramCount); retval = alloca(MAX(invoker->returnType->ffiType->size, FFI_SIZEOF_ARG)); for (i = 0; i < paramCount; ++i) { VALUE rbType = rb_ary_entry(parameterTypes, i); if (!rb_obj_is_kind_of(rbType, rbffi_TypeClass)) { rb_raise(rb_eTypeError, "wrong type. Expected (FFI::Type)"); } Data_Get_Struct(rbType, Type, paramTypes[i]); switch (paramTypes[i]->nativeType) { case NATIVE_INT8: case NATIVE_INT16: case NATIVE_INT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("INT32")); Data_Get_Struct(rbType, Type, paramTypes[i]); break; case NATIVE_UINT8: case NATIVE_UINT16: case NATIVE_UINT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("UINT32")); Data_Get_Struct(rbType, Type, paramTypes[i]); break; case NATIVE_FLOAT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("DOUBLE")); Data_Get_Struct(rbType, Type, paramTypes[i]); break; default: break; } ffiParamTypes[i] = paramTypes[i]->ffiType; if (ffiParamTypes[i] == NULL) { rb_raise(rb_eArgError, "Invalid parameter type #%x", paramTypes[i]->nativeType); } argv[i] = rb_ary_entry(parameterValues, i); } ffiReturnType = invoker->returnType->ffiType; if (ffiReturnType == NULL) { rb_raise(rb_eArgError, "Invalid return type"); } ffiStatus = ffi_prep_cif(&cif, invoker->abi, paramCount, ffiReturnType, ffiParamTypes); switch (ffiStatus) { case FFI_BAD_ABI: rb_raise(rb_eArgError, "Invalid ABI specified"); case FFI_BAD_TYPEDEF: rb_raise(rb_eArgError, "Invalid argument type specified"); case FFI_OK: break; default: rb_raise(rb_eArgError, "Unknown FFI error"); } rbffi_SetupCallParams(paramCount, argv, -1, paramTypes, params, ffiValues, NULL, 0, invoker->rbEnums); #ifndef HAVE_RUBY_THREAD_HAS_GVL_P oldThread = rbffi_active_thread; rbffi_active_thread = rbffi_thread_self(); #endif ffi_call(&cif, FFI_FN(invoker->function), retval, ffiValues); #ifndef HAVE_RUBY_THREAD_HAS_GVL_P rbffi_active_thread = oldThread; #endif rbffi_save_errno(); return rbffi_NativeValue_ToRuby(invoker->returnType, invoker->rbReturnType, retval); } void rbffi_Variadic_Init(VALUE moduleFFI) { classVariadicInvoker = rb_define_class_under(moduleFFI, "VariadicInvoker", rb_cObject); rb_global_variable(&classVariadicInvoker); rb_define_alloc_func(classVariadicInvoker, variadic_allocate); rb_define_method(classVariadicInvoker, "initialize", variadic_initialize, 4); rb_define_method(classVariadicInvoker, "invoke", variadic_invoke, 2); }