@@ -0,0 +1,413 @@
/*
* qjs_ffi.c – QuickJS ↔ libffi bridge
*
* Works on macOS / Linux / Windows. See examples/ffi_test.js.
*/
# include "qjs_ffi.h"
# include <ffi.h>
# include <stdlib.h>
# include <string.h>
# ifdef _WIN32
# include <windows.h>
# else
# include <dlfcn.h>
# endif
# ifndef countof
# define countof(x) (sizeof(x) / sizeof((x)[0]))
# endif
/* -------------------------------------------------------------------------- */
/* internal structs */
/* -------------------------------------------------------------------------- */
typedef struct {
ffi_cif cif ;
ffi_type * * arg_types ;
ffi_type * ret_type ;
void * fn_ptr ;
int nargs ;
} ffi_func ;
typedef struct {
void * ptr ;
int is_library ; /* 1 => real dlopen/LoadLibrary handle */
} ffi_pointer ;
/* -------------------------------------------------------------------------- */
/* QuickJS class plumbing */
/* -------------------------------------------------------------------------- */
static JSClassID js_ffi_func_id ;
static JSClassID js_ffi_pointer_id ;
static void ffi_func_free ( ffi_func * f )
{
if ( ! f ) return ;
if ( f - > arg_types ) {
for ( int i = 0 ; i < f - > nargs ; i + + ) {
ffi_type * t = f - > arg_types [ i ] ;
if ( t & & t ! = & ffi_type_void & & t ! = & ffi_type_pointer & &
t ! = & ffi_type_sint8 & & t ! = & ffi_type_uint8 & &
t ! = & ffi_type_sint16 & & t ! = & ffi_type_uint16 & &
t ! = & ffi_type_sint32 & & t ! = & ffi_type_uint32 & &
t ! = & ffi_type_sint64 & & t ! = & ffi_type_uint64 & &
t ! = & ffi_type_float & & t ! = & ffi_type_double )
free ( t ) ;
}
free ( f - > arg_types ) ;
}
ffi_type * rtp = f - > ret_type ;
if ( rtp & & rtp ! = & ffi_type_void & & rtp ! = & ffi_type_pointer & &
rtp ! = & ffi_type_sint8 & & rtp ! = & ffi_type_uint8 & &
rtp ! = & ffi_type_sint16 & & rtp ! = & ffi_type_uint16 & &
rtp ! = & ffi_type_sint32 & & rtp ! = & ffi_type_uint32 & &
rtp ! = & ffi_type_sint64 & & rtp ! = & ffi_type_uint64 & &
rtp ! = & ffi_type_float & & rtp ! = & ffi_type_double )
free ( rtp ) ;
free ( f ) ;
}
static void ffi_func_finalizer ( JSRuntime * rt , JSValue val )
{
ffi_func * f = JS_GetOpaque ( val , js_ffi_func_id ) ;
if ( f ) ffi_func_free ( f ) ;
}
static void ffi_pointer_free ( ffi_pointer * p )
{
if ( p - > is_library & & p - > ptr ) {
# ifdef _WIN32
FreeLibrary ( ( HMODULE ) p - > ptr ) ;
# else
dlclose ( p - > ptr ) ;
# endif
}
free ( p ) ;
}
static void ffi_pointer_finalizer ( JSRuntime * rt , JSValue val )
{
ffi_pointer * f = JS_GetOpaque ( val , js_ffi_pointer_id ) ;
if ( ! f ) return ;
ffi_pointer_free ( f ) ;
}
static JSClassDef js_ffi_func_class = { " FFIFunction " , . finalizer = ffi_func_finalizer } ;
static JSClassDef js_ffi_pointer_class = { " FFIPointer " , . finalizer = ffi_pointer_finalizer } ;
/* -------------------------------------------------------------------------- */
/* helpers */
/* -------------------------------------------------------------------------- */
static JSValue
js_new_ffi_pointer ( JSContext * ctx , void * ptr , int is_library )
{
JSValue obj = JS_NewObjectClass ( ctx , js_ffi_pointer_id ) ;
if ( JS_IsException ( obj ) ) return obj ;
ffi_pointer * p = malloc ( sizeof * p ) ;
if ( ! p ) { JS_FreeValue ( ctx , obj ) ; return JS_ThrowOutOfMemory ( ctx ) ; }
p - > ptr = ptr ;
p - > is_library = is_library ;
JS_SetOpaque ( obj , p ) ;
return obj ;
}
static void *
js_get_ffi_pointer ( JSContext * ctx , JSValueConst val )
{
ffi_pointer * p = JS_GetOpaque2 ( ctx , val , js_ffi_pointer_id ) ;
return p ? p - > ptr : NULL ;
}
static ffi_type *
js_to_ffi_type ( JSContext * ctx , const char * name )
{
if ( ! strcmp ( name , " void " ) ) return & ffi_type_void ;
if ( ! strcmp ( name , " pointer " ) ) return & ffi_type_pointer ;
if ( ! strcmp ( name , " float " ) ) return & ffi_type_float ;
if ( ! strcmp ( name , " double " ) ) return & ffi_type_double ;
if ( ! strcmp ( name , " int8 " ) ) return & ffi_type_sint8 ;
if ( ! strcmp ( name , " uint8 " ) ) return & ffi_type_uint8 ;
if ( ! strcmp ( name , " int16 " ) ) return & ffi_type_sint16 ;
if ( ! strcmp ( name , " uint16 " ) ) return & ffi_type_uint16 ;
if ( ! strcmp ( name , " int32 " ) ) return & ffi_type_sint32 ;
if ( ! strcmp ( name , " uint32 " ) ) return & ffi_type_uint32 ;
if ( ! strcmp ( name , " int64 " ) ) return & ffi_type_sint64 ;
if ( ! strcmp ( name , " uint64 " ) ) return & ffi_type_uint64 ;
/* synonyms */
if ( ! strcmp ( name , " int " ) ) return & ffi_type_sint32 ;
if ( ! strcmp ( name , " uint " ) ) return & ffi_type_uint32 ;
if ( ! strcmp ( name , " char* " ) | | ! strcmp ( name , " string " ) )
return & ffi_type_pointer ;
JS_ThrowTypeError ( ctx , " unknown FFI type \" %s \" " , name ) ;
return NULL ;
}
/* -------------------------------------------------------------------------- */
/* ffi.dlopen */
/* -------------------------------------------------------------------------- */
static JSValue
js_ffi_dlopen ( JSContext * ctx , JSValueConst this_val ,
int argc , JSValueConst * argv )
{
if ( argc < 1 )
return JS_ThrowTypeError ( ctx , " dlopen: expected path or null " ) ;
const char * path = NULL ;
if ( ! JS_IsNull ( argv [ 0 ] ) ) {
path = JS_ToCString ( ctx , argv [ 0 ] ) ;
if ( ! path ) return JS_EXCEPTION ;
}
void * h ;
# ifdef _WIN32
h = path ? ( void * ) LoadLibraryA ( path )
: ( void * ) GetModuleHandle ( NULL ) ;
# else
h = path ? dlopen ( path , RTLD_LAZY | RTLD_LOCAL )
: dlopen ( NULL , RTLD_LAZY | RTLD_LOCAL ) ;
# endif
if ( path ) JS_FreeCString ( ctx , path ) ;
if ( ! h ) return JS_ThrowInternalError ( ctx , " dlopen failed " ) ;
/* ------------- FIX: only real libraries get is_library = 1 ------------- */
int is_library = ( path ! = NULL ) ; /* self handle must not be closed */
return js_new_ffi_pointer ( ctx , h , is_library ) ;
}
/* -------------------------------------------------------------------------- */
/* ffi.prepare (unchanged from previous answer) */
/* -------------------------------------------------------------------------- */
static JSValue
js_ffi_prepare ( JSContext * ctx , JSValueConst this_val ,
int argc , JSValueConst * argv )
{
if ( argc < 4 )
return JS_ThrowTypeError ( ctx ,
" prepare: expected (lib, symbol, ret_type, arg_type_array) " ) ;
void * lib = js_get_ffi_pointer ( ctx , argv [ 0 ] ) ;
if ( ! lib ) return JS_ThrowTypeError ( ctx , " invalid library handle " ) ;
const char * sym = JS_ToCString ( ctx , argv [ 1 ] ) ;
if ( ! sym ) return JS_EXCEPTION ;
void * fn ;
# ifdef _WIN32
fn = ( void * ) GetProcAddress ( ( HMODULE ) lib , sym ) ;
# else
fn = dlsym ( lib , sym ) ;
# endif
if ( ! fn ) {
JS_FreeCString ( ctx , sym ) ;
return JS_ThrowTypeError ( ctx , " symbol \" %s \" not found " , sym ) ;
}
const char * rt_name = JS_ToCString ( ctx , argv [ 2 ] ) ;
if ( ! rt_name ) { JS_FreeCString ( ctx , sym ) ; return JS_EXCEPTION ; }
ffi_type * ret_type = js_to_ffi_type ( ctx , rt_name ) ;
JS_FreeCString ( ctx , rt_name ) ;
if ( ! ret_type ) { JS_FreeCString ( ctx , sym ) ; return JS_EXCEPTION ; }
if ( ! JS_IsArray ( ctx , argv [ 3 ] ) ) {
JS_FreeCString ( ctx , sym ) ;
return JS_ThrowTypeError ( ctx , " arg_types must be an array " ) ;
}
uint32_t nargs = JS_ArrayLength ( ctx , argv [ 3 ] ) ;
ffi_func * f = calloc ( 1 , sizeof * f ) ;
if ( ! f ) { JS_FreeCString ( ctx , sym ) ; return JS_ThrowOutOfMemory ( ctx ) ; }
f - > fn_ptr = fn ;
f - > ret_type = ret_type ;
f - > nargs = ( int ) nargs ;
if ( nargs ) {
f - > arg_types = calloc ( nargs , sizeof ( ffi_type * ) ) ;
if ( ! f - > arg_types ) {
JS_FreeCString ( ctx , sym ) ;
ffi_func_free ( f ) ;
return JS_ThrowOutOfMemory ( ctx ) ;
}
for ( uint32_t i = 0 ; i < nargs ; i + + ) {
JSValue v = JS_GetPropertyUint32 ( ctx , argv [ 3 ] , i ) ;
const char * tn = JS_ToCString ( ctx , v ) ;
JS_FreeValue ( ctx , v ) ;
if ( ! tn ) { JS_FreeCString ( ctx , sym ) ; ffi_func_free ( f ) ; return JS_EXCEPTION ; }
f - > arg_types [ i ] = js_to_ffi_type ( ctx , tn ) ;
JS_FreeCString ( ctx , tn ) ;
if ( ! f - > arg_types [ i ] ) {
JS_FreeCString ( ctx , sym ) ;
ffi_func_free ( f ) ;
return JS_EXCEPTION ;
}
}
}
JS_FreeCString ( ctx , sym ) ;
if ( ffi_prep_cif ( & f - > cif , FFI_DEFAULT_ABI ,
f - > nargs , f - > ret_type , f - > arg_types ) ! = FFI_OK ) {
ffi_func_free ( f ) ;
return JS_ThrowInternalError ( ctx , " ffi_prep_cif failed " ) ;
}
JSValue obj = JS_NewObjectClass ( ctx , js_ffi_func_id ) ;
JS_SetOpaque ( obj , f ) ;
return obj ;
}
/* -------------------------------------------------------------------------- */
/* box_result + js_ffi_call (unchanged) */
/* -------------------------------------------------------------------------- */
static JSValue
box_result ( JSContext * ctx , ffi_type * t , void * data )
{
if ( t = = & ffi_type_void ) return JS_NULL ;
if ( t = = & ffi_type_pointer ) return data ? js_new_ffi_pointer ( ctx , * ( void * * ) data , 0 )
: JS_NULL ;
if ( t = = & ffi_type_double ) return JS_NewFloat64 ( ctx , * ( double * ) data ) ;
if ( t = = & ffi_type_float ) return JS_NewFloat64 ( ctx , ( double ) * ( float * ) data ) ;
if ( t = = & ffi_type_sint8 ) return JS_NewInt32 ( ctx , * ( int8_t * ) data ) ;
if ( t = = & ffi_type_uint8 ) return JS_NewUint32 ( ctx , * ( uint8_t * ) data ) ;
if ( t = = & ffi_type_sint16 ) return JS_NewInt32 ( ctx , * ( int16_t * ) data ) ;
if ( t = = & ffi_type_uint16 ) return JS_NewUint32 ( ctx , * ( uint16_t * ) data ) ;
if ( t = = & ffi_type_sint32 ) return JS_NewInt32 ( ctx , * ( int32_t * ) data ) ;
if ( t = = & ffi_type_uint32 ) return JS_NewUint32 ( ctx , * ( uint32_t * ) data ) ;
if ( t = = & ffi_type_sint64 ) return JS_NewFloat64 ( ctx , ( double ) * ( int64_t * ) data ) ;
if ( t = = & ffi_type_uint64 ) return JS_NewFloat64 ( ctx , ( double ) * ( uint64_t * ) data ) ;
return JS_ThrowTypeError ( ctx , " unsupported return type " ) ;
}
static JSValue
js_ffi_call ( JSContext * ctx , JSValueConst this_val ,
int argc , JSValueConst * argv )
{
ffi_func * f = JS_GetOpaque2 ( ctx , this_val , js_ffi_func_id ) ;
if ( ! f ) return JS_EXCEPTION ;
if ( argc ! = f - > nargs )
return JS_ThrowTypeError ( ctx , " expected %d arguments, got %d " ,
f - > nargs , argc ) ;
/* one fixed stack block per argument ----------------------------------- */
typedef union {
double d ;
float f ;
int8_t i8 ; uint8_t u8 ;
int16_t i16 ; uint16_t u16 ;
int32_t i32 ; uint32_t u32 ;
int64_t i64 ; uint64_t u64 ;
void * p ;
} arg_data_t ;
arg_data_t data [ f - > nargs ] ;
void * values [ f - > nargs ] ;
const char * tmp_cstr [ f - > nargs ] ; /* only for JS strings */
memset ( tmp_cstr , 0 , sizeof tmp_cstr ) ;
/* ---------- marshal JS → C (no malloc for scalars) ------------------- */
for ( int i = 0 ; i < f - > nargs ; i + + ) {
ffi_type * t = f - > arg_types [ i ] ;
/* numbers ------------------------------------------------------------ */
if ( t ! = & ffi_type_pointer ) {
double d ;
if ( JS_ToFloat64 ( ctx , & d , argv [ i ] ) )
goto arg_error ;
if ( t = = & ffi_type_double ) data [ i ] . d = d ;
else if ( t = = & ffi_type_float ) data [ i ] . f = ( float ) d ;
else if ( t = = & ffi_type_sint8 ) data [ i ] . i8 = ( int8_t ) d ;
else if ( t = = & ffi_type_uint8 ) data [ i ] . u8 = ( uint8_t ) d ;
else if ( t = = & ffi_type_sint16 ) data [ i ] . i16 = ( int16_t ) d ;
else if ( t = = & ffi_type_uint16 ) data [ i ] . u16 = ( uint16_t ) d ;
else if ( t = = & ffi_type_sint32 ) data [ i ] . i32 = ( int32_t ) d ;
else if ( t = = & ffi_type_uint32 ) data [ i ] . u32 = ( uint32_t ) d ;
else if ( t = = & ffi_type_sint64 ) data [ i ] . i64 = ( int64_t ) d ;
else if ( t = = & ffi_type_uint64 ) data [ i ] . u64 = ( uint64_t ) d ;
else goto arg_error ; /* should be unreachable */
values [ i ] = & data [ i ] ;
continue ;
}
/* pointers ----------------------------------------------------------- */
if ( JS_IsString ( argv [ i ] ) ) {
const char * s = JS_ToCString ( ctx , argv [ i ] ) ;
if ( ! s ) goto arg_error ;
tmp_cstr [ i ] = s ; /* remember to free */
data [ i ] . p = ( void * ) s ;
} else {
data [ i ] . p = js_get_ffi_pointer ( ctx , argv [ i ] ) ;
}
values [ i ] = & data [ i ] ;
}
/* ---------------------------- call ---------------------------------- */
uint8_t ret_space [ 16 ] = { 0 } ;
void * ret_buf = f - > ret_type = = & ffi_type_void ? NULL : ( void * ) ret_space ;
ffi_call ( & f - > cif , FFI_FN ( f - > fn_ptr ) , ret_buf , values ) ;
JSValue js_ret = box_result ( ctx , f - > ret_type , ret_buf ) ;
/* --------------------------- cleanup ---------------------------------- */
for ( int i = 0 ; i < f - > nargs ; i + + )
if ( tmp_cstr [ i ] ) JS_FreeCString ( ctx , tmp_cstr [ i ] ) ;
return js_ret ;
arg_error :
for ( int i = 0 ; i < f - > nargs ; i + + )
if ( tmp_cstr [ i ] ) JS_FreeCString ( ctx , tmp_cstr [ i ] ) ;
return JS_EXCEPTION ;
}
/* -------------------------------------------------------------------------- */
/* module init */
/* -------------------------------------------------------------------------- */
static const JSCFunctionListEntry js_ffi_funcs [ ] = {
JS_CFUNC_DEF ( " dlopen " , 1 , js_ffi_dlopen ) ,
JS_CFUNC_DEF ( " prepare " , 4 , js_ffi_prepare ) ,
} ;
static const JSCFunctionListEntry js_ffi_func_funcs [ ] = {
JS_CFUNC_DEF ( " call " , 1 , js_ffi_call ) ,
} ;
JSValue
js_ffi_use ( JSContext * ctx )
{
JS_NewClassID ( & js_ffi_func_id ) ;
JS_NewClass ( JS_GetRuntime ( ctx ) , js_ffi_func_id , & js_ffi_func_class ) ;
JSValue fn_proto = JS_NewObject ( ctx ) ;
JS_SetPropertyFunctionList ( ctx , fn_proto , js_ffi_func_funcs , countof ( js_ffi_func_funcs ) ) ;
JS_SetClassProto ( ctx , js_ffi_func_id , fn_proto ) ;
JS_NewClassID ( & js_ffi_pointer_id ) ;
JS_NewClass ( JS_GetRuntime ( ctx ) , js_ffi_pointer_id , & js_ffi_pointer_class ) ;
JSValue mod = JS_NewObject ( ctx ) ;
JS_SetPropertyFunctionList ( ctx , mod , js_ffi_funcs , countof ( js_ffi_funcs ) ) ;
return mod ;
}