Skip to content

feat(libc): windows native module support #490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,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
Expand All @@ -221,14 +227,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
Expand All @@ -254,14 +266,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
Expand Down
44 changes: 24 additions & 20 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand Down
10 changes: 9 additions & 1 deletion examples/fib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion examples/point.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion examples/test_fib.js
Original file line number Diff line number Diff line change
@@ -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));
5 changes: 4 additions & 1 deletion examples/test_point.js
Original file line number Diff line number Diff line change
@@ -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)
{
Expand Down
64 changes: 40 additions & 24 deletions gen/test_fib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
58 changes: 56 additions & 2 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -482,7 +490,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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nicer to use FormatMessageA() to get a human-readable error message (example)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis
FormatMessageA error message fetching is a bit complicated, whether to consider a separate method to get it

struct JSContext {
    char* errmsg;
}
static int js_error(JSContext *ctx) {
    if (ctx->errmsg) {
    LocalFree(ctx->errmsg);
    ctx->errmsg = NULL;
  }
  // FormatMessageA ……
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis Doing it The Right Way (TM) seems a bit involved: https://github.com/libuv/libuv/blob/0a00e80c3686b93eccb9a801954e86bd7d7fe6ab/src/win/dl.c#L92 perhaps we can simplify here?

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)
{
Expand Down Expand Up @@ -598,7 +652,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;
Expand Down
Loading