From 5f9fdac9f41cab69e5bbb2693e463c507c913788 Mon Sep 17 00:00:00 2001 From: ekibun Date: Sun, 20 Sep 2020 15:57:08 +0800 Subject: [PATCH] ffi wrapper --- cxx/ffi.cpp | 263 ++++++++++++++ lib/ffi.dart | 594 ++++++++++++++++++++++++++++++++ lib/wrapper.dart | 177 ++++++++++ test/flutter_qjs_test.dart | 94 +++-- test/lib/CMakeLists.txt | 2 +- test/lib/ffi.cpp | 39 --- test/lib/{make.cmd => make.bat} | 0 7 files changed, 1104 insertions(+), 65 deletions(-) create mode 100644 cxx/ffi.cpp create mode 100644 lib/ffi.dart create mode 100644 lib/wrapper.dart delete mode 100644 test/lib/ffi.cpp rename test/lib/{make.cmd => make.bat} (100%) diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp new file mode 100644 index 0000000..034d200 --- /dev/null +++ b/cxx/ffi.cpp @@ -0,0 +1,263 @@ +/* + * @Description: + * @Author: ekibun + * @Date: 2020-09-06 18:32:45 + * @LastEditors: ekibun + * @LastEditTime: 2020-09-20 15:50:41 + */ +#include "quickjs/quickjs.h" +#include + +#ifdef _MSC_VER +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT __attribute__((visibility("default"))) +#endif + +extern "C" +{ + typedef JSValue *JSChannel(JSContext *ctx, const char *method, JSValueConst *argv); + + DLLEXPORT JSValue *jsEXCEPTION() + { + return new JSValue{JS_EXCEPTION}; + } + + DLLEXPORT JSValue *jsUNDEFINED() + { + return new JSValue{JS_UNDEFINED}; + } + + DLLEXPORT JSValue *jsNULL() + { + return new JSValue{JS_NULL}; + } + + JSModuleDef *js_module_loader( + JSContext *ctx, + const char *module_name, void *opaque) + { + JSRuntime *rt = JS_GetRuntime(ctx); + JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); + JSValue val = *channel(ctx, "__load_module__", new JSValue{JS_NewString(ctx, module_name)}); + const char *str = JS_ToCString(ctx, val); + JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + JS_FreeCString(ctx, str); + JS_FreeValue(ctx, val); + if (JS_IsException(func_val)) + return NULL; + /* the module is already referenced, so we must free it */ + JSModuleDef *m = (JSModuleDef *)JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + return m; + } + + JSValue js_channel(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) + { + JSRuntime *rt = JS_GetRuntime(ctx); + JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt); + const char *str = JS_ToCString(ctx, argv[0]); + JS_DupValue(ctx, *(argv + 1)); + JSValue ret = *channel(ctx, str, argv + 1); + JS_FreeValue(ctx, *(argv + 1)); + JS_FreeCString(ctx, str); + return ret; + } + + DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel) + { + JSRuntime *rt = JS_NewRuntime(); + JS_SetRuntimeOpaque(rt, channel); + JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr); + return rt; + } + + DLLEXPORT void jsFreeRuntime(JSRuntime *rt) + { + JS_SetRuntimeOpaque(rt, nullptr); + JS_FreeRuntime(rt); + } + + DLLEXPORT JSContext *jsNewContext(JSRuntime *rt) + { + JSContext *ctx = JS_NewContext(rt); + JSAtom atom = JS_NewAtom(ctx, "channel"); + JSValue globalObject = JS_GetGlobalObject(ctx); + JS_SetProperty(ctx, globalObject, atom, JS_NewCFunction(ctx, js_channel, "channel", 2)); + JS_FreeValue(ctx, globalObject); + JS_FreeAtom(ctx, atom); + return ctx; + } + + DLLEXPORT void jsFreeContext(JSContext *ctx) + { + JS_FreeContext(ctx); + } + + DLLEXPORT JSRuntime *jsGetRuntime(JSContext *ctx) + { + return JS_GetRuntime(ctx); + } + + DLLEXPORT JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int eval_flags) + { + return new JSValue{JS_Eval(ctx, input, input_len, filename, eval_flags)}; + } + + DLLEXPORT int32_t jsValueGetTag(JSValue *val) + { + return JS_VALUE_GET_TAG(*val); + } + + DLLEXPORT void *jsValueGetPtr(JSValue *val) + { + return JS_VALUE_GET_PTR(*val); + } + + DLLEXPORT int32_t jsTagIsFloat64(int32_t tag) + { + return JS_TAG_IS_FLOAT64(tag); + } + + DLLEXPORT JSValue *jsNewBool(JSContext *ctx, int val) + { + return new JSValue{JS_NewBool(ctx, val)}; + } + + DLLEXPORT JSValue *jsNewInt64(JSContext *ctx, int64_t val) + { + return new JSValue{JS_NewInt64(ctx, val)}; + } + + DLLEXPORT JSValue *jsNewFloat64(JSContext *ctx, double val) + { + return new JSValue{JS_NewFloat64(ctx, val)}; + } + + DLLEXPORT JSValue *jsNewString(JSContext *ctx, const char *str) + { + return new JSValue{JS_NewString(ctx, str)}; + } + + DLLEXPORT JSValue *jsNewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) + { + return new JSValue{JS_NewArrayBufferCopy(ctx, buf, len)}; + } + + DLLEXPORT JSValue *jsNewArray(JSContext *ctx) + { + return new JSValue{JS_NewArray(ctx)}; + } + + DLLEXPORT JSValue *jsNewObject(JSContext *ctx) + { + return new JSValue{JS_NewObject(ctx)}; + } + + DLLEXPORT void jsFreeValue(JSContext *ctx, JSValue *v) + { + JS_FreeValue(ctx, *v); + } + + DLLEXPORT void jsFreeValueRT(JSRuntime *rt, JSValue *v) + { + JS_FreeValueRT(rt, *v); + } + + DLLEXPORT JSValue *jsDupValue(JSContext *ctx, JSValueConst *v) + { + return new JSValue{JS_DupValue(ctx, *v)}; + } + + DLLEXPORT JSValue *jsDupValueRT(JSRuntime *rt, JSValue *v) + { + return new JSValue{JS_DupValueRT(rt, *v)}; + } + + DLLEXPORT int32_t jsToBool(JSContext *ctx, JSValueConst *val) + { + return JS_ToBool(ctx, *val); + } + + DLLEXPORT int64_t jsToInt64(JSContext *ctx, JSValueConst *val) + { + int64_t p; + JS_ToInt64(ctx, &p, *val); + return p; + } + + DLLEXPORT double jsToFloat64(JSContext *ctx, JSValueConst *val) + { + double p; + JS_ToFloat64(ctx, &p, *val); + return p; + } + + DLLEXPORT const char *jsToCString(JSContext *ctx, JSValueConst *val) + { + return JS_ToCString(ctx, *val); + } + + DLLEXPORT void jsFreeCString(JSContext *ctx, const char *ptr) + { + return JS_FreeCString(ctx, ptr); + } + + DLLEXPORT uint8_t *jsGetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst *obj) + { + return JS_GetArrayBuffer(ctx, psize, *obj); + } + + DLLEXPORT int32_t jsIsFunction(JSContext *ctx, JSValueConst *val) + { + return JS_IsFunction(ctx, *val); + } + + DLLEXPORT int32_t jsIsArray(JSContext *ctx, JSValueConst *val) + { + return JS_IsArray(ctx, *val); + } + + DLLEXPORT void deleteJSValue(JSValueConst *val) + { + delete val; + } + + DLLEXPORT JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj, + JSAtom prop) + { + return new JSValue{JS_GetProperty(ctx, *this_obj, prop)}; + } + + DLLEXPORT int jsDefinePropertyValue(JSContext *ctx, JSValueConst *this_obj, + JSAtom prop, JSValue *val, int flags) + { + return JS_DefinePropertyValue(ctx, *this_obj, prop, *val, flags); + } + + DLLEXPORT void jsFreeAtom(JSContext *ctx, JSAtom v) + { + JS_FreeAtom(ctx, v); + } + + DLLEXPORT JSAtom jsValueToAtom(JSContext *ctx, JSValueConst *val) + { + return JS_ValueToAtom(ctx, *val); + } + + DLLEXPORT JSValue *jsAtomToValue(JSContext *ctx, JSAtom val) + { + return new JSValue{JS_AtomToValue(ctx, val)}; + } + + DLLEXPORT int jsGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst *obj, int flags) + { + return JS_GetOwnPropertyNames(ctx, ptab, plen, *obj, flags); + } + + DLLEXPORT JSAtom jsPropertyEnumGetAtom(JSPropertyEnum *ptab, int i) + { + return ptab[i].atom; + } +} \ No newline at end of file diff --git a/lib/ffi.dart b/lib/ffi.dart new file mode 100644 index 0000000..dbdae5f --- /dev/null +++ b/lib/ffi.dart @@ -0,0 +1,594 @@ +/* + * @Description: + * @Author: ekibun + * @Date: 2020-09-19 10:29:04 + * @LastEditors: ekibun + * @LastEditTime: 2020-09-20 15:41:02 + */ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +abstract class JSRef { + void release(); +} + +/// JS_Eval() flags +class JSEvalType { + static const GLOBAL = 0 << 0; + static const MODULE = 1 << 0; +} + +class JSProp { + static const CONFIGURABLE = (1 << 0); + static const WRITABLE = (1 << 1); + static const ENUMERABLE = (1 << 2); + static const C_W_E = (CONFIGURABLE | WRITABLE | ENUMERABLE); +} + +class JSTag { + static const FIRST = -11; /* first negative tag */ + static const BIG_DECIMAL = -11; + static const BIG_INT = -10; + static const BIG_FLOAT = -9; + static const SYMBOL = -8; + static const STRING = -7; + static const MODULE = -3; /* used internally */ + static const FUNCTION_BYTECODE = -2; /* used internally */ + static const OBJECT = -1; + + static const INT = 0; + static const BOOL = 1; + static const NULL = 2; + static const UNDEFINED = 3; + static const UNINITIALIZED = 4; + static const CATCH_OFFSET = 5; + static const EXCEPTION = 6; + static const FLOAT64 = 7; +} + +final DynamicLibrary qjsLib = DynamicLibrary.open("test/lib/build/Debug/ffi_library.dll"); + +/// JSValue *jsEXCEPTION() +final Pointer Function() jsEXCEPTION = + qjsLib.lookup>("jsEXCEPTION").asFunction(); + +/// JSValue *jsUNDEFINED() +final Pointer Function() jsUNDEFINED = + qjsLib.lookup>("jsUNDEFINED").asFunction(); + +/// JSRuntime *jsNewRuntime(JSChannel channel) +final Pointer Function( + Pointer>, +) _jsNewRuntime = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsNewRuntime") + .asFunction(); + +typedef JSChannel = Pointer Function(Pointer ctx, String method, Pointer argv); + +class RuntimeOpaque { + JSChannel channel; + List ref = List(); +} + +final Map runtimeOpaques = Map(); + +Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) { + return runtimeOpaques[jsGetRuntime(ctx)].channel(ctx, Utf8.fromUtf8(method), argv); +} + +Pointer jsNewRuntime( + JSChannel callback, +) { + var rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher)); + runtimeOpaques[rt] = RuntimeOpaque()..channel = callback; + return rt; +} + +/// void jsFreeRuntime(JSRuntime *rt) +final void Function( + Pointer, +) _jsFreeRuntime = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + )>>("jsFreeRuntime") + .asFunction(); + +void jsFreeRuntime( + Pointer rt, +) { + runtimeOpaques[rt]?.ref?.forEach((val) { + val.release(); + }); + runtimeOpaques.remove(rt); + _jsFreeRuntime(rt); +} + +/// JSContext *jsNewContext(JSRuntime *rt) +final Pointer Function( + Pointer rt, +) jsNewContext = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsNewContext") + .asFunction(); + +/// void jsFreeContext(JSContext *ctx) +final void Function( + Pointer, +) jsFreeContext = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + )>>("jsFreeContext") + .asFunction(); + +/// JSRuntime *jsGetRuntime(JSContext *ctx) +final Pointer Function( + Pointer, +) jsGetRuntime = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsGetRuntime") + .asFunction(); + +/// JSValue *jsEval(JSContext *ctx, const char *input, size_t input_len, const char *filename, int eval_flags) +final Pointer Function( + Pointer ctx, + Pointer input, + int inputLen, + Pointer filename, + int evalFlags, +) _jsEval = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Int64, + Pointer, + Int32, + )>>("jsEval") + .asFunction(); + +Pointer jsEval( + Pointer ctx, + String input, + String filename, + int evalFlags, +) { + var utf8input = Utf8.toUtf8(input); + var utf8filename = Utf8.toUtf8(filename); + var val = _jsEval(ctx, utf8input, Utf8.strlen(utf8input), utf8filename, evalFlags); + free(utf8input); + free(utf8filename); + return val; +} + +/// DLLEXPORT int32_t jsValueGetTag(JSValue *val) +final int Function( + Pointer val, +) jsValueGetTag = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + )>>("jsValueGetTag") + .asFunction(); + +/// void *jsValueGetPtr(JSValue *val) +final Pointer Function( + Pointer val, +) jsValueGetPtr = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsValueGetPtr") + .asFunction(); + +/// DLLEXPORT bool jsTagIsFloat64(int32_t tag) +final int Function( + int val, +) jsTagIsFloat64 = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Int32, + )>>("jsTagIsFloat64") + .asFunction(); + +/// JSValue *jsNewBool(JSContext *ctx, int val) +final Pointer Function( + Pointer ctx, + int val, +) jsNewBool = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Int32, + )>>("jsNewBool") + .asFunction(); + +/// JSValue *jsNewInt64(JSContext *ctx, int64_t val) +final Pointer Function( + Pointer ctx, + int val, +) jsNewInt64 = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Int64, + )>>("jsNewInt64") + .asFunction(); + +/// JSValue *jsNewFloat64(JSContext *ctx, double val) +final Pointer Function( + Pointer ctx, + double val, +) jsNewFloat64 = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Double, + )>>("jsNewFloat64") + .asFunction(); + +/// JSValue *jsNewString(JSContext *ctx, const char *str) +final Pointer Function( + Pointer ctx, + Pointer str, +) _jsNewString = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>("jsNewString") + .asFunction(); + +Pointer jsNewString( + Pointer ctx, + String str, +) { + var utf8str = Utf8.toUtf8(str); + return _jsNewString(ctx, utf8str); +} + +/// JSValue *jsNewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) +final Pointer Function( + Pointer ctx, + Pointer buf, + int len, +) jsNewArrayBufferCopy = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Uint64, + )>>("jsNewArrayBufferCopy") + .asFunction(); + +/// JSValue *jsNewArray(JSContext *ctx) +final Pointer Function( + Pointer ctx, +) jsNewArray = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsNewArray") + .asFunction(); + +/// JSValue *jsNewObject(JSContext *ctx) +final Pointer Function( + Pointer ctx, +) jsNewObject = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsNewObject") + .asFunction(); + +/// void jsFreeValue(JSContext *ctx, JSValue *val) +final void Function( + Pointer ctx, + Pointer val, +) jsFreeValue = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + Pointer, + )>>("jsFreeValue") + .asFunction(); + +/// void jsFreeValueRT(JSRuntime *rt, JSValue *v) +final void Function( + Pointer rt, + Pointer val, +) jsFreeValueRT = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + Pointer, + )>>("jsFreeValueRT") + .asFunction(); + +/// JSValue *jsDupValue(JSContext *ctx, JSValueConst *v) +final Pointer Function( + Pointer ctx, + Pointer val, +) jsDupValue = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>("jsDupValue") + .asFunction(); + +/// JSValue *jsDupValueRT(JSRuntime *rt, JSValue *v) +final Pointer Function( + Pointer rt, + Pointer val, +) jsDupValueRT = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>("jsDupValueRT") + .asFunction(); + +/// int32_t jsToBool(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsToBool = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + )>>("jsToBool") + .asFunction(); + +/// int64_t jsToFloat64(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsToInt64 = qjsLib + .lookup< + NativeFunction< + Int64 Function( + Pointer, + Pointer, + )>>("jsToInt64") + .asFunction(); + +/// double jsToFloat64(JSContext *ctx, JSValueConst *val) +final double Function( + Pointer ctx, + Pointer val, +) jsToFloat64 = qjsLib + .lookup< + NativeFunction< + Double Function( + Pointer, + Pointer, + )>>("jsToFloat64") + .asFunction(); + +/// const char *jsToCString(JSContext *ctx, JSValue *val) +final Pointer Function( + Pointer ctx, + Pointer val, +) _jsToCString = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>("jsToCString") + .asFunction(); + +/// void jsFreeCString(JSContext *ctx, const char *ptr) +final void Function( + Pointer ctx, + Pointer val, +) jsFreeCString = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + Pointer, + )>>("jsFreeCString") + .asFunction(); + +String jsToCString( + Pointer ctx, + Pointer val, +) { + var ptr = _jsToCString(ctx, val); + var str = Utf8.fromUtf8(ptr); + jsFreeCString(ctx, ptr); + return str; +} + +/// uint8_t *jsGetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst *obj) +final Pointer Function( + Pointer ctx, + Pointer psize, + Pointer val, +) jsGetArrayBuffer = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Pointer, + )>>("jsGetArrayBuffer") + .asFunction(); + +/// int32_t jsIsFunction(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsIsFunction = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + )>>("jsIsFunction") + .asFunction(); + +/// int32_t jsIsArray(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsIsArray = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + )>>("jsIsArray") + .asFunction(); + +/// void deleteJSValue(JSValueConst *val) +final void Function( + Pointer val, +) deleteJSValue = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + )>>("deleteJSValue") + .asFunction(); + +/// JSValue *jsGetProperty(JSContext *ctx, JSValueConst *this_obj, +/// JSAtom prop) +final Pointer Function( + Pointer ctx, + Pointer thisObj, + int prop, +) jsGetProperty = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Uint32, + )>>("jsGetProperty") + .asFunction(); + +/// int jsDefinePropertyValue(JSContext *ctx, JSValueConst *this_obj, +/// JSAtom prop, JSValue *val, int flags) +final int Function( + Pointer ctx, + Pointer thisObj, + int prop, + Pointer val, + int flag +) jsDefinePropertyValue = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + Uint32, + Pointer, + Int32, + )>>("jsDefinePropertyValue") + .asFunction(); + +/// void jsFreeAtom(JSContext *ctx, JSAtom v) +final Pointer Function( + Pointer ctx, + int v, +) jsFreeAtom = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Uint32, + )>>("jsFreeAtom") + .asFunction(); + +/// JSAtom jsValueToAtom(JSContext *ctx, JSValueConst *val) +final int Function( + Pointer ctx, + Pointer val, +) jsValueToAtom = qjsLib + .lookup< + NativeFunction< + Uint32 Function( + Pointer, + Pointer, + )>>("jsValueToAtom") + .asFunction(); + +/// JSValue *jsAtomToValue(JSContext *ctx, JSAtom val) +final Pointer Function( + Pointer ctx, + int val, +) jsAtomToValue = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Uint32, + )>>("jsAtomToValue") + .asFunction(); + +/// int jsGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, +/// uint32_t *plen, JSValueConst *obj, int flags) +final int Function( + Pointer ctx, + Pointer ptab, + Pointer plen, + Pointer obj, + int flags, +) jsGetOwnPropertyNames = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + Pointer, + Pointer, + Pointer, + Int32, + )>>("jsGetOwnPropertyNames") + .asFunction(); + +/// JSAtom jsPropertyEnumGetAtom(JSPropertyEnum *ptab, int i) +final int Function( + Pointer ptab, + int i, +) jsPropertyEnumGetAtom = qjsLib + .lookup< + NativeFunction< + Uint32 Function( + Pointer, + Int32, + )>>("jsPropertyEnumGetAtom") + .asFunction(); diff --git a/lib/wrapper.dart b/lib/wrapper.dart new file mode 100644 index 0000000..5137d23 --- /dev/null +++ b/lib/wrapper.dart @@ -0,0 +1,177 @@ +/* + * @Description: + * @Author: ekibun + * @Date: 2020-09-19 22:07:47 + * @LastEditors: ekibun + * @LastEditTime: 2020-09-20 15:41:16 + */ +import 'dart:ffi'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; + +import 'ffi.dart'; + +class JSFunction extends JSRef { + Pointer val; + Pointer ctx; + JSFunction(this.ctx, Pointer val) { + Pointer rt = jsGetRuntime(ctx); + this.val = jsDupValue(ctx, val); + runtimeOpaques[rt]?.ref?.add(this); + } + + @override + void release() { + if (val != null) { + jsFreeValue(ctx, val); + deleteJSValue(val); + val = null; + } + } + + @override + noSuchMethod(Invocation invocation) { + return super.noSuchMethod(invocation); + } +} + +Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { + if (cache == null) cache = Map(); + if (val is bool) return jsNewBool(ctx, val ? 1 : 0); + if (val is int) return jsNewInt64(ctx, val); + if (val is double) return jsNewFloat64(ctx, val); + if (val is String) return jsNewString(ctx, val); + if (val is Uint8List) { + var ptr = allocate(count: val.length); + var byteList = ptr.asTypedList(val.length); + byteList.setAll(0, val); + var ret = jsNewArrayBufferCopy(ctx, ptr, val.length); + free(ptr); + return ret; + } + if (cache.containsKey(val)) { + return cache[val]; + } + if (val is JSFunction) { + return jsDupValue(ctx, val.val); + } + if (val is List) { + Pointer ret = jsNewArray(ctx); + cache[val] = ret; + for (int i = 0; i < val.length; ++i) { + var jsAtomVal = jsNewInt64(ctx, i); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + jsDefinePropertyValue( + ctx, + ret, + jsAtom, + dartToJs(ctx, val[i], cache: cache), + JSProp.C_W_E, + ); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + deleteJSValue(jsAtomVal); + } + return ret; + } + if (val is Map) { + Pointer ret = jsNewObject(ctx); + cache[val] = ret; + for (MapEntry entry in val.entries){ + var jsAtomVal = dartToJs(ctx, entry.key, cache: cache); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + jsDefinePropertyValue( + ctx, + ret, + jsAtom, + dartToJs(ctx, entry.value, cache: cache), + JSProp.C_W_E, + ); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + deleteJSValue(jsAtomVal); + } + return ret; + } + return jsUNDEFINED(); +} + +dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { + if (cache == null) cache = Map(); + int tag = jsValueGetTag(val); + if (jsTagIsFloat64(tag) != 0) { + return jsToFloat64(ctx, val); + } + switch (tag) { + case JSTag.BOOL: + return jsToBool(ctx, val) != 0; + case JSTag.INT: + return jsToInt64(ctx, val); + case JSTag.STRING: + return jsToCString(ctx, val); + case JSTag.OBJECT: + Pointer psize = allocate(); + Pointer buf = jsGetArrayBuffer(ctx, psize, val); + int size = psize.value; + free(psize); + if (buf.address != 0) { + return buf.asTypedList(size); + } + int valptr = jsValueGetPtr(val).address; + if (cache.containsKey(valptr)) { + return cache[valptr]; + } + if (jsIsFunction(ctx, val) != 0) { + return JSFunction(ctx, val); + } else if (jsIsArray(ctx, val) != 0) { + var jsAtomVal = jsNewString(ctx, "length"); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + var jslength = jsGetProperty(ctx, val, jsAtom); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + deleteJSValue(jsAtomVal); + + int length = jsToInt64(ctx, jslength); + deleteJSValue(jslength); + List ret = List(); + cache[valptr] = ret; + for (int i = 0; i < length; ++i) { + var jsAtomVal = jsNewInt64(ctx, i); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + var jsProp = jsGetProperty(ctx, val, jsAtom); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + deleteJSValue(jsAtomVal); + ret.add(jsToDart(ctx, jsProp, cache: cache)); + jsFreeValue(ctx, jsProp); + deleteJSValue(jsProp); + } + return ret; + } else { + Pointer ptab = allocate(); + Pointer plen = allocate(); + if (jsGetOwnPropertyNames(ctx, ptab, plen, val, -1) != 0) return null; + int len = plen.value; + free(plen); + Map ret = Map(); + cache[valptr] = ret; + for (int i = 0; i < len; ++i) { + var jsAtom = jsPropertyEnumGetAtom(ptab.value, i); + var jsAtomValue = jsAtomToValue(ctx, jsAtom); + var jsProp = jsGetProperty(ctx, val, jsAtom); + ret[jsToDart(ctx, jsAtomValue, cache: cache)] = jsToDart(ctx, jsProp, cache: cache); + jsFreeValue(ctx, jsAtomValue); + deleteJSValue(jsAtomValue); + jsFreeValue(ctx, jsProp); + deleteJSValue(jsProp); + jsFreeAtom(ctx, jsAtom); + } + free(ptab); + return ret; + } + break; + default: + } + return null; +} diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart index 178ef66..7404255 100644 --- a/test/flutter_qjs_test.dart +++ b/test/flutter_qjs_test.dart @@ -3,31 +3,75 @@ * @Author: ekibun * @Date: 2020-09-06 13:02:46 * @LastEditors: ekibun - * @LastEditTime: 2020-09-13 22:59:06 + * @LastEditTime: 2020-09-20 15:55:50 */ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; +import 'dart:convert'; +import 'dart:io'; -void main() { - final DynamicLibrary qjsLib = DynamicLibrary.open("test/lib/build/Debug/ffi_library.dll"); - print(qjsLib); - // JSRuntime *js_NewRuntime(void); - final Pointer Function() jsNewRuntime = - qjsLib.lookup>("jsNewRuntime").asFunction(); - final rt = jsNewRuntime(); - print(rt); - // JSContext *js_NewContext(JSRuntime *rt); - final Pointer Function(Pointer rt) jsNewContext = - qjsLib.lookup>("jsNewContext").asFunction(); - final ctx = jsNewContext(rt); - print(ctx); - // JSValue *js_Eval(JSContext *ctx, const char *input, const char *filename, int eval_flags) - final Pointer Function(Pointer rt, Pointer input, Pointer filename, int evalFlags) jsEval = - qjsLib.lookup,Pointer, Int32)>>("jsEval").asFunction(); - final jsval = jsEval(ctx, Utf8.toUtf8("`hello \${'world'}!`"), Utf8.toUtf8(""), 0); - // const char *js_ToCString(JSContext *ctx, JSValue *val) - final Pointer Function(Pointer rt, Pointer val) jsToCString = - qjsLib.lookup Function(Pointer,Pointer)>>("jsToCString").asFunction(); - final str = Utf8.fromUtf8(jsToCString(ctx, jsval)); - print(str); +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_qjs/ffi.dart'; +import 'package:flutter_qjs/wrapper.dart'; + +void main() async { + test('make', () async { + final utf8Encoding = Encoding.getByName('utf-8'); + final cmakePath = + "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe"; + final buildDir = "./build"; + var result = Process.runSync( + cmakePath, + ['-S', './', '-B', buildDir], + workingDirectory: 'test/lib', + stdoutEncoding: utf8Encoding, + stderrEncoding: utf8Encoding, + ); + stdout.write(result.stdout); + stderr.write(result.stderr); + expect(result.exitCode, 0); + + result = Process.runSync( + cmakePath, + ['--build', buildDir, '--verbose'], + workingDirectory: 'test/lib', + stdoutEncoding: utf8Encoding, + stderrEncoding: utf8Encoding, + ); + stdout.write(result.stdout); + stderr.write(result.stderr); + expect(result.exitCode, 0); + }); + test('jsToDart', () async { + final rt = jsNewRuntime((ctx, method, argv) { + var argvs = jsToDart(ctx, argv); + print([method, argvs]); + return dartToJs(ctx, [ + argvs, + { + [233, 2]: {} + } + ]); + }); + final ctx = jsNewContext(rt); + final jsval = jsEval( + ctx, + """ + const a = {}; + a.a = a; + channel('channel', [ + 0.1, true, false, 1, "world", + new ArrayBuffer(2), + ()=>'hello', + a + ]); + """, + "", + JSEvalType.GLOBAL, + ); + print(jsToDart(ctx, jsval)); + jsFreeValue(ctx, jsval); + deleteJSValue(jsval); + jsFreeContext(ctx); + jsFreeRuntime(rt); + }); } diff --git a/test/lib/CMakeLists.txt b/test/lib/CMakeLists.txt index 4e2f7b7..5bc5938 100644 --- a/test/lib/CMakeLists.txt +++ b/test/lib/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.7 FATAL_ERROR) project(ffi_library LANGUAGES CXX) -add_library(ffi_library SHARED ffi.cpp) +add_library(ffi_library SHARED ${CMAKE_CURRENT_SOURCE_DIR}/../../cxx/ffi.cpp) # quickjs set(QUICK_JS_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../cxx/quickjs) diff --git a/test/lib/ffi.cpp b/test/lib/ffi.cpp deleted file mode 100644 index 74e8c6b..0000000 --- a/test/lib/ffi.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * @Description: - * @Author: ekibun - * @Date: 2020-09-06 18:32:45 - * @LastEditors: ekibun - * @LastEditTime: 2020-09-13 17:26:29 - */ -#include "../../cxx/quickjs/quickjs.h" -#include - -#ifdef _MSC_VER -#define DLLEXPORT __declspec(dllexport) -#else -#define DLLEXPORT __attribute__((visibility("default"))) -#endif - -extern "C" -{ - - DLLEXPORT JSRuntime *jsNewRuntime() - { - return JS_NewRuntime(); - } - - DLLEXPORT JSContext *jsNewContext(JSRuntime *rt) - { - return JS_NewContext(rt); - } - - DLLEXPORT JSValue *jsEval(JSContext *ctx, const char *input, const char *filename, int eval_flags) - { - return new JSValue{JS_Eval(ctx, input, strlen(input), filename, eval_flags)}; - } - - DLLEXPORT const char *jsToCString(JSContext *ctx, JSValue *val) - { - return JS_ToCString(ctx, *val); - } -} \ No newline at end of file diff --git a/test/lib/make.cmd b/test/lib/make.bat similarity index 100% rename from test/lib/make.cmd rename to test/lib/make.bat