What does --dll
expect LIBRARY to look like?
Can we import a C++ library into the shell?
What does --dll
expect LIBRARY to look like?
Can we import a C++ library into the shell?
--dll
is a hack with a very specific purpose: to get stack traces out of JS shell runs in continuous integration. The one and only thing it actually does is call dlopen
()/ LoadLibraryExW()
to import a .so
or .dylib
or .dll
. There is no facility for then calling dlsym
or an equivalent, which makes it pretty useless for anything custom.
The JS shell makes use of this by defining a couple of functions with weak linkage. It tests those against nullptr
to decide whether to call them. This works because dlopen
will update those symbols’ values to a pointer to the function within the loaded library. (Actually, it does this with both functions and data, to handle an edge case where we want to declare that a crash is expected and we do not want a stack trace from it.)
The --dll
flag will probably be removed soon(ish), because it relies on PR_LoadLibrary
(well, PR_LoadLibraryWithFlags
) to do its thing, and that is implemented in NSPR. We barely rely on NSPR now, and normally do local testing without it, and we’d like to entirely remove it as a dependency.
In the meantime, you could probably use --dll
to do some tricky and horrible stuff by generating a library with initialization code that sets things up. The linking would probably get messy, but if you had a library that had undefined symbols coming from mozjs, I think you could use JS_DefineFunction
to install JS functions on the global that invoked functionality from the loaded library (or a library that it was linked to, if you wanted to keep your bootstrapping code separate).
I don’t recommend it, and it may stop working before too long, but I think it’s at least theoretically doable. Though I don’t think it would have any advantages, and several disadvantages, to using ctypes instead. (Note that ctypes depends on NSPR as well, so --dll
and ctypes will most likely be going away at the same time.)
In QuickJS we can do this https://github.com/guest271314/webserver-c/tree/quickjs-webserver in C
$ clang -Wall -L./quickjs -fvisibility=hidden -shared \
-I ./quickjs -g -ggdb -O webserver.c -o webserver.so
#include "quickjs.h"
#include "cutils.h"
// ...
void status(JSContext* ctx, JSValue this_val, char* str) {
JSValue callback, params[1];
params[0] = JS_NewString(ctx, str);
callback = JS_Call(ctx, this_val, JS_UNDEFINED, 1, params);
JS_FreeValue(ctx, callback);
JS_FreeValue(ctx, params[0]);
}
static JSValue module_webserver(JSContext* ctx,
JSValueConst this_val,
int argc,
JSValueConst argv[]) {
// Check for correct callback function
if(!JS_IsFunction(ctx, argv[1])) {
return JS_ThrowTypeError(ctx, "argument 2 must be a function");
}
// ...
// Shared-object entry point. when loaded by QuickJS will get executed
// (make the function the only exported name of the shared-object)
__attribute__((visibility("default"))) JSModuleDef* js_init_module(
JSContext* ctx,
const char* module_name) {
JSModuleDef* m;
// Creates a new module, named according to the 'from' string given in the
// import directive and with the initialization function above.
m = JS_NewCModule(ctx, module_name, module_init);
if (!m)
return NULL;
// Adds the exports
JS_AddModuleExportList(ctx, m, module_funcs, countof(module_funcs));
return m;
}
Then do this in the runtime
#!/usr/bin/env -S ./qjs -m --std
// webserver.js
import {webserver} from './webserver.so';
So I was thinking I could import
this https://github.com/guest271314/NativeMessagingHosts/blob/main/nm_cpp.cpp C++ into js
to handle reading standrd input
// C++ Native Messaging host
// https://browserext.github.io/native-messaging/
// https://developer.chrome.com/docs/apps/nativeMessaging/
// https://www.reddit.com/user/Eternal_Weeb/
// guest271314, 2022
#include <iostream>
#include <vector>
using namespace std;
vector<uint8_t> getMessage() {
uint32_t length = 0;
size_t size = fread(&length, sizeof(length), 1, stdin);
vector<uint8_t> message(length);
size = fread(message.data(), sizeof(*message.data()), message.size(), stdin);
return message;
}
I do have a working js
version (and a working d8
version). It’s just that js
and V8’s d8
https://github.com/guest271314/native-messaging-d8 are horrible at reading standard input with readline()
because there’s no consistency or uniformity or compatibility in JavaScript for reading STDIN or writing to STDOUT - it’s unspecified.
This is what I’m doing right now with js
https://github.com/guest271314/native-messaging-spidermonkey-shell/blob/main/nm_spidermonkey.js
#!/usr/bin/env -S JS_STDERR=err.txt /home/user/.jsvu/engines/spidermonkey/spidermonkey
// /home/user/bin/jsshell-linux-x86_64/js
// SpiderMonkey Shell Native Messaging host
// guest271314 7-7-2023, 6-16-2024
function encodeMessage(str) {
return new Uint8Array([...str].map((s) => s.codePointAt()));
}
function getMessage() {
// Call readline() N times to catch `\r\n\r\n"` from 2d port.postMessage()
let stdin;
while (true) {
stdin = readline();
if (stdin !== null) {
break;
}
}
let data = `${stdin}`.replace(/[\r\n]+|\\x([0-9A-Fa-f]{2,4})/gu, "")
.replace(/[^A-Za-z0-9\s\[,\]\{\}:_"]+/igu, "")
.replace(/^"rnrn/gu, "")
.replace(/^[#\r\n\}_]+(?=\[)/gu, "")
.replace(/^"(?=["\{]+)|^"(?!"$)/gu, "")
.replace(/^\[(?=\[(?!.*\]{2}$))/gu, "")
.replace(/^\{(?!\}|.+\}$)/gu, "")
.replace(/^[0-9A-Z]+(?=[\[\{"])/igu, "")
.replace(/^[\]\}](?=\[)/i, "")
.trimStart().trim();
// https://stackoverflow.com/a/52434176
// let previous = redirect("length.txt");
// putstr(data.length);
// redirect(previous); // restore the redirection to stdout
// os.file.writeTypedArrayToFile("input.txt", encodeMessage(data));
return encodeMessage(data);
}
function sendMessage(message) {
os.file.writeTypedArrayToFile(
"/proc/self/fd/1",
new Uint32Array([message.length]),
);
os.file.writeTypedArrayToFile("/proc/self/fd/1", message);
}
function main() {
// Send help() to client
// const previous = redirect("help.txt");
// putstr(help());
// redirect(previous); // restore the redirection to stdout
// const h = read("help.txt", "binary");
// sendMessage(encodeMessage(JSON.stringify([...h])));
while (true) {
// Terminate current process when chrome-extension://<ID> is not a running process
// \x00 or \x01, 512 or 513
if (os.system("pgrep", ["-f", scriptArgs[0]]) == 513) {
break;
}
const message = getMessage();
sendMessage(message);
}
}
try {
main();
} catch (e) {
os.file.writeTypedArrayToFile(
"caught.txt",
encodeMessage(JSON.stringify(e.message)),
);
quit();
}