cons_t* proc_ffi_prep_cif_var(cons_t* p, environment_t*) { assert_length(p, 3, 4); ffi_abi abi = FFI_DEFAULT_ABI; /* * ARGUMENT 1: ABI for foreign function */ abi = parse_ffi_abi(car(p)); /* * ARGUMENT 2: * Return type for foreign function */ ffi_type* rtype = parse_ffi_type(cadr(p)); /* * ARGUMENT 3: * Number of fixed vars */ assert_type(INTEGER, caddr(p)); unsigned int fixedargs = caddr(p)->number.integer; /* * ARGUMENT 4: * Types for foreign function's input parameters. */ ffi_type** argtypes = NULL; unsigned int nargs = 0; if ( length(p) >= 4 ) { cons_t *args = cadddr(p); assert_type(PAIR, args); nargs = length(args); if ( nargs > 0 ) { argtypes = static_cast<ffi_type**>(malloc(nargs*sizeof(ffi_type*))); for ( unsigned int n=0; n<nargs; ++n ) { argtypes[n] = parse_ffi_type(car(args)); args = cdr(args); } } } /* * Initialize returned struct */ ffi_cif *cif = new ffi_cif(); memset(cif, 0, sizeof(ffi_cif)); check(ffi_prep_cif_var(cif, abi, fixedargs, nargs, rtype, argtypes)); return pointer(tag_ffi_cif, cif); /* * In the future, the malloced argtypes should be added to the * pointer-return value here, so that it too can be freed. */ }
callback* create_callback(JNIEnv* env, jobject obj, jobject method, jobjectArray arg_classes, jclass return_class, callconv_t calling_convention, jint options, jstring encoding) { jboolean direct = options & CB_OPTION_DIRECT; jboolean in_dll = options & CB_OPTION_IN_DLL; callback* cb; ffi_abi abi = (calling_convention == CALLCONV_C ? FFI_DEFAULT_ABI : (ffi_abi)calling_convention); ffi_abi java_abi = FFI_DEFAULT_ABI; ffi_type* return_type; ffi_status status; jsize argc; JavaVM* vm; int rtype; char msg[MSG_SIZE]; int i; int cvt = 0; const char* throw_type = NULL; const char* throw_msg = NULL; if ((*env)->GetJavaVM(env, &vm) != JNI_OK) { throwByName(env, EUnsatisfiedLink, "Couldn't obtain Java VM reference when creating native callback"); return NULL; } argc = (*env)->GetArrayLength(env, arg_classes); cb = (callback *)malloc(sizeof(callback)); cb->closure = ffi_closure_alloc(sizeof(ffi_closure), &cb->x_closure); cb->saved_x_closure = cb->x_closure; cb->object = (*env)->NewWeakGlobalRef(env, obj); cb->methodID = (*env)->FromReflectedMethod(env, method); cb->vm = vm; cb->arg_types = (ffi_type**)malloc(sizeof(ffi_type*) * argc); cb->java_arg_types = (ffi_type**)malloc(sizeof(ffi_type*) * (argc + 3)); cb->arg_jtypes = (char*)malloc(sizeof(char) * argc); cb->conversion_flags = (int *)malloc(sizeof(int) * argc); cb->rflag = CVT_DEFAULT; cb->arg_classes = (jobject*)malloc(sizeof(jobject) * argc); cb->direct = direct; cb->java_arg_types[0] = cb->java_arg_types[1] = cb->java_arg_types[2] = &ffi_type_pointer; cb->encoding = newCStringUTF8(env, encoding); for (i=0;i < argc;i++) { int jtype; jclass cls = (*env)->GetObjectArrayElement(env, arg_classes, i); if ((cb->conversion_flags[i] = get_conversion_flag(env, cls)) != CVT_DEFAULT) { cb->arg_classes[i] = (*env)->NewWeakGlobalRef(env, cls); cvt = 1; } else { cb->arg_classes[i] = NULL; } jtype = get_java_type(env, cls); if (jtype == -1) { snprintf(msg, sizeof(msg), "Unsupported callback argument at index %d", i); throw_type = EIllegalArgument; throw_msg = msg; goto failure_cleanup; } cb->arg_jtypes[i] = (char)jtype; cb->java_arg_types[i+3] = cb->arg_types[i] = get_ffi_type(env, cls, cb->arg_jtypes[i]); if (!cb->java_arg_types[i+3]) { goto failure_cleanup; } if (cb->conversion_flags[i] == CVT_NATIVE_MAPPED || cb->conversion_flags[i] == CVT_POINTER_TYPE || cb->conversion_flags[i] == CVT_INTEGER_TYPE) { jclass ncls; ncls = getNativeType(env, cls); jtype = get_java_type(env, ncls); if (jtype == -1) { snprintf(msg, sizeof(msg), "Unsupported NativeMapped callback argument native type at argument %d", i); throw_type = EIllegalArgument; throw_msg = msg; goto failure_cleanup; } cb->arg_jtypes[i] = (char)jtype; cb->java_arg_types[i+3] = &ffi_type_pointer; cb->arg_types[i] = get_ffi_type(env, ncls, cb->arg_jtypes[i]); if (!cb->arg_types[i]) { goto failure_cleanup; } } // Java callback method is called using varargs, so promote floats to // double where appropriate for the platform if (cb->arg_types[i]->type == FFI_TYPE_FLOAT) { cb->java_arg_types[i+3] = &ffi_type_double; cb->conversion_flags[i] = CVT_FLOAT; cvt = 1; } else if (cb->java_arg_types[i+3]->type == FFI_TYPE_STRUCT) { // All callback structure arguments are passed as a jobject cb->java_arg_types[i+3] = &ffi_type_pointer; } } if (!direct || !cvt) { free(cb->conversion_flags); cb->conversion_flags = NULL; free(cb->arg_classes); cb->arg_classes = NULL; } if (direct) { cb->rflag = get_conversion_flag(env, return_class); if (cb->rflag == CVT_NATIVE_MAPPED || cb->rflag == CVT_INTEGER_TYPE || cb->rflag == CVT_POINTER_TYPE) { return_class = getNativeType(env, return_class); } } #if defined(_WIN32) if (calling_convention == CALLCONV_STDCALL) { #if defined(_WIN64) || defined(_WIN32_WCE) // Ignore requests for stdcall on win64/wince abi = FFI_DEFAULT_ABI; #else abi = FFI_STDCALL; // All JNI entry points on win32 use stdcall java_abi = FFI_STDCALL; #endif } #endif // _WIN32 if (!(abi > FFI_FIRST_ABI && abi < FFI_LAST_ABI)) { snprintf(msg, sizeof(msg), "Invalid calling convention %d", abi); throw_type = EIllegalArgument; throw_msg = msg; goto failure_cleanup; } rtype = get_java_type(env, return_class); if (rtype == -1) { throw_type = EIllegalArgument; throw_msg = "Unsupported callback return type"; goto failure_cleanup; } return_type = get_ffi_return_type(env, return_class, (char)rtype); if (!return_type) { throw_type = EIllegalArgument; throw_msg = "Error in callback return type"; goto failure_cleanup; } status = ffi_prep_cif(&cb->cif, abi, argc, return_type, cb->arg_types); if (!ffi_error(env, "callback setup", status)) { ffi_type* java_return_type = return_type; if (cb->rflag == CVT_STRUCTURE_BYVAL || cb->rflag == CVT_NATIVE_MAPPED || cb->rflag == CVT_POINTER_TYPE || cb->rflag == CVT_INTEGER_TYPE) { // Java method returns a jobject, not a struct java_return_type = &ffi_type_pointer; rtype = '*'; } switch(rtype) { #define OFFSETOF(ENV,METHOD) ((size_t)((char *)&(*(ENV))->METHOD - (char *)(*(ENV)))) case 'V': cb->fptr_offset = OFFSETOF(env, CallVoidMethod); break; case 'Z': cb->fptr_offset = OFFSETOF(env, CallBooleanMethod); break; case 'B': cb->fptr_offset = OFFSETOF(env, CallByteMethod); break; case 'S': cb->fptr_offset = OFFSETOF(env, CallShortMethod); break; case 'C': cb->fptr_offset = OFFSETOF(env, CallCharMethod); break; case 'I': cb->fptr_offset = OFFSETOF(env, CallIntMethod); break; case 'J': cb->fptr_offset = OFFSETOF(env, CallLongMethod); break; case 'F': cb->fptr_offset = OFFSETOF(env, CallFloatMethod); break; case 'D': cb->fptr_offset = OFFSETOF(env, CallDoubleMethod); break; default: cb->fptr_offset = OFFSETOF(env, CallObjectMethod); break; } status = ffi_prep_cif_var(&cb->java_cif, java_abi, 2, argc+3, java_return_type, cb->java_arg_types); if (!ffi_error(env, "callback setup (2)", status)) { ffi_prep_closure_loc(cb->closure, &cb->cif, dispatch_callback, cb, cb->x_closure); #ifdef DLL_FPTRS // Find an available function pointer and assign it if (in_dll) { for (i=0;i < DLL_FPTRS;i++) { if (fn[i] == NULL) { fn[i] = cb->x_closure; cb->x_closure = dll_fptrs[i]; break; } } if (i == DLL_FPTRS) { throw_type = EOutOfMemory; throw_msg = "No more DLL callback slots available"; goto failure_cleanup; } } #endif return cb; } } failure_cleanup: free_callback(env, cb); if (throw_type) { throwByName(env, throw_type, throw_msg); } return NULL; }
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); ffi_call(&cif, FFI_FN(invoker->function), retval, ffiValues); 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); }
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); }
int main (void) { ffi_cif cif; ffi_type *arg_types[5]; void *values[5]; double doubles[5]; unsigned int firstarg; double resfp; /* First test, pass float_va_fn(0,2.0) - note there are no actual * variadic parameters, but it's declared variadic so the ABI may be * different. */ /* Call it statically and then via ffi */ resfp=float_va_fn(0,2.0); // { dg-output "0: 2.0 : total: 2.0" } printf("compiled: %.1lf\n", resfp); // { dg-output "\ncompiled: 2.0" } arg_types[0] = &ffi_type_uint; arg_types[1] = &ffi_type_double; arg_types[2] = NULL; CHECK(ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, 2, 2, &ffi_type_double, arg_types) == FFI_OK); firstarg = 0; doubles[0] = 2.0; values[0] = &firstarg; values[1] = &doubles[0]; ffi_call(&cif, FFI_FN(float_va_fn), &resfp, values); // { dg-output "\n0: 2.0 : total: 2.0" } printf("ffi: %.1lf\n", resfp); // { dg-output "\nffi: 2.0" } /* Second test, float_va_fn(2,2.0,3.0,4.0), now with variadic params */ /* Call it statically and then via ffi */ resfp=float_va_fn(2,2.0,3.0,4.0); // { dg-output "\n2: 2.0 : 0:3.0 1:4.0 total: 11.0" } printf("compiled: %.1lf\n", resfp); // { dg-output "\ncompiled: 11.0" } arg_types[0] = &ffi_type_uint; arg_types[1] = &ffi_type_double; arg_types[2] = &ffi_type_double; arg_types[3] = &ffi_type_double; arg_types[4] = NULL; CHECK(ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, 2, 4, &ffi_type_double, arg_types) == FFI_OK); firstarg = 2; doubles[0] = 2.0; doubles[1] = 3.0; doubles[2] = 4.0; values[0] = &firstarg; values[1] = &doubles[0]; values[2] = &doubles[1]; values[3] = &doubles[2]; ffi_call(&cif, FFI_FN(float_va_fn), &resfp, values); // { dg-output "\n2: 2.0 : 0:3.0 1:4.0 total: 11.0" } printf("ffi: %.1lf\n", resfp); // { dg-output "\nffi: 11.0" } exit(0); }