From 917587167818cc32a3d4bda921517831ac01b3bb Mon Sep 17 00:00:00 2001 From: ekibun Date: Mon, 21 Sep 2020 01:41:52 +0800 Subject: [PATCH] windows wrapper --- cxx/ffi.cpp | 51 ++++- example/ios/Flutter/Generated.xcconfig | 14 ++ .../ios/Flutter/flutter_export_environment.sh | 15 ++ .../ios/Runner/GeneratedPluginRegistrant.h | 17 ++ .../ios/Runner/GeneratedPluginRegistrant.m | 12 ++ example/lib/main.dart | 16 +- example/windows/flutter/.template_version | 1 - example/windows/runner/CMakeLists.txt | 2 +- example/windows/runner/Runner.rc | 51 +++++ example/windows/runner/flutter_window.cpp | 22 ++- example/windows/runner/flutter_window.h | 6 +- example/windows/runner/main.cpp | 7 +- example/windows/runner/run_loop.cpp | 14 +- example/windows/runner/run_loop.h | 14 +- example/windows/runner/utils.h | 6 +- example/windows/runner/win32_window.cpp | 17 +- example/windows/runner/win32_window.h | 12 +- .../windows/runner/window_configuration.cpp | 7 - example/windows/runner/window_configuration.h | 18 -- lib/ffi.dart | 153 ++++++++++++--- lib/flutter_qjs.dart | 183 ++++++++---------- lib/wrapper.dart | 173 +++++++++++++++-- test/{lib => }/CMakeLists.txt | 8 +- test/flutter_qjs_test.dart | 58 +++--- test/lib/make.bat | 5 - windows/CMakeLists.txt | 5 +- windows/dart_js_wrapper.hpp | 154 --------------- windows/flutter_qjs_plugin.cpp | 159 +-------------- 28 files changed, 618 insertions(+), 582 deletions(-) create mode 100644 example/ios/Flutter/Generated.xcconfig create mode 100644 example/ios/Flutter/flutter_export_environment.sh create mode 100644 example/ios/Runner/GeneratedPluginRegistrant.h create mode 100644 example/ios/Runner/GeneratedPluginRegistrant.m delete mode 100644 example/windows/flutter/.template_version delete mode 100644 example/windows/runner/window_configuration.cpp delete mode 100644 example/windows/runner/window_configuration.h rename test/{lib => }/CMakeLists.txt (70%) delete mode 100644 test/lib/make.bat delete mode 100644 windows/dart_js_wrapper.hpp diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp index 034d200..45453f5 100644 --- a/cxx/ffi.cpp +++ b/cxx/ffi.cpp @@ -3,10 +3,11 @@ * @Author: ekibun * @Date: 2020-09-06 18:32:45 * @LastEditors: ekibun - * @LastEditTime: 2020-09-20 15:50:41 + * @LastEditTime: 2020-09-21 01:41:39 */ #include "quickjs/quickjs.h" #include +#include #ifdef _MSC_VER #define DLLEXPORT __declspec(dllexport) @@ -16,7 +17,7 @@ extern "C" { - typedef JSValue *JSChannel(JSContext *ctx, const char *method, JSValueConst *argv); + typedef void *JSChannel(JSContext *ctx, const char *method, void *argv); DLLEXPORT JSValue *jsEXCEPTION() { @@ -39,11 +40,13 @@ extern "C" { 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); + const char *str = (char *)channel(ctx, (char *)0, (void *)module_name); + if (str == 0) + { + JS_ThrowReferenceError(ctx, "Module Not Found"); + return NULL; + } 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 */ @@ -58,7 +61,7 @@ extern "C" 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); + JSValue ret = *(JSValue *)channel(ctx, str, argv + 1); JS_FreeValue(ctx, *(argv + 1)); JS_FreeCString(ctx, str); return ret; @@ -230,7 +233,7 @@ extern "C" } DLLEXPORT int jsDefinePropertyValue(JSContext *ctx, JSValueConst *this_obj, - JSAtom prop, JSValue *val, int flags) + JSAtom prop, JSValue *val, int flags) { return JS_DefinePropertyValue(ctx, *this_obj, prop, *val, flags); } @@ -260,4 +263,36 @@ extern "C" { return ptab[i].atom; } + + DLLEXPORT uint32_t sizeOfJSValue() + { + return sizeof JSValue; + } + + DLLEXPORT void setJSValueList(JSValue *list, uint32_t i, JSValue *val) + { + list[i] = *val; + } + + DLLEXPORT JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, + int argc, JSValueConst *argv) + { + return new JSValue{JS_Call(ctx, *func_obj, *this_obj, argc, argv)}; + } + + DLLEXPORT int jsIsException(JSValueConst *val) + { + return JS_IsException(*val); + } + + DLLEXPORT JSValue *jsGetException(JSContext *ctx) + { + return new JSValue{JS_GetException(ctx)}; + } + + DLLEXPORT int jsExecutePendingJob(JSRuntime *rt) + { + JSContext *ctx; + return JS_ExecutePendingJob(rt, &ctx); + } } \ No newline at end of file diff --git a/example/ios/Flutter/Generated.xcconfig b/example/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000..387c366 --- /dev/null +++ b/example/ios/Flutter/Generated.xcconfig @@ -0,0 +1,14 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=D:\flutter +FLUTTER_APPLICATION_PATH=D:\project\vscode\neko\plugins\flutter_qjs\example +FLUTTER_TARGET=lib\main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build\ios +OTHER_LDFLAGS=$(inherited) -framework Flutter +FLUTTER_FRAMEWORK_DIR=D:\flutter\bin\cache\artifacts\engine\ios +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=false +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.packages diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 0000000..e553f63 --- /dev/null +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=D:\flutter" +export "FLUTTER_APPLICATION_PATH=D:\project\vscode\neko\plugins\flutter_qjs\example" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build\ios" +export "OTHER_LDFLAGS=$(inherited) -framework Flutter" +export "FLUTTER_FRAMEWORK_DIR=D:\flutter\bin\cache\artifacts\engine\ios" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=false" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.packages" diff --git a/example/ios/Runner/GeneratedPluginRegistrant.h b/example/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 0000000..ed9a5c6 --- /dev/null +++ b/example/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +NS_ASSUME_NONNULL_END +#endif /* GeneratedPluginRegistrant_h */ diff --git a/example/ios/Runner/GeneratedPluginRegistrant.m b/example/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000..60dfa42 --- /dev/null +++ b/example/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { +} + +@end diff --git a/example/lib/main.dart b/example/lib/main.dart index 17c96df..1dda450 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,7 +3,7 @@ * @Author: ekibun * @Date: 2020-08-08 08:16:51 * @LastEditors: ekibun - * @LastEditTime: 2020-09-06 19:44:32 + * @LastEditTime: 2020-09-21 01:20:17 */ import 'package:flutter/material.dart'; import 'dart:typed_data'; @@ -53,7 +53,7 @@ class _TestPageState extends State { _createEngine() async { if (engine != null) return; engine = FlutterQjs(); - await engine.setMethodHandler((String method, List arg) async { + engine.setMethodHandler((String method, List arg) async { switch (method) { case "http": Response response = await Dio().get(arg[0]); @@ -72,14 +72,16 @@ class _TestPageState extends State { Float32List(2) ]); default: - return JsMethodHandlerNotImplement(); + throw Exception("NotImplement"); } }); - await engine.setModuleHandler((String module) async { + engine.setModuleHandler((String module) { if (module == "test") return "export default '${new DateTime.now()}'"; - return await rootBundle.loadString( - "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); + return ""; + // return await rootBundle.loadString( + // "js/" + module.replaceFirst(new RegExp(r".js$"), "") + ".js"); }); + engine.dispatch(); } @override @@ -119,7 +121,7 @@ class _TestPageState extends State { child: Text("close engine"), onPressed: () async { if (engine == null) return; - await engine.destroy(); + await engine.close(); engine = null; }), ], diff --git a/example/windows/flutter/.template_version b/example/windows/flutter/.template_version deleted file mode 100644 index 1e8b314..0000000 --- a/example/windows/flutter/.template_version +++ /dev/null @@ -1 +0,0 @@ -6 diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt index 83e5aca..977e38b 100644 --- a/example/windows/runner/CMakeLists.txt +++ b/example/windows/runner/CMakeLists.txt @@ -7,12 +7,12 @@ add_executable(${BINARY_NAME} WIN32 "run_loop.cpp" "utils.cpp" "win32_window.cpp" - "window_configuration.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc index 5b41a82..7abe37e 100644 --- a/example/windows/runner/Runner.rc +++ b/example/windows/runner/Runner.rc @@ -54,6 +54,57 @@ END // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "soko.ekibun" "\0" + VALUE "FileDescription", "A new Flutter project." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2020 soko.ekibun. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp index 87823d5..c422723 100644 --- a/example/windows/runner/flutter_window.cpp +++ b/example/windows/runner/flutter_window.cpp @@ -15,22 +15,25 @@ bool FlutterWindow::OnCreate() { return false; } - // The size here is arbitrary since SetChildContent will resize it. - flutter_controller_ = - std::make_unique(100, 100, project_); + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } - RegisterPlugins(flutter_controller_.get()); - run_loop_->RegisterFlutterInstance(flutter_controller_.get()); + RegisterPlugins(flutter_controller_->engine()); + run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { - run_loop_->UnregisterFlutterInstance(flutter_controller_.get()); + run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } @@ -50,5 +53,12 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, return *result; } } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h index 7fcd130..b663ddd 100644 --- a/example/windows/runner/flutter_window.h +++ b/example/windows/runner/flutter_window.h @@ -1,5 +1,5 @@ -#ifndef FLUTTER_WINDOW_H_ -#define FLUTTER_WINDOW_H_ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ #include #include @@ -36,4 +36,4 @@ class FlutterWindow : public Win32Window { std::unique_ptr flutter_controller_; }; -#endif // FLUTTER_WINDOW_H_ +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp index 11b48e9..fc17fec 100644 --- a/example/windows/runner/main.cpp +++ b/example/windows/runner/main.cpp @@ -5,7 +5,6 @@ #include "flutter_window.h" #include "run_loop.h" #include "utils.h" -#include "window_configuration.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { @@ -23,9 +22,9 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); - Win32Window::Point origin(kFlutterWindowOriginX, kFlutterWindowOriginY); - Win32Window::Size size(kFlutterWindowWidth, kFlutterWindowHeight); - if (!window.CreateAndShow(kFlutterWindowTitle, origin, size)) { + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/example/windows/runner/run_loop.cpp b/example/windows/runner/run_loop.cpp index f91d6d4..2d6636a 100644 --- a/example/windows/runner/run_loop.cpp +++ b/example/windows/runner/run_loop.cpp @@ -1,9 +1,6 @@ #include "run_loop.h" -#include -// Don't stomp std::min/std::max -#undef max -#undef min +#include #include @@ -47,20 +44,19 @@ void RunLoop::Run() { } void RunLoop::RegisterFlutterInstance( - flutter::FlutterViewController* flutter_instance) { + flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( - flutter::FlutterViewController* flutter_instance) { + flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); - for (auto flutter_controller : flutter_instances_) { - std::chrono::nanoseconds wait_duration = - flutter_controller->ProcessMessages(); + for (auto instance : flutter_instances_) { + std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); diff --git a/example/windows/runner/run_loop.h b/example/windows/runner/run_loop.h index 442a58e..000d362 100644 --- a/example/windows/runner/run_loop.h +++ b/example/windows/runner/run_loop.h @@ -1,7 +1,7 @@ -#ifndef RUN_LOOP_H_ -#define RUN_LOOP_H_ +#ifndef RUNNER_RUN_LOOP_H_ +#define RUNNER_RUN_LOOP_H_ -#include +#include #include #include @@ -22,11 +22,11 @@ class RunLoop { // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance( - flutter::FlutterViewController* flutter_instance); + flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance( - flutter::FlutterViewController* flutter_instance); + flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; @@ -34,7 +34,7 @@ class RunLoop { // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); - std::set flutter_instances_; + std::set flutter_instances_; }; -#endif // RUN_LOOP_H_ +#endif // RUNNER_RUN_LOOP_H_ diff --git a/example/windows/runner/utils.h b/example/windows/runner/utils.h index d247a66..d792603 100644 --- a/example/windows/runner/utils.h +++ b/example/windows/runner/utils.h @@ -1,8 +1,8 @@ -#ifndef CONSOLE_UTILS_H_ -#define CONSOLE_UTILS_H_ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); -#endif // CONSOLE_UTILS_H_ +#endif // RUNNER_UTILS_H_ diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp index 19fb9e0..efc3eb9 100644 --- a/example/windows/runner/win32_window.cpp +++ b/example/windows/runner/win32_window.cpp @@ -174,8 +174,7 @@ Win32Window::MessageHandler(HWND hwnd, return 0; } case WM_SIZE: - RECT rect; - GetClientRect(hwnd, &rect); + RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, @@ -188,11 +187,6 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; - - // Messages that are directly forwarded to embedding. - case WM_FONTCHANGE: - SendMessage(child_content_, WM_FONTCHANGE, NULL, NULL); - return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -218,8 +212,7 @@ Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); - RECT frame; - GetClientRect(window_handle_, &frame); + RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); @@ -227,6 +220,12 @@ void Win32Window::SetChildContent(HWND content) { SetFocus(child_content_); } +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + HWND Win32Window::GetHandle() { return window_handle_; } diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h index ea09829..17ba431 100644 --- a/example/windows/runner/win32_window.h +++ b/example/windows/runner/win32_window.h @@ -1,8 +1,7 @@ -#ifndef WIN32_WINDOW_H_ -#define WIN32_WINDOW_H_ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ -#include -#include +#include #include #include @@ -52,6 +51,9 @@ class Win32Window { // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that @@ -93,4 +95,4 @@ class Win32Window { HWND child_content_ = nullptr; }; -#endif // WIN32_WINDOW_H_ +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/example/windows/runner/window_configuration.cpp b/example/windows/runner/window_configuration.cpp deleted file mode 100644 index c56ede1..0000000 --- a/example/windows/runner/window_configuration.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "window_configuration.h" - -const wchar_t* kFlutterWindowTitle = L"example"; -const unsigned int kFlutterWindowOriginX = 10; -const unsigned int kFlutterWindowOriginY = 10; -const unsigned int kFlutterWindowWidth = 1280; -const unsigned int kFlutterWindowHeight = 720; diff --git a/example/windows/runner/window_configuration.h b/example/windows/runner/window_configuration.h deleted file mode 100644 index ea5cead..0000000 --- a/example/windows/runner/window_configuration.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef WINDOW_CONFIGURATION_ -#define WINDOW_CONFIGURATION_ - -// This is a temporary approach to isolate changes that people are likely to -// make to main.cpp, where the APIs are still in flux. This will reduce the -// need to resolve conflicts or re-create changes slightly differently every -// time the Windows Flutter API surface changes. -// -// Longer term there should be simpler configuration options for common -// customizations like this, without requiring native code changes. - -extern const wchar_t* kFlutterWindowTitle; -extern const unsigned int kFlutterWindowOriginX; -extern const unsigned int kFlutterWindowOriginY; -extern const unsigned int kFlutterWindowWidth; -extern const unsigned int kFlutterWindowHeight; - -#endif // WINDOW_CONFIGURATION_ diff --git a/lib/ffi.dart b/lib/ffi.dart index dbdae5f..c373987 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -3,9 +3,11 @@ * @Author: ekibun * @Date: 2020-09-19 10:29:04 * @LastEditors: ekibun - * @LastEditTime: 2020-09-20 15:41:02 + * @LastEditTime: 2020-09-21 01:30:41 */ import 'dart:ffi'; +import 'dart:io'; +import 'dart:isolate'; import 'package:ffi/ffi.dart'; @@ -24,7 +26,7 @@ class JSProp { 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 */ @@ -47,7 +49,11 @@ class JSTag { static const FLOAT64 = 7; } -final DynamicLibrary qjsLib = DynamicLibrary.open("test/lib/build/Debug/ffi_library.dll"); +final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true' + ? (Platform.isWindows + ? DynamicLibrary.open("test/build/Debug/flutter_qjs.dll") + : DynamicLibrary.process()) + : (Platform.isWindows ? DynamicLibrary.open("flutter_qjs_plugin.dll") : DynamicLibrary.process()); /// JSValue *jsEXCEPTION() final Pointer Function() jsEXCEPTION = @@ -68,24 +74,28 @@ final Pointer Function( )>>("jsNewRuntime") .asFunction(); -typedef JSChannel = Pointer Function(Pointer ctx, String method, Pointer argv); +typedef JSChannel = Pointer Function(Pointer ctx, Pointer method, Pointer argv); class RuntimeOpaque { JSChannel channel; List ref = List(); + ReceivePort port; + Pointer Function(Future) futureToPromise; + Future Function(Pointer) promsieToFuture; } final Map runtimeOpaques = Map(); -Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) { - return runtimeOpaques[jsGetRuntime(ctx)].channel(ctx, Utf8.fromUtf8(method), argv); +Pointer channelDispacher(Pointer ctx, Pointer method, Pointer argv) { + return runtimeOpaques[jsGetRuntime(ctx)].channel(ctx, method, argv); } Pointer jsNewRuntime( JSChannel callback, + ReceivePort port, ) { var rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher)); - runtimeOpaques[rt] = RuntimeOpaque()..channel = callback; + runtimeOpaques[rt] = RuntimeOpaque()..channel = callback..port = port; return rt; } @@ -173,6 +183,7 @@ Pointer jsEval( var val = _jsEval(ctx, utf8input, Utf8.strlen(utf8input), utf8filename, evalFlags); free(utf8input); free(utf8filename); + runtimeOpaques[jsGetRuntime(ctx)].port.sendPort.send('eval'); return val; } @@ -503,23 +514,18 @@ final Pointer Function( /// 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(); +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( @@ -592,3 +598,102 @@ final int Function( Int32, )>>("jsPropertyEnumGetAtom") .asFunction(); + +/// uint32_t sizeOfJSValue() +final int Function() _sizeOfJSValue = + qjsLib.lookup>("sizeOfJSValue").asFunction(); + +final sizeOfJSValue = _sizeOfJSValue(); + +/// void setJSValueList(JSValue *list, int i, JSValue *val) +final void Function( + Pointer list, + int i, + Pointer val, +) setJSValueList = qjsLib + .lookup< + NativeFunction< + Void Function( + Pointer, + Uint32, + Pointer, + )>>("setJSValueList") + .asFunction(); + +/// JSValue *jsCall(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, +/// int argc, JSValueConst *argv) +final Pointer Function( + Pointer ctx, + Pointer funcObj, + Pointer thisObj, + int argc, + Pointer argv, +) _jsCall = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Pointer, + Int32, + Pointer, + )>>("jsCall") + .asFunction(); + +Pointer jsCall( + Pointer ctx, + Pointer funcObj, + Pointer thisObj, + List argv, +) { + Pointer jsArgs = allocate(count: argv.length > 0 ? sizeOfJSValue * argv.length : 1); + for (int i = 0; i < argv.length; ++i) { + Pointer jsArg = argv[i]; + setJSValueList(jsArgs, i, jsArg); + } + Pointer func1 = jsDupValue(ctx, funcObj); + Pointer _thisObj = thisObj ?? jsUNDEFINED(); + Pointer jsRet = _jsCall(ctx, funcObj, _thisObj, argv.length, jsArgs); + if (thisObj == null) { + jsFreeValue(ctx, _thisObj); + deleteJSValue(_thisObj); + } + jsFreeValue(ctx, func1); + deleteJSValue(func1); + free(jsArgs); + runtimeOpaques[jsGetRuntime(ctx)].port.sendPort.send('call'); + return jsRet; +} + +/// int jsIsException(JSValueConst *val) +final int Function( + Pointer val, +) jsIsException = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + )>>("jsIsException") + .asFunction(); + +/// JSValue *jsGetException(JSContext *ctx) +final Pointer Function( + Pointer ctx, +) jsGetException = qjsLib + .lookup< + NativeFunction< + Pointer Function( + Pointer, + )>>("jsGetException") + .asFunction(); + +/// int jsExecutePendingJob(JSRuntime *rt) +final int Function( + Pointer ctx, +) jsExecutePendingJob = qjsLib + .lookup< + NativeFunction< + Int32 Function( + Pointer, + )>>("jsExecutePendingJob") + .asFunction(); \ No newline at end of file diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart index a43dbc2..afd76fd 100644 --- a/lib/flutter_qjs.dart +++ b/lib/flutter_qjs.dart @@ -3,131 +3,106 @@ * @Author: ekibun * @Date: 2020-08-08 08:29:09 * @LastEditors: ekibun - * @LastEditTime: 2020-09-06 13:03:56 + * @LastEditTime: 2020-09-21 01:36:30 */ import 'dart:async'; -import 'dart:io'; -import 'package:flutter/services.dart'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:ffi/ffi.dart'; +import 'package:flutter_qjs/ffi.dart'; +import 'package:flutter_qjs/wrapper.dart'; /// Handle function to manage js call with `dart(method, ...args)` function. -typedef JsMethodHandler = Future Function(String method, List args); +typedef JsMethodHandler = dynamic Function(String method, List args); /// Handle function to manage js module. -typedef JsModuleHandler = Future Function(String name); +typedef JsModuleHandler = String Function(String name); -/// return this in [JsMethodHandler] to mark method not implemented. -class JsMethodHandlerNotImplement {} - -/// FlutterJs instance. -/// Each [FlutterQjs] object creates a new thread that runs a simple js loop. -/// Make sure call `destroy` to terminate thread and release memory when you don't need it. class FlutterQjs { - dynamic _engine; - dynamic get pointer => _engine; + Pointer _rt; + Pointer _ctx; + ReceivePort port = ReceivePort(); + JsMethodHandler methodHandler; + JsModuleHandler moduleHandler; - _ensureEngine() async { - if (_engine == null) { - _engine = await _FlutterJs.instance._channel.invokeMethod("createEngine"); - } + _ensureEngine() { + if (_rt != null) return; + _rt = jsNewRuntime((ctx, method, argv) { + if (method.address != 0) { + var argvs = jsToDart(ctx, argv); + if (methodHandler == null) throw Exception("No MethodHandler"); + return dartToJs(ctx, methodHandler(Utf8.fromUtf8(method.cast()), argvs)); + } + if (moduleHandler == null) throw Exception("No ModuleHandler"); + var ret = Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(argv.cast()))); + Future.microtask(() { + free(ret); + }); + return ret; + }, port); + _ctx = jsNewContextWithPromsieWrapper(_rt); } /// Set a handler to manage js call with `dart(method, ...args)` function. - setMethodHandler(JsMethodHandler handler) async { - if (handler == null) - return _FlutterJs.instance._methodHandlers.remove(_engine); - await _ensureEngine(); - _FlutterJs.instance._methodHandlers[_engine] = handler; + setMethodHandler(JsMethodHandler handler) { + methodHandler = handler; } /// Set a handler to manage js module. - setModuleHandler(JsModuleHandler handler) async { - if (handler == null) - return _FlutterJs.instance._moduleHandlers.remove(_engine); - await _ensureEngine(); - _FlutterJs.instance._moduleHandlers[_engine] = handler; + setModuleHandler(JsModuleHandler handler) { + moduleHandler = handler; } - /// Terminate thread and release memory. - destroy() async { - if (_engine != null) { - await setMethodHandler(null); - await setModuleHandler(null); - var engine = _engine; - _engine = null; - await _FlutterJs.instance._channel.invokeMethod("close", engine); + /// Free Runtime and Context which can be recreate when evaluate again. + recreate() { + if (_rt != null) { + jsFreeContext(_ctx); + jsFreeRuntime(_rt); + } + _rt = null; + _ctx = null; + } + + /// Close ReceivePort. + close() { + if (port != null) { + port.close(); + recreate(); + } + port = null; + } + + /// DispatchMessage + Future dispatch() async { + await for (var _ in port) { + while (true) { + int err = jsExecutePendingJob(_rt); + if (err <= 0) { + if (err < 0) print(parseJSException(_ctx)); + break; + } + } + List jsPromises = runtimeOpaques[_rt].ref.where((v) => v is JSPromise).toList(); + for (JSPromise jsPromise in jsPromises) { + if (jsPromise.checkResolveReject()) { + jsPromise.release(); + runtimeOpaques[_rt].ref.remove(jsPromise); + } + } } } /// Evaluate js script. Future evaluate(String command, String name) async { - await _ensureEngine(); - var arguments = {"engine": _engine, "script": command, "name": name}; - return _FlutterJs.instance._wrapFunctionArguments( - await _FlutterJs.instance._channel.invokeMethod("evaluate", arguments), - _engine); - } -} - -class _FlutterJs { - factory _FlutterJs() => _getInstance(); - static _FlutterJs get instance => _getInstance(); - static _FlutterJs _instance; - MethodChannel _channel = const MethodChannel('soko.ekibun.flutter_qjs'); - Map _methodHandlers = - Map(); - Map _moduleHandlers = - Map(); - _FlutterJs._internal() { - _channel.setMethodCallHandler((call) async { - var engine = call.arguments["engine"]; - var args = call.arguments["args"]; - if (args is List) { - if (_methodHandlers[engine] == null) return call.noSuchMethod(null); - var ret = await _methodHandlers[engine]( - call.method, _wrapFunctionArguments(args, engine)); - if (ret is JsMethodHandlerNotImplement) return call.noSuchMethod(null); - return ret; - } else { - if (_moduleHandlers[engine] == null) return call.noSuchMethod(null); - var ret = await _moduleHandlers[engine](args); - if (ret is JsMethodHandlerNotImplement) return call.noSuchMethod(null); - return ret; - } - }); - } - dynamic _wrapFunctionArguments(dynamic val, dynamic engine) { - if (val is List && !(val is List)) { - for (var i = 0; i < val.length; ++i) { - val[i] = _wrapFunctionArguments(val[i], engine); - } - } else if (val is Map) { - // wrap boolean in Android see https://github.com/flutter/flutter/issues/45066 - if (Platform.isAndroid && val["__js_boolean__"] != null) { - return val["__js_boolean__"] != 0; - } - if (val["__js_function__"] != null) { - var functionId = val["__js_function__"]; - return (List args) async { - var arguments = { - "engine": engine, - "function": functionId, - "arguments": args, - }; - return _wrapFunctionArguments( - await _channel.invokeMethod("call", arguments), engine); - }; - } else - for (var key in val.keys) { - val[key] = _wrapFunctionArguments(val[key], engine); - } - } - return val; - } - - static _FlutterJs _getInstance() { - if (_instance == null) { - _instance = new _FlutterJs._internal(); - } - return _instance; + _ensureEngine(); + var jsval = jsEval(_ctx, command, name, JSEvalType.GLOBAL); + if (jsIsException(jsval) != 0) { + throw Exception(parseJSException(_ctx)); + } + var ret = runtimeOpaques[_rt]?.promsieToFuture(jsval); + jsFreeValue(_ctx, jsval); + deleteJSValue(jsval); + return ret; } } diff --git a/lib/wrapper.dart b/lib/wrapper.dart index 5137d23..ff8eff8 100644 --- a/lib/wrapper.dart +++ b/lib/wrapper.dart @@ -3,8 +3,9 @@ * @Author: ekibun * @Date: 2020-09-19 22:07:47 * @LastEditors: ekibun - * @LastEditTime: 2020-09-20 15:41:16 + * @LastEditTime: 2020-09-21 01:23:06 */ +import 'dart:async'; import 'dart:ffi'; import 'dart:typed_data'; @@ -12,10 +13,10 @@ import 'package:ffi/ffi.dart'; import 'ffi.dart'; -class JSFunction extends JSRef { +class JSRefValue implements JSRef { Pointer val; Pointer ctx; - JSFunction(this.ctx, Pointer val) { + JSRefValue(this.ctx, Pointer val) { Pointer rt = jsGetRuntime(ctx); this.val = jsDupValue(ctx, val); runtimeOpaques[rt]?.ref?.add(this); @@ -26,17 +27,92 @@ class JSFunction extends JSRef { if (val != null) { jsFreeValue(ctx, val); deleteJSValue(val); - val = null; } - } - - @override - noSuchMethod(Invocation invocation) { - return super.noSuchMethod(invocation); + val = null; + ctx = null; } } +class JSPromise extends JSRefValue { + Completer completer; + JSPromise(Pointer ctx, Pointer val, this.completer) : super(ctx, val); + + @override + void release() { + super.release(); + if (!completer.isCompleted) { + completer.completeError("Promise cannot resolve"); + } + } + + bool checkResolveReject() { + if (val == null || completer.isCompleted) return true; + var status = jsToDart(ctx, val); + if (status["__resolved"] == true) { + completer.complete(status["__value"]); + return true; + } + if (status["__rejected"] == true) { + completer.completeError(status["__error"] ?? "undefined"); + return true; + } + return false; + } +} + +class JSFunction extends JSRefValue { + JSFunction(Pointer ctx, Pointer val) : super(ctx, val); + + @override + noSuchMethod(Invocation invocation) { + if (val == null) return; + List args = invocation.positionalArguments.map((e) => dartToJs(ctx, e)).toList(); + Pointer jsRet = jsCall(ctx, val, null, args); + for (Pointer jsArg in args) { + jsFreeValue(ctx, jsArg); + deleteJSValue(jsArg); + } + bool isException = jsIsException(jsRet) != 0; + var ret = jsToDart(ctx, jsRet); + jsFreeValue(ctx, jsRet); + deleteJSValue(jsRet); + if (isException) { + throw Exception(parseJSException(ctx)); + } + return ret; + } +} + +Pointer jsGetPropertyStr(Pointer ctx, Pointer val, String prop) { + var jsAtomVal = jsNewString(ctx, prop); + var jsAtom = jsValueToAtom(ctx, jsAtomVal); + Pointer jsProp = jsGetProperty(ctx, val, jsAtom); + jsFreeAtom(ctx, jsAtom); + jsFreeValue(ctx, jsAtomVal); + deleteJSValue(jsAtomVal); + return jsProp; +} + +String parseJSException(Pointer ctx) { + Pointer e = jsGetException(ctx); + var err = jsToCString(ctx, e); + if (jsValueGetTag(e) == JSTag.OBJECT) { + Pointer stack = jsGetPropertyStr(ctx, e, "stack"); + if (jsToBool(ctx, stack) != 0) { + err += '\n' + jsToCString(ctx, stack); + } + jsFreeValue(ctx, stack); + deleteJSValue(stack); + } + jsFreeValue(ctx, e); + deleteJSValue(e); + return err; +} + Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { + if (val is Future) { + return runtimeOpaques[jsGetRuntime(ctx)]?.futureToPromise(val); + } if (cache == null) cache = Map(); if (val is bool) return jsNewBool(ctx, val ? 1 : 0); if (val is int) return jsNewInt64(ctx, val); @@ -51,7 +127,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { return ret; } if (cache.containsKey(val)) { - return cache[val]; + return jsDupValue(ctx, cache[val]); } if (val is JSFunction) { return jsDupValue(ctx, val.val); @@ -78,7 +154,7 @@ Pointer dartToJs(Pointer ctx, dynamic val, {Map cache}) { if (val is Map) { Pointer ret = jsNewObject(ctx); cache[val] = ret; - for (MapEntry entry in val.entries){ + for (MapEntry entry in val.entries) { var jsAtomVal = dartToJs(ctx, entry.key, cache: cache); var jsAtom = jsValueToAtom(ctx, jsAtomVal); jsDefinePropertyValue( @@ -125,13 +201,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { 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); - + Pointer jslength = jsGetPropertyStr(ctx, val, "length"); int length = jsToInt64(ctx, jslength); deleteJSValue(jslength); List ret = List(); @@ -175,3 +245,70 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) { } return null; } + +Pointer jsNewContextWithPromsieWrapper(Pointer rt) { + var ctx = jsNewContext(rt); + var jsPromiseCtor = jsEval( + ctx, + """ + () => { + const __resolver = {}; + const __ret = new Promise((res, rej) => { + __resolver.__res = res; + __resolver.__rej = rej; + }); + __ret.__res = __resolver.__res; + __ret.__rej = __resolver.__rej; + return __ret; + } + """, + "", + JSEvalType.GLOBAL); + var promiseCtor = JSRefValue(ctx, jsPromiseCtor); + jsFreeValue(ctx, jsPromiseCtor); + deleteJSValue(jsPromiseCtor); + runtimeOpaques[rt].futureToPromise = (future) { + var ctor = promiseCtor.val; + if (ctor == null) throw Exception("Runtime has been released!"); + var jsPromise = jsCall(ctx, ctor, null, List()); + var promise = jsToDart(ctx, jsPromise); + future.then((value) { + promise['__res'](value); + }).catchError((err) { + promise['__rej'](err); + }); + return jsPromise; + }; + var jsPromiseWrapper = jsEval( + ctx, + """ + (value) => { + const __ret = Promise.resolve(value) + .then(v => { + __ret.__value = v; + __ret.__resolved = true; + }).catch(e => { + __ret.__error = e; + __ret.__rejected = true; + }); + return __ret; + } + """, + "", + JSEvalType.GLOBAL); + var promiseWrapper = JSRefValue(ctx, jsPromiseWrapper); + jsFreeValue(ctx, jsPromiseWrapper); + deleteJSValue(jsPromiseWrapper); + runtimeOpaques[rt].promsieToFuture = (promise) { + var completer = Completer(); + var wrapper = promiseWrapper.val; + if (wrapper == null) completer.completeError(Exception("Runtime has been released!")); + var jsPromise = jsCall(ctx, wrapper, null, [promise]); + runtimeOpaques[rt].ref.add(JSPromise(ctx, jsPromise, completer)); + jsFreeValue(ctx, jsPromise); + deleteJSValue(jsPromise); + return completer.future; + }; + + return ctx; +} diff --git a/test/lib/CMakeLists.txt b/test/CMakeLists.txt similarity index 70% rename from test/lib/CMakeLists.txt rename to test/CMakeLists.txt index 5bc5938..11b7fee 100644 --- a/test/lib/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,11 @@ cmake_minimum_required(VERSION 3.7 FATAL_ERROR) project(ffi_library LANGUAGES CXX) -add_library(ffi_library SHARED ${CMAKE_CURRENT_SOURCE_DIR}/../../cxx/ffi.cpp) +set(CXX_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../cxx) + +add_library(flutter_qjs SHARED ${CXX_LIB_DIR}/ffi.cpp) # quickjs -set(QUICK_JS_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../cxx/quickjs) +set(QUICK_JS_LIB_DIR ${CXX_LIB_DIR}/quickjs) file (STRINGS "${QUICK_JS_LIB_DIR}/VERSION" QUICKJS_VERSION) add_library(libquickjs STATIC ${QUICK_JS_LIB_DIR}/cutils.c @@ -16,4 +18,4 @@ project(libquickjs LANGUAGES C) target_compile_options(libquickjs PRIVATE "-DCONFIG_VERSION=\"${QUICKJS_VERSION}\"") target_compile_options(libquickjs PRIVATE "-DDUMP_LEAKS") -target_link_libraries(ffi_library PRIVATE libquickjs) +target_link_libraries(flutter_qjs PRIVATE libquickjs) diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart index 7404255..87c4e6b 100644 --- a/test/flutter_qjs_test.dart +++ b/test/flutter_qjs_test.dart @@ -3,16 +3,14 @@ * @Author: ekibun * @Date: 2020-09-06 13:02:46 * @LastEditors: ekibun - * @LastEditTime: 2020-09-20 15:55:50 + * @LastEditTime: 2020-09-21 01:39:49 */ import 'dart:convert'; import 'dart:io'; +import 'package:flutter_qjs/flutter_qjs.dart'; 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'); @@ -22,7 +20,7 @@ void main() async { var result = Process.runSync( cmakePath, ['-S', './', '-B', buildDir], - workingDirectory: 'test/lib', + workingDirectory: 'test', stdoutEncoding: utf8Encoding, stderrEncoding: utf8Encoding, ); @@ -33,7 +31,7 @@ void main() async { result = Process.runSync( cmakePath, ['--build', buildDir, '--verbose'], - workingDirectory: 'test/lib', + workingDirectory: 'test', stdoutEncoding: utf8Encoding, stderrEncoding: utf8Encoding, ); @@ -42,36 +40,28 @@ void main() async { 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 qjs = FlutterQjs(); + qjs.setMethodHandler((method, args) { + print([method, args]); + return args; }); - final ctx = jsNewContext(rt); - final jsval = jsEval( - ctx, - """ + qjs.setModuleHandler((name) { + print(name); + return "export default '${new DateTime.now()}'"; + }); + qjs.evaluate(""" 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); + import("test").then((module) => channel('channel', [ + (...a)=>`hello \${a}`, + 0.1, true, false, 1, "world", module + ])); + """, "").then((value) { + print(value); + }); + Future.delayed(Duration(seconds: 5)).then((v) { + qjs.close(); + }); + await qjs.dispatch(); }); } diff --git a/test/lib/make.bat b/test/lib/make.bat deleted file mode 100644 index 094703f..0000000 --- a/test/lib/make.bat +++ /dev/null @@ -1,5 +0,0 @@ -cd %~dp0 -set CMAKE="C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -set BUILD_DIR="./build" -%CMAKE% -S ./ -B %BUILD_DIR% -%CMAKE% --build %BUILD_DIR% --verbose \ No newline at end of file diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index cb7fa15..bf2d2af 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,7 +1,9 @@ cmake_minimum_required(VERSION 3.15) +set(CXX_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../cxx) + # quickjs -set(QUICK_JS_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../cxx/quickjs) +set(QUICK_JS_LIB_DIR ${CXX_LIB_DIR}/quickjs) file (STRINGS "${QUICK_JS_LIB_DIR}/VERSION" QUICKJS_VERSION) add_library(libquickjs STATIC ${QUICK_JS_LIB_DIR}/cutils.c @@ -21,6 +23,7 @@ set(PLUGIN_NAME "${PROJECT_NAME}_plugin") add_library(${PLUGIN_NAME} SHARED "${PLUGIN_NAME}.cpp" + "${CXX_LIB_DIR}/ffi.cpp" ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES diff --git a/windows/dart_js_wrapper.hpp b/windows/dart_js_wrapper.hpp deleted file mode 100644 index 990d004..0000000 --- a/windows/dart_js_wrapper.hpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * @Description: - * @Author: ekibun - * @Date: 2020-08-14 21:45:02 - * @LastEditors: ekibun - * @LastEditTime: 2020-08-25 18:08:45 - */ -#include "../cxx/js_engine.hpp" -#include -#include - -namespace qjs -{ - JSValue dartToJs(JSContext *ctx, flutter::EncodableValue val) - { - if (val.IsNull()) - return JS_UNDEFINED; - if (std::holds_alternative(val)) - return JS_NewBool(ctx, std::get(val)); - if (std::holds_alternative(val)) - return JS_NewInt32(ctx, std::get(val)); - if (std::holds_alternative(val)) - return JS_NewInt64(ctx, std::get(val)); - if (std::holds_alternative(val)) - return JS_NewFloat64(ctx, std::get(val)); - if (std::holds_alternative(val)) - return JS_NewString(ctx, std::get(val).c_str()); - if (std::holds_alternative>(val)) - { - auto buf = std::get>(val); - return JS_NewArrayBufferCopy(ctx, buf.data(), buf.size()); - } - if (std::holds_alternative>(val)) - { - auto buf = std::get>(val); - return JS_NewArrayBufferCopy(ctx, (uint8_t *)buf.data(), buf.size() * 4); - } - if (std::holds_alternative>(val)) - { - auto buf = std::get>(val); - return JS_NewArrayBufferCopy(ctx, (uint8_t *)buf.data(), buf.size() * 8); - } - if (std::holds_alternative>(val)) - { - auto buf = std::get>(val); - JSValue array = JS_NewArray(ctx); - auto size = (uint32_t)buf.size(); - for (uint32_t i = 0; i < size; ++i) - { - auto atom = JS_NewAtomUInt32(ctx, i); - JS_DefinePropertyValue( - ctx, array, atom, JS_NewFloat64(ctx, buf[i]), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - } - return array; - } - if (std::holds_alternative(val)) - { - auto list = std::get(val); - JSValue array = JS_NewArray(ctx); - auto size = (uint32_t)list.size(); - for (uint32_t i = 0; i < size; i++) - { - auto atom = JS_NewAtomUInt32(ctx, i); - JS_DefinePropertyValue( - ctx, array, atom, dartToJs(ctx, list[i]), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - } - - return array; - } - if (std::holds_alternative(val)) - { - auto map = std::get(val); - JSValue obj = JS_NewObject(ctx); - for (auto iter = map.begin(); iter != map.end(); ++iter) - { - auto atomvalue = dartToJs(ctx, iter->first); - auto atom = JS_ValueToAtom(ctx, atomvalue); - JS_DefinePropertyValue( - ctx, obj, atom, dartToJs(ctx, iter->second), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, atomvalue); - } - - return obj; - } - return JS_UNDEFINED; - } - - flutter::EncodableValue jsToDart(Value val, std::unordered_map cache = std::unordered_map()) - { - int tag = JS_VALUE_GET_TAG(val.v); - if (JS_TAG_IS_FLOAT64(tag)) - return (double)val; - switch (tag) - { - case JS_TAG_BOOL: - return (bool)val; - case JS_TAG_INT: - return (int64_t)val; - case JS_TAG_STRING: - return (std::string)val; - case JS_TAG_OBJECT: - { // ArrayBuffer - size_t size; - uint8_t *buf = JS_GetArrayBuffer(val.ctx, &size, val.v); - if (buf) - return (std::vector(buf, buf + size)); - } - if (cache.find(val) != cache.end()) - return cache[val]; - if (JS_IsFunction(val.ctx, val.v)) - { - flutter::EncodableMap retMap; - retMap[std::string("__js_function__")] = (int64_t) new JSValue{js_add_ref(val)}; - return retMap; - } - else if (JS_IsArray(val.ctx, val.v) > 0) - { - flutter::EncodableList retList; - cache[val] = retList; - uint32_t arrlen = (uint32_t)val["length"]; - for (uint32_t i = 0; i < arrlen; i++) - { - retList.push_back(jsToDart(val[i], cache)); - } - return retList; - } - else - { - qjs::JSPropertyEnum *ptab; - uint32_t plen; - if (JS_GetOwnPropertyNames(val.ctx, &ptab, &plen, val.v, -1)) - return flutter::EncodableValue(); - flutter::EncodableMap retMap; - cache[val] = retMap; - for (uint32_t i = 0; i < plen; i++) - { - retMap[jsToDart({val.ctx, JS_AtomToValue(val.ctx, ptab[i].atom)}, cache)] = - jsToDart({val.ctx, JS_GetProperty(val.ctx, val.v, ptab[i].atom)}, cache); - JS_FreeAtom(val.ctx, ptab[i].atom); - } - js_free(val.ctx, ptab); - return retMap; - } - default: - return flutter::EncodableValue(); - } - } -} // namespace qjs diff --git a/windows/flutter_qjs_plugin.cpp b/windows/flutter_qjs_plugin.cpp index 5508905..29a5343 100644 --- a/windows/flutter_qjs_plugin.cpp +++ b/windows/flutter_qjs_plugin.cpp @@ -1,14 +1,16 @@ +/* + * @Description: empty plugin + * @Author: ekibun + * @Date: 2020-08-25 21:09:20 + * @LastEditors: ekibun + * @LastEditTime: 2020-09-20 16:00:15 + */ #include "include/flutter_qjs/flutter_qjs_plugin.h" // This must be included before many other Windows headers. #include -#include #include -#include -#include - -#include "dart_js_wrapper.hpp" namespace { @@ -21,160 +23,15 @@ namespace FlutterQjsPlugin(); virtual ~FlutterQjsPlugin(); - - private: - // Called when a method is called on this plugin's channel from Dart. - void HandleMethodCall( - const flutter::MethodCall &method_call, - std::unique_ptr> result); }; - std::shared_ptr> channel; - std::promise *invokeChannelMethod(std::string name, qjs::Value args, qjs::Engine *engine) - { - auto promise = new std::promise(); - auto map = new flutter::EncodableMap(); - (*map)[std::string("engine")] = (int64_t)engine; - (*map)[std::string("args")] = qjs::jsToDart(args); - channel->InvokeMethod( - name, - std::make_unique(*map), - std::make_unique>( - (flutter::ResultHandlerSuccess)[promise]( - const flutter::EncodableValue *result) { - promise->set_value((qjs::JSFutureReturn)[result = result ? *result : flutter::EncodableValue()](qjs::JSContext * ctx) { - qjs::JSValue *ret = new qjs::JSValue{qjs::dartToJs(ctx, result)}; - return qjs::JSOSFutureArgv{1, ret}; - }); - }, - (flutter::ResultHandlerError)[promise]( - const std::string &error_code, - const std::string &error_message, - const flutter::EncodableValue *error_details) { - promise->set_value((qjs::JSFutureReturn)[error_message](qjs::JSContext * ctx) { - qjs::JSValue *ret = new qjs::JSValue{JS_NewString(ctx, error_message.c_str())}; - return qjs::JSOSFutureArgv{-1, ret}; - }); - }, - (flutter::ResultHandlerNotImplemented)[promise]() { - promise->set_value((qjs::JSFutureReturn)[](qjs::JSContext * ctx) { - qjs::JSValue *ret = new qjs::JSValue{JS_NewString(ctx, "NotImplemented")}; - return qjs::JSOSFutureArgv{-1, ret}; - }); - })); - return promise; - } - // static void FlutterQjsPlugin::RegisterWithRegistrar( - flutter::PluginRegistrarWindows *registrar) - { - channel = - std::make_unique>( - registrar->messenger(), "soko.ekibun.flutter_qjs", - &flutter::StandardMethodCodec::GetInstance()); - - auto plugin = std::make_unique(); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto &call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - - registrar->AddPlugin(std::move(plugin)); - } + flutter::PluginRegistrarWindows *registrar) {} FlutterQjsPlugin::FlutterQjsPlugin() {} FlutterQjsPlugin::~FlutterQjsPlugin() {} - - const flutter::EncodableValue &ValueOrNull(const flutter::EncodableMap &map, const char *key) - { - static flutter::EncodableValue null_value; - auto it = map.find(flutter::EncodableValue(key)); - if (it == map.end()) - { - return null_value; - } - return it->second; - } - - void FlutterQjsPlugin::HandleMethodCall( - const flutter::MethodCall &method_call, - std::unique_ptr> result) - { - // Replace "getPlatformVersion" check with your plugin's method. - // See: - // https://github.com/flutter/engine/tree/master/shell/platform/common/cpp/client_wrapper/include/flutter - // and - // https://github.com/flutter/engine/tree/master/shell/platform/glfw/client_wrapper/include/flutter - // for the relevant Flutter APIs. - if (method_call.method_name().compare("createEngine") == 0) - { - qjs::Engine *engine = new qjs::Engine(invokeChannelMethod); - flutter::EncodableValue response = (int64_t)engine; - result->Success(&response); - } - else if (method_call.method_name().compare("evaluate") == 0) - { - flutter::EncodableMap args = *std::get_if(method_call.arguments()); - qjs::Engine *engine = (qjs::Engine *)std::get(ValueOrNull(args, "engine")); - std::string script = std::get(ValueOrNull(args, "script")); - std::string name = std::get(ValueOrNull(args, "name")); - std::shared_ptr> presult = std::move(result); - engine->commit(qjs::EngineTask{ - [script, name](qjs::Context &ctx) { - return ctx.eval(script, name.c_str(), JS_EVAL_TYPE_GLOBAL); - }, - [presult](qjs::Value resolve) { - flutter::EncodableValue response = qjs::jsToDart(resolve); - presult->Success(&response); - }, - [presult](qjs::Value reject) { - presult->Error("FlutterJSException", qjs::getStackTrack(reject)); - }}); - } - else if (method_call.method_name().compare("call") == 0) - { - flutter::EncodableMap args = *std::get_if(method_call.arguments()); - qjs::Engine *engine = (qjs::Engine *)std::get(ValueOrNull(args, "engine")); - qjs::JSValue *function = (qjs::JSValue *)std::get(ValueOrNull(args, "function")); - flutter::EncodableList arguments = std::get(ValueOrNull(args, "arguments")); - std::shared_ptr> presult = std::move(result); - engine->commit(qjs::EngineTask{ - [function, arguments](qjs::Context &ctx) { - size_t argscount = arguments.size(); - qjs::JSValue *callargs = new qjs::JSValue[argscount]; - for (size_t i = 0; i < argscount; i++) - { - callargs[i] = qjs::dartToJs(ctx.ctx, arguments[i]); - } - qjs::JSValue ret = qjs::call_handler(ctx.ctx, *function, (int)argscount, callargs); - delete[] callargs; - if (qjs::JS_IsException(ret)) - throw qjs::exception{}; - return qjs::Value{ctx.ctx, ret}; - }, - [presult](qjs::Value resolve) { - flutter::EncodableValue response = qjs::jsToDart(resolve); - presult->Success(&response); - }, - [presult](qjs::Value reject) { - presult->Error("FlutterJSException", qjs::getStackTrack(reject)); - }}); - } - else if (method_call.method_name().compare("close") == 0) - { - qjs::Engine *engine = (qjs::Engine *)*std::get_if(method_call.arguments()); - delete engine; - result->Success(); - } - else - { - result->NotImplemented(); - } - } - } // namespace void FlutterQjsPluginRegisterWithRegistrar(