├── LICENSE ├── Makefile ├── README.md ├── quickjs-ffi.c ├── quickjs-ffi.js ├── test-lib.c └── test.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 shajunxing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ffi: quickjs-ffi.c test-lib.c 2 | gcc quickjs-ffi.c -o quickjs-ffi.so -ldl -lffi -shared -fPIC 3 | gcc test-lib.c -o test-lib.so -shared -fPIC 4 | qjs test.js 5 | 6 | test1: test1.c 7 | gcc test1.c -o test1 -lffi -ldl -fPIC 8 | ./test1 9 | 10 | fib: fib.c 11 | gcc fib.c -o fib.so -shared -fPIC 12 | 13 | null: null.c 14 | gcc null.c -o null1 15 | gcc null.c -o null2 -fPIC 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libffi wrapper for QuickJS. Now supports almost every features of C including primitive types, structs, callbacks and so on 2 | 3 | ## License 4 | 5 | MIT License 6 | 7 | Copyright (c) 2021 shajunxing 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | ## Links 28 | 29 | * quickjs-ffi *Any issues please report here* 30 | * quickjs-ffi *Due to some well known reasons, here is China mirror* 31 | * quickjs-misc *My miscellaneous modules which can be seen as examples* 32 | * quickjs 33 | * quickjs 34 | * libffi 35 | * libffi 36 | 37 | ## Intruduction 38 | 39 | Although there's already one wrapper I found through duckduckgo, to be honest I'm not satisfied with this design. My idea is to keep C code as simple as possible, and put complex logic into JS. Existing library functions, variables, macro definitions and all the things exposed should keep their original look as much as possible. 40 | 41 | So I wrote my own from scratch. My module has two layers, low layer is `quickjs-ffi.c`, compiled to `quickjs-ffi.so`, containing minimal necessary things from libc, libdl, libffi, and using it is almost the same as C, high layer `quickjs-ffi.js` makes low layer easy to use. 42 | 43 | It's east to compile, just `make`, it will produce module `quickjs-ffi.so`, test lib `test-lib.so` and will run `test.js`. 44 | 45 | Assume 3 C functions (defined in `test-lib.c`): 46 | 47 | void test1(); 48 | double test2(float a, double b, const char *c); 49 | typedef struct { 50 | int i; 51 | float f; 52 | } s1; 53 | typedef struct { 54 | long l; 55 | double d; 56 | s1 s; 57 | } s2; 58 | void test3(s2 s); 59 | 60 | They can be invoked in JS like this: 61 | 62 | import { CFunction } from './quickjs-ffi.js' 63 | let test1 = new CFunction('test-lib.so', 'test1', null, 'void'); 64 | let test2 = new CFunction('test-lib.so', 'test2', null, 'double', 'float', 'double', 'string'); 65 | let test3 = new CFunction('test-lib.so', 'test3', null, 'void', ['long', 'double', ['int', 'float']]); 66 | test1.invoke(); 67 | console.log(test2.invoke(3.141592654, 2.718281829, 'How do you do?')); 68 | test3.invoke([123456789, 3.141592654, 54321, 2.718281829]); 69 | 70 | `CFunction`'s constructor definition is: 71 | 72 | library name, function name, nfixedargs, return value type representation, ...arguments type representation 73 | 74 | If C function arguments length is fixed, `nfixedargs` must be null, or be the number of fixed arguments. 75 | 76 | Primitive types are representated by short string literals according to `libffi`'s type definition, I added more C friendly ones, see `primitiveTypes` in `quickjs-ffi.js` for details. **Note:** All C pointers are `pointers`, they are actually memory addresses. but `char *` can be represented by `string`, and will automatically inbox/outbox with JS string, inbox is not safe and may cause pointer oob, you know that, so do it at your own risk. C complex type is not yet implemented. 77 | 78 | Structure types are represented by array of primitive types. Nested structures must be defined by nested array, but putting/getting element values must be flattened, which means all structure's primitive elements, are in natural order, eg. 'depth first' order. 79 | 80 | If function's return value is structure type, it will returns a flattened array. 81 | 82 | High layer caches `dlopen`, `dlsym` and `ffi_prep_cif` results, including corresponding memory allocations. Same library and function will only load once, and also will be cif of same function definitions, eg. same arguments and return value representation. 83 | 84 | Each `CFunction` instance shares dl and ffi cache, but keep it's own memory allocations for arguments and return value, this design is for possible multithread situation. 85 | 86 | To invoke C functions with variadic arguments such as `printf`, if the function definition is fully dynamic, don't forget to free resources when no longer used. And **Cautions:** Be very carefully dealing with type promotions of C variadic function arguments. See C standards for details. Here is an example: 87 | 88 | import { CFunction, freeCif } from './quickjs-ffi.js' 89 | import { LIBC_SO } from './quickjs-ffi.so' 90 | let printf = new CFunction(LIBC_SO, 'printf', 1, 'int', 'string', 'double', 'double', 'int'); 91 | printf.invoke('%g %g %d\n', 3.141592654, 2.718281829, 299792458); 92 | freeCif(printf.cifcacheindex); 93 | printf.free(); 94 | printf = new CFunction(LIBC_SO, 'printf', 1, 'int', 'string', 'string', 'string'); 95 | printf.invoke('%s %s\n', 'hello', 'world'); 96 | freeCif(printf.cifcacheindex); 97 | printf.free(); 98 | 99 | C callbacks are fully supported by `CCallback` class, which will wrap a JS function using libffi closure mechanism, and returns a C function pointer which can be used as another C function's parameter. `CCallback`'s constructor is: 100 | 101 | JS function, nfixedargs, return value type representation, ...arguments type representation 102 | 103 | Return value and arguments definitions are the same as `CFunction`. 104 | 105 | For example, there are test4 in `test-lib.c`: 106 | 107 | char *test4(s2 (*fn)(float, double, const char *)) { 108 | puts("test4 begins"); 109 | s2 ret = fn(3.141592654, 2.718281829, "Hi there"); 110 | printf("callback returns %ld %f %d %f\n", ret.l, ret.d, ret.s.i, ret.s.f); 111 | puts("test4 ends"); 112 | return "greetings"; 113 | } 114 | 115 | Define and execute a JS callback is: 116 | 117 | import * as ffi from './quickjs-ffi.js' 118 | let test4 = new ffi.CFunction('test-lib.so', 'test4', null, 'string', 'pointer'); 119 | let cb = new ffi.CCallback((a, b, c) => { 120 | console.log('callback begins'); 121 | console.log('arguments are', a, b, c); 122 | console.log('callback ends'); 123 | return [1, 2, 3, 4]; 124 | }, null, ['long', 'double', ['int', 'float']], 'float', 'double', 'string'); 125 | console.log('test4 returns', test4.invoke(cb.cfuncptr)); 126 | 127 | Which will output: 128 | 129 | test4 begins 130 | callback begins 131 | arguments are 3.1415927410125732 2.718281829 Hi there 132 | callback ends 133 | callback returns 1 2.000000 3 4.000000 134 | test4 ends 135 | test4 returns greetings 136 | 137 | ## Low layer 138 | 139 | This is an example of importing this module and print all exports: 140 | 141 | import * as ffi from './quickjs-ffi.so' 142 | 143 | for (let k in ffi) { 144 | console.log(k, '=', ffi[k].toString()); 145 | } 146 | 147 | Some rules: 148 | 149 | * Any C numeric types such as `int`, `float`, are all `number` in JS. 150 | * All C pointer types are actually `uintptr_t` in C, and `number` in JS, the value are exactly memory addresses. 151 | * C `char *` string can be `string` in JS. 152 | * JS `bool` is C `bool`, in C99 there are `bool` definitions although they are actually integers. 153 | * C functions which have no return value, will return `undefined` in JS. 154 | 155 | The module exposes many constant values, which varies by C or machine implementation or different compilations, it's necessary. For example: C int size varies, so i exposed `sizeof_int`. Any `sizeof_xxx` is actually value of `sizeof(xxx)`. Also in order to properly operate structure members, I exposed some `offsetof_xxx_yyy` values which means `offsetof(xxx, yyy)` in C. 156 | 157 | I will do my best checking function arguments, including their count and types, although I know it's not enough. 158 | 159 | C needs lots of memory operations, so I exposed necessary libc functions as below: 160 | 161 | * `pointer malloc(number size)` 162 | * `undefined free(pointer ptr)` 163 | * `pointer memset(pointer s, number c, number n)` 164 | * `pointer memcpy(pointer dest, pointer src, number n)` 165 | * `number strlen(pointer s)` 166 | 167 | And I added my own functions below. Also I will check out-of-bound error in them, yet still not enough, so do it at your own risk. 168 | 169 | * `undefined fprinthex(pointer stream, pointer data, number size)`
print a memory block like `hexdump -C` format. 170 | * `undefined printhex(pointer data, number size)`
print to stdout. 171 | * `number memreadint(pointer buf, number buflen, number offset, bool issigned, number bytewidth)`
read specified width integer from `offset` of `buf`, `buflen` is for oob checking, `bytewidth` can only be 1, 2, 4 or 8. 172 | * `undefined memwriteint(pointer buf, number buflen, number offset, number bytewidth, number val)`
write integer `val` to `offset`, signed/unsigned is not necessary to speciy. 173 | * `number memreadfloat(pointer buf, number buflen, number offset, bool isdouble)`
read float/double from `offset`, in C float is always 4 bytes and double is 8. 174 | * `undefined memwritefloat(pointer buf, number buflen, number offset, bool isdouble, double val)`
write float/double `val` to `offset`. 175 | * `string memreadstring(pointer buf, number buflen, number offset, number len)`
read `len` bytes from `offset`, returns JS string. 176 | * `undefined memwritestring(pointer buf, number buflen, number offset, string str)`
write `str` to `offset`. 177 | * `pointer tocstring(string str)`
wrapper of `JS_ToCString()`. 178 | * `undefined freecstring(pointer cstr)`
wrapper of `JS_FreeCString()`. 179 | * `string newstring(pointer cstr)`
wrapper of `JS_NewString()`. **Unsafe!** 180 | 181 | Here is an example: 182 | 183 | let buflen = 8; 184 | let buf = ffi.malloc(buflen); 185 | ffi.printhex(buf, buflen); 186 | ffi.memset(buf, 0xff, buflen); 187 | ffi.printhex(buf, buflen); 188 | console.log( 189 | ffi.memreadint(buf, buflen, 0, true, 1), 190 | ffi.memreadint(buf, buflen, 0, true, 2), 191 | ffi.memreadint(buf, buflen, 0, true, 4), 192 | ffi.memreadint(buf, buflen, 0, true, 8), 193 | ffi.memreadint(buf, buflen, 0, false, 1), 194 | ffi.memreadint(buf, buflen, 0, false, 2), 195 | ffi.memreadint(buf, buflen, 0, false, 4), 196 | ffi.memreadint(buf, buflen, 0, false, 8) 197 | ); 198 | ffi.memwriteint(buf, buflen, 6, 2, 0x1234); 199 | ffi.printhex(buf, buflen); 200 | console.log(ffi.memreadint(buf, buflen, 6, false, 2).toString(16)); 201 | ffi.memwritestring(buf, buflen, 2, "hello"); 202 | ffi.printhex(buf, buflen); 203 | print(ffi.memreadstring(buf, buflen, 2, 5)); 204 | ffi.memwritefloat(buf, buflen, 0, true, 6.66666666666666666666666666); 205 | ffi.printhex(buf, buflen); 206 | console.log(ffi.memreadfloat(buf, buflen, 0, true)); 207 | ffi.free(buf); 208 | 209 | Functions in `libdl` and `libffi` are almost the same as it's C style: 210 | 211 | * `pointer dlopen(string/null filename, number flags)` 212 | * `number dlclose(pointer handle)` 213 | * `pointer dlsym(pointer handle, string symbol)` 214 | * `string/null dlerror()` 215 | * `number ffi_prep_cif(pointer cif, number abi, number nargs, pointer rtype, pointer atypes)` 216 | * `number ffi_prep_cif_var(pointer cif, number abi, number nfixedargs, number ntotalargs, pointer rtype, pointer atypes)` 217 | * `undefined ffi_call(pointer cif, pointer fn, pointer rvalue, pointer avalues)` 218 | * `number ffi_get_struct_offsets(number abi, pointer struct_type, pointer offsets)` 219 | * `number ffi_closure_alloc(number, number)` 220 | * `undefined ffi_closure_free(number)` 221 | * `number ffi_prep_closure_loc(number, number, number, number, number)` 222 | 223 | And many necessary constant values or addresses such as `RTLD_XXX` `FFI_XXX` `ffi_type_xxx` are exposed. 224 | 225 | **Cautions:** 226 | 227 | * Since there is no way to define C numeric variables in JS, only dynamic creation using `malloc` is reasonable, so don't forget to `free` them when no longer used. 228 | * `ffi_type_xxx` are pointers, eg. memory addresses. 229 | 230 | Here is a simple example invoking `test1`: 231 | 232 | let handle = ffi.dlopen("test-lib.so", ffi.RTLD_NOW); 233 | let test1 = ffi.dlsym(handle, 'test1'); 234 | let cif = ffi.malloc(ffi.sizeof_ffi_cif); 235 | ffi.ffi_prep_cif(cif, ffi.FFI_DEFAULT_ABI, 0, ffi.ffi_type_void, 0); 236 | ffi.ffi_call(cif, test1, 0, 0) 237 | ffi.free(cif); 238 | ffi.dlclose(handle); 239 | 240 | And here is a slightly complex example invoking `test2`: 241 | 242 | let handle = ffi.dlopen('test-lib.so', ffi.RTLD_NOW); 243 | if (handle != ffi.NULL) { 244 | let test2 = ffi.dlsym(handle, 'test2'); 245 | if (test2 != ffi.NULL) { 246 | let cif = ffi.malloc(ffi.sizeof_ffi_cif); 247 | let nargs = 3; 248 | let rtype = ffi.ffi_type_double; 249 | let atypes_size = ffi.sizeof_uintptr_t * 3; 250 | let atypes = ffi.malloc(atypes_size); 251 | ffi.memwriteint(atypes, atypes_size, ffi.sizeof_uintptr_t * 0, ffi.sizeof_uintptr_t, ffi.ffi_type_float); 252 | ffi.memwriteint(atypes, atypes_size, ffi.sizeof_uintptr_t * 1, ffi.sizeof_uintptr_t, ffi.ffi_type_double); 253 | ffi.memwriteint(atypes, atypes_size, ffi.sizeof_uintptr_t * 2, ffi.sizeof_uintptr_t, ffi.ffi_type_pointer); 254 | if (ffi.ffi_prep_cif(cif, ffi.FFI_DEFAULT_ABI, nargs, rtype, atypes) == ffi.FFI_OK) { 255 | let arg1 = ffi.malloc(4); 256 | ffi.memwritefloat(arg1, 4, 0, false, 3.141592654); 257 | let arg2 = ffi.malloc(8); 258 | ffi.memwritefloat(arg2, 8, 0, true, 2.718281829); 259 | let str = ffi.malloc(1000); 260 | ffi.memwritestring(str, 1000, 0, "How do you do?"); 261 | let arg3 = ffi.malloc(ffi.sizeof_uintptr_t); 262 | ffi.memwriteint(arg3, ffi.sizeof_uintptr_t, 0, ffi.sizeof_uintptr_t, str); 263 | let avalues_size = ffi.sizeof_uintptr_t * 3; 264 | let avalues = ffi.malloc(avalues_size); 265 | ffi.memwriteint(avalues, avalues_size, ffi.sizeof_uintptr_t * 0, ffi.sizeof_uintptr_t, arg1); 266 | ffi.memwriteint(avalues, avalues_size, ffi.sizeof_uintptr_t * 1, ffi.sizeof_uintptr_t, arg2); 267 | ffi.memwriteint(avalues, avalues_size, ffi.sizeof_uintptr_t * 2, ffi.sizeof_uintptr_t, arg3); 268 | let rvalue = ffi.malloc(8); 269 | ffi.ffi_call(cif, test2, rvalue, avalues); 270 | console.log(ffi.memreadfloat(rvalue, 8, 0, true)); 271 | ffi.free(rvalue); 272 | ffi.free(avalues); 273 | ffi.free(arg3); 274 | ffi.free(str); 275 | ffi.free(arg2); 276 | ffi.free(arg1); 277 | } 278 | ffi.free(atypes); 279 | ffi.free(cif); 280 | } 281 | ffi.dlclose(handle); 282 | } 283 | 284 | In libffi document there are detailed instructions on how to pass C structures. Here is how to invoke `test3`. JS code looks very similar to C, except almost all variables are dynamically created, so be very careful dealing memories and pointers. 285 | 286 | let handle = ffi.dlopen('test-lib.so', ffi.RTLD_NOW); 287 | if (handle != ffi.NULL) { 288 | let test3 = ffi.dlsym(handle, 'test3'); 289 | if (test3 != ffi.NULL) { 290 | let cif = ffi.malloc(ffi.sizeof_ffi_cif); 291 | let atypes = ffi.malloc(ffi.sizeof_uintptr_t); 292 | 293 | let s1_elements = ffi.malloc(ffi.sizeof_uintptr_t * 3); 294 | ffi.memwriteint(s1_elements, ffi.sizeof_uintptr_t * 3, ffi.sizeof_uintptr_t * 0, ffi.sizeof_uintptr_t, ffi.ffi_type_sint); 295 | ffi.memwriteint(s1_elements, ffi.sizeof_uintptr_t * 3, ffi.sizeof_uintptr_t * 1, ffi.sizeof_uintptr_t, ffi.ffi_type_float); 296 | ffi.memwriteint(s1_elements, ffi.sizeof_uintptr_t * 3, ffi.sizeof_uintptr_t * 2, ffi.sizeof_uintptr_t, ffi.NULL); 297 | 298 | let s1 = ffi.malloc(ffi.sizeof_ffi_type); 299 | // I wrote C code, filled ffi_type members one by one with -1, and got it's structure is: 300 | // "size" 8 bytes, "alignment" 2 bytes, "type" 2 bytes, useless blank 4 bytes, "**elements" 8 bytes 301 | // I don't know why 4 bytes blank exists, C structure alignment rule? 302 | // In order to solve this problem, I added some "offsetof_xxx" members. 303 | ffi.memset(s1, 0, ffi.sizeof_ffi_type); 304 | ffi.memwriteint(s1, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_type, 2, ffi.FFI_TYPE_STRUCT); 305 | ffi.memwriteint(s1, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_elements, ffi.sizeof_uintptr_t, s1_elements); 306 | 307 | let s2_elements = ffi.malloc(ffi.sizeof_uintptr_t * 4); 308 | ffi.memwriteint(s2_elements, ffi.sizeof_uintptr_t * 4, ffi.sizeof_uintptr_t * 0, ffi.sizeof_uintptr_t, ffi.ffi_type_slong); 309 | ffi.memwriteint(s2_elements, ffi.sizeof_uintptr_t * 4, ffi.sizeof_uintptr_t * 1, ffi.sizeof_uintptr_t, ffi.ffi_type_double); 310 | ffi.memwriteint(s2_elements, ffi.sizeof_uintptr_t * 4, ffi.sizeof_uintptr_t * 2, ffi.sizeof_uintptr_t, s1); 311 | ffi.memwriteint(s2_elements, ffi.sizeof_uintptr_t * 4, ffi.sizeof_uintptr_t * 3, ffi.sizeof_uintptr_t, ffi.NULL); 312 | 313 | let s2 = ffi.malloc(ffi.sizeof_ffi_type); 314 | ffi.memset(s2, 0, ffi.sizeof_ffi_type); 315 | ffi.memwriteint(s2, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_type, 2, ffi.FFI_TYPE_STRUCT); 316 | ffi.memwriteint(s2, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_elements, ffi.sizeof_uintptr_t, s2_elements); 317 | 318 | ffi.printhex(s1, ffi.sizeof_ffi_type) 319 | ffi.printhex(s2, ffi.sizeof_ffi_type) 320 | 321 | ffi.memwriteint(atypes, ffi.sizeof_uintptr_t, 0, ffi.sizeof_uintptr_t, s2); 322 | 323 | if (ffi.ffi_prep_cif(cif, ffi.FFI_DEFAULT_ABI, 1, ffi.ffi_type_void, atypes) == ffi.FFI_OK) { 324 | let sizeof_struct_s2 = 24; 325 | let arg1 = ffi.malloc(sizeof_struct_s2); 326 | ffi.memwriteint(arg1, sizeof_struct_s2, 0, 8, 123456789); 327 | ffi.memwritefloat(arg1, sizeof_struct_s2, 8, true, 3.141592654); 328 | ffi.memwriteint(arg1, sizeof_struct_s2, 16, 4, 54321); 329 | ffi.memwritefloat(arg1, sizeof_struct_s2, 20, false, 2.718281829); 330 | 331 | let avalues_size = ffi.sizeof_uintptr_t; 332 | let avalues = ffi.malloc(avalues_size); 333 | ffi.memwriteint(avalues, avalues_size, 0, ffi.sizeof_uintptr_t, arg1); 334 | 335 | ffi.ffi_call(cif, test3, ffi.NULL, avalues); 336 | 337 | ffi.memset(arg1, 0xff, sizeof_struct_s2); 338 | ffi.ffi_call(cif, test3, ffi.NULL, avalues); 339 | 340 | ffi.free(avalues); 341 | ffi.free(arg1); 342 | } 343 | 344 | ffi.free(s2); 345 | ffi.free(s2_elements); 346 | ffi.free(s1); 347 | ffi.free(s1_elements); 348 | ffi.free(atypes); 349 | ffi.free(cif); 350 | } 351 | ffi.dlclose(handle); 352 | } 353 | 354 | ## Examples 355 | 356 | ### More examples visit 357 | 358 | ### JS version of 359 | 360 | import { CFunction } from 'quickjs-ffi.js' 361 | 362 | const LIBCURL_SO = '/usr/lib/x86_64-linux-gnu/libcurl.so.4' 363 | const curl_easy_init = new CFunction(LIBCURL_SO, 'curl_easy_init', null, 'pointer').invoke; 364 | const CURLOPT_URL = 10000 + 2; 365 | const CURLOPT_FOLLOWLOCATION = 0 + 52; 366 | const curl_easy_perform = new CFunction(LIBCURL_SO, 'curl_easy_perform', null, 'int', 'pointer').invoke; 367 | const CURLE_OK = 0; 368 | const curl_easy_strerror = new CFunction(LIBCURL_SO, 'curl_easy_strerror', null, 'string', 'int').invoke; 369 | const curl_easy_cleanup = new CFunction(LIBCURL_SO, 'curl_easy_cleanup', null, 'void', 'pointer').invoke; 370 | 371 | let curl = curl_easy_init(); 372 | if (curl > 0) { 373 | let curl_easy_setopt = new CFunction(LIBCURL_SO, 'curl_easy_setopt', 2, 'int', 'pointer', 'int', 'string'); 374 | curl_easy_setopt.invoke(curl, CURLOPT_URL, "https://example.com"); 375 | curl_easy_setopt.free(); 376 | curl_easy_setopt = new CFunction(LIBCURL_SO, 'curl_easy_setopt', 2, 'int', 'pointer', 'int', 'long'); 377 | curl_easy_setopt.invoke(curl, CURLOPT_FOLLOWLOCATION, 1); 378 | curl_easy_setopt.free(); 379 | let res = curl_easy_perform(curl); 380 | if (res != CURLE_OK) { 381 | console.log("curl_easy_perform() failed:", curl_easy_strerror(res)); 382 | } 383 | curl_easy_cleanup(curl); 384 | } 385 | -------------------------------------------------------------------------------- /quickjs-ffi.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 shajunxing 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #define _GNU_SOURCE 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #define C_MACRO_STRING_DEF(x) JS_PROP_STRING_DEF(#x, x, JS_PROP_CONFIGURABLE) 40 | 41 | #if UINTPTR_MAX == UINT32_MAX 42 | #define JS_TO_UINTPTR_T(ctx, pres, val) JS_ToInt32(ctx, (int32_t *)(pres), val) 43 | #define JS_NEW_UINTPTR_T(ctx, val) JS_NewInt32(ctx, (int32_t)(val)) 44 | #define C_MACRO_UINTPTR_T_DEF(x) JS_PROP_INT32_DEF(#x, (int32_t)(x), JS_PROP_CONFIGURABLE) 45 | // 不能#define C_MACRO_INTPTR_DEF(x) C_MACRO_INT_DEF(x)否则#会展开为x所定义的内容而非x本身 46 | #define C_VAR_ADDRESS_DEF(x) JS_PROP_INT32_DEF(#x, (int32_t)(&x), JS_PROP_CONFIGURABLE) 47 | #define ffi_type_intptr_t ffi_type_sint32 48 | #define ffi_type_uintptr_t ffi_type_uint32 49 | #elif UINTPTR_MAX == UINT64_MAX 50 | #define JS_TO_UINTPTR_T(ctx, pres, val) JS_ToInt64(ctx, (int64_t *)(pres), val) 51 | #define JS_NEW_UINTPTR_T(ctx, val) JS_NewInt64(ctx, (int64_t)(val)) 52 | #define C_MACRO_UINTPTR_T_DEF(x) JS_PROP_INT64_DEF(#x, (int64_t)(x), JS_PROP_CONFIGURABLE) 53 | #define C_VAR_ADDRESS_DEF(x) JS_PROP_INT64_DEF(#x, (int64_t)(&x), JS_PROP_CONFIGURABLE) 54 | #define ffi_type_intptr_t ffi_type_sint64 55 | #define ffi_type_uintptr_t ffi_type_uint64 56 | #else 57 | #error "'uintptr_t' neither 32bit nor 64 bit, I don't know how to handle it." 58 | #endif 59 | 60 | #define STR(x) #x 61 | #if SIZE_MAX == UINT32_MAX 62 | #define JS_TO_SIZE_T(ctx, pres, val) JS_ToInt32(ctx, (int32_t *)(pres), val) 63 | #define JS_NEW_SIZE_T(ctx, val) JS_NewInt32(ctx, (int32_t)(val)) 64 | #define JS_PROP_SIZE_T_DEF(name, val) JS_PROP_INT32_DEF(name, (int32_t)(val), JS_PROP_CONFIGURABLE) 65 | #define C_SIZEOF_DEF(x) JS_PROP_INT32_DEF(STR(sizeof_##x), (int32_t)(sizeof(x)), JS_PROP_CONFIGURABLE) 66 | #define ffi_type_size_t ffi_type_uint32 67 | #elif SIZE_MAX == UINT64_MAX 68 | #define JS_TO_SIZE_T(ctx, pres, val) JS_ToInt64(ctx, (int64_t *)(pres), val) 69 | #define JS_NEW_SIZE_T(ctx, val) JS_NewInt64(ctx, (int64_t)(val)) 70 | #define JS_PROP_SIZE_T_DEF(name, val) JS_PROP_INT64_DEF(name, (int64_t)(val), JS_PROP_CONFIGURABLE) 71 | #define C_SIZEOF_DEF(x) JS_PROP_INT64_DEF(STR(sizeof_##x), (int64_t)(sizeof(x)), JS_PROP_CONFIGURABLE) 72 | #define C_OFFSETOF_DEF(t, d) JS_PROP_INT64_DEF(STR(offsetof_##t##_##d), (int64_t)(offsetof(t, d)), JS_PROP_CONFIGURABLE) 73 | #define ffi_type_size_t ffi_type_sint32 74 | #else 75 | #error "'size_t' neither 32bit nor 64 bit, I don't know how to handle it." 76 | #endif 77 | 78 | #if INT_MAX == INT32_MAX 79 | #define C_MACRO_INT_DEF(x) JS_PROP_INT32_DEF(#x, (int32_t)(x), JS_PROP_CONFIGURABLE) 80 | #define C_ENUM_DEF(x) JS_PROP_INT32_DEF(#x, (int32_t)(x), JS_PROP_CONFIGURABLE) 81 | #define JS_TO_INT(ctx, pres, val) JS_ToInt32(ctx, (int32_t *)(pres), val) 82 | #define JS_NEW_INT(ctx, val) JS_NewInt32(ctx, (int32_t)(val)) 83 | #elif INT_MAX == INT64_MAX 84 | #define C_MACRO_INT_DEF(x) JS_PROP_INT64_DEF(#x, (int64_t)(x), JS_PROP_CONFIGURABLE) 85 | #define C_ENUM_DEF(x) JS_PROP_INT64_DEF(#x, (int64_t)(x), JS_PROP_CONFIGURABLE) 86 | #define JS_TO_INT(ctx, pres, val) JS_ToInt64(ctx, (int64_t *)(pres), val) 87 | #define JS_NEW_INT(ctx, val) JS_NewInt64(ctx, (int64_t)(val)) 88 | #else 89 | #error "'int' neither 32bit nor 64 bit, I don't know how to handle it." 90 | #endif 91 | 92 | #define COUNTOF(x) (sizeof(x) / sizeof((x)[0])) 93 | 94 | enum argtype { 95 | t_null, 96 | t_bool, 97 | t_number, 98 | t_string, 99 | t_string_or_null, 100 | t_function, 101 | }; 102 | 103 | static bool check_args(JSContext *ctx, int argc, JSValueConst *argv, enum argtype argtype_list[], int argtype_count) { 104 | if (argc != argtype_count) { 105 | JS_ThrowTypeError(ctx, "argc must be %d, got %d", argtype_count, argc); 106 | return false; 107 | } 108 | for (int i = 0; i < argtype_count; i++) { 109 | switch (argtype_list[i]) { 110 | case t_null: 111 | if (!JS_IsNull(argv[i])) { 112 | JS_ThrowTypeError(ctx, "argv[%d] must be null", i); 113 | return false; 114 | } 115 | break; 116 | case t_bool: 117 | if (!JS_IsBool(argv[i])) { 118 | JS_ThrowTypeError(ctx, "argv[%d] must be boolean", i); 119 | return false; 120 | } 121 | break; 122 | case t_number: 123 | if (!JS_IsNumber(argv[i])) { 124 | JS_ThrowTypeError(ctx, "argv[%d] must be number", i); 125 | return false; 126 | } 127 | break; 128 | case t_string: 129 | if (!JS_IsString(argv[i])) { 130 | JS_ThrowTypeError(ctx, "argv[%d] must be string", i); 131 | return false; 132 | } 133 | break; 134 | case t_string_or_null: 135 | if (!(JS_IsString(argv[i]) || JS_IsNull(argv[i]))) { 136 | JS_ThrowTypeError(ctx, "argv[%d] must be string or null", i); 137 | return false; 138 | } 139 | break; 140 | case t_function: 141 | if (!JS_IsFunction(ctx, argv[i])) { 142 | JS_ThrowTypeError(ctx, "argv[%d] must be function", i); 143 | return false; 144 | } 145 | break; 146 | default: 147 | JS_ThrowTypeError(ctx, "argv[%d] type definition is not yet supported", i); 148 | return false; 149 | } 150 | } 151 | return true; 152 | } 153 | 154 | #define CHECK_ARGS(ctx, argc, argv, tlist) \ 155 | if (!check_args(ctx, argc, argv, (tlist), COUNTOF(tlist))) { \ 156 | return JS_EXCEPTION; \ 157 | } 158 | 159 | static JSValue js_libc_malloc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 160 | size_t size; 161 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 162 | JS_TO_SIZE_T(ctx, &size, argv[0]); 163 | return JS_NEW_UINTPTR_T(ctx, malloc(size)); 164 | } 165 | 166 | static JSValue js_libc_realloc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 167 | void *ptr; 168 | size_t size; 169 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number})) 170 | JS_TO_UINTPTR_T(ctx, &ptr, argv[0]); 171 | JS_TO_SIZE_T(ctx, &size, argv[1]); 172 | return JS_NEW_UINTPTR_T(ctx, realloc(ptr, size)); 173 | } 174 | 175 | static JSValue js_libc_free(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 176 | void *ptr; 177 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 178 | JS_TO_UINTPTR_T(ctx, &ptr, argv[0]); 179 | free(ptr); 180 | return JS_UNDEFINED; 181 | } 182 | 183 | static JSValue js_libc_memset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 184 | void *s; 185 | int c; 186 | size_t n; 187 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number})) 188 | JS_TO_UINTPTR_T(ctx, &s, argv[0]); 189 | JS_TO_INT(ctx, &c, argv[1]); 190 | JS_TO_SIZE_T(ctx, &n, argv[2]); 191 | return JS_NEW_UINTPTR_T(ctx, memset(s, c, n)); 192 | } 193 | 194 | static JSValue js_libc_memcpy(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 195 | void *dest; 196 | void *src; 197 | size_t n; 198 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number})) 199 | JS_TO_UINTPTR_T(ctx, &dest, argv[0]); 200 | JS_TO_UINTPTR_T(ctx, &src, argv[1]); 201 | JS_TO_SIZE_T(ctx, &n, argv[2]); 202 | return JS_NEW_UINTPTR_T(ctx, memcpy(dest, src, n)); 203 | } 204 | 205 | static JSValue js_libc_strlen(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 206 | char *s; 207 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 208 | JS_TO_UINTPTR_T(ctx, &s, argv[0]); 209 | return JS_NEW_SIZE_T(ctx, strlen(s)); 210 | } 211 | 212 | static void fprinthex(FILE *stream, const void *data, size_t size) { // https://gist.github.com/ccbrown/9722406 213 | char ascii[17]; 214 | size_t i, j; 215 | ascii[16] = '\0'; 216 | for (i = 0; i < size; ++i) { 217 | fprintf(stream, "%02X ", ((unsigned char *)data)[i]); 218 | if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') { 219 | ascii[i % 16] = ((unsigned char *)data)[i]; 220 | } else { 221 | ascii[i % 16] = '.'; 222 | } 223 | if ((i + 1) % 8 == 0 || i + 1 == size) { 224 | fprintf(stream, " "); 225 | if ((i + 1) % 16 == 0) { 226 | fprintf(stream, "| %s \n", ascii); 227 | } else if (i + 1 == size) { 228 | ascii[(i + 1) % 16] = '\0'; 229 | if ((i + 1) % 16 <= 8) { 230 | fprintf(stream, " "); 231 | } 232 | for (j = (i + 1) % 16; j < 16; ++j) { 233 | fprintf(stream, " "); 234 | } 235 | fprintf(stream, "| %s \n", ascii); 236 | } 237 | } 238 | } 239 | } 240 | 241 | static JSValue js_fprinthex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 242 | FILE *stream; 243 | void *data; 244 | size_t size; 245 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number})) 246 | JS_TO_UINTPTR_T(ctx, &stream, argv[0]); 247 | JS_TO_UINTPTR_T(ctx, &data, argv[1]); 248 | JS_TO_SIZE_T(ctx, &size, argv[2]); 249 | fprinthex(stream, data, size); 250 | return JS_UNDEFINED; 251 | } 252 | 253 | static JSValue js_printhex(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 254 | void *data; 255 | size_t size; 256 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number})) 257 | JS_TO_UINTPTR_T(ctx, &data, argv[0]); 258 | JS_TO_SIZE_T(ctx, &size, argv[1]); 259 | fprinthex(stdout, data, size); 260 | return JS_UNDEFINED; 261 | } 262 | 263 | static JSValue js_memreadint(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 264 | void *buf; 265 | size_t buflen; 266 | size_t offset; 267 | bool issigned; 268 | size_t bytewidth; 269 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_bool, t_number})) 270 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 271 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 272 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 273 | issigned = JS_ToBool(ctx, argv[3]); 274 | JS_TO_SIZE_T(ctx, &bytewidth, argv[4]); 275 | if ((buflen < 0) || (offset < 0) || (bytewidth < 0) || (offset + bytewidth > buflen)) { 276 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 277 | return JS_EXCEPTION; 278 | } 279 | // printf("%p %d %d %d %d\n", buf, buflen, offset, issigned, bytewidth); 280 | switch (bytewidth) { 281 | case 1: 282 | return issigned ? JS_NewInt32(ctx, *((int8_t *)(buf + offset))) : JS_NewUint32(ctx, *((uint8_t *)(buf + offset))); 283 | case 2: 284 | return issigned ? JS_NewInt32(ctx, *((int16_t *)(buf + offset))) : JS_NewUint32(ctx, *((uint16_t *)(buf + offset))); 285 | case 4: 286 | return issigned ? JS_NewInt32(ctx, *((int32_t *)(buf + offset))) : JS_NewUint32(ctx, *((uint32_t *)(buf + offset))); 287 | case 8: 288 | // TODO: unsigned int64 ??? 289 | return issigned ? JS_NewInt64(ctx, *((int64_t *)(buf + offset))) : JS_NewInt64(ctx, *((uint64_t *)(buf + offset))); 290 | default: 291 | JS_ThrowTypeError(ctx, "bytewidth must only be 1, 2, 4, or 8"); 292 | return JS_EXCEPTION; 293 | } 294 | } 295 | 296 | static JSValue js_memwriteint(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 297 | void *buf; 298 | size_t buflen; 299 | size_t offset; 300 | size_t bytewidth; 301 | int64_t val; 302 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number, t_number})) 303 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 304 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 305 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 306 | JS_TO_SIZE_T(ctx, &bytewidth, argv[3]); 307 | JS_ToInt64(ctx, &val, argv[4]); 308 | if ((buflen < 0) || (offset < 0) || (bytewidth < 0) || (offset + bytewidth > buflen)) { 309 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 310 | return JS_EXCEPTION; 311 | } 312 | // printf("%p %d %d %d %d\n", buf, buflen, offset, issigned, bytewidth); 313 | switch (bytewidth) { 314 | case 1: 315 | *((int8_t *)(buf + offset)) = (int8_t)val; 316 | break; 317 | case 2: 318 | *((int16_t *)(buf + offset)) = (int16_t)val; 319 | break; 320 | case 4: 321 | *((int32_t *)(buf + offset)) = (int32_t)val; 322 | break; 323 | case 8: 324 | *((int64_t *)(buf + offset)) = (int64_t)val; 325 | break; 326 | default: 327 | JS_ThrowTypeError(ctx, "bytewidth must only be 1, 2, 4, or 8"); 328 | return JS_EXCEPTION; 329 | } 330 | return JS_UNDEFINED; 331 | } 332 | 333 | static JSValue js_memreadfloat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 334 | void *buf; 335 | size_t buflen; 336 | size_t offset; 337 | bool isdouble; 338 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_bool})) 339 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 340 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 341 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 342 | isdouble = JS_ToBool(ctx, argv[3]); 343 | if ((buflen < 0) || (offset < 0) || (offset + (isdouble ? sizeof(double) : sizeof(float)) > buflen)) { 344 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 345 | return JS_EXCEPTION; 346 | } 347 | return isdouble ? JS_NewFloat64(ctx, *((double *)(buf + offset))) : JS_NewFloat64(ctx, *((float *)(buf + offset))); 348 | } 349 | 350 | static JSValue js_memwritefloat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 351 | void *buf; 352 | size_t buflen; 353 | size_t offset; 354 | bool isdouble; 355 | double val; 356 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_bool, t_number})) 357 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 358 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 359 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 360 | isdouble = JS_ToBool(ctx, argv[3]); 361 | JS_ToFloat64(ctx, &val, argv[4]); 362 | // printf("%f\n", val); 363 | if ((buflen < 0) || (offset < 0) || (offset + (isdouble ? sizeof(double) : sizeof(float)) > buflen)) { 364 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 365 | return JS_EXCEPTION; 366 | } 367 | if (isdouble) { 368 | *((double *)(buf + offset)) = (double)val; 369 | } else { 370 | *((float *)(buf + offset)) = (float)val; 371 | } 372 | return JS_UNDEFINED; 373 | } 374 | 375 | static JSValue js_memreadstring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 376 | void *buf; 377 | size_t buflen; 378 | size_t offset; 379 | size_t len; 380 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number})) 381 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 382 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 383 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 384 | JS_TO_SIZE_T(ctx, &len, argv[3]); 385 | if ((buflen < 0) || (offset < 0) || (len < 0) || (offset + len > buflen)) { 386 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 387 | return JS_EXCEPTION; 388 | } 389 | return JS_NewStringLen(ctx, (char *)(buf + offset), len); 390 | } 391 | 392 | static JSValue js_memwritestring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 393 | void *buf; 394 | size_t buflen; 395 | size_t offset; 396 | const char *str; 397 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_string})) 398 | JS_TO_UINTPTR_T(ctx, &buf, argv[0]); 399 | JS_TO_SIZE_T(ctx, &buflen, argv[1]); 400 | JS_TO_SIZE_T(ctx, &offset, argv[2]); 401 | str = JS_ToCString(ctx, argv[3]); 402 | size_t len = strlen(str); 403 | if ((buflen < 0) || (offset < 0) || (len < 0) || (offset + len > buflen)) { 404 | JS_ThrowRangeError(ctx, "pointer out of bounds"); 405 | JS_FreeCString(ctx, str); 406 | return JS_EXCEPTION; 407 | } 408 | memcpy(buf + offset, str, len); 409 | JS_FreeCString(ctx, str); 410 | return JS_UNDEFINED; 411 | } 412 | 413 | static JSValue js_tocstring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 414 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_string})) 415 | return JS_NEW_UINTPTR_T(ctx, JS_ToCString(ctx, argv[0])); 416 | } 417 | 418 | static JSValue js_freecstring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 419 | char *str; 420 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 421 | JS_TO_UINTPTR_T(ctx, &str, argv[0]); 422 | JS_FreeCString(ctx, str); 423 | return JS_UNDEFINED; 424 | } 425 | 426 | static JSValue js_newstring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 427 | char *str; 428 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 429 | JS_TO_UINTPTR_T(ctx, &str, argv[0]); 430 | return JS_NewString(ctx, str); 431 | } 432 | 433 | static JSValue js_libdl_dlopen(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 434 | const char *filename; 435 | int flags; 436 | void *ret; 437 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_string_or_null, t_number})) 438 | filename = JS_IsString(argv[0]) ? JS_ToCString(ctx, argv[0]) : NULL; 439 | JS_ToInt32(ctx, &flags, argv[1]); 440 | ret = dlopen(filename, flags); 441 | // printf("%p\n", ret); 442 | if (filename) { 443 | JS_FreeCString(ctx, filename); 444 | } 445 | return JS_NEW_UINTPTR_T(ctx, ret); 446 | } 447 | 448 | static JSValue js_libdl_dlclose(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 449 | void *handle; 450 | int ret; 451 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 452 | JS_TO_UINTPTR_T(ctx, &handle, argv[0]); 453 | // printf("%p\n", handle); 454 | ret = dlclose(handle); 455 | return JS_NewInt32(ctx, ret); 456 | } 457 | 458 | static JSValue js_libdl_dlsym(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 459 | void *handle; 460 | const char *symbol; 461 | void *ret; 462 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_string})) 463 | JS_TO_UINTPTR_T(ctx, &handle, argv[0]); 464 | symbol = JS_ToCString(ctx, argv[1]); 465 | // printf("%p %s\n", handle, symbol); 466 | ret = dlsym(handle, symbol); 467 | // printf("%p\n", ret); 468 | if (symbol) { 469 | JS_FreeCString(ctx, symbol); 470 | } 471 | return JS_NEW_UINTPTR_T(ctx, ret); 472 | } 473 | 474 | static JSValue js_libdl_dlerror(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 475 | char *ret; 476 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){})) 477 | ret = dlerror(); 478 | return ret ? JS_NewString(ctx, ret) : JS_NULL; 479 | } 480 | 481 | static JSValue js_libffi_ffi_prep_cif(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 482 | ffi_cif *cif; 483 | ffi_abi abi; 484 | unsigned int nargs; 485 | ffi_type *rtype; 486 | ffi_type **atypes; 487 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number, t_number})) 488 | JS_TO_UINTPTR_T(ctx, &cif, argv[0]); 489 | JS_TO_INT(ctx, &abi, argv[1]); 490 | JS_TO_INT(ctx, &nargs, argv[2]); 491 | JS_TO_UINTPTR_T(ctx, &rtype, argv[3]); 492 | JS_TO_UINTPTR_T(ctx, &atypes, argv[4]); 493 | return JS_NEW_INT(ctx, ffi_prep_cif(cif, abi, nargs, rtype, atypes)); 494 | } 495 | 496 | static JSValue js_libffi_ffi_prep_cif_var(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 497 | ffi_cif *cif; 498 | ffi_abi abi; 499 | unsigned int nfixedargs; 500 | unsigned int ntotalargs; 501 | ffi_type *rtype; 502 | ffi_type **atypes; 503 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number, t_number, t_number})) 504 | JS_TO_UINTPTR_T(ctx, &cif, argv[0]); 505 | JS_TO_INT(ctx, &abi, argv[1]); 506 | JS_TO_INT(ctx, &nfixedargs, argv[2]); 507 | JS_TO_INT(ctx, &ntotalargs, argv[3]); 508 | JS_TO_UINTPTR_T(ctx, &rtype, argv[4]); 509 | JS_TO_UINTPTR_T(ctx, &atypes, argv[5]); 510 | return JS_NEW_INT(ctx, ffi_prep_cif_var(cif, abi, nfixedargs, ntotalargs, rtype, atypes)); 511 | } 512 | 513 | static JSValue js_libffi_ffi_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 514 | ffi_cif *cif; 515 | void *fn; 516 | void *rvalue; 517 | void **avalues; 518 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number})) 519 | JS_TO_UINTPTR_T(ctx, &cif, argv[0]); 520 | JS_TO_UINTPTR_T(ctx, &fn, argv[1]); 521 | JS_TO_UINTPTR_T(ctx, &rvalue, argv[2]); 522 | JS_TO_UINTPTR_T(ctx, &avalues, argv[3]); 523 | ffi_call(cif, fn, rvalue, avalues); 524 | return JS_UNDEFINED; 525 | } 526 | 527 | static JSValue js_ffi_get_struct_offsets(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 528 | ffi_abi abi; 529 | ffi_type *struct_type; 530 | size_t *offsets; 531 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number})) 532 | JS_TO_INT(ctx, &abi, argv[0]); 533 | JS_TO_UINTPTR_T(ctx, &struct_type, argv[1]); 534 | JS_TO_UINTPTR_T(ctx, &offsets, argv[2]); 535 | return JS_NEW_INT(ctx, ffi_get_struct_offsets(abi, struct_type, offsets)); 536 | } 537 | 538 | static JSValue js_ffi_closure_alloc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 539 | size_t size; 540 | void **code; 541 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number})) 542 | JS_TO_SIZE_T(ctx, &size, argv[0]); 543 | JS_TO_UINTPTR_T(ctx, &code, argv[1]); 544 | return JS_NEW_UINTPTR_T(ctx, ffi_closure_alloc(size, code)); 545 | } 546 | 547 | static JSValue js_ffi_closure_free(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 548 | void *writable; 549 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number})) 550 | JS_TO_UINTPTR_T(ctx, &writable, argv[0]); 551 | ffi_closure_free(writable); 552 | return JS_UNDEFINED; 553 | } 554 | 555 | static JSValue js_ffi_prep_closure_loc(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 556 | ffi_closure *closure; 557 | ffi_cif *cif; 558 | void *fun; 559 | void *user_data; 560 | void *codeloc; 561 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_number, t_number, t_number, t_number})) 562 | JS_TO_UINTPTR_T(ctx, &closure, argv[0]); 563 | JS_TO_UINTPTR_T(ctx, &cif, argv[1]); 564 | JS_TO_UINTPTR_T(ctx, &fun, argv[2]); 565 | JS_TO_UINTPTR_T(ctx, &user_data, argv[3]); 566 | JS_TO_UINTPTR_T(ctx, &codeloc, argv[4]); 567 | return JS_NEW_INT(ctx, ffi_prep_closure_loc(closure, cif, fun, user_data, codeloc)); 568 | } 569 | 570 | typedef struct { 571 | JSContext *ctx; 572 | JSValue this; 573 | JSValue func; 574 | } ffi_closure_js_func_data; 575 | 576 | static JSValue js_fill_ffi_closure_js_func_data(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 577 | ffi_closure_js_func_data *data; 578 | CHECK_ARGS(ctx, argc, argv, ((enum argtype[]){t_number, t_function})) 579 | JS_TO_UINTPTR_T(ctx, &data, argv[0]); 580 | data->ctx = ctx; 581 | data->this = this_val; 582 | data->func = argv[1]; 583 | // puts("js_fill_ffi_closure_js_func_data"); 584 | // printf("%lu %lu %lu %lu %lu\n", data->ctx, (data->func).u.ptr, (data->func).tag, (data->this).u.ptr, (data->this).tag); 585 | return JS_UNDEFINED; 586 | } 587 | 588 | static void ffi_closure_js_func_adapter(ffi_cif *cif, void *ret, void *args[], void *user_data) { 589 | ffi_closure_js_func_data *data = (ffi_closure_js_func_data *)user_data; 590 | // puts("ffi_closure_js_func_adapter"); 591 | // printf("%lu %lu %lu %lu %lu\n", data->ctx, (data->func).u.ptr, (data->func).tag, (data->this).u.ptr, (data->this).tag); 592 | // printf("%lu %lu\n", ret, args); 593 | JSValue result = JS_Call(data->ctx, data->func, data->this, 2, 594 | (JSValueConst[]){JS_NEW_UINTPTR_T(data->ctx, ret), JS_NEW_UINTPTR_T(data->ctx, args)}); 595 | if (JS_IsException(result)) { // js_std_dump_error in quickjs-libc.c 596 | js_std_dump_error(data->ctx); 597 | } 598 | } 599 | 600 | static JSCFunctionListEntry funcs[] = { 601 | // 602 | // basic memory handling functions, partly from libc and quickjs itself 603 | // 604 | JS_CFUNC_DEF("malloc", 1, js_libc_malloc), 605 | JS_CFUNC_DEF("realloc", 2, js_libc_realloc), 606 | JS_CFUNC_DEF("free", 1, js_libc_free), 607 | JS_CFUNC_DEF("memset", 3, js_libc_memset), 608 | JS_CFUNC_DEF("memcpy", 3, js_libc_memcpy), 609 | JS_CFUNC_DEF("strlen", 1, js_libc_strlen), 610 | JS_CFUNC_DEF("fprinthex", 3, js_fprinthex), 611 | JS_CFUNC_DEF("printhex", 2, js_printhex), 612 | JS_CFUNC_DEF("memreadint", 5, js_memreadint), 613 | JS_CFUNC_DEF("memwriteint", 5, js_memwriteint), 614 | JS_CFUNC_DEF("memreadfloat", 4, js_memreadfloat), 615 | JS_CFUNC_DEF("memwritefloat", 5, js_memwritefloat), 616 | JS_CFUNC_DEF("memreadstring", 4, js_memreadstring), 617 | JS_CFUNC_DEF("memwritestring", 4, js_memwritestring), 618 | JS_CFUNC_DEF("tocstring", 1, js_tocstring), 619 | JS_CFUNC_DEF("freecstring", 1, js_freecstring), 620 | JS_CFUNC_DEF("newstring", 1, js_newstring), 621 | C_MACRO_UINTPTR_T_DEF(NULL), 622 | C_SIZEOF_DEF(uintptr_t), 623 | C_SIZEOF_DEF(int), 624 | C_SIZEOF_DEF(size_t), 625 | C_MACRO_STRING_DEF(LIBC_SO), 626 | C_MACRO_STRING_DEF(LIBM_SO), 627 | // 628 | // libdl 629 | // 630 | JS_CFUNC_DEF("dlopen", 2, js_libdl_dlopen), 631 | JS_CFUNC_DEF("dlclose", 1, js_libdl_dlclose), 632 | JS_CFUNC_DEF("dlsym", 2, js_libdl_dlsym), 633 | JS_CFUNC_DEF("dlerror", 0, js_libdl_dlerror), 634 | C_MACRO_INT_DEF(RTLD_LAZY), 635 | C_MACRO_INT_DEF(RTLD_NOW), 636 | C_MACRO_INT_DEF(RTLD_GLOBAL), 637 | C_MACRO_INT_DEF(RTLD_LOCAL), 638 | C_MACRO_INT_DEF(RTLD_NODELETE), 639 | C_MACRO_INT_DEF(RTLD_NOLOAD), 640 | C_MACRO_INT_DEF(RTLD_DEEPBIND), 641 | #if defined(_GNU_SOURCE) 642 | C_MACRO_UINTPTR_T_DEF(RTLD_DEFAULT), 643 | C_MACRO_UINTPTR_T_DEF(RTLD_NEXT), 644 | #endif 645 | // 646 | // libffi 647 | // 648 | JS_CFUNC_DEF("ffi_prep_cif", 5, js_libffi_ffi_prep_cif), 649 | JS_CFUNC_DEF("ffi_prep_cif_var", 6, js_libffi_ffi_prep_cif_var), 650 | JS_CFUNC_DEF("ffi_call", 4, js_libffi_ffi_call), 651 | JS_CFUNC_DEF("ffi_get_struct_offsets", 3, js_ffi_get_struct_offsets), 652 | JS_CFUNC_DEF("ffi_closure_alloc", 2, js_ffi_closure_alloc), 653 | JS_CFUNC_DEF("ffi_closure_free", 1, js_ffi_closure_free), 654 | JS_CFUNC_DEF("ffi_prep_closure_loc", 5, js_ffi_prep_closure_loc), 655 | C_ENUM_DEF(FFI_OK), 656 | C_ENUM_DEF(FFI_BAD_TYPEDEF), 657 | C_ENUM_DEF(FFI_BAD_ABI), 658 | C_SIZEOF_DEF(ffi_cif), 659 | C_ENUM_DEF(FFI_DEFAULT_ABI), 660 | C_SIZEOF_DEF(ffi_type), 661 | C_OFFSETOF_DEF(ffi_type, size), 662 | C_OFFSETOF_DEF(ffi_type, alignment), 663 | C_OFFSETOF_DEF(ffi_type, type), 664 | C_OFFSETOF_DEF(ffi_type, elements), 665 | C_SIZEOF_DEF(ffi_closure), 666 | #ifndef LIBFFI_HIDE_BASIC_TYPES 667 | C_VAR_ADDRESS_DEF(ffi_type_void), 668 | C_VAR_ADDRESS_DEF(ffi_type_uint8), 669 | C_VAR_ADDRESS_DEF(ffi_type_sint8), 670 | C_VAR_ADDRESS_DEF(ffi_type_uint16), 671 | C_VAR_ADDRESS_DEF(ffi_type_sint16), 672 | C_VAR_ADDRESS_DEF(ffi_type_uint32), 673 | C_VAR_ADDRESS_DEF(ffi_type_sint32), 674 | C_VAR_ADDRESS_DEF(ffi_type_uint64), 675 | C_VAR_ADDRESS_DEF(ffi_type_sint64), 676 | C_VAR_ADDRESS_DEF(ffi_type_float), 677 | C_VAR_ADDRESS_DEF(ffi_type_double), 678 | C_VAR_ADDRESS_DEF(ffi_type_pointer), 679 | C_VAR_ADDRESS_DEF(ffi_type_longdouble), 680 | #ifdef FFI_TARGET_HAS_COMPLEX_TYPE 681 | C_VAR_ADDRESS_DEF(ffi_type_complex_float), 682 | C_VAR_ADDRESS_DEF(ffi_type_complex_double), 683 | C_VAR_ADDRESS_DEF(ffi_type_complex_longdouble), 684 | #endif 685 | C_VAR_ADDRESS_DEF(ffi_type_uchar), 686 | C_VAR_ADDRESS_DEF(ffi_type_schar), 687 | C_VAR_ADDRESS_DEF(ffi_type_ushort), 688 | C_VAR_ADDRESS_DEF(ffi_type_sshort), 689 | C_VAR_ADDRESS_DEF(ffi_type_uint), 690 | C_VAR_ADDRESS_DEF(ffi_type_sint), 691 | C_VAR_ADDRESS_DEF(ffi_type_ulong), 692 | C_VAR_ADDRESS_DEF(ffi_type_slong), 693 | C_VAR_ADDRESS_DEF(ffi_type_uintptr_t), 694 | C_VAR_ADDRESS_DEF(ffi_type_intptr_t), 695 | C_VAR_ADDRESS_DEF(ffi_type_size_t), 696 | C_MACRO_INT_DEF(FFI_TYPE_STRUCT), 697 | C_MACRO_INT_DEF(FFI_TYPE_COMPLEX), 698 | // 699 | // libffi closure custom things 700 | // 701 | JS_CFUNC_DEF("fill_ffi_closure_js_func_data", 1, js_fill_ffi_closure_js_func_data), 702 | C_SIZEOF_DEF(ffi_closure_js_func_data), 703 | C_VAR_ADDRESS_DEF(ffi_closure_js_func_adapter), 704 | #endif 705 | }; 706 | 707 | // static void js_dtor_class_finalizer(JSRuntime *rt, JSValue obj) { 708 | // JSObject *p = JS_VALUE_GET_OBJ(obj); 709 | // JSContext *ctx = p->u.cfunc.realm; 710 | // if (ctx) { 711 | // JSValue dtor = JS_GetPropertyStr(ctx, obj, "destructor"); 712 | // if (JS_IsFunction(ctx, dtor)) { 713 | // JSValue result = JS_Call(ctx, dtor, obj, 0, NULL); 714 | // if (JS_IsException(result)) { // js_std_dump_error in quickjs-libc.c 715 | // js_std_dump_error(ctx); 716 | // } 717 | // } 718 | // } 719 | // } 720 | 721 | // static JSClassDef js_dtor_class = { 722 | // "ClassWithDestructor", 723 | // .finalizer = js_dtor_class_finalizer, 724 | // }; 725 | 726 | // static JSClassID js_dtor_class_id; 727 | 728 | static int init(JSContext *ctx, JSModuleDef *m) { 729 | // JS_NewClassID(&js_dtor_class_id); 730 | // JS_NewClass(JS_GetRuntime(ctx), js_dtor_class_id, &js_dtor_class); 731 | JS_SetModuleExportList(ctx, m, funcs, COUNTOF(funcs)); 732 | // stdin stdout stderr cannot be added in the list above, compiler error: 733 | // "initializer element is not constant ... in expansion of macro ..." 734 | // see https://stackoverflow.com/questions/35596220/file-stdout-error-initializer-element-is-not-constant 735 | // and https://stackoverflow.com/questions/7623735/error-initializer-element-is-not-constant 736 | JS_SetModuleExport(ctx, m, "stdin", JS_NEW_UINTPTR_T(ctx, stdin)); 737 | JS_SetModuleExport(ctx, m, "stdout", JS_NEW_UINTPTR_T(ctx, stdout)); 738 | JS_SetModuleExport(ctx, m, "stderr", JS_NEW_UINTPTR_T(ctx, stderr)); 739 | return 0; 740 | } 741 | 742 | JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) { 743 | JSModuleDef *m; 744 | m = JS_NewCModule(ctx, module_name, init); 745 | if (!m) 746 | return NULL; 747 | JS_AddModuleExportList(ctx, m, funcs, COUNTOF(funcs)); 748 | JS_AddModuleExport(ctx, m, "stdin"); 749 | JS_AddModuleExport(ctx, m, "stdout"); 750 | JS_AddModuleExport(ctx, m, "stderr"); 751 | return m; 752 | } -------------------------------------------------------------------------------- /quickjs-ffi.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 shajunxing 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | import * as ffi from './quickjs-ffi.so' 26 | 27 | const dlCache = {}; // {'filename': {handle: ..., symbols: {'...': ...}}} 28 | 29 | function dlOpen(filename) { 30 | if (dlCache.hasOwnProperty(filename)) { 31 | return dlCache[filename]; 32 | } 33 | // console.log(filename); 34 | let h = ffi.dlopen(filename, ffi.RTLD_NOW); 35 | if (h === 0) { 36 | throw new TypeError(ffi.dlerror()); 37 | } 38 | let file = { handle: h, symbols: {} }; 39 | dlCache[filename] = file; 40 | return file; 41 | } 42 | 43 | function dlSym(filename, symbol) { 44 | let file = dlOpen(filename); 45 | if (file.symbols.hasOwnProperty(symbol)) { 46 | return file.symbols[symbol]; 47 | } 48 | // console.log(filename, symbol); 49 | let pointer = ffi.dlsym(file.handle, symbol); 50 | if (pointer === 0) { 51 | throw new TypeError(ffi.dlerror()); 52 | } 53 | file.symbols[symbol] = pointer; 54 | return pointer; 55 | } 56 | 57 | function dlClose(filename) { 58 | if (dlCache.hasOwnProperty(filename)) { 59 | ffi.dlclose(dlCache[filename].handle); 60 | delete dlCache[filename]; 61 | } 62 | } 63 | 64 | const dummy = () => { } 65 | 66 | const rint = (issigned, bytewidth) => 67 | ptr => ffi.memreadint(ptr, bytewidth, 0, issigned, bytewidth); 68 | 69 | const wint = bytewidth => 70 | (ptr, val) => ffi.memwriteint(ptr, bytewidth, 0, bytewidth, val); 71 | 72 | const rfloat = isdouble => 73 | ptr => ffi.memreadfloat(ptr, isdouble ? 8 : 4, 0, isdouble); 74 | 75 | const wfloat = isdouble => 76 | (ptr, val) => ffi.memwritefloat(ptr, isdouble ? 8 : 4, 0, isdouble, val); 77 | 78 | // const primitiveTypes = { // [ffi_type address, byte width, read function, write function] 79 | // 'void': [ffi.ffi_type_void, 0, dummy, dummy], 80 | // 'uint8_t': [ffi.ffi_type_uint8, 1, rint(false, 1), wint(1)], 81 | // 'int8_t': [ffi.ffi_type_sint8, 1, rint(true, 1), wint(1)], 82 | // 'uint16_t': [ffi.ffi_type_uint16, 2, rint(false, 2), wint(2)], 83 | // 'int16_t': [ffi.ffi_type_sint16, 2, rint(true, 2), wint(2)], 84 | // 'uint32_t': [ffi.ffi_type_uint32, 4, rint(false, 4), wint(4)], 85 | // 'int32_t': [ffi.ffi_type_sint32, 4, rint(true, 4), wint(4)], 86 | // 'uint64_t': [ffi.ffi_type_uint64, 8, rint(false, 8), wint(8)], 87 | // 'int64_t': [ffi.ffi_type_sint64, 8, rint(true, 8), wint(8)], 88 | // 'float': [ffi.ffi_type_float, 4, rfloat(false), wfloat(false)], 89 | // 'double': [ffi.ffi_type_double, 8, rfloat(true), wfloat(true)], 90 | // 'pointer': [ffi.ffi_type_pointer, ffi.sizeof_uintptr_t, rint(false, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)], 91 | // 'longdouble': [ffi.ffi_type_longdouble, 8, rfloat(true), wfloat(true)], 92 | // 'complex_float': [ffi.ffi_type_complex_float, undefined, undefined, undefined], 93 | // 'complex_double': [ffi.ffi_type_complex_double, undefined, undefined, undefined], 94 | // 'complex_longdouble': [ffi.ffi_type_complex_longdouble, undefined, undefined, undefined], 95 | // 'uchar': [ffi.ffi_type_uchar, 1, rint(false, 1), wint(1)], 96 | // 'char': [ffi.ffi_type_schar, 1, rint(true, 1), wint(1)], 97 | // 'ushort': [ffi.ffi_type_ushort, 2, rint(false, 2), wint(2)], 98 | // 'short': [ffi.ffi_type_sshort, 2, rint(true, 2), wint(2)], 99 | // 'uint': [ffi.ffi_type_uint, ffi.sizeof_int, rint(false, ffi.sizeof_int), wint(ffi.sizeof_int)], 100 | // 'int': [ffi.ffi_type_sint, ffi.sizeof_int, rint(true, ffi.sizeof_int), wint(ffi.sizeof_int)], 101 | // 'ulong': [ffi.ffi_type_ulong, 8, rint(false, 8), wint(8)], 102 | // 'long': [ffi.ffi_type_slong, 8, rint(true, 8), wint(8)], 103 | // 'uintptr_t': [ffi.ffi_type_uintptr_t, ffi.sizeof_uintptr_t, rint(false, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)], 104 | // 'intptr_t': [ffi.ffi_type_intptr_t, ffi.sizeof_uintptr_t, rint(true, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)], 105 | // 'size_t': [ffi.ffi_type_size_t, ffi.sizeof_size_t, rint(false, ffi.sizeof_size_t), wint(ffi.sizeof_size_t)], 106 | // 'string': [ffi.ffi_type_pointer, ffi.sizeof_uintptr_t, rint(false, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)], 107 | // } 108 | 109 | const primitiveTypes = { // [ffi_type address, byte width, read function, write function] 110 | void: [ffi.ffi_type_void, 0, dummy, dummy], 111 | uint8: [ffi.ffi_type_uint8, 1, rint(false, 1), wint(1)], 112 | sint8: [ffi.ffi_type_sint8, 1, rint(true, 1), wint(1)], 113 | uint16: [ffi.ffi_type_uint16, 2, rint(false, 2), wint(2)], 114 | sint16: [ffi.ffi_type_sint16, 2, rint(true, 2), wint(2)], 115 | uint32: [ffi.ffi_type_uint32, 4, rint(false, 4), wint(4)], 116 | sint32: [ffi.ffi_type_sint32, 4, rint(true, 4), wint(4)], 117 | uint64: [ffi.ffi_type_uint64, 8, rint(false, 8), wint(8)], 118 | sint64: [ffi.ffi_type_sint64, 8, rint(true, 8), wint(8)], 119 | float: [ffi.ffi_type_float, 4, rfloat(false), wfloat(false)], 120 | double: [ffi.ffi_type_double, 8, rfloat(true), wfloat(true)], 121 | uchar: [ffi.ffi_type_uchar, 1, rint(false, 1), wint(1)], 122 | schar: [ffi.ffi_type_schar, 1, rint(true, 1), wint(1)], 123 | ushort: [ffi.ffi_type_ushort, 2, rint(false, 2), wint(2)], 124 | sshort: [ffi.ffi_type_sshort, 2, rint(true, 2), wint(2)], 125 | uint: [ffi.ffi_type_uint, ffi.sizeof_int, rint(false, ffi.sizeof_int), wint(ffi.sizeof_int)], 126 | sint: [ffi.ffi_type_sint, ffi.sizeof_int, rint(true, ffi.sizeof_int), wint(ffi.sizeof_int)], 127 | ulong: [ffi.ffi_type_ulong, 8, rint(false, 8), wint(8)], 128 | slong: [ffi.ffi_type_slong, 8, rint(true, 8), wint(8)], 129 | longdouble: [ffi.ffi_type_longdouble, 8, rfloat(true), wfloat(true)], 130 | pointer: [ffi.ffi_type_pointer, ffi.sizeof_uintptr_t, rint(false, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)], 131 | complex_float: [ffi.ffi_type_complex_float, undefined, undefined, undefined], 132 | complex_double: [ffi.ffi_type_complex_double, undefined, undefined, undefined], 133 | complex_longdouble: [ffi.ffi_type_complex_longdouble, undefined, undefined, undefined], 134 | }; 135 | primitiveTypes.uint8_t = primitiveTypes.uint8; 136 | primitiveTypes.int8_t = primitiveTypes.sint8; 137 | primitiveTypes.uint16_t = primitiveTypes.uint16; 138 | primitiveTypes.int16_t = primitiveTypes.sint16; 139 | primitiveTypes.uint32_t = primitiveTypes.uint32; 140 | primitiveTypes.int32_t = primitiveTypes.sint32; 141 | primitiveTypes.char = primitiveTypes.schar; 142 | primitiveTypes.short = primitiveTypes.sshort; 143 | primitiveTypes.int = primitiveTypes.sint; 144 | primitiveTypes.long = primitiveTypes.slong; 145 | primitiveTypes.string = primitiveTypes.pointer; 146 | primitiveTypes.uintptr_t = [ffi.ffi_type_uintptr_t, ffi.sizeof_uintptr_t, rint(false, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)]; 147 | primitiveTypes.intptr_t = [ffi.ffi_type_intptr_t, ffi.sizeof_uintptr_t, rint(true, ffi.sizeof_uintptr_t), wint(ffi.sizeof_uintptr_t)]; 148 | primitiveTypes.size_t = [ffi.ffi_type_size_t, ffi.sizeof_size_t, rint(false, ffi.sizeof_size_t), wint(ffi.sizeof_size_t)]; 149 | 150 | class MemoryAllocator { 151 | pointers = [] 152 | alloc = size => { 153 | let ptr = ffi.malloc(size); 154 | this.pointers.push(ptr); 155 | return ptr; 156 | } 157 | free = () => { 158 | while (this.pointers.length > 0) { 159 | ffi.free(this.pointers.pop()); 160 | } 161 | } 162 | } 163 | 164 | function allocUintptrArray(mem, ...vals) { 165 | let buflen = ffi.sizeof_uintptr_t * vals.length; 166 | let buf = mem.alloc(buflen); 167 | for (let i = 0; i < vals.length; i++) { 168 | ffi.memwriteint(buf, buflen, ffi.sizeof_uintptr_t * i, ffi.sizeof_uintptr_t, vals[i]); 169 | } 170 | return buf; 171 | } 172 | 173 | export function readUintptrArray(buf, i) { 174 | return ffi.memreadint(buf + ffi.sizeof_uintptr_t * i, ffi.sizeof_uintptr_t, 0, true, ffi.sizeof_uintptr_t); 175 | } 176 | 177 | export function readUintptr(buf) { 178 | return ffi.memreadint(buf, ffi.sizeof_uintptr_t, 0, true, ffi.sizeof_uintptr_t); 179 | } 180 | 181 | function allocStructType(mem, ...elems) { 182 | let typ = mem.alloc(ffi.sizeof_ffi_type); 183 | ffi.memset(typ, 0, ffi.sizeof_ffi_type); 184 | ffi.memwriteint(typ, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_type, 2, ffi.FFI_TYPE_STRUCT); 185 | ffi.memwriteint(typ, ffi.sizeof_ffi_type, ffi.offsetof_ffi_type_elements, ffi.sizeof_uintptr_t, 186 | allocUintptrArray(mem, ...elems, ffi.NULL)); 187 | return typ; 188 | } 189 | 190 | function getStructOffsets(struct_typ, elem_count) { 191 | let ptr = ffi.malloc(ffi.sizeof_size_t * elem_count); 192 | let status = ffi.ffi_get_struct_offsets(ffi.FFI_DEFAULT_ABI, struct_typ, ptr); 193 | if (status != ffi.FFI_OK) { 194 | ffi.free(ptr); 195 | throw new TypeError('get_struct_offsets failed with return code ' + status); 196 | } 197 | let offsets = [] 198 | for (let i = 0; i < elem_count; i++) { 199 | offsets.push(ffi.memreadint(ptr, ffi.sizeof_size_t * elem_count, ffi.sizeof_size_t * i, false, ffi.sizeof_size_t)) 200 | } 201 | ffi.free(ptr); 202 | return offsets; 203 | } 204 | 205 | function parseType(mem, repr) { 206 | let elementsRepresentations = [] 207 | class Node { 208 | ffiType = null; 209 | nBytes = null; 210 | absOffset = 0; 211 | children = null; 212 | childrenRelOffsets = null; 213 | } 214 | function buildTree(mem, repr) { 215 | if (typeof repr === 'string') { 216 | if (!primitiveTypes.hasOwnProperty(repr)) { 217 | throw new TypeError('primitive type \"' + repr + '\" not supported'); 218 | } 219 | elementsRepresentations.push(repr); 220 | let node = new Node(); 221 | node.ffiType = primitiveTypes[repr][0]; 222 | node.nBytes = primitiveTypes[repr][1]; 223 | return node; 224 | } else if (Array.isArray(repr)) { 225 | let node = new Node(); 226 | node.children = []; 227 | for (let pr of repr) { 228 | node.children.push(buildTree(mem, pr)); 229 | } 230 | node.ffiType = allocStructType(mem, ...node.children.map(child => child.ffiType)); 231 | node.childrenRelOffsets = getStructOffsets(node.ffiType, node.children.length); 232 | return node; 233 | } else { 234 | throw new TypeError('type representation neither string nor array'); 235 | } 236 | } 237 | let root = buildTree(mem, repr); 238 | let elementsOffsets = []; 239 | let lastPrimitiveElementOffset = 0; 240 | let lastPrimitiveElementByteWidth = 0; 241 | function walkTree(node) { 242 | if (node.children !== null) { 243 | for (let i = 0; i < node.children.length; i++) { 244 | node.children[i].absOffset = node.childrenRelOffsets[i] + node.absOffset; 245 | walkTree(node.children[i]); 246 | } 247 | } else { 248 | elementsOffsets.push(node.absOffset); 249 | lastPrimitiveElementOffset = node.absOffset; 250 | lastPrimitiveElementByteWidth = node.nBytes; 251 | } 252 | } 253 | walkTree(root); 254 | let ret = { 255 | typ: root.ffiType, 256 | nbytes: lastPrimitiveElementOffset + lastPrimitiveElementByteWidth, 257 | ereprs: elementsRepresentations, 258 | eoffsets: elementsOffsets, 259 | }; 260 | return ret; 261 | } 262 | 263 | const cifCache = {}; 264 | 265 | function getCifCacheIndex(nfixedargs, rrepr, ...areprs) { 266 | return JSON.stringify([nfixedargs, rrepr, [areprs]]); 267 | } 268 | 269 | function prepCif(nfixedargs, rrepr, ...areprs) { 270 | if (typeof nfixedargs === 'number') { 271 | if (nfixedargs > areprs.length) { 272 | throw new TypeError('nfixedargs must <= areprs.length'); 273 | } else if (nfixedargs <= 0) { 274 | throw new TypeError('nfixedargs must > 0'); 275 | } 276 | } else if (nfixedargs !== null) { 277 | throw new TypeError('nfixedargs must be null or number'); 278 | } 279 | let index = getCifCacheIndex(nfixedargs, rrepr, ...areprs); 280 | if (cifCache.hasOwnProperty(index)) { 281 | return cifCache[index]; 282 | } 283 | let mem = new MemoryAllocator(); 284 | let nargs = areprs.length; 285 | let aparsed = areprs.map(repr => parseType(mem, repr)); 286 | let rparsed = parseType(mem, rrepr); 287 | let atypes = allocUintptrArray(mem, ...aparsed.map(parsed => parsed.typ)); 288 | let rtype = rparsed.typ; 289 | let cif = mem.alloc(ffi.sizeof_ffi_cif); 290 | let status = nfixedargs === null ? 291 | ffi.ffi_prep_cif(cif, ffi.FFI_DEFAULT_ABI, nargs, rtype, atypes) : 292 | ffi.ffi_prep_cif_var(cif, ffi.FFI_DEFAULT_ABI, nfixedargs, nargs, rtype, atypes); 293 | if (status != ffi.FFI_OK) { 294 | mem.free(); 295 | throw new TypeError('ffi_prep_cif failed with return code ' + status); 296 | } 297 | let cache = { 298 | index: index, 299 | mem: mem, 300 | cif: cif, 301 | rnbytes: rparsed.nbytes, 302 | anbytes: aparsed.map(p => p.nbytes), 303 | rereprs: rparsed.ereprs, 304 | aereprs: aparsed.map(p => p.ereprs), 305 | reoffsets: rparsed.eoffsets, 306 | aeoffsets: aparsed.map(p => p.eoffsets), 307 | }; 308 | cifCache[index] = cache; 309 | return cache; 310 | } 311 | 312 | export function freeCif(index) { 313 | if (cifCache.hasOwnProperty(index)) { 314 | cifCache[index].mem.free(); 315 | delete cifCache[index]; 316 | } 317 | } 318 | 319 | class CStringAllocator { 320 | pointers = [] 321 | to = s => { 322 | let cstr = ffi.tocstring(s); 323 | this.pointers.push(cstr); 324 | return cstr; 325 | } 326 | free = () => { 327 | while (this.pointers.length > 0) { 328 | ffi.freecstring(this.pointers.pop()); 329 | } 330 | } 331 | } 332 | 333 | export class CFunction { 334 | mem = new MemoryAllocator(); 335 | cstr = new CStringAllocator(); 336 | cif; 337 | cifcacheindex; 338 | cfuncptr; 339 | rvalue; 340 | avalues; 341 | avaluesptr; 342 | rereprs; 343 | aereprs; 344 | reoffsets; 345 | aeoffsets; 346 | constructor(filename, symbol, ...args) { 347 | this.cfuncptr = dlSym(filename, symbol); 348 | let c = prepCif(...args); 349 | this.cif = c.cif; 350 | this.cifcacheindex = c.index; 351 | this.rvalue = this.mem.alloc(c.rnbytes); 352 | this.avalues = c.anbytes.map(n => this.mem.alloc(n)); 353 | this.avaluesptr = allocUintptrArray(this.mem, ...this.avalues); 354 | this.rereprs = c.rereprs; 355 | this.aereprs = c.aereprs; 356 | this.reoffsets = c.reoffsets; 357 | this.aeoffsets = c.aeoffsets; 358 | } 359 | invoke = (...args) => { 360 | let writeArg = (a, e, val) => { 361 | // console.log(a, e, val) 362 | let repr = this.aereprs[a][e]; 363 | let f = primitiveTypes[repr][3]; 364 | let p = this.avalues[a] + this.aeoffsets[a][e]; 365 | repr == 'string' ? f(p, this.cstr.to(val)) : f(p, val); 366 | } 367 | for (let a = 0; a < args.length; a++) { 368 | let arg = args[a]; 369 | if (Array.isArray(arg)) { 370 | for (let e = 0; e < arg.length; e++) { 371 | writeArg(a, e, arg[e]); 372 | } 373 | } else { 374 | writeArg(a, 0, arg); 375 | } 376 | } 377 | ffi.ffi_call(this.cif, this.cfuncptr, this.rvalue, this.avaluesptr); 378 | this.cstr.free(); 379 | let readRet = e => { 380 | let repr = this.rereprs[e]; 381 | let f = primitiveTypes[repr][2]; 382 | let p = this.rvalue + this.reoffsets[e]; 383 | return repr == 'string' ? ffi.newstring(f(p)) : f(p); 384 | } 385 | if (this.rereprs.length == 1) { 386 | return readRet(0); 387 | } else { 388 | let ret = []; 389 | for (let e = 0; e < this.rereprs.length; e++) { 390 | ret.push(readRet(e)); 391 | } 392 | return ret; 393 | } 394 | } 395 | free = () => { 396 | this.mem.free(); 397 | this.cstr.free(); 398 | } 399 | } 400 | 401 | export class CCallback { 402 | mem = new MemoryAllocator(); 403 | cstr = new CStringAllocator(); 404 | cif; 405 | cifcacheindex; 406 | cfuncptr; 407 | rereprs; 408 | aereprs; 409 | reoffsets; 410 | aeoffsets; 411 | closure; 412 | jsfunc; 413 | userdata; 414 | constructor(jsfunc, ...args) { 415 | this.jsfunc = jsfunc; 416 | let pp = this.mem.alloc(ffi.sizeof_uintptr_t); 417 | this.closure = ffi.ffi_closure_alloc(ffi.sizeof_ffi_closure, pp); 418 | this.cfuncptr = ffi.memreadint(pp, ffi.sizeof_uintptr_t, 0, true, ffi.sizeof_uintptr_t); 419 | let c = prepCif(...args); 420 | this.cif = c.cif; 421 | this.cifcacheindex = c.index; 422 | this.rereprs = c.rereprs; 423 | this.aereprs = c.aereprs; 424 | this.reoffsets = c.reoffsets; 425 | this.aeoffsets = c.aeoffsets; 426 | this.userdata = this.mem.alloc(ffi.sizeof_ffi_closure_js_func_data); 427 | ffi.fill_ffi_closure_js_func_data(this.userdata, this.adapter); 428 | let status = ffi.ffi_prep_closure_loc( 429 | this.closure, this.cif, ffi.ffi_closure_js_func_adapter, this.userdata, this.cfuncptr); 430 | if (status != ffi.FFI_OK) { 431 | this.mem.free(); 432 | this.cstr.free(); 433 | throw new TypeError('ffi_prep_closure_loc failed with return code ' + status); 434 | } 435 | } 436 | adapter = (rvalueptr, avaluesptr) => { 437 | // console.log('adapter'); 438 | // console.log(rvalueptr, avaluesptr); 439 | let args = []; 440 | for (let a = 0; a < this.aereprs.length; a++) { 441 | let ereprs = this.aereprs[a]; 442 | if (ereprs.length > 1) { 443 | let arg = []; 444 | for (let e = 0; e < ereprs.length; e++) { 445 | let repr = ereprs[e]; 446 | let f = primitiveTypes[repr][2]; 447 | let p = readUintptrArray(avaluesptr, a) + this.aeoffsets[a][e]; 448 | arg[e] = repr == 'string' ? ffi.newstring(f(p)) : f(p); 449 | } 450 | args[a] = arg; 451 | } else { 452 | let repr = ereprs[0]; 453 | let f = primitiveTypes[repr][2]; 454 | let p = readUintptrArray(avaluesptr, a); 455 | // console.log(repr, f, p, f(p)); 456 | args[a] = repr == 'string' ? ffi.newstring(f(p)) : f(p); 457 | } 458 | // console.log(a, args[a]) 459 | } 460 | let ret = this.jsfunc(...args); 461 | // console.log(ret) 462 | this.cstr.free(); // free previous call 463 | // console.log(this.rereprs, typeof this.rereprs) 464 | if (this.rereprs.length > 1) { 465 | // console.log('if') 466 | for (let e = 0; e < this.rereprs.length; e++) { 467 | let repr = this.rereprs[e]; 468 | let f = primitiveTypes[repr][3]; 469 | let p = rvalueptr + this.reoffsets[e]; 470 | // console.log(repr, f, p); 471 | repr == 'string' ? f(p, this.cstr.to(ret[e])) : f(p, ret[e]); 472 | } 473 | } else { 474 | // console.log('else') 475 | let repr = this.rereprs[0]; 476 | let f = primitiveTypes[repr][3]; 477 | let p = rvalueptr; 478 | // console.log(repr, f, p); 479 | repr == 'string' ? f(p, this.cstr.to(ret)) : f(p, ret); 480 | } 481 | } 482 | free = () => { 483 | ffi.ffi_closure_free(this.closure); 484 | this.mem.free(); 485 | this.cstr.free(); 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /test-lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void test1() { 5 | printf("Hello\n"); 6 | } 7 | 8 | double test2(float a, double b, const char *c) { 9 | printf("%f %f %s\n", a, b, c); 10 | return (double)a + b; 11 | } 12 | 13 | typedef struct { 14 | int i; 15 | float f; 16 | } s1; 17 | 18 | typedef struct { 19 | long l; 20 | double d; 21 | s1 s; 22 | } s2; 23 | 24 | void test3(s2 s) { 25 | printf("%ld %f %d %f\n", s.l, s.d, s.s.i, s.s.f); 26 | } 27 | 28 | char *test4(s2 (*fn)(float, double, const char *)) { 29 | puts("test4 begins"); 30 | s2 ret = fn(3.141592654, 2.718281829, "Hi there"); 31 | printf("callback returns %ld %f %d %f\n", ret.l, ret.d, ret.s.i, ret.s.f); 32 | puts("test4 ends"); 33 | return "greetings"; 34 | } 35 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import { CFunction, freeCif } from './quickjs-ffi.js' 2 | import { LIBC_SO } from './quickjs-ffi.so' 3 | let printf = new CFunction(LIBC_SO, 'printf', 1, 'int', 'string', 'double', 'double', 'int'); 4 | printf.invoke('%g %g %d\n', 3.141592654, 2.718281829, 299792458); 5 | freeCif(printf.cifcacheindex); 6 | printf.free(); 7 | printf = new CFunction(LIBC_SO, 'printf', 1, 'int', 'string', 'string', 'string'); 8 | printf.invoke('%s %s\n', 'hello', 'world'); 9 | freeCif(printf.cifcacheindex); 10 | printf.free(); 11 | --------------------------------------------------------------------------------