windows wrapper

This commit is contained in:
ekibun
2020-09-21 01:41:52 +08:00
parent 5f9fdac9f4
commit 9175871678
28 changed files with 618 additions and 582 deletions

View File

@@ -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 <functional>
#include <future>
#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);
}
}

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1,17 @@
//
// Generated file. Do not edit.
//
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

View File

@@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
#import "GeneratedPluginRegistrant.h"
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
}
@end

View File

@@ -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<TestPage> {
_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<TestPage> {
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<TestPage> {
child: Text("close engine"),
onPressed: () async {
if (engine == null) return;
await engine.destroy();
await engine.close();
engine = null;
}),
],

View File

@@ -1 +0,0 @@
6

View File

@@ -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)

View File

@@ -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
/////////////////////////////////////////////////////////////////////////////

View File

@@ -15,22 +15,25 @@ bool FlutterWindow::OnCreate() {
return false;
}
// The size here is arbitrary since SetChildContent will resize it.
flutter_controller_ =
std::make_unique<flutter::FlutterViewController>(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<flutter::FlutterViewController>(
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);
}

View File

@@ -1,5 +1,5 @@
#ifndef FLUTTER_WINDOW_H_
#define FLUTTER_WINDOW_H_
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
@@ -36,4 +36,4 @@ class FlutterWindow : public Win32Window {
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // FLUTTER_WINDOW_H_
#endif // RUNNER_FLUTTER_WINDOW_H_

View File

@@ -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);

View File

@@ -1,9 +1,6 @@
#include "run_loop.h"
#include <Windows.h>
// Don't stomp std::min/std::max
#undef max
#undef min
#include <windows.h>
#include <algorithm>
@@ -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);

View File

@@ -1,7 +1,7 @@
#ifndef RUN_LOOP_H_
#define RUN_LOOP_H_
#ifndef RUNNER_RUN_LOOP_H_
#define RUNNER_RUN_LOOP_H_
#include <flutter/flutter_view_controller.h>
#include <flutter/flutter_engine.h>
#include <chrono>
#include <set>
@@ -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::FlutterViewController*> flutter_instances_;
std::set<flutter::FlutterEngine*> flutter_instances_;
};
#endif // RUN_LOOP_H_
#endif // RUNNER_RUN_LOOP_H_

View File

@@ -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_

View File

@@ -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_;
}

View File

@@ -1,8 +1,7 @@
#ifndef WIN32_WINDOW_H_
#define WIN32_WINDOW_H_
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <Windows.h>
#include <Windowsx.h>
#include <windows.h>
#include <functional>
#include <memory>
@@ -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_

View File

@@ -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;

View File

@@ -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_

View File

@@ -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';
@@ -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<JSRef> ref = List();
ReceivePort port;
Pointer Function(Future) futureToPromise;
Future Function(Pointer) promsieToFuture;
}
final Map<Pointer, RuntimeOpaque> runtimeOpaques = Map();
Pointer channelDispacher(Pointer ctx, Pointer<Utf8> 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<NativeFunction<Uint32 Function()>>("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<Pointer> argv,
) {
Pointer jsArgs = allocate<Uint8>(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();

View File

@@ -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<dynamic> Function(String method, List args);
typedef JsMethodHandler = dynamic Function(String method, List args);
/// Handle function to manage js module.
typedef JsModuleHandler = Future<String> 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<Utf8>()), argvs));
}
if (moduleHandler == null) throw Exception("No ModuleHandler");
var ret = Utf8.toUtf8(moduleHandler(Utf8.fromUtf8(argv.cast<Utf8>())));
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<void> 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<dynamic> 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<dynamic, JsMethodHandler> _methodHandlers =
Map<dynamic, JsMethodHandler>();
Map<dynamic, JsModuleHandler> _moduleHandlers =
Map<dynamic, JsModuleHandler>();
_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<int>)) {
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<dynamic> 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;
}
}

View File

@@ -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<Pointer> 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<dynamic, dynamic> 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<dynamic, dynamic> 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<dynamic, dynamic> cache}) {
if (val is Map) {
Pointer ret = jsNewObject(ctx);
cache[val] = ret;
for (MapEntry<dynamic, dynamic> entry in val.entries){
for (MapEntry<dynamic, dynamic> 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<int, dynamic> 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<dynamic> ret = List();
@@ -175,3 +245,70 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> 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;
}
""",
"<future>",
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;
}
""",
"<future>",
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;
}

View File

@@ -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)

View File

@@ -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
]);
""",
"<eval>",
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
]));
""", "<eval>").then((value) {
print(value);
});
Future.delayed(Duration(seconds: 5)).then((v) {
qjs.close();
});
await qjs.dispatch();
});
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 <flutter/standard_method_codec.h>
#include <variant>
namespace qjs
{
JSValue dartToJs(JSContext *ctx, flutter::EncodableValue val)
{
if (val.IsNull())
return JS_UNDEFINED;
if (std::holds_alternative<bool>(val))
return JS_NewBool(ctx, std::get<bool>(val));
if (std::holds_alternative<int32_t>(val))
return JS_NewInt32(ctx, std::get<int32_t>(val));
if (std::holds_alternative<int64_t>(val))
return JS_NewInt64(ctx, std::get<int64_t>(val));
if (std::holds_alternative<double>(val))
return JS_NewFloat64(ctx, std::get<double>(val));
if (std::holds_alternative<std::string>(val))
return JS_NewString(ctx, std::get<std::string>(val).c_str());
if (std::holds_alternative<std::vector<uint8_t>>(val))
{
auto buf = std::get<std::vector<uint8_t>>(val);
return JS_NewArrayBufferCopy(ctx, buf.data(), buf.size());
}
if (std::holds_alternative<std::vector<int32_t>>(val))
{
auto buf = std::get<std::vector<int32_t>>(val);
return JS_NewArrayBufferCopy(ctx, (uint8_t *)buf.data(), buf.size() * 4);
}
if (std::holds_alternative<std::vector<int64_t>>(val))
{
auto buf = std::get<std::vector<int64_t>>(val);
return JS_NewArrayBufferCopy(ctx, (uint8_t *)buf.data(), buf.size() * 8);
}
if (std::holds_alternative<std::vector<double>>(val))
{
auto buf = std::get<std::vector<double>>(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<flutter::EncodableList>(val))
{
auto list = std::get<flutter::EncodableList>(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<flutter::EncodableMap>(val))
{
auto map = std::get<flutter::EncodableMap>(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<Value, flutter::EncodableValue> cache = std::unordered_map<Value, flutter::EncodableValue>())
{
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<uint8_t>(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

View File

@@ -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 <windows.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <flutter/method_result_functions.h>
#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<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
};
std::shared_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel;
std::promise<qjs::JSFutureReturn> *invokeChannelMethod(std::string name, qjs::Value args, qjs::Engine *engine)
{
auto promise = new std::promise<qjs::JSFutureReturn>();
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<flutter::EncodableValue>(*map),
std::make_unique<flutter::MethodResultFunctions<flutter::EncodableValue>>(
(flutter::ResultHandlerSuccess<flutter::EncodableValue>)[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<flutter::EncodableValue>)[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<flutter::EncodableValue>)[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<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "soko.ekibun.flutter_qjs",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<FlutterQjsPlugin>();
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<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> 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<flutter::EncodableMap>(method_call.arguments());
qjs::Engine *engine = (qjs::Engine *)std::get<int64_t>(ValueOrNull(args, "engine"));
std::string script = std::get<std::string>(ValueOrNull(args, "script"));
std::string name = std::get<std::string>(ValueOrNull(args, "name"));
std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>> 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<flutter::EncodableMap>(method_call.arguments());
qjs::Engine *engine = (qjs::Engine *)std::get<int64_t>(ValueOrNull(args, "engine"));
qjs::JSValue *function = (qjs::JSValue *)std::get<int64_t>(ValueOrNull(args, "function"));
flutter::EncodableList arguments = std::get<flutter::EncodableList>(ValueOrNull(args, "arguments"));
std::shared_ptr<flutter::MethodResult<flutter::EncodableValue>> 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<int64_t>(method_call.arguments());
delete engine;
result->Success();
}
else
{
result->NotImplemented();
}
}
} // namespace
void FlutterQjsPluginRegisterWithRegistrar(