mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
windows wrapper
This commit is contained in:
51
cxx/ffi.cpp
51
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 <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);
|
||||
}
|
||||
}
|
14
example/ios/Flutter/Generated.xcconfig
Normal file
14
example/ios/Flutter/Generated.xcconfig
Normal 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
|
15
example/ios/Flutter/flutter_export_environment.sh
Normal file
15
example/ios/Flutter/flutter_export_environment.sh
Normal 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"
|
17
example/ios/Runner/GeneratedPluginRegistrant.h
Normal file
17
example/ios/Runner/GeneratedPluginRegistrant.h
Normal 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 */
|
12
example/ios/Runner/GeneratedPluginRegistrant.m
Normal file
12
example/ios/Runner/GeneratedPluginRegistrant.m
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
|
||||
@implementation GeneratedPluginRegistrant
|
||||
|
||||
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
|
||||
}
|
||||
|
||||
@end
|
@@ -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;
|
||||
}),
|
||||
],
|
||||
|
@@ -1 +0,0 @@
|
||||
6
|
@@ -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)
|
||||
|
@@ -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
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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_
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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_
|
||||
|
@@ -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_
|
||||
|
@@ -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_;
|
||||
}
|
||||
|
@@ -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_
|
||||
|
@@ -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;
|
@@ -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_
|
153
lib/ffi.dart
153
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<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();
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
173
lib/wrapper.dart
173
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<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;
|
||||
}
|
||||
|
@@ -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)
|
@@ -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();
|
||||
});
|
||||
}
|
||||
|
@@ -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
|
@@ -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
|
||||
|
@@ -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
|
@@ -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(
|
||||
|
Reference in New Issue
Block a user