-
Notifications
You must be signed in to change notification settings - Fork 287
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
Dynamic loading sample #645
Conversation
@@ -5,16 +5,20 @@ authors = ["Dave Herman <[email protected]>"] | |||
description = "Bindings to the Node.js native addon API, used by the Neon implementation." | |||
repository = "https://github.com/neon-bindings/neon" | |||
license = "MIT/Apache-2.0" | |||
edition = "2018" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to edition 2018 because I only know how to use macro_rules!
in this edition
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
#[allow(non_camel_case_types)] | ||
#[allow(dead_code)] | ||
pub(crate) enum NapiStatus { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy-paste from the N-API docs; with repr(C)
this is fine. N-API will only ever add new values at the end
} | ||
|
||
pub(crate) struct Napi<'a> { | ||
pub napi_get_undefined: Symbol<'a, unsafe extern "C" fn(env: NapiEnv, out: *mut NapiValue) -> NapiStatus>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the actual declaration is quite hard to read here, because of the Symbol<>
wrapper and because it's in the middle of the line instead of at the start. Could we make it simpler somehow?
@goto-bus-stop This is really awesome! I think you're right that it would need to be a proc-macro because of the function arguments; I don't think there's a reasonable way to match all the variations there. But, I think a proc-macro might make this a bit too complicated. We could potentially write some python or node.js to generate it offline. Overall, I really like this strategy. I think it's worth writing the trampoline functions to eliminate the fn must_initialize() -> ! {
panic!("Must initialize N-API calls");
}
unsafe extern "C" fn _napi_get_undefined(
_env: NapiEnv,
_out: *mut NapiValue,
) -> NapiStatus {
must_initialize()
}
static mut NAPI_GET_UNDEFINED: unsafe extern "C" fn(
NapiEnv,
*mut NapiValue,
) -> NapiStatus = _napi_get_undefined;
#[inline]
pub(crate) unsafe fn napi_get_undefined(
env: NapiEnv,
out: *mut NapiValue,
) -> NapiStatus {
NAPI_GET_UNDEFINED(env, out)
}
unsafe extern "C" fn _napi_create_double(
_env: NapiEnv,
_value: f64,
_out: *mut NapiValue,
) -> NapiStatus {
must_initialize()
}
static mut NAPI_CREATE_DOUBLE: unsafe extern "C" fn(
NapiEnv,
f64,
*mut NapiValue,
) -> NapiStatus = _napi_create_double;
#[inline]
pub(crate) unsafe fn napi_create_double(
env: NapiEnv,
value: f64,
out: *mut NapiValue,
) -> NapiStatus {
NAPI_CREATE_DOUBLE(env, value, out)
} It's very verbose, but fairly straightforward. Each function has 3 copies of the signature:
Unfortunately, the never type ( With the trampoline functions, I don't think we need |
(FWIW the approach in this PR also compiles to a single indirect call in release mode) |
I agree that a proc macro would be too complicated, I guess I just wanted to explore that a bit after the recent I do like the simplicity of that function pointer approach. Having to also duplicate the arguments list is a bit unfortunate. Maybe it's fine since the compiler will yell at you if they are out of sync, and it should be one of the least-modified parts of the codebase. The lazy_static isn't strictly necessary for this PR either. I figured that the cost of lazy_static is irrelevant since it's only paid once during initialization. But there is also a cognitive cost to having the lifetimes on the |
Last commit removes the |
6ce7cf0 removes the |
I gave it a try. Lots and lots of typing. 😆 |
This PR implements the approach described in #584, and uses it in the functions in
neon_runtime::napi::primitive
.The N-API symbols are loaded from the host process, which could be Node.js or Electron. We have to provide our own N-API type declarations with this approach. Since N-API is ABI-stable, that should not require a lot of maintenance.
Here, we use
libloading
to retrieve symbols from the host executable. We store raw function pointers, bypassing somelibloading
safety features. This is OK for us because we run as a module inside the host executable: the executable will not be unloaded before we are, so the pointers will not become invalid at any point while we are running. Currently, this sample uses hand-written trampoline functions to call the actual function pointers. The original PR used a macro (described below).Original PR description
Some changes were made to the approach. This was the original description:
Silly(?) macro idea
Maybe we can make a macro like this to ease future maintenance:
I think it would have to be a proc macro, since we need to output those declarations several
times in different shapes. It would allow generating both the property declarations in the Napi
struct, and the
library.get()
calls in thefrom_host
constructor. It could also generatetrampoline functions so we don't have to use an
napi!()
macro wrapper for napi function calls:Might well be a case where writing the abstraction would be a lot more work than just doing it by hand, though.