/* * Copyright (c) 2008-2010 Wayne Meissner * Copyright (C) 2009 Andrea Fazzi * Copyright (c) 2008-2013, Ruby FFI project contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Ruby FFI project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _MSC_VER #include #endif #include #include #ifndef _MSC_VER # include # include #else # include "win32/stdbool.h" # include "win32/stdint.h" #endif #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; bool blocking; } 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; invoker->blocking = false; 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; #if defined(X86_WIN32) VALUE rbConventionStr; #endif 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; invoker->blocking = RTEST(rb_hash_aref(options, ID2SYM(rb_intern("blocking")))); #if defined(X86_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, fixedCount = 0, i; ffi_status ffiStatus; rbffi_frame_t frame = { 0 }; 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"); } /*Get the number of fixed args from @fixed array*/ fixedCount = RARRAY_LEN(rb_iv_get(self, "@fixed")); #ifdef HAVE_FFI_PREP_CIF_VAR ffiStatus = ffi_prep_cif_var(&cif, invoker->abi, fixedCount, paramCount, ffiReturnType, ffiParamTypes); #else ffiStatus = ffi_prep_cif(&cif, invoker->abi, paramCount, ffiReturnType, ffiParamTypes); #endif 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); rbffi_frame_push(&frame); #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL /* In Call.c, blocking: true is supported on older ruby variants * without rb_thread_call_without_gvl by allocating on the heap instead * of the stack. Since this functionality is being added later, * we’re skipping support for old rubies here. */ if(unlikely(invoker->blocking)) { rbffi_blocking_call_t* bc; bc = ALLOCA_N(rbffi_blocking_call_t, 1); bc->retval = retval; bc->function = invoker->function; bc->ffiValues = ffiValues; bc->params = params; bc->frame = &frame; bc->cif = cif; rb_rescue2(rbffi_do_blocking_call, (VALUE) bc, rbffi_save_frame_exception, (VALUE) &frame, rb_eException, (VALUE) 0); } else { ffi_call(&cif, FFI_FN(invoker->function), retval, ffiValues); } #else ffi_call(&cif, FFI_FN(invoker->function), retval, ffiValues); #endif rbffi_frame_pop(&frame); rbffi_save_errno(); if (RTEST(frame.exc) && frame.exc != Qnil) { rb_exc_raise(frame.exc); } 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); }