From 902cc2cf0eb1fdda5095c9b5a1b6a7127f35e30d Mon Sep 17 00:00:00 2001 From: zeromake Date: Wed, 11 Sep 2024 04:47:40 +0800 Subject: [PATCH] Add native module support on Windows --- .github/workflows/ci.yml | 24 +++++++++++++-- CMakeLists.txt | 44 ++++++++++++++------------- examples/fib.c | 10 ++++++- examples/point.c | 10 ++++++- examples/test_fib.js | 4 ++- examples/test_point.js | 5 +++- gen/test_fib.c | 64 +++++++++++++++++++++++++--------------- quickjs-libc.c | 58 ++++++++++++++++++++++++++++++++++-- 8 files changed, 166 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb05f8a..21162c8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,14 +195,20 @@ jobs: - uses: actions/checkout@v4 - name: build run: | - cmake -B build -G "Visual Studio 17 2022" -A ${{matrix.arch}} + cmake -B build -DBUILD_EXAMPLES=ON -G "Visual Studio 17 2022" -A ${{matrix.arch}} cmake --build build --config ${{matrix.buildType}} --target qjs_exe cmake --build build --config ${{matrix.buildType}} --target function_source + cmake --build build --config ${{matrix.buildType}} --target fib + cmake --build build --config ${{matrix.buildType}} --target point - name: stats run: | build\${{matrix.buildType}}\qjs.exe -qd - name: test run: | + cp build\${{matrix.buildType}}\fib.dll examples\ + cp build\${{matrix.buildType}}\point.dll examples\ + build\${{matrix.buildType}}\qjs.exe examples\test_fib.js + build\${{matrix.buildType}}\qjs.exe examples\test_point.js build\${{matrix.buildType}}\qjs.exe tests\test_bigint.js build\${{matrix.buildType}}\qjs.exe tests\test_bjson.js build\${{matrix.buildType}}\qjs.exe tests\test_closure.js @@ -224,14 +230,20 @@ jobs: - uses: actions/checkout@v4 - name: build run: | - cmake -B build -G "Visual Studio 17 2022" -T ClangCL + cmake -B build -DBUILD_EXAMPLES=ON -G "Visual Studio 17 2022" -T ClangCL cmake --build build --config ${{matrix.buildType}} --target qjs_exe cmake --build build --config ${{matrix.buildType}} --target function_source + cmake --build build --config ${{matrix.buildType}} --target fib + cmake --build build --config ${{matrix.buildType}} --target point - name: stats run: | build\${{matrix.buildType}}\qjs.exe -qd - name: test run: | + cp build\${{matrix.buildType}}\fib.dll examples\ + cp build\${{matrix.buildType}}\point.dll examples\ + build\${{matrix.buildType}}\qjs.exe examples\test_fib.js + build\${{matrix.buildType}}\qjs.exe examples\test_point.js build\${{matrix.buildType}}\qjs.exe tests\test_bigint.js build\${{matrix.buildType}}\qjs.exe tests\test_bjson.js build\${{matrix.buildType}}\qjs.exe tests\test_closure.js @@ -257,14 +269,20 @@ jobs: ninja.exe --version - name: build run: | - cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.buildType}} -G "Ninja" + cmake -B build -DBUILD_EXAMPLES=ON -DCMAKE_BUILD_TYPE=${{matrix.buildType}} -G "Ninja" cmake --build build --target qjs_exe cmake --build build --target function_source + cmake --build build --target fib + cmake --build build --target point - name: stats run: | build\qjs.exe -qd - name: test run: | + cp build\fib.dll examples\ + cp build\point.dll examples\ + build\qjs.exe examples\test_fib.js + build\qjs.exe examples\test_point.js build\qjs.exe tests\test_bigint.js build\qjs.exe tests\test_bjson.js build\qjs.exe tests\test_closure.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 5988d74d..671b9b81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,7 +273,7 @@ target_link_libraries(function_source ${qjs_libs}) # Examples # -if(BUILD_EXAMPLES AND NOT WIN32) +if(BUILD_EXAMPLES) add_executable(hello gen/hello.c ) @@ -290,27 +290,31 @@ if(BUILD_EXAMPLES AND NOT WIN32) target_compile_definitions(hello_module PRIVATE ${qjs_defines}) target_link_libraries(hello_module ${qjs_libs}) - if(NOT WIN32) - add_library(fib MODULE examples/fib.c) - set_target_properties(fib PROPERTIES - PREFIX "" - C_VISIBILITY_PRESET default - ) - target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) - if(APPLE) - target_link_options(fib PRIVATE -undefined dynamic_lookup) - endif() + add_library(fib MODULE examples/fib.c) + set_target_properties(fib PROPERTIES + PREFIX "" + C_VISIBILITY_PRESET default + ) + target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY) + if(WIN32) + target_link_libraries(fib ${qjs_libs}) + endif() + if(APPLE) + target_link_options(fib PRIVATE -undefined dynamic_lookup) + endif() - add_library(point MODULE examples/point.c) - set_target_properties(point PROPERTIES - PREFIX "" - C_VISIBILITY_PRESET default - ) - target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) - if(APPLE) - target_link_options(point PRIVATE -undefined dynamic_lookup) - endif() + add_library(point MODULE examples/point.c) + set_target_properties(point PROPERTIES + PREFIX "" + C_VISIBILITY_PRESET default + ) + target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY) + if(WIN32) + target_link_libraries(point ${qjs_libs}) endif() + if(APPLE) + target_link_options(point PRIVATE -undefined dynamic_lookup) + endif() add_executable(test_fib examples/fib.c diff --git a/examples/fib.c b/examples/fib.c index 0786378f..b965acc5 100644 --- a/examples/fib.c +++ b/examples/fib.c @@ -61,7 +61,15 @@ static int js_fib_init(JSContext *ctx, JSModuleDef *m) #define JS_INIT_MODULE js_init_module_fib #endif -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) +#ifndef JS_EXTERN +#ifdef _WIN32 +#define JS_EXTERN __declspec(dllexport) +#else +#define JS_EXTERN +#endif +#endif + +JS_EXTERN JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) { JSModuleDef *m; m = JS_NewCModule(ctx, module_name, js_fib_init); diff --git a/examples/point.c b/examples/point.c index da22d6f5..a82df151 100644 --- a/examples/point.c +++ b/examples/point.c @@ -141,7 +141,15 @@ static int js_point_init(JSContext *ctx, JSModuleDef *m) return 0; } -JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) +#ifndef JS_EXTERN +#ifdef _WIN32 +#define JS_EXTERN __declspec(dllexport) +#else +#define JS_EXTERN +#endif +#endif + +JS_EXTERN JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) { JSModuleDef *m; m = JS_NewCModule(ctx, module_name, js_point_init); diff --git a/examples/test_fib.js b/examples/test_fib.js index 70d26bd8..4bdf9dc9 100644 --- a/examples/test_fib.js +++ b/examples/test_fib.js @@ -1,6 +1,8 @@ /* example of JS module importing a C module */ +import * as os from "os"; -import { fib } from "./fib.so"; +const isWin = os.platform === 'win32'; +const { fib } = await import(`./fib.${isWin ? 'dll' : 'so'}`); console.log("Hello World"); console.log("fib(10)=", fib(10)); diff --git a/examples/test_point.js b/examples/test_point.js index 0659bc35..7400420c 100644 --- a/examples/test_point.js +++ b/examples/test_point.js @@ -1,5 +1,8 @@ /* example of JS module importing a C module */ -import { Point } from "./point.so"; +import * as os from "os"; + +const isWin = os.platform === 'win32'; +const { Point } = await import(`./point.${isWin ? 'dll' : 'so'}`); function assert(b, str) { diff --git a/gen/test_fib.c b/gen/test_fib.c index 151bd4d0..4cbe0cf5 100644 --- a/gen/test_fib.c +++ b/gen/test_fib.c @@ -2,30 +2,46 @@ #include "quickjs-libc.h" -const uint32_t qjsc_test_fib_size = 167; +const uint32_t qjsc_test_fib_size = 293; -const uint8_t qjsc_test_fib[167] = { - 0x0d, 0x07, 0x28, 0x65, 0x78, 0x61, 0x6d, 0x70, +const uint8_t qjsc_test_fib[293] = { + 0x0d, 0x0d, 0x28, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x66, 0x69, 0x62, 0x2e, 0x6a, 0x73, 0x10, - 0x2e, 0x2f, 0x66, 0x69, 0x62, 0x2e, 0x73, 0x6f, - 0x06, 0x66, 0x69, 0x62, 0x0e, 0x63, 0x6f, 0x6e, - 0x73, 0x6f, 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, - 0x16, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, - 0x6f, 0x72, 0x6c, 0x64, 0x10, 0x66, 0x69, 0x62, - 0x28, 0x31, 0x30, 0x29, 0x3d, 0x0d, 0xb8, 0x03, - 0x01, 0xba, 0x03, 0x00, 0x00, 0x01, 0x00, 0xbc, - 0x03, 0x00, 0x00, 0x0c, 0x20, 0xfa, 0x01, 0xa2, - 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x32, - 0x00, 0xbc, 0x03, 0x00, 0x0c, 0x08, 0xe9, 0x02, - 0x29, 0x38, 0xdf, 0x00, 0x00, 0x00, 0x42, 0xe0, - 0x00, 0x00, 0x00, 0x04, 0xe1, 0x00, 0x00, 0x00, - 0x24, 0x01, 0x00, 0x0e, 0x38, 0xdf, 0x00, 0x00, - 0x00, 0x42, 0xe0, 0x00, 0x00, 0x00, 0x04, 0xe2, - 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0xbb, 0x0a, - 0xee, 0x24, 0x02, 0x00, 0x0e, 0x06, 0x2e, 0xb8, - 0x03, 0x01, 0x01, 0x0a, 0x01, 0x01, 0x00, 0x04, - 0x0a, 0x02, 0x62, 0x00, 0x4d, 0x30, 0x00, + 0x5f, 0x66, 0x69, 0x62, 0x2e, 0x6a, 0x73, 0x04, + 0x6f, 0x73, 0x0a, 0x69, 0x73, 0x57, 0x69, 0x6e, + 0x06, 0x66, 0x69, 0x62, 0x10, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x0a, 0x77, 0x69, + 0x6e, 0x33, 0x32, 0x0c, 0x2e, 0x2f, 0x66, 0x69, + 0x62, 0x2e, 0x06, 0x64, 0x6c, 0x6c, 0x04, 0x73, + 0x6f, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, + 0x64, 0x10, 0x66, 0x69, 0x62, 0x28, 0x31, 0x30, + 0x29, 0x3d, 0x0d, 0xb8, 0x03, 0x01, 0xba, 0x03, + 0x00, 0x00, 0x01, 0x00, 0xfc, 0x01, 0x00, 0x01, + 0x0c, 0x20, 0xfa, 0x01, 0xa2, 0x01, 0x00, 0x00, + 0x00, 0x05, 0x03, 0x00, 0x73, 0x00, 0xba, 0x03, + 0x00, 0x0d, 0xbc, 0x03, 0x00, 0x0d, 0xbe, 0x03, + 0x01, 0x0d, 0x08, 0xe9, 0x02, 0x29, 0x65, 0x00, + 0x00, 0x41, 0xe0, 0x00, 0x00, 0x00, 0x04, 0xe1, + 0x00, 0x00, 0x00, 0xae, 0xe1, 0x06, 0x11, 0xf1, + 0xea, 0x0b, 0x70, 0x42, 0xdf, 0x00, 0x00, 0x00, + 0xe2, 0x0e, 0xeb, 0x24, 0x0e, 0x04, 0xe2, 0x00, + 0x00, 0x00, 0x42, 0x5d, 0x00, 0x00, 0x00, 0x65, + 0x01, 0x00, 0xe9, 0x08, 0x04, 0xe3, 0x00, 0x00, + 0x00, 0xeb, 0x06, 0x04, 0xe4, 0x00, 0x00, 0x00, + 0x24, 0x01, 0x00, 0x35, 0x8b, 0xeb, 0xd4, 0x38, + 0xe5, 0x00, 0x00, 0x00, 0x42, 0xe6, 0x00, 0x00, + 0x00, 0x04, 0xe7, 0x00, 0x00, 0x00, 0x24, 0x01, + 0x00, 0x0e, 0x38, 0xe5, 0x00, 0x00, 0x00, 0x42, + 0xe6, 0x00, 0x00, 0x00, 0x04, 0xe8, 0x00, 0x00, + 0x00, 0x65, 0x02, 0x00, 0xbb, 0x0a, 0xee, 0x24, + 0x02, 0x00, 0x0e, 0x06, 0x2e, 0xb8, 0x03, 0x01, + 0x01, 0x22, 0x01, 0x01, 0x00, 0x04, 0x08, 0x1e, + 0x2a, 0x18, 0x1b, 0x08, 0x0d, 0x3b, 0x1b, 0x0c, + 0x07, 0x04, 0x25, 0x08, 0x0c, 0x04, 0x07, 0x10, + 0x43, 0x2c, 0x25, 0x10, 0x25, 0x04, 0x27, 0x6b, + 0x62, 0x00, 0x4d, 0x30, 0x00, }; static JSContext *JS_NewCustomContext(JSRuntime *rt) @@ -34,8 +50,8 @@ static JSContext *JS_NewCustomContext(JSRuntime *rt) if (!ctx) return NULL; { - extern JSModuleDef *js_init_module_fib(JSContext *ctx, const char *name); - js_init_module_fib(ctx, "examples/fib.so"); + extern JSModuleDef *js_init_module_os(JSContext *ctx, const char *name); + js_init_module_os(ctx, "os"); } return ctx; } diff --git a/quickjs-libc.c b/quickjs-libc.c index b56029ff..2955e4e0 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -96,6 +96,14 @@ extern char **environ; #define MAX_SAFE_INTEGER (((int64_t) 1 << 53) - 1) +#ifndef QJS_NATIVE_MODULE_SUFFIX +#ifdef _WIN32 +#define QJS_NATIVE_MODULE_SUFFIX ".dll" +#else +#define QJS_NATIVE_MODULE_SUFFIX ".so" +#endif +#endif + /* TODO: - add socket calls */ @@ -483,7 +491,53 @@ typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, const char *module_name); -#if defined(_WIN32) || defined(__wasi__) +#if defined(_WIN32) +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JSModuleDef *m; + HINSTANCE hd; + JSInitModuleFunc *init; + char *filename = NULL; + size_t len = strlen(module_name); + JS_BOOL is_absolute = len > 2 && ((module_name[0] >= 'A' && module_name[0] <= 'Z') || + (module_name[0] >= 'a' && module_name[0] <= 'z')) && module_name[1] == ':'; + JS_BOOL is_relative = len > 2 && module_name[0] == '.' && (module_name[1] == '/' || module_name[1] == '\\'); + if (is_absolute || is_relative) { + filename = (char *)module_name; + } else { + filename = js_malloc(ctx, len + 2 + 1); + if (!filename) + return NULL; + strcpy(filename, "./"); + strcpy(filename + 2, module_name); + } + hd = LoadLibraryA(filename); + if (filename != module_name) + js_free(ctx, filename); + if (hd == NULL) { + JS_ThrowReferenceError(ctx, "js_load_module '%s' error: %lu", + module_name, GetLastError()); + goto fail; + } + init = (JSInitModuleFunc *)(uintptr_t)GetProcAddress(hd, "js_init_module"); + if (!init) { + JS_ThrowReferenceError(ctx, "js_init_module '%s' not found: %lu", + module_name, GetLastError()); + goto fail; + } + m = init(ctx, module_name); + if (!m) { + JS_ThrowReferenceError(ctx, "js_call_module '%s' initialization error", + module_name); + fail: + if (hd != NULL) + FreeLibrary(hd); + return NULL; + } + return m; +} +#elif defined(__wasi__) static JSModuleDef *js_module_loader_so(JSContext *ctx, const char *module_name) { @@ -599,7 +653,7 @@ JSModuleDef *js_module_loader(JSContext *ctx, { JSModuleDef *m; - if (has_suffix(module_name, ".so")) { + if (has_suffix(module_name, QJS_NATIVE_MODULE_SUFFIX)) { m = js_module_loader_so(ctx, module_name); } else { size_t buf_len;