From a84c2dc34f71df87106dc293927cc1b66f82dc4e Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Dec 2024 15:45:34 -0800 Subject: [PATCH 01/11] New libNode API --- bench/Benchmarks.cs | 24 +- .../JSRuntimeContextExtensions.cs | 2 +- src/NodeApi/NodeApiStatusExtensions.cs | 25 + src/NodeApi/Runtime/JSRuntime.Types.cs | 489 +++++++++++++++++- src/NodeApi/Runtime/JSRuntime.cs | 107 +++- src/NodeApi/Runtime/NodeJSRuntime.cs | 61 +++ src/NodeApi/Runtime/NodejsEmbedding.cs | 373 +++++++++++++ .../Runtime/NodejsEmbeddingModuleInfo.cs | 13 + .../Runtime/NodejsEmbeddingNodeApiScope.cs | 42 ++ .../Runtime/NodejsEmbeddingPlatform.cs | 141 +++++ .../NodejsEmbeddingPlatformSettings.cs | 35 ++ src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs | 126 +++++ .../Runtime/NodejsEmbeddingRuntimeSettings.cs | 121 +++++ ...ent.cs => NodejsEmbeddingThreadRuntime.cs} | 71 +-- src/NodeApi/Runtime/NodejsPlatform.cs | 93 ---- .../Runtime/NodejsRuntime.Embedding.cs | 353 ++++++++++--- src/NodeApi/Runtime/TracingJSRuntime.cs | 197 +++++-- src/NodeApi/Runtime/Utf8StringArray.cs | 95 ++++ test/GCTests.cs | 14 +- test/NodejsEmbeddingTests.cs | 62 ++- 20 files changed, 2134 insertions(+), 310 deletions(-) create mode 100644 src/NodeApi/Runtime/NodejsEmbedding.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs create mode 100644 src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs rename src/NodeApi/Runtime/{NodejsEnvironment.cs => NodejsEmbeddingThreadRuntime.cs} (88%) delete mode 100644 src/NodeApi/Runtime/NodejsPlatform.cs create mode 100644 src/NodeApi/Runtime/Utf8StringArray.cs diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index d4b81f7d..25d165b1 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -50,7 +50,8 @@ public static void Main(string[] args) GetCurrentPlatformRuntimeIdentifier(), "libnode" + GetSharedLibraryExtension()); - private napi_env _env; + private NodejsEmbeddingRuntime? _runtime; + private NodejsEmbeddingNodeApiScope? _nodeApiScope; private JSValue _jsString; private JSFunction _jsFunction; private JSFunction _jsFunctionWithArgs; @@ -84,16 +85,17 @@ public static void Method() { } /// protected void Setup() { - NodejsPlatform platform = new(LibnodePath/*, args: new[] { "node", "--expose-gc" }*/); - - // This setup avoids using NodejsEnvironment so benchmarks can run on the same thread. - // NodejsEnvironment creates a separate thread that would slow down the micro-benchmarks. - platform.Runtime.CreateEnvironment( - platform, Console.WriteLine, null, NodejsEnvironment.NodeApiVersion, out _env) - .ThrowIfFailed(); - - // The new scope instance saves itself as the thread-local JSValueScope.Current. - JSValueScope scope = new(JSValueScopeType.Root, _env, platform.Runtime); + NodejsEmbeddingPlatform platform = new( + LibnodePath, + new NodejsEmbeddingPlatformSettings { Args = new[] { "node", "--expose-gc" } }); + + // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on + // the same thread. NodejsEmbeddingThreadRuntime creates a separate thread that would slow + // down the micro-benchmarks. + _runtime = new(platform); + // The nodeApiScope creates JSValueScope instance that saves itself as + // the thread-local JSValueScope.Current. + _nodeApiScope = new(_runtime); // Create some JS values that will be used by the benchmarks. diff --git a/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs b/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs index 16bf7067..04e35c5e 100644 --- a/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs +++ b/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs @@ -54,7 +54,7 @@ public static T Import( /// Both and /// are null. public static T Import( - this NodejsEnvironment nodejs, + this NodejsEmbeddingThreadRuntime nodejs, string? module, string? property, bool esModule, diff --git a/src/NodeApi/NodeApiStatusExtensions.cs b/src/NodeApi/NodeApiStatusExtensions.cs index 62596899..65a23c45 100644 --- a/src/NodeApi/NodeApiStatusExtensions.cs +++ b/src/NodeApi/NodeApiStatusExtensions.cs @@ -59,5 +59,30 @@ public static T ThrowIfFailed(this napi_status status, status.ThrowIfFailed(memberName, sourceFilePath, sourceLineNumber); return value; } + + [StackTraceHidden] + public static void ThrowIfFailed([DoesNotReturnIf(true)] this node_embedding_status status, + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + if (status == node_embedding_status.ok) + return; + + throw new JSException($"Error in {memberName} at {sourceFilePath}:{sourceLineNumber}"); + } + + // Throw if status is not napi_ok. Otherwise, return the provided value. + // This function helps writing compact wrappers for the interop calls. + [StackTraceHidden] + public static T ThrowIfFailed(this node_embedding_status status, + T value, + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + status.ThrowIfFailed(memberName, sourceFilePath, sourceLineNumber); + return value; + } } diff --git a/src/NodeApi/Runtime/JSRuntime.Types.cs b/src/NodeApi/Runtime/JSRuntime.Types.cs index 54d3b20e..fa8bb070 100644 --- a/src/NodeApi/Runtime/JSRuntime.Types.cs +++ b/src/NodeApi/Runtime/JSRuntime.Types.cs @@ -31,7 +31,12 @@ public record struct napi_handle_scope(nint Handle); public record struct napi_escapable_handle_scope(nint Handle); public record struct napi_callback_info(nint Handle); public record struct napi_deferred(nint Handle); - public record struct napi_platform(nint Handle); + + public record struct node_embedding_platform(nint Handle); + public record struct node_embedding_runtime(nint Handle); + public record struct node_embedding_platform_config(nint Handle); + public record struct node_embedding_runtime_config(nint Handle); + public record struct node_embedding_node_api_scope(nint Handle); //=========================================================================== // Enum types @@ -111,6 +116,119 @@ public enum napi_status : int napi_would_deadlock, } + public enum node_embedding_status : int + { + ok = 0, + generic_error = 1, + null_arg = 2, + bad_arg = 3, + // This value is added to the exit code in cases when Node.js API returns + // an error exit code. + error_exit_code = 512, + } + + [Flags] + public enum node_embedding_platform_flags : int + { + none = 0, + // Enable stdio inheritance, which is disabled by default. + // This flag is also implied by + // node_embedding_platform_flags_no_stdio_initialization. + enable_stdio_inheritance = 1 << 0, + // Disable reading the NODE_OPTIONS environment variable. + disable_node_options_env = 1 << 1, + // Do not parse CLI options. + disable_cli_options = 1 << 2, + // Do not initialize ICU. + no_icu = 1 << 3, + // Do not modify stdio file descriptor or TTY state. + no_stdio_initialization = 1 << 4, + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + no_default_signal_handling = 1 << 5, + // Do not initialize OpenSSL config. + no_init_openssl = 1 << 8, + // Do not initialize Node.js debugging based on environment variables. + no_parse_global_debug_variables = 1 << 9, + // Do not adjust OS resource limits for this process. + no_adjust_resource_limits = 1 << 10, + // Do not map code segments into large pages for this process. + no_use_large_pages = 1 << 11, + // Skip printing output for --help, --version, --v8-options. + no_print_help_or_version_output = 1 << 12, + // Initialize the process for predictable snapshot generation. + generate_predictable_snapshot = 1 << 14, + } + + // The flags for the Node.js runtime initialization. + // They match the internal EnvironmentFlags::Flags enum. + [Flags] + public enum node_embedding_runtime_flags : int + { + none = 0, + // Use the default behavior for Node.js instances. + default_flags = 1 << 0, + // Controls whether this Environment is allowed to affect per-process state + // (e.g. cwd, process title, uid, etc.). + // This is set when using default. + owns_process_state = 1 << 1, + // Set if this Environment instance is associated with the global inspector + // handling code (i.e. listening on SIGUSR1). + // This is set when using default. + owns_inspector = 1 << 2, + // Set if Node.js should not run its own esm loader. This is needed by some + // embedders, because it's possible for the Node.js esm loader to conflict + // with another one in an embedder environment, e.g. Blink's in Chromium. + no_register_esm_loader = 1 << 3, + // Set this flag to make Node.js track "raw" file descriptors, i.e. managed + // by fs.open() and fs.close(), and close them during + // node_embedding_delete_runtime(). + track_unmanaged_fds = 1 << 4, + // Set this flag to force hiding console windows when spawning child + // processes. This is usually used when embedding Node.js in GUI programs on + // Windows. + hide_console_windows = 1 << 5, + // Set this flag to disable loading native addons via `process.dlopen`. + // This environment flag is especially important for worker threads + // so that a worker thread can't load a native addon even if `execArgv` + // is overwritten and `--no-addons` is not specified but was specified + // for this Environment instance. + no_native_addons = 1 << 6, + // Set this flag to disable searching modules from global paths like + // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that + // do not expect to have their behaviors changed because of globally + // installed modules. + no_global_search_paths = 1 << 7, + // Do not export browser globals like setTimeout, console, etc. + no_browser_globals = 1 << 8, + // Controls whether or not the Environment should call V8Inspector::create(). + // This control is needed by embedders who may not want to initialize the V8 + // inspector in situations where one has already been created, + // e.g. Blink's in Chromium. + no_create_inspector = 1 << 9, + // Controls whether or not the InspectorAgent for this Environment should + // call StartDebugSignalHandler. This control is needed by embedders who may + // not want to allow other processes to start the V8 inspector. + no_start_debug_signal_handler = 1 << 10, + // Controls whether the InspectorAgent created for this Environment waits for + // Inspector frontend events during the Environment creation. It's used to + // call node::Stop(env) on a Worker thread that is waiting for the events. + no_wait_for_inspector_frontend = 1 << 11 + } + + public enum node_embedding_event_loop_run_mode : int + { + // Run the event loop until it is completed. + // It matches the UV_RUN_DEFAULT behavior. + default_mode = 0, + // Run the event loop once and wait if there are no items. + // It matches the UV_RUN_ONCE behavior. + once = 1, + // Run the event loop once and do not wait if there are no items. + // It matches the UV_RUN_NOWAIT behavior. + nowait = 2, + } + public record struct napi_callback(nint Handle) { #if UNMANAGED_DELEGATES @@ -141,15 +259,239 @@ public napi_finalize(napi_finalize.Delegate callback) : this(Marshal.GetFunctionPointerForDelegate(callback)) { } } - public struct napi_error_message_handler + public record struct node_embedding_release_data_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_release_data_callback(delegate* unmanaged[Cdecl] handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate(nint data); + + public node_embedding_release_data_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_handle_error_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_handle_error_callback(delegate* unmanaged[Cdecl]< + nint, nint, nuint, node_embedding_status, node_embedding_status> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate node_embedding_status Delegate( + nint cb_data, + nint messages, + nuint messages_size, + node_embedding_status status); + + public node_embedding_handle_error_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_configure_platform_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_configure_platform_callback(delegate* unmanaged[Cdecl]< + nint, node_embedding_platform_config, node_embedding_status> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate node_embedding_status Delegate( + nint cb_data, + node_embedding_platform_config platform_config); + + public node_embedding_configure_platform_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_configure_runtime_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_configure_runtime_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_platform, + node_embedding_runtime_config, + node_embedding_status> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate node_embedding_status Delegate( + nint cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config); + + public node_embedding_configure_runtime_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_get_args_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_get_args_callback(delegate* unmanaged[Cdecl]< + nint, int, nint, void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate(nint cb_data, int argc, nint argv); + + public node_embedding_get_args_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_preload_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_preload_callback(delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require); + + public node_embedding_preload_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_start_execution_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_start_execution_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + napi_value, + napi_value, + napi_value, + napi_value> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate napi_value Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs); + + public node_embedding_start_execution_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_handle_result_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_handle_result_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + napi_value, + void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value value); + + public node_embedding_handle_result_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_initialize_module_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_initialize_module_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + nint, + napi_value, + napi_value> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate napi_value Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + nint module_name, + napi_value exports); + + public node_embedding_initialize_module_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_run_task_callback(nint Handle) { - public nint Handle; +#if UNMANAGED_DELEGATES + public node_embedding_run_task_callback(delegate* unmanaged[Cdecl] handle) + : this((nint)handle) { } +#endif [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate(byte* message); + public delegate void Delegate(nint cb_data); - public napi_error_message_handler(napi_error_message_handler.Delegate handler) - => Handle = Marshal.GetFunctionPointerForDelegate(handler); + public node_embedding_run_task_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_post_task_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_post_task_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_run_task_functor, + void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_run_task_functor run_task); + + public node_embedding_post_task_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_run_node_api_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_run_node_api_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env); + + public node_embedding_run_node_api_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } } public struct napi_property_descriptor @@ -210,4 +552,139 @@ public readonly struct c_bool public static readonly c_bool True = new(true); public static readonly c_bool False = new(false); } + + public struct node_embedding_handle_error_functor + { + public nint data; + public node_embedding_handle_error_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_get_args_functor_ref : IDisposable + { + public nint data; + public node_embedding_get_args_callback invoke; + + public node_embedding_get_args_functor_ref( + object? functor, node_embedding_get_args_callback invoke) + { + data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; + this.invoke = (functor != null) ? invoke : new node_embedding_get_args_callback(0); + } + + public void Dispose() + { + if (data == 0) return; + GCHandle.FromIntPtr(data).Free(); + data = 0; + } + } + + public struct node_embedding_configure_platform_functor_ref : IDisposable + { + public nint data; + public node_embedding_configure_platform_callback invoke; + + public node_embedding_configure_platform_functor_ref( + object? functor, node_embedding_configure_platform_callback invoke) + { + data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; + this.invoke = (functor != null) + ? invoke + : new node_embedding_configure_platform_callback(0); + } + + public void Dispose() + { + if (data == 0) return; + GCHandle.FromIntPtr(data).Free(); + data = 0; + } + } + + public struct node_embedding_configure_runtime_functor_ref + { + public nint data; + public node_embedding_configure_runtime_callback invoke; + + public node_embedding_configure_runtime_functor_ref( + object? functor, node_embedding_configure_runtime_callback invoke) + { + data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; + this.invoke = (functor != null) + ? invoke + : new node_embedding_configure_runtime_callback(0); + } + + public void Dispose() + { + if (data == 0) return; + GCHandle.FromIntPtr(data).Free(); + data = 0; + } + } + + public struct node_embedding_preload_functor + { + public nint data; + public node_embedding_preload_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_start_execution_functor + { + public nint data; + public node_embedding_start_execution_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_handle_result_functor + { + public nint data; + public node_embedding_handle_result_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_initialize_module_functor + { + public nint data; + public node_embedding_initialize_module_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_run_task_functor + { + public nint data; + public node_embedding_run_task_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_post_task_functor + { + public nint data; + public node_embedding_post_task_callback invoke; + public node_embedding_release_data_callback release; + } + + public struct node_embedding_run_node_api_functor_ref : IDisposable + { + public nint data; + public node_embedding_run_node_api_callback invoke; + + public node_embedding_run_node_api_functor_ref( + object? functor, node_embedding_run_node_api_callback invoke) + { + data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; + this.invoke = (functor != null) + ? invoke + : new node_embedding_run_node_api_callback(0); + } + + public void Dispose() + { + if (data == 0) return; + GCHandle.FromIntPtr(data).Free(); + data = 0; + } + } } diff --git a/src/NodeApi/Runtime/JSRuntime.cs b/src/NodeApi/Runtime/JSRuntime.cs index 8177e8cc..86f574f6 100644 --- a/src/NodeApi/Runtime/JSRuntime.cs +++ b/src/NodeApi/Runtime/JSRuntime.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -519,20 +520,98 @@ public virtual napi_status GetBufferInfo( #region Embedding - public virtual napi_status CreatePlatform( - string[]? args, - Action? errorHandler, - out napi_platform result) => throw NS(); - public virtual napi_status DestroyPlatform(napi_platform platform) => throw NS(); - public virtual napi_status CreateEnvironment( - napi_platform platform, - Action? errorHandler, - string? mainScript, - int apiVersion, - out napi_env result) => throw NS(); - public virtual napi_status DestroyEnvironment(napi_env env, out int exitCode) => throw NS(); - public virtual napi_status RunEnvironment(napi_env env) => throw NS(); - public virtual napi_status AwaitPromise(napi_env env, napi_value promise, out napi_value result) => throw NS(); + public virtual node_embedding_status + EmbeddingOnError(node_embedding_handle_error_functor error_handler) => throw NS(); + + public virtual node_embedding_status EmbeddingSetApiVersion( + int embedding_api_version, + int node_api_version) => throw NS(); + + public virtual node_embedding_status EmbeddingRunMain( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_configure_runtime_functor_ref configure_runtime) => throw NS(); + + public virtual node_embedding_status EmbeddingCreatePlatform( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + out node_embedding_platform result) => throw NS(); + + public virtual node_embedding_status + EmbeddingDeletePlatform(node_embedding_platform platform) => throw NS(); + + public virtual node_embedding_status EmbeddingPlatformSetFlags( + node_embedding_platform_config platform_config, + node_embedding_platform_flags flags) => throw NS(); + + public virtual node_embedding_status EmbeddingPlatformGetParsedArgs( + node_embedding_platform platform, + node_embedding_get_args_functor_ref get_args, + node_embedding_get_args_functor_ref get_runtime_args) => throw NS(); + + public virtual node_embedding_status EmbeddingRunRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime) => throw NS(); + + public virtual node_embedding_status EmbeddingCreateRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime, + out node_embedding_runtime result) => throw NS(); + + public virtual node_embedding_status + EmbeddingDeleteRuntime(node_embedding_runtime runtime) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeSetFlags( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_flags flags) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeSetArgs( + node_embedding_runtime_config runtime_config, + ReadOnlySpan args, + ReadOnlySpan runtime_args) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeOnPreload( + node_embedding_runtime_config runtime_config, + node_embedding_preload_functor run_preload) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeOnStartExecution( + node_embedding_runtime_config runtime_config, + node_embedding_start_execution_functor start_execution, + node_embedding_handle_result_functor handle_result) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeAddModule( + node_embedding_runtime_config runtime_config, + string moduleName, + node_embedding_initialize_module_functor init_module, + int module_node_api_version) => throw NS(); + + public virtual node_embedding_status EmbeddingRuntimeSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_post_task_functor post_task) => throw NS(); + + public virtual node_embedding_status EmbeddingRunEventLoop( + node_embedding_runtime runtime, + node_embedding_event_loop_run_mode run_mode, + out bool has_more_work) => throw NS(); + + public virtual node_embedding_status + EmbeddingCompleteEventLoop(node_embedding_runtime runtime) => throw NS(); + + public virtual node_embedding_status + EmbeddingTerminateEventLoop(node_embedding_runtime runtime) => throw NS(); + + public virtual node_embedding_status EmbeddingRunNodeApi( + node_embedding_runtime runtime, + node_embedding_run_node_api_functor_ref run_node_api) => throw NS(); + + public virtual node_embedding_status EmbeddingOpenNodeApiScope( + node_embedding_runtime runtime, + out node_embedding_node_api_scope node_api_scope, + out napi_env env) => throw NS(); + + public virtual node_embedding_status EmbeddingCloseNodeApiScope( + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope) => throw NS(); #endregion } diff --git a/src/NodeApi/Runtime/NodeJSRuntime.cs b/src/NodeApi/Runtime/NodeJSRuntime.cs index acdec041..72c711cc 100644 --- a/src/NodeApi/Runtime/NodeJSRuntime.cs +++ b/src/NodeApi/Runtime/NodeJSRuntime.cs @@ -100,6 +100,67 @@ private nint Import(string functionName) return function; } + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl])Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] + Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] + Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + private static unsafe string? PtrToStringUTF8(byte* ptr) { if (ptr == null) return null; diff --git a/src/NodeApi/Runtime/NodejsEmbedding.cs b/src/NodeApi/Runtime/NodejsEmbedding.cs new file mode 100644 index 00000000..50becd21 --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbedding.cs @@ -0,0 +1,373 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +#if UNMANAGED_DELEGATES +using System.Runtime.CompilerServices; +#endif +using System.Runtime.InteropServices; +using static JSRuntime; + +/// +/// Shared code for the Node.js embedding classes. +/// +public sealed class NodejsEmbedding +{ + public static readonly int EmbeddingApiVersion = 1; + public static readonly int NodeApiVersion = 9; + + private static JSRuntime? _jsRuntime; + + public static JSRuntime JSRuntime + { + get + { + if (_jsRuntime == null) + { + throw new InvalidOperationException("The JSRuntime is not initialized."); + } + return _jsRuntime; + } + } + + public static void Initialize(string libnodePath) + { + if (string.IsNullOrEmpty(libnodePath)) throw new ArgumentNullException(nameof(libnodePath)); + if (_jsRuntime != null) + { + throw new InvalidOperationException( + "The JSRuntime can be initialized only once per process."); + } + nint libnodeHandle = NativeLibrary.Load(libnodePath); + _jsRuntime = new NodejsRuntime(libnodeHandle); + } + + public delegate node_embedding_status HandleErrorCallback( + string[] messages, node_embedding_status status); + public delegate void ConfigurePlatformCallback( + node_embedding_platform_config platformConfig); + public delegate void ConfigureRuntimeCallback( + node_embedding_platform platform, node_embedding_runtime_config platformConfig); + public delegate void GetArgsCallback(string[] args); + public delegate void PreloadCallback( + NodejsEmbeddingRuntime runtime, JSValue process, JSValue require); + public delegate JSValue StartExecutionCallback( + NodejsEmbeddingRuntime runtime, JSValue process, JSValue require, JSValue runCommonJS); + public delegate void HandleResultCallback( + NodejsEmbeddingRuntime runtime, JSValue value); + public delegate JSValue InitializeModuleCallback( + NodejsEmbeddingRuntime runtime, string moduleName, JSValue exports); + public delegate void RunTaskCallback(); + public delegate void PostTaskCallback(node_embedding_run_task_functor runTask); + public delegate void RunNodeApiCallback(NodejsEmbeddingRuntime runtime); + +#if UNMANAGED_DELEGATES + internal static readonly unsafe delegate* unmanaged[Cdecl] + s_releaseDataCallback = &ReleaseDataCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, nint, nuint, node_embedding_status, node_embedding_status> + s_handleErrorCallback = &HandleErrorCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_platform_config, node_embedding_status> + s_configurePlatformCallback = &ConfigurePlatformCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, + node_embedding_platform, + node_embedding_runtime_config, + node_embedding_status> + s_configureRuntimeCallback = &ConfigureRuntimeCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, int, nint, void> + s_getArgsCallback = &GetArgsCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> + s_preloadCallback = &PreloadCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, napi_value, napi_value> + s_startExecutionCallback = &StartExecutionCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, void> + s_handleResultCallback = &HandleResultCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, nint, napi_value, napi_value> + s_initializeModuleCallback = &InitializeModuleCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl] + s_runTaskCallback = &RunTaskCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_run_task_functor, void> + s_postTaskCallback = &PostTaskCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, void> + s_runNodeApiCallback = &RunNodeApiCallbackAdapter; +#else + internal static readonly node_embedding_release_data_callback.Delegate + s_releaseDataCallback = ReleaseDataCallbackAdapter; + internal static readonly node_embedding_handle_error_callback.Delegate + s_handleErrorCallback = HandleErrorCallbackAdapter; + internal static readonly node_embedding_configure_platform_callback.Delegate + s_configurePlatformCallback = ConfigurePlatformCallbackAdapter; + internal static readonly node_embedding_configure_runtime_callback.Delegate + s_configureRuntimeCallback = ConfigureRuntimeCallbackAdapter; + internal static readonly node_embedding_get_args_callback.Delegate + s_getArgsCallback = GetArgsCallbackAdapter; + internal static readonly node_embedding_preload_callback.Delegate + s_preloadCallback = PreloadCallbackAdapter; + internal static readonly node_embedding_start_execution_callback.Delegate + s_startExecutionCallback = StartExecutionCallbackAdapter; + internal static readonly node_embedding_handle_result_callback.Delegate + s_handleResultCallback = HandleResultCallbackAdapter; + internal static readonly node_embedding_initialize_module_callback.Delegate + s_initializeModuleCallback = InitializeModuleCallbackAdapter; + internal static readonly node_embedding_run_task_callback.Delegate + s_runTaskCallback = RunTaskCallbackAdapter; + internal static readonly node_embedding_post_task_callback.Delegate + s_postTaskCallback = PostTaskCallbackAdapter; + internal static readonly node_embedding_run_node_api_callback.Delegate + s_runNodeApiCallback = RunNodeApiCallbackAdapter; +#endif + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void ReleaseDataCallbackAdapter(nint data) + { + if (data != default) + { + GCHandle.FromIntPtr(data).Free(); + } + } + + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe node_embedding_status HandleErrorCallbackAdapter( + nint cb_data, + nint messages, + nuint messages_size, + node_embedding_status status) + { + try + { + var callback = (HandleErrorCallback)GCHandle.FromIntPtr(cb_data).Target!; + return callback(Utf8StringArray.ToStringArray(messages, (int)messages_size), status); + } + catch (Exception) + { + return node_embedding_status.generic_error; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe node_embedding_status ConfigurePlatformCallbackAdapter( + nint cb_data, + node_embedding_platform_config platform_config) + { + try + { + var callback = (ConfigurePlatformCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(platform_config); + return node_embedding_status.ok; + } + catch (Exception) + { + return node_embedding_status.generic_error; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe node_embedding_status ConfigureRuntimeCallbackAdapter( + nint cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) + { + try + { + var callback = (ConfigureRuntimeCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(platform, runtime_config); + return node_embedding_status.ok; + } + catch (Exception) + { + return node_embedding_status.generic_error; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void GetArgsCallbackAdapter(nint cb_data, int argc, nint argv) + { + try + { + var callback = (GetArgsCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(Utf8StringArray.ToStringArray(argv, argc)); + } + catch (Exception) + { + // TODO: Handle exception. + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void PreloadCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require) + { + try + { + var callback = (PreloadCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + callback(embeddingRuntime, new JSValue(process), new JSValue(require)); + } + catch (Exception) + { + // TODO: Handle exception. + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe napi_value StartExecutionCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) + { + try + { + var callback = (StartExecutionCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + return (napi_value)callback( + embeddingRuntime, new JSValue(process), new JSValue(require), new JSValue(run_cjs)); + } + catch (Exception) + { + return default; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void HandleResultCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value value) + { + try + { + var callback = (HandleResultCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + callback(embeddingRuntime, new JSValue(value)); + } + catch (Exception) + { + // TODO: Handle exception. + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe napi_value InitializeModuleCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + nint module_name, + napi_value exports) + { + try + { + var callback = (InitializeModuleCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + return (napi_value)callback( + embeddingRuntime, + Utf8StringArray.PtrToStringUTF8((byte*)module_name), + new JSValue(exports)); + } + catch (Exception) + { + return default; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void RunTaskCallbackAdapter(nint cb_data) + { + try + { + var callback = (RunTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(); + } + catch (Exception) + { + // TODO: Handle exception. + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void PostTaskCallbackAdapter( + nint cb_data, + node_embedding_run_task_functor run_task) + { + try + { + var callback = (PostTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(run_task); + } + catch (Exception) + { + // TODO: Handle exception. + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void RunNodeApiCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env) + { + try + { + var callback = (RunNodeApiCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.FromHandle(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + callback(embeddingRuntime); + } + catch (Exception) + { + // TODO: Handle exception. + } + } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs b/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs new file mode 100644 index 00000000..44cdb2f3 --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using static NodejsEmbedding; + +public class NodejsEmbeddingModuleInfo +{ + public string? Name { get; set; } + public InitializeModuleCallback? OnInitialize { get; set; } + public int? NodeApiVersion { get; set; } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs b/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs new file mode 100644 index 00000000..35fe360d --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using static JSRuntime; + +public sealed class NodejsEmbeddingNodeApiScope : IDisposable +{ + NodejsEmbeddingRuntime _runtime; + private node_embedding_node_api_scope _nodeApiScope; + private JSValueScope _valueScope; + + public NodejsEmbeddingNodeApiScope(NodejsEmbeddingRuntime runtime) + { + _runtime = runtime; + NodejsEmbeddingRuntime.JSRuntime.EmbeddingOpenNodeApiScope( + runtime, out _nodeApiScope, out napi_env env) + .ThrowIfFailed(); + _valueScope = new JSValueScope( + JSValueScopeType.Root, env, NodejsEmbeddingRuntime.JSRuntime); + } + + /// + /// Gets a value indicating whether the Node.js embedding Node-API scope is disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the Node.js embedding Node-API scope. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + + _valueScope.Dispose(); + NodejsEmbeddingRuntime.JSRuntime.EmbeddingCloseNodeApiScope(_runtime, _nodeApiScope) + .ThrowIfFailed(); + } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs new file mode 100644 index 00000000..8c57bed1 --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using System.Runtime.InteropServices; +using static JSRuntime; +using static NodejsEmbedding; + +/// +/// Manages a Node.js platform instance, provided by `libnode`. +/// +/// +/// Only one Node.js platform instance can be created per process. Once the platform is disposed, +/// another platform instance cannot be re-initialized. One or more +/// instances may be created using the platform. +/// +public sealed class NodejsEmbeddingPlatform : IDisposable +{ + private node_embedding_platform _platform; + + public static implicit operator node_embedding_platform(NodejsEmbeddingPlatform platform) + => platform._platform; + + /// + /// Initializes the Node.js platform. + /// + /// Path to the `libnode` shared library, including extension. + /// Optional platform settings. + /// A Node.js platform instance has already been + /// loaded in the current process. + public unsafe NodejsEmbeddingPlatform( + string libnodePath, NodejsEmbeddingPlatformSettings? settings) + { + if (Current != null) + { + throw new InvalidOperationException( + "Only one Node.js platform instance per process is allowed."); + } + Current = this; + Initialize(libnodePath); + + if (settings?.OnError != null) + { + var handle_error_functor = new node_embedding_handle_error_functor + { + data = (nint)GCHandle.Alloc(settings.OnError), + invoke = new node_embedding_handle_error_callback(s_handleErrorCallback), + release = new node_embedding_release_data_callback(s_releaseDataCallback), + }; + JSRuntime.EmbeddingOnError(handle_error_functor).ThrowIfFailed(); + } + + JSRuntime.EmbeddingSetApiVersion(EmbeddingApiVersion, NodeApiVersion).ThrowIfFailed(); + + using node_embedding_configure_platform_functor_ref configurePlatformFunctorRef = + settings ?? new NodejsEmbeddingPlatformSettings(); + + JSRuntime.EmbeddingCreatePlatform( + settings?.Args, configurePlatformFunctorRef, out _platform) + .ThrowIfFailed(); + } + + internal NodejsEmbeddingPlatform(node_embedding_platform platform) + { + _platform = platform; + } + + /// + /// Gets a value indicating whether the current platform has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Gets the Node.js platform instance for the current process, or null if not initialized. + /// + public static NodejsEmbeddingPlatform? Current { get; private set; } + + public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + + /// + /// Disposes the platform. After disposal, another platform instance may not be initialized + /// in the current process. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + JSRuntime.EmbeddingDeletePlatform(_platform); + } + + /// + /// Creates a new Node.js embedding runtime with a dedicated main thread. + /// + /// Optional directory that is used as the base directory when resolving + /// imported modules, and also as the value of the global `__dirname` property. If unspecified, + /// importing modules is not enabled and `__dirname` is undefined. + /// Optional script to run in the environment. (Literal script content, + /// not a path to a script file.) + /// A new instance. + public NodejsEmbeddingThreadRuntime CreateThreadRuntime( + string? baseDir = null, + NodejsEmbeddingRuntimeSettings? settings = null) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); + + return new NodejsEmbeddingThreadRuntime(this, baseDir, settings); + } + + public unsafe void GetParsedArgs(GetArgsCallback? getArgs, GetArgsCallback? getRuntimeArgs) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); + + using var getArgsFunctorRef = new node_embedding_get_args_functor_ref( + getArgs, new node_embedding_get_args_callback(s_getArgsCallback)); + using var getRuntimeArgsFunctorRef = new node_embedding_get_args_functor_ref( + getRuntimeArgs, new node_embedding_get_args_callback(s_getArgsCallback)); + + JSRuntime.EmbeddingPlatformGetParsedArgs( + _platform, getArgsFunctorRef, getRuntimeArgsFunctorRef).ThrowIfFailed(); + } + + public string[] GetParsedArgs() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); + + string[]? result = null; + GetParsedArgs((string[] args) => result = args, null); + return result ?? Array.Empty(); + } + + public string[] GetRuntimeParsedArgs() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); + + string[]? result = null; + GetParsedArgs(null, (string[] args) => result = args); + return result ?? Array.Empty(); + } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs b/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs new file mode 100644 index 00000000..ff197ad2 --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using static JSRuntime; +using static NodejsEmbedding; + +public class NodejsEmbeddingPlatformSettings +{ + public node_embedding_platform_flags? PlatformFlags { get; set; } + public string[]? Args { get; set; } + public HandleErrorCallback? OnError { get; set; } + public ConfigurePlatformCallback? ConfigurePlatform { get; set; } + + public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + + public static unsafe implicit operator node_embedding_configure_platform_functor_ref( + NodejsEmbeddingPlatformSettings? settings) + { + var confgurePlatform = new ConfigurePlatformCallback((config) => + { + if (settings?.PlatformFlags != null) + { + JSRuntime.EmbeddingPlatformSetFlags(config, settings.PlatformFlags.Value) + .ThrowIfFailed(); + } + settings?.ConfigurePlatform?.Invoke(config); + }); + + return new node_embedding_configure_platform_functor_ref( + confgurePlatform, + new node_embedding_configure_platform_callback(s_configurePlatformCallback)); + } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs new file mode 100644 index 00000000..1af6788a --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using System.Collections.Generic; +using static JSRuntime; +using static NodejsEmbedding; + +/// +/// A Node.js runtime. +/// +/// +/// Multiple Node.js environments may be created (concurrently) in the same process. +/// +public sealed class NodejsEmbeddingRuntime : IDisposable +{ + private node_embedding_runtime _runtime; + private static readonly + Dictionary _embeddedRuntimes = new(); + + public static implicit operator node_embedding_runtime(NodejsEmbeddingRuntime runtime) + => runtime._runtime; + + public struct Module + { + public string Name { get; set; } + public InitializeModuleCallback OnInitialize { get; set; } + public int? NodeApiVersion { get; set; } + } + + public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + + public NodejsEmbeddingRuntime( + NodejsEmbeddingPlatform platform, NodejsEmbeddingRuntimeSettings? settings = null) + { + JSRuntime.EmbeddingCreateRuntime( + platform, settings ?? new NodejsEmbeddingRuntimeSettings(), out _runtime) + .ThrowIfFailed(); + } + + private NodejsEmbeddingRuntime(node_embedding_runtime runtime) + { + _runtime = runtime; + lock (_embeddedRuntimes) { _embeddedRuntimes.Add(runtime, this); } + } + + public static NodejsEmbeddingRuntime? FromHandle(node_embedding_runtime runtime) + { + lock (_embeddedRuntimes) + { + if (_embeddedRuntimes.TryGetValue( + runtime, out NodejsEmbeddingRuntime? embeddingRuntime)) + { + return embeddingRuntime; + } + return null; + } + } + + public static NodejsEmbeddingRuntime GetOrCreate(node_embedding_runtime runtime) + { + NodejsEmbeddingRuntime? embeddingRuntime = FromHandle(runtime); + if (embeddingRuntime == null) + { + embeddingRuntime = new NodejsEmbeddingRuntime(runtime); + } + return embeddingRuntime; + } + + public static void Run(NodejsEmbeddingPlatform platform, + NodejsEmbeddingRuntimeSettings? settings = null) + { + JSRuntime.EmbeddingRunRuntime(platform, settings ?? new NodejsEmbeddingRuntimeSettings()) + .ThrowIfFailed(); + } + + /// + /// Gets a value indicating whether the Node.js environment is disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the Node.js environment, causing its main thread to exit. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + + lock (_embeddedRuntimes) { _embeddedRuntimes.Remove(_runtime); } + JSRuntime.EmbeddingDeleteRuntime(_runtime).ThrowIfFailed(); + } + + public unsafe bool RunEventLoop(node_embedding_event_loop_run_mode runMode) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); + + return JSRuntime.EmbeddingRunEventLoop(_runtime, runMode, out bool hasMoreWork) + .ThrowIfFailed(hasMoreWork); + } + + public unsafe void CompleteEventLoop() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); + + JSRuntime.EmbeddingCompleteEventLoop(_runtime).ThrowIfFailed(); + } + + public unsafe void TerminateEventLoop() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); + + JSRuntime.EmbeddingTerminateEventLoop(_runtime).ThrowIfFailed(); + } + + public unsafe void RunNodeApi(RunNodeApiCallback runNodeApi) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); + + using var runNodeApiFunctorRef = new node_embedding_run_node_api_functor_ref( + runNodeApi, new node_embedding_run_node_api_callback(s_runNodeApiCallback)); + JSRuntime.EmbeddingRunNodeApi(_runtime, runNodeApiFunctorRef).ThrowIfFailed(); + } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs b/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs new file mode 100644 index 00000000..acc3be9e --- /dev/null +++ b/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using static JSRuntime; +using static NodejsEmbedding; + +public sealed class NodejsEmbeddingRuntimeSettings +{ + public node_embedding_runtime_flags? RuntimeFlags { get; set; } + public string[]? Args { get; set; } + public string[]? RuntimeArgs { get; set; } + public PreloadCallback? OnPreload { get; set; } + public StartExecutionCallback? StartExecution { get; set; } + public string? MainScript { get; set; } + public HandleResultCallback? HandleStartExecutionResult { get; set; } + public IEnumerable? Modules { get; set; } + public PostTaskCallback? OnPostTask { get; set; } + public ConfigureRuntimeCallback? ConfigureRuntime { get; set; } + + public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + + public static unsafe implicit operator node_embedding_configure_runtime_functor_ref( + NodejsEmbeddingRuntimeSettings? settings) + { + var confgureRuntime = new ConfigureRuntimeCallback((platform, config) => + { + if (settings?.RuntimeFlags != null) + { + JSRuntime.EmbeddingRuntimeSetFlags(config, settings.RuntimeFlags.Value) + .ThrowIfFailed(); + } + if (settings?.Args != null || settings?.RuntimeArgs != null) + { + JSRuntime.EmbeddingRuntimeSetArgs(config, settings.Args, settings.RuntimeArgs) + .ThrowIfFailed(); + } + if (settings?.OnPreload != null) + { + var preloadFunctor = new node_embedding_preload_functor + { + data = (nint)GCHandle.Alloc(settings.OnPreload), + invoke = new node_embedding_preload_callback(s_preloadCallback), + release = new node_embedding_release_data_callback(s_releaseDataCallback), + }; + JSRuntime.EmbeddingRuntimeOnPreload(config, preloadFunctor).ThrowIfFailed(); + } + if (settings?.StartExecution != null + || settings?.MainScript != null + || settings?.HandleStartExecutionResult != null) + { + StartExecutionCallback? startExecutionCallback = + settings?.MainScript != null + ? (NodejsEmbeddingRuntime runtime, JSValue process, JSValue require, JSValue runCommonJS) + => runCommonJS.Call(JSValue.Null, (JSValue)settings.MainScript) + : settings?.StartExecution; + node_embedding_start_execution_functor startExecutionFunctor = + startExecutionCallback != null + ? new node_embedding_start_execution_functor + { + data = (nint)GCHandle.Alloc(startExecutionCallback), + invoke = new node_embedding_start_execution_callback( + s_startExecutionCallback), + release = new node_embedding_release_data_callback(s_releaseDataCallback), + } : default; + node_embedding_handle_result_functor handleStartExecutionResultFunctor = + settings?.HandleStartExecutionResult != null + ? new node_embedding_handle_result_functor + { + data = (nint)GCHandle.Alloc(settings.HandleStartExecutionResult), + invoke = new node_embedding_handle_result_callback(s_handleResultCallback), + release = new node_embedding_release_data_callback(s_releaseDataCallback), + } : default; + JSRuntime.EmbeddingRuntimeOnStartExecution( + config, startExecutionFunctor, handleStartExecutionResultFunctor) + .ThrowIfFailed(); + } + if (settings?.Modules != null) + { + foreach (NodejsEmbeddingModuleInfo module in settings.Modules) + { + var moduleFunctor = new node_embedding_initialize_module_functor + { + data = (nint)GCHandle.Alloc(module.OnInitialize + ?? throw new ArgumentException("Module initialization is missing")), + invoke = new node_embedding_initialize_module_callback( + s_initializeModuleCallback), + release = new node_embedding_release_data_callback( + s_releaseDataCallback), + }; + + JSRuntime.EmbeddingRuntimeAddModule( + config, + module.Name ?? throw new ArgumentException("Module name is missing"), + moduleFunctor, + module.NodeApiVersion ?? NodeApiVersion) + .ThrowIfFailed(); + } + } + if (settings?.OnPostTask != null) + { + var postTaskFunctor = new node_embedding_post_task_functor + { + data = (nint)GCHandle.Alloc(settings.OnPostTask), + invoke = new node_embedding_post_task_callback(s_postTaskCallback), + release = new node_embedding_release_data_callback(s_releaseDataCallback), + }; + JSRuntime.EmbeddingRuntimeSetTaskRunner(config, postTaskFunctor).ThrowIfFailed(); + } + settings?.ConfigureRuntime?.Invoke(platform, config); + }); + + return new node_embedding_configure_runtime_functor_ref( + confgureRuntime, + new node_embedding_configure_runtime_callback(s_configureRuntimeCallback)); + } +} diff --git a/src/NodeApi/Runtime/NodejsEnvironment.cs b/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs similarity index 88% rename from src/NodeApi/Runtime/NodejsEnvironment.cs rename to src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs index 07c3b8f6..616ce018 100644 --- a/src/NodeApi/Runtime/NodejsEnvironment.cs +++ b/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs @@ -20,41 +20,41 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; /// environment instance has its own dedicated execution thread. Except where otherwise documented, /// all interaction with the environment and JavaScript values associated with the environment MUST /// be executed on the environment's thread. Use the -/// to switch to the thread. +/// to switch to the thread. /// -public sealed class NodejsEnvironment : IDisposable +public sealed class NodejsEmbeddingThreadRuntime : IDisposable { - /// - /// Corresponds to NAPI_VERSION from js_native_api.h. - /// - public const int NodeApiVersion = 8; - private readonly JSValueScope _scope; private readonly Thread _thread; - private readonly TaskCompletionSource _completion = new(); + private readonly JSThreadSafeFunction? _completion; - public static explicit operator napi_env(NodejsEnvironment environment) => + public static explicit operator napi_env(NodejsEmbeddingThreadRuntime environment) => (napi_env)environment._scope; - public static implicit operator JSValueScope(NodejsEnvironment environment) => + public static implicit operator JSValueScope(NodejsEmbeddingThreadRuntime environment) => environment._scope; - internal NodejsEnvironment(NodejsPlatform platform, string? baseDir, string? mainScript) + internal NodejsEmbeddingThreadRuntime( + NodejsEmbeddingPlatform platform, + string? baseDir, + NodejsEmbeddingRuntimeSettings? settings) { JSValueScope scope = null!; JSSynchronizationContext syncContext = null!; + JSThreadSafeFunction? completion = null; using ManualResetEvent loadedEvent = new(false); _thread = new(() => { - platform.Runtime.CreateEnvironment( - (napi_platform)platform, - (error) => Console.WriteLine(error), - mainScript, - NodeApiVersion, - out napi_env env).ThrowIfFailed(); - + using var runtime = new NodejsEmbeddingRuntime(platform, settings); // The new scope instance saves itself as the thread-local JSValueScope.Current. - scope = new JSValueScope(JSValueScopeType.Root, env, platform.Runtime); + using var nodeApiScope = new NodejsEmbeddingNodeApiScope(runtime); + + completion = new JSThreadSafeFunction( + maxQueueSize: 0, + initialThreadCount: 1, + asyncResourceName: (JSValue)nameof(NodejsEmbeddingThreadRuntime)); + + scope = JSValueScope.Current; syncContext = scope.RuntimeContext.SynchronizationContext; if (!string.IsNullOrEmpty(baseDir)) @@ -65,22 +65,30 @@ internal NodejsEnvironment(NodejsPlatform platform, string? baseDir, string? mai loadedEvent.Set(); - // Run the JS event loop until disposal completes the completion source. - platform.Runtime.AwaitPromise( - env, (napi_value)(JSValue)_completion.Task.AsPromise(), out _).ThrowIfFailed(); + // Run the JS event loop until disposal unrefs the completion thread safe function. + try + { + runtime.CompleteEventLoop(); + ExitCode = 0; + } + catch (Exception) + { + ExitCode = 1; + } syncContext.Dispose(); - platform.Runtime.DestroyEnvironment(env, out int exitCode).ThrowIfFailed(); - ExitCode = exitCode; }); _thread.Start(); loadedEvent.WaitOne(); + _completion = completion; _scope = scope; SynchronizationContext = syncContext; } + public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + private static void InitializeModuleImportFunctions( JSRuntimeContext runtimeContext, string baseDir) @@ -110,10 +118,10 @@ private static void InitializeModuleImportFunctions( // The import keyword is not a function and is only available through use of an // external helper module. #if NETFRAMEWORK || NETSTANDARD - string assemblyLocation = new Uri(typeof(NodejsEnvironment).Assembly.CodeBase).LocalPath; + string assemblyLocation = new Uri(typeof(NodejsEmbeddingThreadRuntime).Assembly.CodeBase).LocalPath; #else #pragma warning disable IL3000 // Assembly.Location returns an empty string for assemblies embedded in a single-file app - string assemblyLocation = typeof(NodejsEnvironment).Assembly.Location; + string assemblyLocation = typeof(NodejsEmbeddingThreadRuntime).Assembly.Location; #pragma warning restore IL3000 #endif if (!string.IsNullOrEmpty(assemblyLocation)) @@ -198,8 +206,13 @@ public void Dispose() if (IsDisposed) return; IsDisposed = true; - // Setting the completion causes `AwaitPromise()` to return so the thread exits. - _completion.TrySetResult(true); + // Unreffing the completion should complete the Node.js event loop + // if it has nothing else to do. + if (_completion != null) + { + // The Unref must be called in the JS thread. + _completion.BlockingCall(() => _completion.Unref()); + } _thread.Join(); Debug.WriteLine($"Node.js environment exited with code: {ExitCode}"); @@ -207,7 +220,7 @@ public void Dispose() public Uri StartInspector(int? port = null, string? host = null, bool? wait = null) { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEnvironment)); + if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingThreadRuntime)); return SynchronizationContext.Run(() => { diff --git a/src/NodeApi/Runtime/NodejsPlatform.cs b/src/NodeApi/Runtime/NodejsPlatform.cs deleted file mode 100644 index ec8e5556..00000000 --- a/src/NodeApi/Runtime/NodejsPlatform.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using static JSRuntime; - -/// -/// Manages a Node.js platform instance, provided by `libnode`. -/// -/// -/// Only one Node.js platform instance can be created per process. Once the platform is disposed, -/// another platform instance cannot be re-initialized. One or more -/// instances may be created using the platform. -/// -public sealed class NodejsPlatform : IDisposable -{ - private readonly napi_platform _platform; - - public static implicit operator napi_platform(NodejsPlatform platform) => platform._platform; - - /// - /// Initializes the Node.js platform. - /// - /// Path to the `libnode` shared library, including extension. - /// Optional platform arguments. - /// A Node.js platform instance has already been - /// loaded in the current process. - public NodejsPlatform( - string libnodePath, - string[]? args = null) - { - if (string.IsNullOrEmpty(libnodePath)) throw new ArgumentNullException(nameof(libnodePath)); - - if (Current != null) - { - throw new InvalidOperationException( - "Only one Node.js platform instance per process is allowed."); - } - - nint libnodeHandle = NativeLibrary.Load(libnodePath); - Runtime = new NodejsRuntime(libnodeHandle); - - Runtime.CreatePlatform(args, (error) => Console.WriteLine(error), out _platform) - .ThrowIfFailed(); - Current = this; - } - - /// - /// Gets the Node.js platform instance for the current process, or null if not initialized. - /// - public static NodejsPlatform? Current { get; private set; } - - public JSRuntime Runtime { get; } - - /// - /// Gets a value indicating whether the current platform has been disposed. - /// - public bool IsDisposed { get; private set; } - - /// - /// Disposes the platform. After disposal, another platform instance may not be initialized - /// in the current process. - /// - public void Dispose() - { - if (IsDisposed) return; - IsDisposed = true; - - Runtime.DestroyPlatform(_platform); - } - - /// - /// Creates a new Node.js environment with a dedicated main thread. - /// - /// Optional directory that is used as the base directory when resolving - /// imported modules, and also as the value of the global `__dirname` property. If unspecified, - /// importing modules is not enabled and `__dirname` is undefined. - /// Optional script to run in the environment. (Literal script content, - /// not a path to a script file.) - /// A new instance. - public NodejsEnvironment CreateEnvironment( - string? baseDir = null, - string? mainScript = null) - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsPlatform)); - - return new NodejsEnvironment(this, baseDir, mainScript); - } -} diff --git a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs index 0e9a667b..76769303 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Runtime.InteropServices; namespace Microsoft.JavaScript.NodeApi.Runtime; @@ -11,121 +10,309 @@ public unsafe partial class NodejsRuntime { #pragma warning disable IDE1006 // Naming: missing prefix '_' - private delegate* unmanaged[Cdecl] - napi_create_platform; + private delegate* unmanaged[Cdecl] + node_embedding_on_error; - public override napi_status CreatePlatform( - string[]? args, - Action? errorHandler, - out napi_platform result) + private delegate* unmanaged[Cdecl] + node_embedding_set_api_version; + + private delegate* unmanaged[Cdecl]< + int, + nint, + node_embedding_configure_platform_functor_ref, + node_embedding_configure_runtime_functor_ref, + node_embedding_status> node_embedding_run_main; + + private delegate* unmanaged[Cdecl]< + int, + nint, + node_embedding_configure_platform_functor_ref, + nint, + node_embedding_status> node_embedding_create_platform; + + private delegate* unmanaged[Cdecl] + node_embedding_delete_platform; + + private delegate* unmanaged[Cdecl]< + node_embedding_platform_config, + node_embedding_platform_flags, + node_embedding_status> node_embedding_platform_set_flags; + + private delegate* unmanaged[Cdecl]< + node_embedding_platform, + node_embedding_get_args_functor_ref, + node_embedding_get_args_functor_ref, + node_embedding_status> node_embedding_platform_get_parsed_args; + + private delegate* unmanaged[Cdecl]< + node_embedding_platform, + node_embedding_configure_runtime_functor_ref, + node_embedding_status> node_embedding_run_runtime; + + private delegate* unmanaged[Cdecl]< + node_embedding_platform, + node_embedding_configure_runtime_functor_ref, + nint, + node_embedding_status> node_embedding_create_runtime; + + private delegate* unmanaged[Cdecl] + node_embedding_delete_runtime; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + node_embedding_runtime_flags, + node_embedding_status> node_embedding_runtime_set_flags; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + int, + nint, + int, + nint, + node_embedding_status> node_embedding_runtime_set_args; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + node_embedding_preload_functor, + node_embedding_status> node_embedding_runtime_on_preload; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + node_embedding_start_execution_functor, + node_embedding_handle_result_functor, + node_embedding_status> node_embedding_runtime_on_start_execution; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + nint, + node_embedding_initialize_module_functor, + int, + node_embedding_status> node_embedding_runtime_add_module; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + node_embedding_post_task_functor, + node_embedding_status> node_embedding_runtime_set_task_runner; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime, + node_embedding_event_loop_run_mode, + nint, + node_embedding_status> node_embedding_run_event_loop; + + private delegate* unmanaged[Cdecl] + node_embedding_complete_event_loop; + + private delegate* unmanaged[Cdecl] + node_embedding_terminate_event_loop; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime, + node_embedding_run_node_api_functor_ref, + node_embedding_status> node_embedding_run_node_api; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime, + nint, + nint, + node_embedding_status> node_embedding_open_node_api_scope; + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime, + node_embedding_node_api_scope, + node_embedding_status> node_embedding_close_node_api_scope; + + public override node_embedding_status + EmbeddingOnError(node_embedding_handle_error_functor error_handler) { - napi_error_message_handler native_error_handler = errorHandler == null ? default : - new((byte* error) => - { - string? message = PtrToStringUTF8(error); - if (message is not null) errorHandler(message); - }); + return Import(ref node_embedding_on_error)(error_handler); + } - nint args_ptr = StringsToHGlobalUtf8(args, out int args_count); + public override node_embedding_status EmbeddingSetApiVersion( + int embedding_api_version, + int node_api_version) + { + return Import(ref node_embedding_set_api_version)( + embedding_api_version, node_api_version); + } - try + public override node_embedding_status EmbeddingRunMain( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_configure_runtime_functor_ref configure_runtime) + { + using Utf8StringArray utf8Args = new(args); + nint argsPtr = utf8Args.Pin(); + return Import(ref node_embedding_run_main)( + args.Length, argsPtr, configure_platform, configure_runtime); + } + + public override node_embedding_status EmbeddingCreatePlatform( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + out node_embedding_platform result) + { + using Utf8StringArray utf8Args = new(args); + fixed (nint* argsPtr = &utf8Args.Pin()) + fixed (node_embedding_platform* result_ptr = &result) { - result = default; - fixed (napi_platform* result_ptr = &result) - { - if (napi_create_platform == null) - { - napi_create_platform = (delegate* unmanaged[Cdecl]< - int, nint, napi_error_message_handler, nint, napi_status>) - Import(nameof(napi_create_platform)); - } - - return napi_create_platform( - args_count, - args_ptr, - native_error_handler, - (nint)result_ptr); - } + return Import(ref node_embedding_create_platform)( + args.Length, (nint)argsPtr, configure_platform, (nint)result_ptr); } - finally + } + + public override node_embedding_status + EmbeddingDeletePlatform(node_embedding_platform platform) + { + return Import(ref node_embedding_delete_platform)(platform); + } + + public override node_embedding_status EmbeddingPlatformSetFlags( + node_embedding_platform_config platform_config, + node_embedding_platform_flags flags) + { + return Import(ref node_embedding_platform_set_flags)(platform_config, flags); + } + + public override node_embedding_status EmbeddingPlatformGetParsedArgs( + node_embedding_platform platform, + node_embedding_get_args_functor_ref get_args, + node_embedding_get_args_functor_ref get_runtime_args) + { + return Import(ref node_embedding_platform_get_parsed_args)( + platform, get_args, get_runtime_args); + } + + public override node_embedding_status EmbeddingRunRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime) + { + return Import(ref node_embedding_run_runtime)(platform, configure_runtime); + } + + public override node_embedding_status EmbeddingCreateRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime, + out node_embedding_runtime result) + { + fixed (node_embedding_runtime* result_ptr = &result) { - FreeStringsHGlobal(args_ptr, args_count); + return Import(ref node_embedding_create_runtime)( + platform, configure_runtime, (nint)result_ptr); } } - private delegate* unmanaged[Cdecl] - napi_destroy_platform; + public override node_embedding_status + EmbeddingDeleteRuntime(node_embedding_runtime runtime) + { + return Import(ref node_embedding_delete_runtime)(runtime); + } - public override napi_status DestroyPlatform(napi_platform platform) + public override node_embedding_status EmbeddingRuntimeSetFlags( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_flags flags) { - return Import(ref napi_destroy_platform)(platform); + return Import(ref node_embedding_runtime_set_flags)(runtime_config, flags); } - private delegate* unmanaged[Cdecl]< - napi_platform, napi_error_message_handler, nint, int, nint, napi_status> - napi_create_environment; + public override node_embedding_status EmbeddingRuntimeSetArgs( + node_embedding_runtime_config runtime_config, + ReadOnlySpan args, + ReadOnlySpan runtime_args) + { + using Utf8StringArray utf8Args = new(args); + nint argsPtr = utf8Args.Pin(); + using Utf8StringArray utf8RuntimeArgs = new(runtime_args); + nint runtimeArgsPtr = utf8RuntimeArgs.Pin(); + return Import(ref node_embedding_runtime_set_args)( + runtime_config, args.Length, argsPtr, runtime_args.Length, runtimeArgsPtr); + } - public override napi_status CreateEnvironment( - napi_platform platform, - Action? errorHandler, - string? mainScript, - int apiVersion, - out napi_env result) + public override node_embedding_status EmbeddingRuntimeOnPreload( + node_embedding_runtime_config runtime_config, + node_embedding_preload_functor run_preload) { - napi_error_message_handler native_error_handler = errorHandler == null ? default : - new((byte* error) => - { - string? message = PtrToStringUTF8(error); - if (message is not null) errorHandler(message); - }); + return Import(ref node_embedding_runtime_on_preload)(runtime_config, run_preload); + } - nint main_script_ptr = StringToHGlobalUtf8(mainScript); + public override node_embedding_status EmbeddingRuntimeOnStartExecution( + node_embedding_runtime_config runtime_config, + node_embedding_start_execution_functor start_execution, + node_embedding_handle_result_functor handle_result) + { + return Import(ref node_embedding_runtime_on_start_execution)( + runtime_config, start_execution, handle_result); + } - try - { - fixed (napi_env* result_ptr = &result) - { - return Import(ref napi_create_environment)( - platform, native_error_handler, main_script_ptr, apiVersion, (nint)result_ptr); - } - } - finally - { - if (main_script_ptr != default) Marshal.FreeHGlobal(main_script_ptr); - } + public override node_embedding_status EmbeddingRuntimeAddModule( + node_embedding_runtime_config runtime_config, + string moduleName, + node_embedding_initialize_module_functor init_module, + int module_node_api_version) + { + using (PooledBuffer moduleNameBuffer = PooledBuffer.FromStringUtf8(moduleName)) + fixed (byte* moduleNamePtr = &moduleNameBuffer.Pin()) + return Import(ref node_embedding_runtime_add_module)( + runtime_config, (nint)moduleNamePtr, init_module, module_node_api_version); } - private delegate* unmanaged[Cdecl] - napi_destroy_environment; + public override node_embedding_status EmbeddingRuntimeSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_post_task_functor post_task) + { + return Import(ref node_embedding_runtime_set_task_runner)(runtime_config, post_task); + } - public override napi_status DestroyEnvironment(napi_env env, out int exitCode) + public override node_embedding_status EmbeddingRunEventLoop( + node_embedding_runtime runtime, + node_embedding_event_loop_run_mode run_mode, + out bool has_more_work) { - fixed (int* exit_code_ptr = &exitCode) - { - return Import(ref napi_destroy_environment)(env, (nint)exit_code_ptr); - } + c_bool resultBool = default; + c_bool* result_ptr = &resultBool; + node_embedding_status status = Import(ref node_embedding_run_event_loop)( + runtime, run_mode, (nint)result_ptr); + has_more_work = (bool)resultBool; + return status; } - private delegate* unmanaged[Cdecl] - napi_run_environment; + public override node_embedding_status EmbeddingCompleteEventLoop(node_embedding_runtime runtime) + { + return Import(ref node_embedding_complete_event_loop)(runtime); + } - public override napi_status RunEnvironment(napi_env env) + public override node_embedding_status + EmbeddingTerminateEventLoop(node_embedding_runtime runtime) { - return Import(ref napi_run_environment)(env); + return Import(ref node_embedding_terminate_event_loop)(runtime); } - private delegate* unmanaged[Cdecl] - napi_await_promise; + public override node_embedding_status EmbeddingRunNodeApi( + node_embedding_runtime runtime, + node_embedding_run_node_api_functor_ref run_node_api) + { + return Import(ref node_embedding_run_node_api)(runtime, run_node_api); + } - public override napi_status AwaitPromise( - napi_env env, napi_value promise, out napi_value result) + public override node_embedding_status EmbeddingOpenNodeApiScope( + node_embedding_runtime runtime, + out node_embedding_node_api_scope node_api_scope, + out napi_env env) { - result = default; - fixed (napi_value* result_ptr = &result) + fixed (node_embedding_node_api_scope* scopePtr = &node_api_scope) + fixed (napi_env* envPtr = &env) { - return Import(ref napi_await_promise)(env, promise, (nint)result_ptr); + return Import(ref node_embedding_open_node_api_scope)( + runtime, (nint)scopePtr, (nint)envPtr); } } + public override node_embedding_status EmbeddingCloseNodeApiScope( + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope) + { + return Import(ref node_embedding_close_node_api_scope)(runtime, node_api_scope); + } + #pragma warning restore IDE1006 } diff --git a/src/NodeApi/Runtime/TracingJSRuntime.cs b/src/NodeApi/Runtime/TracingJSRuntime.cs index 7e269cb0..fbe6cfd0 100644 --- a/src/NodeApi/Runtime/TracingJSRuntime.cs +++ b/src/NodeApi/Runtime/TracingJSRuntime.cs @@ -61,7 +61,7 @@ public TracingJSRuntime(JSRuntime runtime, TraceSource trace) #region Formatting - private static string Format(napi_platform platform) => platform.Handle.ToString("X16"); + //private static string Format(napi_platform platform) => platform.Handle.ToString("X16"); private static string Format(napi_env env) => env.Handle.ToString("X16"); private static string Format(napi_handle_scope scope) => scope.Handle.ToString("X16"); private static string Format(napi_escapable_handle_scope scope) => scope.Handle.ToString("X16"); @@ -74,6 +74,9 @@ private static string Format(napi_threadsafe_function function) private static string Format(napi_async_cleanup_hook_handle hook) => hook.Handle.ToString("X16"); private static string Format(uv_loop_t loop) => loop.Handle.ToString("X16"); + private static string Format(node_embedding_node_api_scope node_api_scope) + => node_api_scope.Handle.ToString("X16"); + private string GetValueString(napi_env env, napi_value value) { @@ -2655,69 +2658,167 @@ public override napi_status GetNodeVersion(napi_env env, out napi_node_version r #region Embedding - public override napi_status CreatePlatform( - string[]? args, Action? errorHandler, out napi_platform result) + public override node_embedding_status + EmbeddingOnError(node_embedding_handle_error_functor error_handler) { - napi_platform resultValue = default; - napi_status status = TraceCall( - [ - $"[{string.Join(", ", args ?? [])}]", - ], - () => (_runtime.CreatePlatform(args, errorHandler, out resultValue), - Format(resultValue))); - result = resultValue; - return status; + return _runtime.EmbeddingOnError(error_handler); } - public override napi_status DestroyPlatform(napi_platform platform) + public override node_embedding_status EmbeddingSetApiVersion( + int embedding_api_version, + int node_api_version) { - return TraceCall( - [Format(platform)], - () => _runtime.DestroyPlatform(platform)); + return _runtime.EmbeddingSetApiVersion(embedding_api_version, node_api_version); } - public override napi_status CreateEnvironment( - napi_platform platform, - Action? errorHandler, - string? mainScript, - int apiVersion, - out napi_env result) + public override node_embedding_status EmbeddingRunMain( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_configure_runtime_functor_ref configure_runtime) { - napi_env resultValue = default; - napi_status status = TraceCall( - [Format(platform), Format(mainScript)], - () => (_runtime.CreateEnvironment( - platform, errorHandler, mainScript, apiVersion, out resultValue), - Format(resultValue))); - result = resultValue; - return status; + return _runtime.EmbeddingRunMain(args, configure_platform, configure_runtime); } - public override napi_status DestroyEnvironment(napi_env env, out int exitCode) + public override node_embedding_status EmbeddingCreatePlatform( + ReadOnlySpan args, + node_embedding_configure_platform_functor_ref configure_platform, + out node_embedding_platform result) { - int exitCodeValue = default; - napi_status status = TraceCall( - [Format(env)], - () => (_runtime.DestroyEnvironment(env, out exitCodeValue), - exitCodeValue.ToString())); - exitCode = exitCodeValue; - return status; + return _runtime.EmbeddingCreatePlatform(args, configure_platform, out result); } - public override napi_status RunEnvironment(napi_env env) + public override node_embedding_status + EmbeddingDeletePlatform(node_embedding_platform platform) { - return TraceCall([Format(env)], () => _runtime.RunEnvironment(env)); + return _runtime.EmbeddingDeletePlatform(platform); } - public override napi_status AwaitPromise( - napi_env env, napi_value promise, out napi_value result) + public override node_embedding_status EmbeddingPlatformSetFlags( + node_embedding_platform_config platform_config, + node_embedding_platform_flags flags) { - napi_value resultValue = default; - napi_status status = TraceCall( - [Format(env, promise)], - () => (_runtime.AwaitPromise(env, promise, out resultValue), Format(env, resultValue))); - result = resultValue; - return status; + return _runtime.EmbeddingPlatformSetFlags(platform_config, flags); + } + + public override node_embedding_status EmbeddingPlatformGetParsedArgs( + node_embedding_platform platform, + node_embedding_get_args_functor_ref get_args, + node_embedding_get_args_functor_ref get_runtime_args) + { + return _runtime.EmbeddingPlatformGetParsedArgs(platform, get_args, get_runtime_args); + } + + public override node_embedding_status EmbeddingRunRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime) + { + return _runtime.EmbeddingRunRuntime(platform, configure_runtime); + } + + public override node_embedding_status EmbeddingCreateRuntime( + node_embedding_platform platform, + node_embedding_configure_runtime_functor_ref configure_runtime, + out node_embedding_runtime result) + { + return _runtime.EmbeddingCreateRuntime(platform, configure_runtime, out result); + } + + public override node_embedding_status + EmbeddingDeleteRuntime(node_embedding_runtime runtime) + { + return _runtime.EmbeddingDeleteRuntime(runtime); + } + + public override node_embedding_status EmbeddingRuntimeSetFlags( + node_embedding_runtime_config runtime_config, + node_embedding_runtime_flags flags) + { + return _runtime.EmbeddingRuntimeSetFlags(runtime_config, flags); + } + + public override node_embedding_status EmbeddingRuntimeSetArgs( + node_embedding_runtime_config runtime_config, + ReadOnlySpan args, + ReadOnlySpan runtime_args) + { + return _runtime.EmbeddingRuntimeSetArgs(runtime_config, args, runtime_args); + } + + public override node_embedding_status EmbeddingRuntimeOnPreload( + node_embedding_runtime_config runtime_config, + node_embedding_preload_functor run_preload) + { + return _runtime.EmbeddingRuntimeOnPreload(runtime_config, run_preload); + } + + public override node_embedding_status EmbeddingRuntimeOnStartExecution( + node_embedding_runtime_config runtime_config, + node_embedding_start_execution_functor start_execution, + node_embedding_handle_result_functor handle_result) + { + return _runtime.EmbeddingRuntimeOnStartExecution( + runtime_config, start_execution, handle_result); + } + + public override node_embedding_status EmbeddingRuntimeAddModule( + node_embedding_runtime_config runtime_config, + string moduleName, + node_embedding_initialize_module_functor init_module, + int module_node_api_version) + { + return _runtime.EmbeddingRuntimeAddModule( + runtime_config, moduleName, init_module, module_node_api_version); + } + + public override node_embedding_status EmbeddingRuntimeSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_post_task_functor post_task) + { + return _runtime.EmbeddingRuntimeSetTaskRunner(runtime_config, post_task); + } + + public override node_embedding_status EmbeddingRunEventLoop( + node_embedding_runtime runtime, + node_embedding_event_loop_run_mode run_mode, + out bool has_more_work) + { + return _runtime.EmbeddingRunEventLoop(runtime, run_mode, out has_more_work); + } + + public override node_embedding_status + EmbeddingCompleteEventLoop(node_embedding_runtime runtime) + { + return _runtime.EmbeddingCompleteEventLoop(runtime); + } + + public override node_embedding_status + EmbeddingTerminateEventLoop(node_embedding_runtime runtime) + { + return _runtime.EmbeddingTerminateEventLoop(runtime); + } + + public override node_embedding_status EmbeddingRunNodeApi( + node_embedding_runtime runtime, + node_embedding_run_node_api_functor_ref run_node_api) + { + return _runtime.EmbeddingRunNodeApi(runtime, run_node_api); + } + + public override node_embedding_status EmbeddingOpenNodeApiScope( + node_embedding_runtime runtime, + out node_embedding_node_api_scope node_api_scope, + out napi_env env) + { + return _runtime.EmbeddingOpenNodeApiScope(runtime, out node_api_scope, out env); + } + + public override node_embedding_status EmbeddingCloseNodeApiScope( + node_embedding_runtime runtime, + node_embedding_node_api_scope node_api_scope) + { + return TraceCall( + [Format(node_api_scope)], + () => _runtime.EmbeddingCloseNodeApiScope(runtime, node_api_scope)); } #endregion diff --git a/src/NodeApi/Runtime/Utf8StringArray.cs b/src/NodeApi/Runtime/Utf8StringArray.cs new file mode 100644 index 00000000..52a3b2d1 --- /dev/null +++ b/src/NodeApi/Runtime/Utf8StringArray.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +internal struct Utf8StringArray : IDisposable +{ + // Use one contiguous buffer for all UTF-8 strings. + private byte[] _stringBuffer; + private GCHandle _pinnedStringBuffer; + + public unsafe Utf8StringArray(ReadOnlySpan strings) + { + int byteLength = 0; + for (int i = 0; i < strings.Length; i++) + { + byteLength += Encoding.UTF8.GetByteCount(strings[i]) + 1; + } + +#if NETFRAMEWORK || NETSTANDARD + // Avoid a dependency on System.Buffers with .NET Framework. + // It is available as a Nuget package, but might not be installed in the application. + // In this case the buffer is not actually pooled. + + Utf8Strings = new nint[strings.Length]; + _stringBuffer = new byte[byteLength]; +#else + Utf8Strings = System.Buffers.ArrayPool.Shared.Rent(strings.Length); + _stringBuffer = System.Buffers.ArrayPool.Shared.Rent(byteLength); +#endif + + // Pin the string buffer + _pinnedStringBuffer = GCHandle.Alloc(_stringBuffer, GCHandleType.Pinned); + nint stringBufferPtr = _pinnedStringBuffer.AddrOfPinnedObject(); + int offset = 0; + for (int i = 0; i < strings.Length; i++) + { + fixed (char* src = strings[i]) + { + Utf8Strings[i] = stringBufferPtr + offset; + offset += Encoding.UTF8.GetBytes( + src, strings[i].Length, (byte*)(stringBufferPtr + offset), byteLength - offset) + + 1; // +1 for the string Null-terminator. + } + } + } + + public void Dispose() + { + if (!Disposed) + { + Disposed = true; + _pinnedStringBuffer.Free(); + +#if !(NETFRAMEWORK || NETSTANDARD) + System.Buffers.ArrayPool.Shared.Return(Utf8Strings); + System.Buffers.ArrayPool.Shared.Return(_stringBuffer); +#endif + } + } + + + public readonly nint[] Utf8Strings { get; } + + public bool Disposed { get; private set; } + + public readonly ref nint Pin() + { + if (Disposed) throw new ObjectDisposedException(nameof(Utf8StringArray)); + Span span = Utf8Strings; + return ref span.GetPinnableReference(); + } + + public static unsafe string[] ToStringArray(nint utf8StringArray, int size) + { + var utf8Strings = new ReadOnlySpan((void*)utf8StringArray, size); + string[] strings = new string[size]; + for (int i = 0; i < utf8Strings.Length; i++) + { + strings[i] = PtrToStringUTF8((byte*)utf8Strings[i]); + } + return strings; + } + + public static unsafe string PtrToStringUTF8(byte* ptr) + { +#if NETFRAMEWORK || NETSTANDARD + if (ptr == null) throw new ArgumentNullException(nameof(ptr)); + int length = 0; + while (ptr[length] != 0) length++; + return Encoding.UTF8.GetString(ptr, length); +#else + return Marshal.PtrToStringUTF8((nint)ptr) ?? throw new ArgumentNullException(nameof(ptr)); +#endif + } +} diff --git a/test/GCTests.cs b/test/GCTests.cs index d27052c3..c4d95756 100644 --- a/test/GCTests.cs +++ b/test/GCTests.cs @@ -18,11 +18,11 @@ public void GCHandles() Skip.If( NodejsEmbeddingTests.NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using NodejsEnvironment nodejs = NodejsEmbeddingTests.NodejsPlatform.CreateEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => { - Assert.Equal(0, JSRuntimeContext.Current.GCHandleCount); + Assert.Equal(3, JSRuntimeContext.Current.GCHandleCount); JSClassBuilder classBuilder = new(nameof(DotnetClass), () => new DotnetClass()); @@ -43,7 +43,7 @@ public void GCHandles() // - JSPropertyDescriptor: DotnetClass.property // - JSPropertyDescriptor: DotnetClass.method // - JSPropertyDescriptor: DotnetClass.toString - Assert.Equal(5, JSRuntimeContext.Current.GCHandleCount); + Assert.Equal(3 + 5, JSRuntimeContext.Current.GCHandleCount); using JSValueScope innerScope = new(JSValueScopeType.Callback); jsCreateInstanceFunction.CallAsStatic(dotnetClass); @@ -51,7 +51,7 @@ public void GCHandles() // Two more handles should have been allocated by the JS create-instance function call. // - One for the 'external' type value passed to the constructor. // - One for the JS object wrapper. - Assert.Equal(7, JSRuntimeContext.Current.GCHandleCount); + Assert.Equal(3 + 7, JSRuntimeContext.Current.GCHandleCount); }); nodejs.GC(); @@ -59,7 +59,7 @@ public void GCHandles() nodejs.Run(() => { // After GC, the handle count should have reverted back to the original set. - Assert.Equal(5, JSRuntimeContext.Current.GCHandleCount); + Assert.Equal(3 + 5, JSRuntimeContext.Current.GCHandleCount); }); } @@ -69,7 +69,7 @@ public void GCObjects() Skip.If( NodejsEmbeddingTests.NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using NodejsEnvironment nodejs = NodejsEmbeddingTests.NodejsPlatform.CreateEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => { @@ -86,7 +86,7 @@ public void GCObjects() "function jsCreateInstanceFunction(Class) { new Class() }; " + "jsCreateInstanceFunction"); - Assert.Equal(5, JSRuntimeContext.Current.GCHandleCount); + Assert.Equal(8, JSRuntimeContext.Current.GCHandleCount); using (JSValueScope innerScope = new(JSValueScopeType.Callback)) { diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index f1c452ce..e954e2ed 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -19,28 +19,53 @@ namespace Microsoft.JavaScript.NodeApi.Test; public class NodejsEmbeddingTests { + private static string MainScript { get; } = + "globalThis.require = require('module').createRequire(process.execPath);\n"; + private static string LibnodePath { get; } = GetLibnodePath(); // The Node.js platform may only be initialized once per process. - internal static NodejsPlatform? NodejsPlatform { get; } = - File.Exists(LibnodePath) ? new(LibnodePath, args: new[] { "node", "--expose-gc" }) : null; + internal static NodejsEmbeddingPlatform? NodejsPlatform { get; } = + File.Exists(LibnodePath) + ? new(LibnodePath, new NodejsEmbeddingPlatformSettings + { + Args = new[] { "node", "--expose-gc" } + }) + : null; - internal static NodejsEnvironment CreateNodejsEnvironment() + internal static NodejsEmbeddingThreadRuntime CreateNodejsEnvironment() { Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - return NodejsPlatform.CreateEnvironment(Path.Combine(GetRepoRootDirectory(), "test")); + return NodejsPlatform.CreateThreadRuntime( + Path.Combine(GetRepoRootDirectory(), "test"), + new NodejsEmbeddingRuntimeSettings { MainScript = MainScript }); } internal static void RunInNodejsEnvironment(Action action) { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.SynchronizationContext.Run(action); } + [SkippableFact] + public void LoadMainScriptNoThread() + { + Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); + using var runtime = new NodejsEmbeddingRuntime(NodejsPlatform, + new NodejsEmbeddingRuntimeSettings { MainScript = MainScript }); + runtime.CompleteEventLoop(); + } + + [SkippableFact] + public void LoadMainScriptWithThread() + { + using var runtime = CreateNodejsEnvironment(); + } + [SkippableFact] public void StartEnvironment() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -55,7 +80,7 @@ public void StartEnvironment() [SkippableFact] public void RestartEnvironment() { - // Create and destory a Node.js environment twice, using the same platform instance. + // Create and destroy a Node.js environment twice, using the same platform instance. StartEnvironment(); StartEnvironment(); } @@ -65,11 +90,12 @@ public interface IConsole { void Log(string message); } [SkippableFact] public void CallFunction() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.SynchronizationContext.Run(() => { - JSFunction func = (JSFunction)JSValue.RunScript("function jsFunction() { }; jsFunction"); + JSFunction func = (JSFunction)JSValue.RunScript( + "function jsFunction() { }; jsFunction"); func.CallAsStatic(); }); @@ -80,7 +106,7 @@ public void CallFunction() [SkippableFact] public void ImportBuiltinModule() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -100,7 +126,7 @@ public void ImportBuiltinModule() [SkippableFact] public void ImportCommonJSModule() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -117,7 +143,7 @@ public void ImportCommonJSModule() [SkippableFact] public void ImportCommonJSPackage() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -134,7 +160,7 @@ public void ImportCommonJSPackage() [SkippableFact] public async Task ImportESModule() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { @@ -152,7 +178,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public async Task ImportESPackage() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { @@ -184,7 +210,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public void UnhandledRejection() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); string? errorMessage = null; nodejs.UnhandledPromiseRejection += (_, e) => @@ -206,7 +232,7 @@ public void UnhandledRejection() [SkippableFact] public void ErrorPropagation() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); JSException exception = Assert.Throws(() => { @@ -362,7 +388,7 @@ private static async Task TestWorker( string workerScript, Func mainRun) { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { NodeWorker.Options workerOptions = mainPrepare.Invoke(); @@ -394,7 +420,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public void MarshalClass() { - using NodejsEnvironment nodejs = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { From 8ade3b30be240255fbe1251900ec027fdcd1f7de Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Dec 2024 16:48:50 -0800 Subject: [PATCH 02/11] Fix compilation issue --- src/NodeApi/Runtime/TracingJSRuntime.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NodeApi/Runtime/TracingJSRuntime.cs b/src/NodeApi/Runtime/TracingJSRuntime.cs index fbe6cfd0..a273da3c 100644 --- a/src/NodeApi/Runtime/TracingJSRuntime.cs +++ b/src/NodeApi/Runtime/TracingJSRuntime.cs @@ -2816,9 +2816,7 @@ public override node_embedding_status EmbeddingCloseNodeApiScope( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope) { - return TraceCall( - [Format(node_api_scope)], - () => _runtime.EmbeddingCloseNodeApiScope(runtime, node_api_scope)); + return _runtime.EmbeddingCloseNodeApiScope(runtime, node_api_scope); } #endregion From 6468941add4e3a02ce6c566baf2254e739716b5d Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 6 Dec 2024 17:09:04 -0800 Subject: [PATCH 03/11] Fix formatting --- bench/Benchmarks.cs | 3 ++- src/NodeApi/Runtime/NodejsEmbedding.cs | 10 +++++----- .../Runtime/NodejsEmbeddingNodeApiScope.cs | 4 ++-- src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs | 4 ++-- src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs | 15 ++++++--------- .../Runtime/NodejsEmbeddingThreadRuntime.cs | 7 ++----- src/NodeApi/Runtime/Utf8StringArray.cs | 2 +- test/NodejsEmbeddingTests.cs | 2 +- 8 files changed, 21 insertions(+), 26 deletions(-) diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index 25d165b1..59248402 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -65,6 +65,7 @@ public static void Main(string[] args) private JSFunction _jsFunctionCallMethod; private JSFunction _jsFunctionCallMethodWithArgs; private JSReference _reference = null!; + private static readonly string[] s_settings = new[] { "node", "--expose-gc" }; /// /// Simple class that is exported to JS and used in some benchmarks. @@ -87,7 +88,7 @@ protected void Setup() { NodejsEmbeddingPlatform platform = new( LibnodePath, - new NodejsEmbeddingPlatformSettings { Args = new[] { "node", "--expose-gc" } }); + new NodejsEmbeddingPlatformSettings { Args = s_settings }); // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on // the same thread. NodejsEmbeddingThreadRuntime creates a separate thread that would slow diff --git a/src/NodeApi/Runtime/NodejsEmbedding.cs b/src/NodeApi/Runtime/NodejsEmbedding.cs index 50becd21..441140e4 100644 --- a/src/NodeApi/Runtime/NodejsEmbedding.cs +++ b/src/NodeApi/Runtime/NodejsEmbedding.cs @@ -18,30 +18,30 @@ public sealed class NodejsEmbedding public static readonly int EmbeddingApiVersion = 1; public static readonly int NodeApiVersion = 9; - private static JSRuntime? _jsRuntime; + private static JSRuntime? s_jsRuntime; public static JSRuntime JSRuntime { get { - if (_jsRuntime == null) + if (s_jsRuntime == null) { throw new InvalidOperationException("The JSRuntime is not initialized."); } - return _jsRuntime; + return s_jsRuntime; } } public static void Initialize(string libnodePath) { if (string.IsNullOrEmpty(libnodePath)) throw new ArgumentNullException(nameof(libnodePath)); - if (_jsRuntime != null) + if (s_jsRuntime != null) { throw new InvalidOperationException( "The JSRuntime can be initialized only once per process."); } nint libnodeHandle = NativeLibrary.Load(libnodePath); - _jsRuntime = new NodejsRuntime(libnodeHandle); + s_jsRuntime = new NodejsRuntime(libnodeHandle); } public delegate node_embedding_status HandleErrorCallback( diff --git a/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs b/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs index 35fe360d..784285a4 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs +++ b/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs @@ -8,9 +8,9 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; public sealed class NodejsEmbeddingNodeApiScope : IDisposable { - NodejsEmbeddingRuntime _runtime; + readonly NodejsEmbeddingRuntime _runtime; private node_embedding_node_api_scope _nodeApiScope; - private JSValueScope _valueScope; + private readonly JSValueScope _valueScope; public NodejsEmbeddingNodeApiScope(NodejsEmbeddingRuntime runtime) { diff --git a/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs index 8c57bed1..483a5d2e 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs +++ b/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs @@ -127,7 +127,7 @@ public string[] GetParsedArgs() string[]? result = null; GetParsedArgs((string[] args) => result = args, null); - return result ?? Array.Empty(); + return result ?? []; } public string[] GetRuntimeParsedArgs() @@ -136,6 +136,6 @@ public string[] GetRuntimeParsedArgs() string[]? result = null; GetParsedArgs(null, (string[] args) => result = args); - return result ?? Array.Empty(); + return result ?? []; } } diff --git a/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs index 1af6788a..c41172bc 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs +++ b/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs @@ -18,7 +18,7 @@ public sealed class NodejsEmbeddingRuntime : IDisposable { private node_embedding_runtime _runtime; private static readonly - Dictionary _embeddedRuntimes = new(); + Dictionary s_embeddedRuntimes = new(); public static implicit operator node_embedding_runtime(NodejsEmbeddingRuntime runtime) => runtime._runtime; @@ -43,14 +43,14 @@ public NodejsEmbeddingRuntime( private NodejsEmbeddingRuntime(node_embedding_runtime runtime) { _runtime = runtime; - lock (_embeddedRuntimes) { _embeddedRuntimes.Add(runtime, this); } + lock (s_embeddedRuntimes) { s_embeddedRuntimes.Add(runtime, this); } } public static NodejsEmbeddingRuntime? FromHandle(node_embedding_runtime runtime) { - lock (_embeddedRuntimes) + lock (s_embeddedRuntimes) { - if (_embeddedRuntimes.TryGetValue( + if (s_embeddedRuntimes.TryGetValue( runtime, out NodejsEmbeddingRuntime? embeddingRuntime)) { return embeddingRuntime; @@ -62,10 +62,7 @@ private NodejsEmbeddingRuntime(node_embedding_runtime runtime) public static NodejsEmbeddingRuntime GetOrCreate(node_embedding_runtime runtime) { NodejsEmbeddingRuntime? embeddingRuntime = FromHandle(runtime); - if (embeddingRuntime == null) - { - embeddingRuntime = new NodejsEmbeddingRuntime(runtime); - } + embeddingRuntime ??= new NodejsEmbeddingRuntime(runtime); return embeddingRuntime; } @@ -89,7 +86,7 @@ public void Dispose() if (IsDisposed) return; IsDisposed = true; - lock (_embeddedRuntimes) { _embeddedRuntimes.Remove(_runtime); } + lock (s_embeddedRuntimes) { s_embeddedRuntimes.Remove(_runtime); } JSRuntime.EmbeddingDeleteRuntime(_runtime).ThrowIfFailed(); } diff --git a/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs b/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs index 616ce018..76fd0b73 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs +++ b/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs @@ -208,11 +208,8 @@ public void Dispose() // Unreffing the completion should complete the Node.js event loop // if it has nothing else to do. - if (_completion != null) - { - // The Unref must be called in the JS thread. - _completion.BlockingCall(() => _completion.Unref()); - } + // The Unref must be called in the JS thread. + _completion?.BlockingCall(() => _completion.Unref()); _thread.Join(); Debug.WriteLine($"Node.js environment exited with code: {ExitCode}"); diff --git a/src/NodeApi/Runtime/Utf8StringArray.cs b/src/NodeApi/Runtime/Utf8StringArray.cs index 52a3b2d1..6d5c4a0c 100644 --- a/src/NodeApi/Runtime/Utf8StringArray.cs +++ b/src/NodeApi/Runtime/Utf8StringArray.cs @@ -5,7 +5,7 @@ internal struct Utf8StringArray : IDisposable { // Use one contiguous buffer for all UTF-8 strings. - private byte[] _stringBuffer; + private readonly byte[] _stringBuffer; private GCHandle _pinnedStringBuffer; public unsafe Utf8StringArray(ReadOnlySpan strings) diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index e954e2ed..348931c1 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -59,7 +59,7 @@ public void LoadMainScriptNoThread() [SkippableFact] public void LoadMainScriptWithThread() { - using var runtime = CreateNodejsEnvironment(); + using NodejsEmbeddingThreadRuntime runtime = CreateNodejsEnvironment(); } [SkippableFact] From a2a59f5fb0b2619f6fd1de1afe7c44b2e57c5971 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Wed, 5 Feb 2025 17:08:59 -0800 Subject: [PATCH 04/11] Update API --- .ado/publish.yml | 1 + .github/workflows/build.yml | 1 + bench/Benchmarks.cs | 8 +- examples/jsdom/Program.cs | 11 +- examples/winui-fluid/App.xaml.cs | 11 +- examples/winui-fluid/CollabEditBox.xaml.cs | 2 +- .../JSRuntimeContextExtensions.cs | 2 +- src/NodeApi/Interop/EmptyAttributes.cs | 17 + src/NodeApi/NodeApiStatusExtensions.cs | 25 +- src/NodeApi/Runtime/JSRuntime.Types.cs | 493 +----------- src/NodeApi/Runtime/JSRuntime.cs | 129 ++-- src/NodeApi/Runtime/NodeEmbedding.cs | 420 ++++++++++ .../Runtime/NodeEmbeddingModuleInfo.cs | 13 + ...iScope.cs => NodeEmbeddingNodeApiScope.cs} | 16 +- src/NodeApi/Runtime/NodeEmbeddingPlatform.cs | 116 +++ .../Runtime/NodeEmbeddingPlatformSettings.cs | 27 + src/NodeApi/Runtime/NodeEmbeddingRuntime.cs | 156 ++++ .../Runtime/NodeEmbeddingRuntimeSettings.cs | 119 +++ ...ntime.cs => NodeEmbeddingThreadRuntime.cs} | 35 +- src/NodeApi/Runtime/NodeJSRuntime.cs | 81 +- src/NodeApi/Runtime/NodejsEmbedding.cs | 373 --------- .../Runtime/NodejsEmbeddingModuleInfo.cs | 13 - .../Runtime/NodejsEmbeddingPlatform.cs | 141 ---- .../NodejsEmbeddingPlatformSettings.cs | 35 - src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs | 123 --- .../Runtime/NodejsEmbeddingRuntimeSettings.cs | 121 --- .../Runtime/NodejsRuntime.Embedding.cs | 730 ++++++++++++++---- src/NodeApi/Runtime/NodejsRuntime.JS.cs | 26 +- src/NodeApi/Runtime/NodejsRuntime.Node.cs | 8 +- src/NodeApi/Runtime/NodejsRuntime.Types.cs | 4 +- src/NodeApi/Runtime/PooledBuffer.cs | 35 +- src/NodeApi/Runtime/TracingJSRuntime.cs | 204 +++-- src/NodeApi/Runtime/Utf8StringArray.cs | 22 +- test/GCTests.cs | 4 +- test/NodejsEmbeddingTests.cs | 40 +- 35 files changed, 1884 insertions(+), 1678 deletions(-) create mode 100644 src/NodeApi/Runtime/NodeEmbedding.cs create mode 100644 src/NodeApi/Runtime/NodeEmbeddingModuleInfo.cs rename src/NodeApi/Runtime/{NodejsEmbeddingNodeApiScope.cs => NodeEmbeddingNodeApiScope.cs} (60%) create mode 100644 src/NodeApi/Runtime/NodeEmbeddingPlatform.cs create mode 100644 src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs create mode 100644 src/NodeApi/Runtime/NodeEmbeddingRuntime.cs create mode 100644 src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs rename src/NodeApi/Runtime/{NodejsEmbeddingThreadRuntime.cs => NodeEmbeddingThreadRuntime.cs} (93%) delete mode 100644 src/NodeApi/Runtime/NodejsEmbedding.cs delete mode 100644 src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs delete mode 100644 src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs delete mode 100644 src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs delete mode 100644 src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs delete mode 100644 src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs diff --git a/.ado/publish.yml b/.ado/publish.yml index 07269945..6c4505d2 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -259,6 +259,7 @@ extends: - script: > dotnet test -f ${{ MatrixEntry.DotNetVersion }} --configuration Release + --property:ParallelizeTestCollections=false --logger trx --results-directory "$(Build.StagingDirectory)/test/${{ MatrixEntry.DotNetVersion }}-Release" displayName: Run tests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d1e0ac0..f82c58d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,7 @@ jobs: run: > dotnet test -f ${{ matrix.dotnet-version }} --configuration ${{ matrix.configuration }} + --property:ParallelizeTestCollections=false --logger trx --results-directory "out/test/${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}" continue-on-error: true diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index 59248402..e5e4b1f4 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -50,8 +50,8 @@ public static void Main(string[] args) GetCurrentPlatformRuntimeIdentifier(), "libnode" + GetSharedLibraryExtension()); - private NodejsEmbeddingRuntime? _runtime; - private NodejsEmbeddingNodeApiScope? _nodeApiScope; + private NodeEmbeddingRuntime? _runtime; + private NodeEmbeddingNodeApiScope? _nodeApiScope; private JSValue _jsString; private JSFunction _jsFunction; private JSFunction _jsFunctionWithArgs; @@ -86,9 +86,9 @@ public static void Method() { } /// protected void Setup() { - NodejsEmbeddingPlatform platform = new( + NodeEmbeddingPlatform platform = new( LibnodePath, - new NodejsEmbeddingPlatformSettings { Args = s_settings }); + new NodeEmbeddingPlatformSettings { Args = s_settings }); // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on // the same thread. NodejsEmbeddingThreadRuntime creates a separate thread that would slow diff --git a/examples/jsdom/Program.cs b/examples/jsdom/Program.cs index 59b26d05..2a18aace 100644 --- a/examples/jsdom/Program.cs +++ b/examples/jsdom/Program.cs @@ -11,8 +11,13 @@ public static void Main() { string appDir = Path.GetDirectoryName(typeof(Program).Assembly.Location)!; string libnodePath = Path.Combine(appDir, "libnode.dll"); - using NodejsPlatform nodejsPlatform = new(libnodePath); - using NodejsEnvironment nodejs = nodejsPlatform.CreateEnvironment(appDir); + using NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null); + using NodeEmbeddingThreadRuntime nodejs = nodejsPlatform.CreateThreadRuntime(appDir, + new NodeEmbeddingRuntimeSettings + { + MainScript = + "globalThis.require = require('module').createRequire(process.execPath);\n" + }); if (Debugger.IsAttached) { int pid = Process.GetCurrentProcess().Id; @@ -25,7 +30,7 @@ public static void Main() Console.WriteLine(content); } - private static string GetContent(NodejsEnvironment nodejs, string html) + private static string GetContent(NodeEmbeddingThreadRuntime nodejs, string html) { JSValue jsdomClass = nodejs.Import(module: "jsdom", property: "JSDOM"); JSValue dom = jsdomClass.CallAsConstructor(html); diff --git a/examples/winui-fluid/App.xaml.cs b/examples/winui-fluid/App.xaml.cs index 1c5840c6..5033c6de 100644 --- a/examples/winui-fluid/App.xaml.cs +++ b/examples/winui-fluid/App.xaml.cs @@ -26,9 +26,12 @@ public App() string appDir = Path.GetDirectoryName(typeof(App).Assembly.Location)!; string libnodePath = Path.Combine(appDir, "libnode.dll"); - NodejsPlatform nodejsPlatform = new(libnodePath); - - Nodejs = nodejsPlatform.CreateEnvironment(appDir); + NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null); + Nodejs = nodejsPlatform.CreateThreadRuntime(appDir, new NodeEmbeddingRuntimeSettings + { + MainScript = + "globalThis.require = require('module').createRequire(process.execPath);\n" + }); if (Debugger.IsAttached) { int pid = Process.GetCurrentProcess().Id; @@ -60,5 +63,5 @@ private void OnMainWindowClosed(object sender, WindowEventArgs args) public static new App Current => (App)Application.Current; - public NodejsEnvironment Nodejs { get; } + public NodeEmbeddingThreadRuntime Nodejs { get; } } diff --git a/examples/winui-fluid/CollabEditBox.xaml.cs b/examples/winui-fluid/CollabEditBox.xaml.cs index cf6a310f..920bd764 100644 --- a/examples/winui-fluid/CollabEditBox.xaml.cs +++ b/examples/winui-fluid/CollabEditBox.xaml.cs @@ -28,7 +28,7 @@ public sealed partial class CollabEditBox : UserControl private const string FluidServiceUri = "http://localhost:7070/"; private readonly SynchronizationContext uiSyncContext; - private readonly NodejsEnvironment nodejs; + private readonly NodeEmbeddingThreadRuntime nodejs; private readonly JSMarshaller marshaller; private ITinyliciousClient fluidClient = null!; diff --git a/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs b/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs index 04e35c5e..41c327fa 100644 --- a/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs +++ b/src/NodeApi.DotNetHost/JSRuntimeContextExtensions.cs @@ -54,7 +54,7 @@ public static T Import( /// Both and /// are null. public static T Import( - this NodejsEmbeddingThreadRuntime nodejs, + this NodeEmbeddingThreadRuntime nodejs, string? module, string? property, bool esModule, diff --git a/src/NodeApi/Interop/EmptyAttributes.cs b/src/NodeApi/Interop/EmptyAttributes.cs index d9617639..6cc66618 100644 --- a/src/NodeApi/Interop/EmptyAttributes.cs +++ b/src/NodeApi/Interop/EmptyAttributes.cs @@ -55,6 +55,23 @@ public CallerArgumentExpressionAttribute(string parameterName) public string ParameterName { get; } } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property + | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] + public sealed class RequiredMemberAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + public sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute (string featureName) + { + FeatureName = featureName; + } + + public string FeatureName { get; } + } } namespace System.Diagnostics diff --git a/src/NodeApi/NodeApiStatusExtensions.cs b/src/NodeApi/NodeApiStatusExtensions.cs index 65a23c45..d1c6a6f9 100644 --- a/src/NodeApi/NodeApiStatusExtensions.cs +++ b/src/NodeApi/NodeApiStatusExtensions.cs @@ -4,7 +4,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Microsoft.JavaScript.NodeApi.Runtime; using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime; +using static Microsoft.JavaScript.NodeApi.Runtime.NodejsRuntime; namespace Microsoft.JavaScript.NodeApi; @@ -61,28 +63,17 @@ public static T ThrowIfFailed(this napi_status status, } [StackTraceHidden] - public static void ThrowIfFailed([DoesNotReturnIf(true)] this node_embedding_status status, + public static void ThrowIfFailed([DoesNotReturnIf(true)] this NodeEmbeddingStatus status, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - if (status == node_embedding_status.ok) + if (status == NodeEmbeddingStatus.OK) return; - - throw new JSException($"Error in {memberName} at {sourceFilePath}:{sourceLineNumber}"); - } - - // Throw if status is not napi_ok. Otherwise, return the provided value. - // This function helps writing compact wrappers for the interop calls. - [StackTraceHidden] - public static T ThrowIfFailed(this node_embedding_status status, - T value, - [CallerMemberName] string memberName = "", - [CallerFilePath] string sourceFilePath = "", - [CallerLineNumber] int sourceLineNumber = 0) - { - status.ThrowIfFailed(memberName, sourceFilePath, sourceLineNumber); - return value; + throw new JSException($""" + Error in {memberName} at {sourceFilePath}:{sourceLineNumber} + {NodeEmbedding.JSRuntime.EmbeddingGetLastErrorMessage()} + """); } } diff --git a/src/NodeApi/Runtime/JSRuntime.Types.cs b/src/NodeApi/Runtime/JSRuntime.Types.cs index fa8bb070..437b1065 100644 --- a/src/NodeApi/Runtime/JSRuntime.Types.cs +++ b/src/NodeApi/Runtime/JSRuntime.Types.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; using System.Runtime.InteropServices; -namespace Microsoft.JavaScript.NodeApi.Runtime; - // Type definitions from Node.JS js_native_api.h and js_native_api_types.h public unsafe partial class JSRuntime { @@ -32,12 +32,6 @@ public record struct napi_escapable_handle_scope(nint Handle); public record struct napi_callback_info(nint Handle); public record struct napi_deferred(nint Handle); - public record struct node_embedding_platform(nint Handle); - public record struct node_embedding_runtime(nint Handle); - public record struct node_embedding_platform_config(nint Handle); - public record struct node_embedding_runtime_config(nint Handle); - public record struct node_embedding_node_api_scope(nint Handle); - //=========================================================================== // Enum types //=========================================================================== @@ -116,119 +110,6 @@ public enum napi_status : int napi_would_deadlock, } - public enum node_embedding_status : int - { - ok = 0, - generic_error = 1, - null_arg = 2, - bad_arg = 3, - // This value is added to the exit code in cases when Node.js API returns - // an error exit code. - error_exit_code = 512, - } - - [Flags] - public enum node_embedding_platform_flags : int - { - none = 0, - // Enable stdio inheritance, which is disabled by default. - // This flag is also implied by - // node_embedding_platform_flags_no_stdio_initialization. - enable_stdio_inheritance = 1 << 0, - // Disable reading the NODE_OPTIONS environment variable. - disable_node_options_env = 1 << 1, - // Do not parse CLI options. - disable_cli_options = 1 << 2, - // Do not initialize ICU. - no_icu = 1 << 3, - // Do not modify stdio file descriptor or TTY state. - no_stdio_initialization = 1 << 4, - // Do not register Node.js-specific signal handlers - // and reset other signal handlers to default state. - no_default_signal_handling = 1 << 5, - // Do not initialize OpenSSL config. - no_init_openssl = 1 << 8, - // Do not initialize Node.js debugging based on environment variables. - no_parse_global_debug_variables = 1 << 9, - // Do not adjust OS resource limits for this process. - no_adjust_resource_limits = 1 << 10, - // Do not map code segments into large pages for this process. - no_use_large_pages = 1 << 11, - // Skip printing output for --help, --version, --v8-options. - no_print_help_or_version_output = 1 << 12, - // Initialize the process for predictable snapshot generation. - generate_predictable_snapshot = 1 << 14, - } - - // The flags for the Node.js runtime initialization. - // They match the internal EnvironmentFlags::Flags enum. - [Flags] - public enum node_embedding_runtime_flags : int - { - none = 0, - // Use the default behavior for Node.js instances. - default_flags = 1 << 0, - // Controls whether this Environment is allowed to affect per-process state - // (e.g. cwd, process title, uid, etc.). - // This is set when using default. - owns_process_state = 1 << 1, - // Set if this Environment instance is associated with the global inspector - // handling code (i.e. listening on SIGUSR1). - // This is set when using default. - owns_inspector = 1 << 2, - // Set if Node.js should not run its own esm loader. This is needed by some - // embedders, because it's possible for the Node.js esm loader to conflict - // with another one in an embedder environment, e.g. Blink's in Chromium. - no_register_esm_loader = 1 << 3, - // Set this flag to make Node.js track "raw" file descriptors, i.e. managed - // by fs.open() and fs.close(), and close them during - // node_embedding_delete_runtime(). - track_unmanaged_fds = 1 << 4, - // Set this flag to force hiding console windows when spawning child - // processes. This is usually used when embedding Node.js in GUI programs on - // Windows. - hide_console_windows = 1 << 5, - // Set this flag to disable loading native addons via `process.dlopen`. - // This environment flag is especially important for worker threads - // so that a worker thread can't load a native addon even if `execArgv` - // is overwritten and `--no-addons` is not specified but was specified - // for this Environment instance. - no_native_addons = 1 << 6, - // Set this flag to disable searching modules from global paths like - // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that - // do not expect to have their behaviors changed because of globally - // installed modules. - no_global_search_paths = 1 << 7, - // Do not export browser globals like setTimeout, console, etc. - no_browser_globals = 1 << 8, - // Controls whether or not the Environment should call V8Inspector::create(). - // This control is needed by embedders who may not want to initialize the V8 - // inspector in situations where one has already been created, - // e.g. Blink's in Chromium. - no_create_inspector = 1 << 9, - // Controls whether or not the InspectorAgent for this Environment should - // call StartDebugSignalHandler. This control is needed by embedders who may - // not want to allow other processes to start the V8 inspector. - no_start_debug_signal_handler = 1 << 10, - // Controls whether the InspectorAgent created for this Environment waits for - // Inspector frontend events during the Environment creation. It's used to - // call node::Stop(env) on a Worker thread that is waiting for the events. - no_wait_for_inspector_frontend = 1 << 11 - } - - public enum node_embedding_event_loop_run_mode : int - { - // Run the event loop until it is completed. - // It matches the UV_RUN_DEFAULT behavior. - default_mode = 0, - // Run the event loop once and wait if there are no items. - // It matches the UV_RUN_ONCE behavior. - once = 1, - // Run the event loop once and do not wait if there are no items. - // It matches the UV_RUN_NOWAIT behavior. - nowait = 2, - } - public record struct napi_callback(nint Handle) { #if UNMANAGED_DELEGATES @@ -259,241 +140,6 @@ public napi_finalize(napi_finalize.Delegate callback) : this(Marshal.GetFunctionPointerForDelegate(callback)) { } } - public record struct node_embedding_release_data_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_release_data_callback(delegate* unmanaged[Cdecl] handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate(nint data); - - public node_embedding_release_data_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_handle_error_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_handle_error_callback(delegate* unmanaged[Cdecl]< - nint, nint, nuint, node_embedding_status, node_embedding_status> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate node_embedding_status Delegate( - nint cb_data, - nint messages, - nuint messages_size, - node_embedding_status status); - - public node_embedding_handle_error_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_configure_platform_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_configure_platform_callback(delegate* unmanaged[Cdecl]< - nint, node_embedding_platform_config, node_embedding_status> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate node_embedding_status Delegate( - nint cb_data, - node_embedding_platform_config platform_config); - - public node_embedding_configure_platform_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_configure_runtime_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_configure_runtime_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_platform, - node_embedding_runtime_config, - node_embedding_status> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate node_embedding_status Delegate( - nint cb_data, - node_embedding_platform platform, - node_embedding_runtime_config runtime_config); - - public node_embedding_configure_runtime_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_get_args_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_get_args_callback(delegate* unmanaged[Cdecl]< - nint, int, nint, void> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate(nint cb_data, int argc, nint argv); - - public node_embedding_get_args_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_preload_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_preload_callback(delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require); - - public node_embedding_preload_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_start_execution_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_start_execution_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_runtime, - napi_env, - napi_value, - napi_value, - napi_value, - napi_value> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate napi_value Delegate( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs); - - public node_embedding_start_execution_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_handle_result_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_handle_result_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_runtime, - napi_env, - napi_value, - void> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value value); - - public node_embedding_handle_result_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_initialize_module_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_initialize_module_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_runtime, - napi_env, - nint, - napi_value, - napi_value> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate napi_value Delegate( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - nint module_name, - napi_value exports); - - public node_embedding_initialize_module_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_run_task_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_run_task_callback(delegate* unmanaged[Cdecl] handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate(nint cb_data); - - public node_embedding_run_task_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_post_task_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_post_task_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_run_task_functor, - void> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate( - nint cb_data, - node_embedding_run_task_functor run_task); - - public node_embedding_post_task_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - - public record struct node_embedding_run_node_api_callback(nint Handle) - { -#if UNMANAGED_DELEGATES - public node_embedding_run_node_api_callback(delegate* unmanaged[Cdecl]< - nint, - node_embedding_runtime, - napi_env, - void> handle) - : this((nint)handle) { } -#endif - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void Delegate( - nint cb_data, - node_embedding_runtime runtime, - napi_env env); - - public node_embedding_run_node_api_callback(Delegate callback) - : this(Marshal.GetFunctionPointerForDelegate(callback)) { } - } - public struct napi_property_descriptor { // One of utf8name or name should be NULL. @@ -552,139 +198,4 @@ public readonly struct c_bool public static readonly c_bool True = new(true); public static readonly c_bool False = new(false); } - - public struct node_embedding_handle_error_functor - { - public nint data; - public node_embedding_handle_error_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_get_args_functor_ref : IDisposable - { - public nint data; - public node_embedding_get_args_callback invoke; - - public node_embedding_get_args_functor_ref( - object? functor, node_embedding_get_args_callback invoke) - { - data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; - this.invoke = (functor != null) ? invoke : new node_embedding_get_args_callback(0); - } - - public void Dispose() - { - if (data == 0) return; - GCHandle.FromIntPtr(data).Free(); - data = 0; - } - } - - public struct node_embedding_configure_platform_functor_ref : IDisposable - { - public nint data; - public node_embedding_configure_platform_callback invoke; - - public node_embedding_configure_platform_functor_ref( - object? functor, node_embedding_configure_platform_callback invoke) - { - data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; - this.invoke = (functor != null) - ? invoke - : new node_embedding_configure_platform_callback(0); - } - - public void Dispose() - { - if (data == 0) return; - GCHandle.FromIntPtr(data).Free(); - data = 0; - } - } - - public struct node_embedding_configure_runtime_functor_ref - { - public nint data; - public node_embedding_configure_runtime_callback invoke; - - public node_embedding_configure_runtime_functor_ref( - object? functor, node_embedding_configure_runtime_callback invoke) - { - data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; - this.invoke = (functor != null) - ? invoke - : new node_embedding_configure_runtime_callback(0); - } - - public void Dispose() - { - if (data == 0) return; - GCHandle.FromIntPtr(data).Free(); - data = 0; - } - } - - public struct node_embedding_preload_functor - { - public nint data; - public node_embedding_preload_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_start_execution_functor - { - public nint data; - public node_embedding_start_execution_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_handle_result_functor - { - public nint data; - public node_embedding_handle_result_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_initialize_module_functor - { - public nint data; - public node_embedding_initialize_module_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_run_task_functor - { - public nint data; - public node_embedding_run_task_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_post_task_functor - { - public nint data; - public node_embedding_post_task_callback invoke; - public node_embedding_release_data_callback release; - } - - public struct node_embedding_run_node_api_functor_ref : IDisposable - { - public nint data; - public node_embedding_run_node_api_callback invoke; - - public node_embedding_run_node_api_functor_ref( - object? functor, node_embedding_run_node_api_callback invoke) - { - data = (functor != null) ? (nint)GCHandle.Alloc(functor) : default; - this.invoke = (functor != null) - ? invoke - : new node_embedding_run_node_api_callback(0); - } - - public void Dispose() - { - if (data == 0) return; - GCHandle.FromIntPtr(data).Free(); - data = 0; - } - } } diff --git a/src/NodeApi/Runtime/JSRuntime.cs b/src/NodeApi/Runtime/JSRuntime.cs index 86f574f6..66fa3629 100644 --- a/src/NodeApi/Runtime/JSRuntime.cs +++ b/src/NodeApi/Runtime/JSRuntime.cs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace Microsoft.JavaScript.NodeApi.Runtime; - using static NodejsRuntime; /// @@ -520,96 +519,128 @@ public virtual napi_status GetBufferInfo( #region Embedding - public virtual node_embedding_status - EmbeddingOnError(node_embedding_handle_error_functor error_handler) => throw NS(); + public virtual string EmbeddingGetLastErrorMessage() => throw NS(); - public virtual node_embedding_status EmbeddingSetApiVersion( - int embedding_api_version, - int node_api_version) => throw NS(); + public virtual void EmbeddingSetLastErrorMessage(ReadOnlySpan message) => throw NS(); - public virtual node_embedding_status EmbeddingRunMain( + public virtual NodeEmbeddingStatus EmbeddingRunMain( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime) => throw NS(); + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) => throw NS(); - public virtual node_embedding_status EmbeddingCreatePlatform( + public virtual NodeEmbeddingStatus EmbeddingCreatePlatform( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, out node_embedding_platform result) => throw NS(); - public virtual node_embedding_status + public virtual NodeEmbeddingStatus EmbeddingDeletePlatform(node_embedding_platform platform) => throw NS(); - public virtual node_embedding_status EmbeddingPlatformSetFlags( + public virtual NodeEmbeddingStatus EmbeddingPlatformConfigSetFlags( node_embedding_platform_config platform_config, - node_embedding_platform_flags flags) => throw NS(); + NodeEmbeddingPlatformFlags flags) => throw NS(); - public virtual node_embedding_status EmbeddingPlatformGetParsedArgs( + public virtual NodeEmbeddingStatus EmbeddingPlatformGetParsedArgs( node_embedding_platform platform, - node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_runtime_args) => throw NS(); + nint args_count, + nint args, + nint runtime_args_count, + nint runtime_args) => throw NS(); - public virtual node_embedding_status EmbeddingRunRuntime( + public virtual NodeEmbeddingStatus EmbeddingRunRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime) => throw NS(); + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) => throw NS(); - public virtual node_embedding_status EmbeddingCreateRuntime( + public virtual NodeEmbeddingStatus EmbeddingCreateRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data, out node_embedding_runtime result) => throw NS(); - public virtual node_embedding_status + public virtual NodeEmbeddingStatus EmbeddingDeleteRuntime(node_embedding_runtime runtime) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeSetFlags( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigSetNodeApiVersion( + node_embedding_runtime_config runtime_config, + int node_api_version) => throw NS(); + + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigSetFlags( node_embedding_runtime_config runtime_config, - node_embedding_runtime_flags flags) => throw NS(); + NodeEmbeddingRuntimeFlags flags) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeSetArgs( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigSetArgs( node_embedding_runtime_config runtime_config, ReadOnlySpan args, ReadOnlySpan runtime_args) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeOnPreload( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigOnPreload( node_embedding_runtime_config runtime_config, - node_embedding_preload_functor run_preload) => throw NS(); + node_embedding_runtime_preload_callback preload, + nint preload_data, + node_embedding_data_release_callback release_preload_data) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeOnStartExecution( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoading( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution, - node_embedding_handle_result_functor handle_result) => throw NS(); + node_embedding_runtime_loading_callback run_load, + nint load_data, + node_embedding_data_release_callback release_load_data) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeAddModule( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoaded( node_embedding_runtime_config runtime_config, - string moduleName, - node_embedding_initialize_module_functor init_module, - int module_node_api_version) => throw NS(); + node_embedding_runtime_loaded_callback handle_loaded, + nint handle_loaded_data, + node_embedding_data_release_callback release_handle_loaded_data) => throw NS(); - public virtual node_embedding_status EmbeddingRuntimeSetTaskRunner( + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigAddModule( node_embedding_runtime_config runtime_config, - node_embedding_post_task_functor post_task) => throw NS(); + ReadOnlySpan module_name, + node_embedding_module_initialize_callback init_module, + nint init_module_data, + node_embedding_data_release_callback release_init_module_data, + int module_node_api_version) => throw NS(); + + public virtual NodeEmbeddingStatus EmbeddingRuntimeSetUserData( + node_embedding_runtime runtime, + nint user_data, + node_embedding_data_release_callback release_user_data) => throw NS(); - public virtual node_embedding_status EmbeddingRunEventLoop( + public virtual NodeEmbeddingStatus EmbeddingRuntimeGetUserData( node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - out bool has_more_work) => throw NS(); + out nint user_data) => throw NS(); + + public virtual NodeEmbeddingStatus EmbeddingRuntimeConfigSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_task_post_callback post_task, + nint post_task_data, + node_embedding_data_release_callback release_post_task_data) => throw NS(); + + public virtual NodeEmbeddingStatus EmbeddingRuntimeRunEventLoop( + node_embedding_runtime runtime) => throw NS(); + + public virtual NodeEmbeddingStatus EmbeddingRuntimeTerminateEventLoop( + node_embedding_runtime runtime) => throw NS(); - public virtual node_embedding_status - EmbeddingCompleteEventLoop(node_embedding_runtime runtime) => throw NS(); + public virtual NodeEmbeddingStatus EmbeddingRuntimeRunOnceEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) => throw NS(); - public virtual node_embedding_status - EmbeddingTerminateEventLoop(node_embedding_runtime runtime) => throw NS(); + public virtual NodeEmbeddingStatus EmbeddingRuntimeRunNoWaitEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) => throw NS(); - public virtual node_embedding_status EmbeddingRunNodeApi( + public virtual NodeEmbeddingStatus EmbeddingRuntimeRunNodeApi( node_embedding_runtime runtime, - node_embedding_run_node_api_functor_ref run_node_api) => throw NS(); + node_embedding_node_api_run_callback run_node_api, + nint run_node_api_data) => throw NS(); - public virtual node_embedding_status EmbeddingOpenNodeApiScope( + public virtual NodeEmbeddingStatus EmbeddingRuntimeOpenNodeApiScope( node_embedding_runtime runtime, out node_embedding_node_api_scope node_api_scope, out napi_env env) => throw NS(); - public virtual node_embedding_status EmbeddingCloseNodeApiScope( + public virtual NodeEmbeddingStatus EmbeddingRuntimeCloseNodeApiScope( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope) => throw NS(); diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs new file mode 100644 index 00000000..57ef9294 --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -0,0 +1,420 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +#if UNMANAGED_DELEGATES +using System.Runtime.CompilerServices; +#endif +using System.Runtime.InteropServices; +using static JSRuntime; +using static NodejsRuntime; + +/// +/// Shared code for the Node.js embedding classes. +/// +public sealed class NodeEmbedding +{ + public static readonly int EmbeddingApiVersion = 1; + public static readonly int NodeApiVersion = 8; + + private static JSRuntime? s_jsRuntime; + + public static JSRuntime JSRuntime + { + get + { + if (s_jsRuntime == null) + { + throw new InvalidOperationException("The JSRuntime is not initialized."); + } + return s_jsRuntime; + } + } + + public static void Initialize(string libNodePath) + { + if (string.IsNullOrEmpty(libNodePath)) throw new ArgumentNullException(nameof(libNodePath)); + if (s_jsRuntime != null) + { + throw new InvalidOperationException( + "The JSRuntime can be initialized only once per process."); + } + nint libnodeHandle = NativeLibrary.Load(libNodePath); + s_jsRuntime = new NodejsRuntime(libnodeHandle); + } + + public delegate void ConfigurePlatformCallback(node_embedding_platform_config platformConfig); + public delegate void ConfigureRuntimeCallback( + node_embedding_platform platform, node_embedding_runtime_config runtimeConfig); + public delegate void PreloadCallback( + NodeEmbeddingRuntime runtime, JSValue process, JSValue require); + public delegate JSValue LoadingCallback( + NodeEmbeddingRuntime runtime, JSValue process, JSValue require, JSValue runCommonJS); + public delegate void LoadedCallback( + NodeEmbeddingRuntime runtime, JSValue loadResul); + public delegate JSValue InitializeModuleCallback( + NodeEmbeddingRuntime runtime, string moduleName, JSValue exports); + public delegate void RunTaskCallback(); + public delegate bool PostTaskCallback( + node_embedding_task_run_callback runTask, + nint taskData, + node_embedding_data_release_callback releaseTaskData); + public delegate void RunNodeApiCallback(); + + public struct Functor + { + public nint Data; + public T Callback; + public readonly unsafe node_embedding_data_release_callback DataRelease => + new(s_releaseDataCallback); + } + + public struct FunctorRef : IDisposable + { + public nint Data; + public T Callback; + + public readonly void Dispose() + { + if (Data != default) + GCHandle.FromIntPtr(Data).Free(); + } + } + + public static unsafe FunctorRef + CreatePlatformConfigureFunctorRef(ConfigurePlatformCallback? callback) => new() + { + Data = callback != null ? (nint)GCHandle.Alloc(callback) : default, + Callback = callback != null + ? new node_embedding_platform_configure_callback(s_platformConfigureCallback) + : default + }; + + public static unsafe FunctorRef + CreateRuntimeConfigureFunctorRef(ConfigureRuntimeCallback? callback) => new() + { + Data = callback != null ? (nint)GCHandle.Alloc(callback) : default, + Callback = callback != null + ? new node_embedding_runtime_configure_callback(s_runtimeConfigureCallback) + : default + }; + + public static unsafe Functor + CreateRuntimePreloadFunctor(PreloadCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_runtime_preload_callback(s_runtimePreloadCallback) + }; + + public static unsafe Functor + CreateRuntimeLoadingFunctor(LoadingCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_runtime_loading_callback(s_runtimeLoadingCallback) + }; + + public static unsafe Functor + CreateRuntimeLoadedFunctor(LoadedCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_runtime_loaded_callback(s_runtimeLoadedCallback) + }; + + public static unsafe Functor + CreateModuleInitializeFunctor(InitializeModuleCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_module_initialize_callback(s_moduleInitializeCallback) + }; + + public static unsafe Functor + CreateTaskPostFunctor(PostTaskCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_task_post_callback(s_taskPostCallback) + }; + +#if UNMANAGED_DELEGATES + internal static readonly unsafe delegate* unmanaged[Cdecl] + s_releaseDataCallback = &ReleaseDataCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_platform_config, NodeEmbeddingStatus> + s_platformConfigureCallback = &PlatformConfigureCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, + node_embedding_platform, + node_embedding_runtime_config, + NodeEmbeddingStatus> + s_runtimeConfigureCallback = &RuntimeConfigureCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> + s_runtimePreloadCallback = &RuntimePreloadCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, napi_value, napi_value> + s_runtimeLoadingCallback = &RuntimeLoadingCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, void> + s_runtimeLoadedCallback = &RuntimeLoadedCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, nint, napi_value, napi_value> + s_moduleInitializeCallback = &ModuleInitializeCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl] + s_taskRunTaskCallback = &TaskRunCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, + node_embedding_task_run_callback, + nint, + node_embedding_data_release_callback, + nint, + NodeEmbeddingStatus> + s_taskPostCallback = &TaskPostCallbackAdapter; + internal static readonly unsafe delegate* unmanaged[Cdecl]< + nint, napi_env, void> + s_nodeApiRunCallback = &NodeApiRunCallbackAdapter; +#else + internal static readonly node_embedding_data_release_callback.Delegate + s_releaseDataCallback = ReleaseDataCallbackAdapter; + internal static readonly node_embedding_platform_configure_callback.Delegate + s_platformConfigureCallback = PlatformConfigureCallbackAdapter; + internal static readonly node_embedding_runtime_configure_callback.Delegate + s_runtimeConfigureCallback = RuntimeConfigureCallbackAdapter; + internal static readonly node_embedding_runtime_preload_callback.Delegate + s_runtimePreloadCallback = RuntimePreloadCallbackAdapter; + internal static readonly node_embedding_runtime_loading_callback.Delegate + s_runtimeLoadingCallback = RuntimeLoadingCallbackAdapter; + internal static readonly node_embedding_runtime_loaded_callback.Delegate + s_runtimeLoadedCallback = RuntimeLoadedCallbackAdapter; + internal static readonly node_embedding_module_initialize_callback.Delegate + s_moduleInitializeCallback = ModuleInitializeCallbackAdapter; + internal static readonly node_embedding_task_run_callback.Delegate + s_taskRunCallback = TaskRunCallbackAdapter; + internal static readonly node_embedding_task_post_callback.Delegate + s_taskPostCallback = TaskPostCallbackAdapter; + internal static readonly node_embedding_node_api_run_callback.Delegate + s_nodeApiRunCallback = NodeApiRunCallbackAdapter; +#endif + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe NodeEmbeddingStatus ReleaseDataCallbackAdapter(nint data) + { + if (data != default) + { + GCHandle.FromIntPtr(data).Free(); + } + return NodeEmbeddingStatus.OK; + } + + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe NodeEmbeddingStatus PlatformConfigureCallbackAdapter( + nint cb_data, + node_embedding_platform_config platform_config) + { + try + { + var callback = (ConfigurePlatformCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(platform_config); + return NodeEmbeddingStatus.OK; + } + catch (Exception ex) + { + JSRuntime.EmbeddingSetLastErrorMessage(ex.Message.AsSpan()); + return NodeEmbeddingStatus.GenericError; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe NodeEmbeddingStatus RuntimeConfigureCallbackAdapter( + nint cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config) + { + try + { + var callback = (ConfigureRuntimeCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(platform, runtime_config); + return NodeEmbeddingStatus.OK; + } + catch (Exception ex) + { + JSRuntime.EmbeddingSetLastErrorMessage(ex.Message.AsSpan()); + return NodeEmbeddingStatus.GenericError; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void RuntimePreloadCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require) + { + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + try + { + var callback = (PreloadCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + callback(embeddingRuntime, new JSValue(process), new JSValue(require)); + } + catch (Exception ex) + { + JSError.ThrowError(ex); + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe napi_value RuntimeLoadingCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs) + { + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + try + { + var callback = (LoadingCallback)GCHandle.FromIntPtr(cb_data).Target!; + //TODO: Unwrap from runtime + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + return (napi_value)callback( + embeddingRuntime, new JSValue(process), new JSValue(require), new JSValue(run_cjs)); + } + catch (Exception ex) + { + JSError.ThrowError(ex); + return napi_value.Null; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void RuntimeLoadedCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value loading_result) + { + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + try + { + var callback = (LoadedCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + callback(embeddingRuntime, new JSValue(loading_result)); + } + catch (Exception ex) + { + JSError.ThrowError(ex); + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe napi_value ModuleInitializeCallbackAdapter( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + nint module_name, + napi_value exports) + { + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + try + { + var callback = (InitializeModuleCallback)GCHandle.FromIntPtr(cb_data).Target!; + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) + ?? throw new InvalidOperationException("Embedding runtime is not found"); + return (napi_value)callback( + embeddingRuntime, + Utf8StringArray.PtrToStringUTF8((byte*)module_name), + new JSValue(exports)); + } + catch (Exception ex) + { + JSError.ThrowError(ex); + return napi_value.Null; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe NodeEmbeddingStatus TaskRunCallbackAdapter(nint cb_data) + { + try + { + var callback = (RunTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(); + return NodeEmbeddingStatus.OK; + } + catch (Exception ex) + { + JSRuntime.EmbeddingSetLastErrorMessage(ex.Message.AsSpan()); + return NodeEmbeddingStatus.GenericError; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe NodeEmbeddingStatus TaskPostCallbackAdapter( + nint cb_data, + node_embedding_task_run_callback run_task, + nint task_data, + node_embedding_data_release_callback release_task_data, + nint is_posted) + { + try + { + var callback = (PostTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; + bool isPosted = callback(run_task, task_data, release_task_data); + if (is_posted != default) + { + *(c_bool*)is_posted = isPosted; + } + + return NodeEmbeddingStatus.OK; + } + catch (Exception ex) + { + JSRuntime.EmbeddingSetLastErrorMessage(ex.Message.AsSpan()); + return NodeEmbeddingStatus.GenericError; + } + } + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + internal static unsafe void NodeApiRunCallbackAdapter( + nint cb_data, + napi_env env) + { + using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); + try + { + var callback = (RunNodeApiCallback)GCHandle.FromIntPtr(cb_data).Target!; + callback(); + } + catch (Exception ex) + { + JSError.ThrowError(ex); + } + } +} diff --git a/src/NodeApi/Runtime/NodeEmbeddingModuleInfo.cs b/src/NodeApi/Runtime/NodeEmbeddingModuleInfo.cs new file mode 100644 index 00000000..739c023c --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbeddingModuleInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using static NodeEmbedding; + +public class NodeEmbeddingModuleInfo +{ + public required string Name { get; set; } + public required InitializeModuleCallback OnInitialize { get; set; } + public int? NodeApiVersion { get; set; } +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs b/src/NodeApi/Runtime/NodeEmbeddingNodeApiScope.cs similarity index 60% rename from src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs rename to src/NodeApi/Runtime/NodeEmbeddingNodeApiScope.cs index 784285a4..a63f603d 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingNodeApiScope.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingNodeApiScope.cs @@ -5,21 +5,22 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; using System; using static JSRuntime; +using static NodejsRuntime; -public sealed class NodejsEmbeddingNodeApiScope : IDisposable +public sealed class NodeEmbeddingNodeApiScope : IDisposable { - readonly NodejsEmbeddingRuntime _runtime; + readonly NodeEmbeddingRuntime _runtime; private node_embedding_node_api_scope _nodeApiScope; private readonly JSValueScope _valueScope; - public NodejsEmbeddingNodeApiScope(NodejsEmbeddingRuntime runtime) + public NodeEmbeddingNodeApiScope(NodeEmbeddingRuntime runtime) { _runtime = runtime; - NodejsEmbeddingRuntime.JSRuntime.EmbeddingOpenNodeApiScope( - runtime, out _nodeApiScope, out napi_env env) + NodeEmbeddingRuntime.JSRuntime.EmbeddingRuntimeOpenNodeApiScope( + runtime.Handle, out _nodeApiScope, out napi_env env) .ThrowIfFailed(); _valueScope = new JSValueScope( - JSValueScopeType.Root, env, NodejsEmbeddingRuntime.JSRuntime); + JSValueScopeType.Root, env, NodeEmbeddingRuntime.JSRuntime); } /// @@ -36,7 +37,8 @@ public void Dispose() IsDisposed = true; _valueScope.Dispose(); - NodejsEmbeddingRuntime.JSRuntime.EmbeddingCloseNodeApiScope(_runtime, _nodeApiScope) + NodeEmbeddingRuntime.JSRuntime.EmbeddingRuntimeCloseNodeApiScope( + _runtime.Handle, _nodeApiScope) .ThrowIfFailed(); } } diff --git a/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs new file mode 100644 index 00000000..0c56f1d5 --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using static NodeEmbedding; +using static NodejsRuntime; + +/// +/// Manages a Node.js platform instance, provided by `libnode`. +/// +/// +/// Only one Node.js platform instance can be created per process. Once the platform is disposed, +/// another platform instance cannot be re-initialized. One or more +/// instances may be created using the platform. +/// +public sealed class NodeEmbeddingPlatform : IDisposable +{ + private node_embedding_platform _platform; + + public static explicit operator node_embedding_platform(NodeEmbeddingPlatform platform) + => platform._platform; + + /// + /// Initializes the Node.js platform. + /// + /// Path to the `libnode` shared library, including extension. + /// Optional platform settings. + /// A Node.js platform instance has already been + /// loaded in the current process. + public NodeEmbeddingPlatform(string libNodePath, NodeEmbeddingPlatformSettings? settings) + { + if (Current != null) + { + throw new InvalidOperationException( + "Only one Node.js platform instance per process is allowed."); + } + Current = this; + Initialize(libNodePath); + + using FunctorRef functorRef = + CreatePlatformConfigureFunctorRef(settings?.CreateConfigurePlatformCallback()); + JSRuntime.EmbeddingCreatePlatform( + settings?.Args ?? new string[] { "node" }, + functorRef.Callback, + functorRef.Data, + out _platform) + .ThrowIfFailed(); + } + + public node_embedding_platform Handle => _platform; + + /// + /// Gets a value indicating whether the current platform has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Gets the Node.js platform instance for the current process, or null if not initialized. + /// + public static NodeEmbeddingPlatform? Current { get; private set; } + + public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; + + /// + /// Disposes the platform. After disposal, another platform instance may not be initialized + /// in the current process. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + JSRuntime.EmbeddingDeletePlatform(_platform); + } + + /// + /// Creates a new Node.js embedding runtime with a dedicated main thread. + /// + /// Optional directory that is used as the base directory when resolving + /// imported modules, and also as the value of the global `__dirname` property. If unspecified, + /// importing modules is not enabled and `__dirname` is undefined. + /// Optional script to run in the environment. (Literal script content, + /// not a path to a script file.) + /// A new instance. + public NodeEmbeddingThreadRuntime CreateThreadRuntime( + string? baseDir = null, + NodeEmbeddingRuntimeSettings? settings = null) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingPlatform)); + + return new NodeEmbeddingThreadRuntime(this, baseDir, settings); + } + + public unsafe string[] GetParsedArgs() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingPlatform)); + + int argc = 0; + nint argv = 0; + JSRuntime.EmbeddingPlatformGetParsedArgs( + _platform, (nint)(&argc), (nint)(&argv), 0, 0).ThrowIfFailed(); + return Utf8StringArray.ToStringArray(argv, argc); + } + + public unsafe string[] GetRuntimeParsedArgs() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingPlatform)); + + int argc = 0; + nint argv = 0; + JSRuntime.EmbeddingPlatformGetParsedArgs( + _platform, 0, 0, (nint)(&argc), (nint)(&argv)).ThrowIfFailed(); + return Utf8StringArray.ToStringArray(argv, argc); + } +} diff --git a/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs b/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs new file mode 100644 index 00000000..4297de6b --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using static NodeEmbedding; +using static NodejsRuntime; + +public class NodeEmbeddingPlatformSettings +{ + public NodeEmbeddingPlatformFlags? PlatformFlags { get; set; } + public string[]? Args { get; set; } + public ConfigurePlatformCallback? ConfigurePlatform { get; set; } + + public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; + + public unsafe ConfigurePlatformCallback CreateConfigurePlatformCallback() + => new((config) => + { + if (PlatformFlags != null) + { + JSRuntime.EmbeddingPlatformConfigSetFlags(config, PlatformFlags.Value) + .ThrowIfFailed(); + } + ConfigurePlatform?.Invoke(config); + }); +} diff --git a/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs new file mode 100644 index 00000000..6327530f --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using static NodeEmbedding; +using static NodejsRuntime; + +/// +/// A Node.js runtime. +/// +/// +/// Multiple Node.js environments may be created (concurrently) in the same process. +/// +public sealed class NodeEmbeddingRuntime : IDisposable +{ + private node_embedding_runtime _runtime; + private static readonly + Dictionary s_embeddedRuntimes = new(); + + public static explicit operator node_embedding_runtime(NodeEmbeddingRuntime runtime) + => runtime._runtime; + + public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; + + public unsafe NodeEmbeddingRuntime( + NodeEmbeddingPlatform platform, NodeEmbeddingRuntimeSettings? settings = null) + { + using FunctorRef functorRef = + CreateRuntimeConfigureFunctorRef(settings?.CreateConfigureRuntimeCallback()); + JSRuntime.EmbeddingCreateRuntime( + platform.Handle, + functorRef.Callback, + functorRef.Data, + out _runtime) + .ThrowIfFailed(); + } + + private NodeEmbeddingRuntime(node_embedding_runtime runtime) + { + _runtime = runtime; + lock (s_embeddedRuntimes) { s_embeddedRuntimes.Add(runtime, this); } + } + + public node_embedding_runtime Handle => _runtime; + + public static NodeEmbeddingRuntime? FromHandle(node_embedding_runtime runtime) + { + lock (s_embeddedRuntimes) + { + if (s_embeddedRuntimes.TryGetValue( + runtime, out NodeEmbeddingRuntime? embeddingRuntime)) + { + return embeddingRuntime; + } + return null; + } + } + + public static NodeEmbeddingRuntime GetOrCreate(node_embedding_runtime runtime) + { + NodeEmbeddingRuntime? embeddingRuntime = FromHandle(runtime); + embeddingRuntime ??= new NodeEmbeddingRuntime(runtime); + return embeddingRuntime; + } + + public static unsafe void Run(NodeEmbeddingPlatform platform, + NodeEmbeddingRuntimeSettings? settings = null) + { + ConfigureRuntimeCallback? configureRuntime = settings?.CreateConfigureRuntimeCallback(); + nint callbackData = configureRuntime != null + ? (nint)GCHandle.Alloc(configureRuntime) + : default; + try + { + JSRuntime.EmbeddingRunRuntime( + platform.Handle, + new node_embedding_runtime_configure_callback(s_runtimeConfigureCallback), + callbackData) + .ThrowIfFailed(); + } + finally + { + if (callbackData != default) GCHandle.FromIntPtr(callbackData).Free(); + } + } + + /// + /// Gets a value indicating whether the Node.js environment is disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the Node.js environment, causing its main thread to exit. + /// + public void Dispose() + { + if (IsDisposed) return; + IsDisposed = true; + + lock (s_embeddedRuntimes) { s_embeddedRuntimes.Remove(_runtime); } + JSRuntime.EmbeddingDeleteRuntime(_runtime).ThrowIfFailed(); + } + + public unsafe void RunEventLoop() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); + + JSRuntime.EmbeddingRuntimeRunEventLoop(_runtime).ThrowIfFailed(); + } + + public unsafe void TerminateEventLoop() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); + + JSRuntime.EmbeddingRuntimeTerminateEventLoop(_runtime).ThrowIfFailed(); + } + + public unsafe bool RunEventLoopOnce() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); + + JSRuntime.EmbeddingRuntimeRunOnceEventLoop(_runtime, out bool result).ThrowIfFailed(); + return result; + } + + public unsafe bool RunEventLoopNoWait() + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); + + JSRuntime.EmbeddingRuntimeRunNoWaitEventLoop(_runtime, out bool result).ThrowIfFailed(); + return result; + } + + public unsafe void RunNodeApi(RunNodeApiCallback runNodeApi) + { + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); + + nint callbackData = (nint)GCHandle.Alloc(runNodeApi); + try + { + JSRuntime.EmbeddingRuntimeRunNodeApi( + _runtime, + new node_embedding_node_api_run_callback(s_nodeApiRunCallback), + callbackData) + .ThrowIfFailed(); + } + finally + { + if (callbackData != default) GCHandle.FromIntPtr(callbackData).Free(); + } + } +} diff --git a/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs b/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs new file mode 100644 index 00000000..958990c0 --- /dev/null +++ b/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using static NodeEmbedding; +using static NodejsRuntime; + +public class NodeEmbeddingRuntimeSettings +{ + public int? NodeApiVersion { get; set; } + public NodeEmbeddingRuntimeFlags? RuntimeFlags { get; set; } + public string[]? Args { get; set; } + public string[]? RuntimeArgs { get; set; } + public PreloadCallback? OnPreload { get; set; } + public string? MainScript { get; set; } + public LoadingCallback? OnLoading { get; set; } + public LoadedCallback? OnLoaded { get; set; } + public IEnumerable? Modules { get; set; } + public PostTaskCallback? OnPostTask { get; set; } + public ConfigureRuntimeCallback? ConfigureRuntime { get; set; } + + public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; + + public unsafe ConfigureRuntimeCallback CreateConfigureRuntimeCallback() + => new((platform, config) => + { + if (NodeApiVersion != null) + { + JSRuntime.EmbeddingRuntimeConfigSetNodeApiVersion( + config, NodeApiVersion.Value) + .ThrowIfFailed(); + } + if (RuntimeFlags != null) + { + JSRuntime.EmbeddingRuntimeConfigSetFlags(config, RuntimeFlags.Value) + .ThrowIfFailed(); + } + if (Args != null || RuntimeArgs != null) + { + JSRuntime.EmbeddingRuntimeConfigSetArgs(config, Args, RuntimeArgs) + .ThrowIfFailed(); + } + + if (OnPreload != null) + { + Functor functor = + CreateRuntimePreloadFunctor(OnPreload); + JSRuntime.EmbeddingRuntimeConfigOnPreload( + config, functor.Callback, functor.Data, functor.DataRelease) + .ThrowIfFailed(); + } + + if (MainScript != null) + { + JSValue onLoading(NodeEmbeddingRuntime runtime, + JSValue process, + JSValue require, + JSValue runCommonJS) + => runCommonJS.Call(JSValue.Null, (JSValue)MainScript); + + Functor functor = + CreateRuntimeLoadingFunctor(onLoading); + JSRuntime.EmbeddingRuntimeConfigOnLoading( + config, functor.Callback, functor.Data, functor.DataRelease) + .ThrowIfFailed(); + } + else if (OnLoading != null) + { + Functor functor = + CreateRuntimeLoadingFunctor(OnLoading); + JSRuntime.EmbeddingRuntimeConfigOnLoading( + config, functor.Callback, functor.Data, functor.DataRelease) + .ThrowIfFailed(); + } + + if (OnLoaded != null) + { + Functor functor = + CreateRuntimeLoadedFunctor(OnLoaded); + JSRuntime.EmbeddingRuntimeConfigOnLoaded( + config, functor.Callback, functor.Data, functor.DataRelease) + .ThrowIfFailed(); + } + + if (Modules != null) + { + foreach (NodeEmbeddingModuleInfo module in Modules) + { + Functor functor = + CreateModuleInitializeFunctor(module.OnInitialize); + JSRuntime.EmbeddingRuntimeConfigAddModule( + config, + module.Name.AsSpan(), + functor.Callback, + functor.Data, + functor.DataRelease, + module.NodeApiVersion ?? 0) + .ThrowIfFailed(); + } + } + + if (OnPostTask != null) + { + Functor functor = + CreateTaskPostFunctor(OnPostTask); + JSRuntime.EmbeddingRuntimeConfigSetTaskRunner( + config, + new node_embedding_task_post_callback(s_taskPostCallback), + (nint)GCHandle.Alloc(OnPostTask), + new node_embedding_data_release_callback(s_releaseDataCallback)) + .ThrowIfFailed(); + } + ConfigureRuntime?.Invoke(platform, config); + }); +} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs b/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs similarity index 93% rename from src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs rename to src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs index 76fd0b73..9b126dbc 100644 --- a/src/NodeApi/Runtime/NodejsEmbeddingThreadRuntime.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.JavaScript.NodeApi.Interop; - -namespace Microsoft.JavaScript.NodeApi.Runtime; - using static JSRuntime; /// @@ -20,23 +19,23 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; /// environment instance has its own dedicated execution thread. Except where otherwise documented, /// all interaction with the environment and JavaScript values associated with the environment MUST /// be executed on the environment's thread. Use the -/// to switch to the thread. +/// to switch to the thread. /// -public sealed class NodejsEmbeddingThreadRuntime : IDisposable +public sealed class NodeEmbeddingThreadRuntime : IDisposable { private readonly JSValueScope _scope; private readonly Thread _thread; private readonly JSThreadSafeFunction? _completion; - public static explicit operator napi_env(NodejsEmbeddingThreadRuntime environment) => + public static explicit operator napi_env(NodeEmbeddingThreadRuntime environment) => (napi_env)environment._scope; - public static implicit operator JSValueScope(NodejsEmbeddingThreadRuntime environment) => + public static implicit operator JSValueScope(NodeEmbeddingThreadRuntime environment) => environment._scope; - internal NodejsEmbeddingThreadRuntime( - NodejsEmbeddingPlatform platform, + internal NodeEmbeddingThreadRuntime( + NodeEmbeddingPlatform platform, string? baseDir, - NodejsEmbeddingRuntimeSettings? settings) + NodeEmbeddingRuntimeSettings? settings) { JSValueScope scope = null!; JSSynchronizationContext syncContext = null!; @@ -45,14 +44,14 @@ internal NodejsEmbeddingThreadRuntime( _thread = new(() => { - using var runtime = new NodejsEmbeddingRuntime(platform, settings); + using var runtime = new NodeEmbeddingRuntime(platform, settings); // The new scope instance saves itself as the thread-local JSValueScope.Current. - using var nodeApiScope = new NodejsEmbeddingNodeApiScope(runtime); + using var nodeApiScope = new NodeEmbeddingNodeApiScope(runtime); completion = new JSThreadSafeFunction( maxQueueSize: 0, initialThreadCount: 1, - asyncResourceName: (JSValue)nameof(NodejsEmbeddingThreadRuntime)); + asyncResourceName: (JSValue)nameof(NodeEmbeddingThreadRuntime)); scope = JSValueScope.Current; syncContext = scope.RuntimeContext.SynchronizationContext; @@ -68,7 +67,7 @@ internal NodejsEmbeddingThreadRuntime( // Run the JS event loop until disposal unrefs the completion thread safe function. try { - runtime.CompleteEventLoop(); + runtime.RunEventLoop(); ExitCode = 0; } catch (Exception) @@ -87,7 +86,7 @@ internal NodejsEmbeddingThreadRuntime( SynchronizationContext = syncContext; } - public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; + public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; private static void InitializeModuleImportFunctions( JSRuntimeContext runtimeContext, @@ -118,10 +117,10 @@ private static void InitializeModuleImportFunctions( // The import keyword is not a function and is only available through use of an // external helper module. #if NETFRAMEWORK || NETSTANDARD - string assemblyLocation = new Uri(typeof(NodejsEmbeddingThreadRuntime).Assembly.CodeBase).LocalPath; + string assemblyLocation = new Uri(typeof(NodeEmbeddingThreadRuntime).Assembly.CodeBase).LocalPath; #else #pragma warning disable IL3000 // Assembly.Location returns an empty string for assemblies embedded in a single-file app - string assemblyLocation = typeof(NodejsEmbeddingThreadRuntime).Assembly.Location; + string assemblyLocation = typeof(NodeEmbeddingThreadRuntime).Assembly.Location; #pragma warning restore IL3000 #endif if (!string.IsNullOrEmpty(assemblyLocation)) @@ -217,7 +216,7 @@ public void Dispose() public Uri StartInspector(int? port = null, string? host = null, bool? wait = null) { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingThreadRuntime)); + if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingThreadRuntime)); return SynchronizationContext.Run(() => { diff --git a/src/NodeApi/Runtime/NodeJSRuntime.cs b/src/NodeApi/Runtime/NodeJSRuntime.cs index 72c711cc..6855874b 100644 --- a/src/NodeApi/Runtime/NodeJSRuntime.cs +++ b/src/NodeApi/Runtime/NodeJSRuntime.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Text; -namespace Microsoft.JavaScript.NodeApi.Runtime; // This part of the class includes the constructor and private helper methods. // See the other parts of this class for the actual imported APIs. [SuppressUnmanagedCodeSecurity] @@ -100,62 +101,110 @@ private nint Import(string functionName) return function; } - private delegate* unmanaged[Cdecl] Import( - ref delegate* unmanaged[Cdecl] function, + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl])Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, [CallerArgumentExpression(nameof(function))] string functionName = "") { if (function == null) { - function = (delegate* unmanaged[Cdecl])Import(functionName); + function = (delegate* unmanaged[Cdecl])Import(functionName); } return function; } - private delegate* unmanaged[Cdecl] Import( - ref delegate* unmanaged[Cdecl] function, + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, [CallerArgumentExpression(nameof(function))] string functionName = "") { if (function == null) { - function = (delegate* unmanaged[Cdecl]) + function = (delegate* unmanaged[Cdecl])Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) Import(functionName); } return function; } - private delegate* unmanaged[Cdecl] Import( - ref delegate* unmanaged[Cdecl] function, + private delegate* unmanaged[Cdecl] Import( + ref delegate* unmanaged[Cdecl] function, [CallerArgumentExpression(nameof(function))] string functionName = "") { if (function == null) { - function = (delegate* unmanaged[Cdecl]) + function = (delegate* unmanaged[Cdecl]) Import(functionName); } return function; } - private delegate* unmanaged[Cdecl] + private delegate* unmanaged[Cdecl] Import( - ref delegate* unmanaged[Cdecl] function, + ref delegate* unmanaged[Cdecl] function, [CallerArgumentExpression(nameof(function))] string functionName = "") { if (function == null) { - function = (delegate* unmanaged[Cdecl]) + function = (delegate* unmanaged[Cdecl]) Import(functionName); } return function; } - private delegate* unmanaged[Cdecl] + private delegate* unmanaged[Cdecl] Import( - ref delegate* unmanaged[Cdecl] function, + ref delegate* unmanaged[Cdecl] function, [CallerArgumentExpression(nameof(function))] string functionName = "") { if (function == null) { - function = (delegate* unmanaged[Cdecl]) + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] + Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) + Import(functionName); + } + return function; + } + + private delegate* unmanaged[Cdecl] + Import( + ref delegate* unmanaged[Cdecl] function, + [CallerArgumentExpression(nameof(function))] string functionName = "") + { + if (function == null) + { + function = (delegate* unmanaged[Cdecl]) Import(functionName); } return function; diff --git a/src/NodeApi/Runtime/NodejsEmbedding.cs b/src/NodeApi/Runtime/NodejsEmbedding.cs deleted file mode 100644 index 441140e4..00000000 --- a/src/NodeApi/Runtime/NodejsEmbedding.cs +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using System; -#if UNMANAGED_DELEGATES -using System.Runtime.CompilerServices; -#endif -using System.Runtime.InteropServices; -using static JSRuntime; - -/// -/// Shared code for the Node.js embedding classes. -/// -public sealed class NodejsEmbedding -{ - public static readonly int EmbeddingApiVersion = 1; - public static readonly int NodeApiVersion = 9; - - private static JSRuntime? s_jsRuntime; - - public static JSRuntime JSRuntime - { - get - { - if (s_jsRuntime == null) - { - throw new InvalidOperationException("The JSRuntime is not initialized."); - } - return s_jsRuntime; - } - } - - public static void Initialize(string libnodePath) - { - if (string.IsNullOrEmpty(libnodePath)) throw new ArgumentNullException(nameof(libnodePath)); - if (s_jsRuntime != null) - { - throw new InvalidOperationException( - "The JSRuntime can be initialized only once per process."); - } - nint libnodeHandle = NativeLibrary.Load(libnodePath); - s_jsRuntime = new NodejsRuntime(libnodeHandle); - } - - public delegate node_embedding_status HandleErrorCallback( - string[] messages, node_embedding_status status); - public delegate void ConfigurePlatformCallback( - node_embedding_platform_config platformConfig); - public delegate void ConfigureRuntimeCallback( - node_embedding_platform platform, node_embedding_runtime_config platformConfig); - public delegate void GetArgsCallback(string[] args); - public delegate void PreloadCallback( - NodejsEmbeddingRuntime runtime, JSValue process, JSValue require); - public delegate JSValue StartExecutionCallback( - NodejsEmbeddingRuntime runtime, JSValue process, JSValue require, JSValue runCommonJS); - public delegate void HandleResultCallback( - NodejsEmbeddingRuntime runtime, JSValue value); - public delegate JSValue InitializeModuleCallback( - NodejsEmbeddingRuntime runtime, string moduleName, JSValue exports); - public delegate void RunTaskCallback(); - public delegate void PostTaskCallback(node_embedding_run_task_functor runTask); - public delegate void RunNodeApiCallback(NodejsEmbeddingRuntime runtime); - -#if UNMANAGED_DELEGATES - internal static readonly unsafe delegate* unmanaged[Cdecl] - s_releaseDataCallback = &ReleaseDataCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, nint, nuint, node_embedding_status, node_embedding_status> - s_handleErrorCallback = &HandleErrorCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_platform_config, node_embedding_status> - s_configurePlatformCallback = &ConfigurePlatformCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, - node_embedding_platform, - node_embedding_runtime_config, - node_embedding_status> - s_configureRuntimeCallback = &ConfigureRuntimeCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, int, nint, void> - s_getArgsCallback = &GetArgsCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> - s_preloadCallback = &PreloadCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, napi_value, napi_value, napi_value, napi_value> - s_startExecutionCallback = &StartExecutionCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, napi_value, void> - s_handleResultCallback = &HandleResultCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, nint, napi_value, napi_value> - s_initializeModuleCallback = &InitializeModuleCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl] - s_runTaskCallback = &RunTaskCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_run_task_functor, void> - s_postTaskCallback = &PostTaskCallbackAdapter; - internal static readonly unsafe delegate* unmanaged[Cdecl]< - nint, node_embedding_runtime, napi_env, void> - s_runNodeApiCallback = &RunNodeApiCallbackAdapter; -#else - internal static readonly node_embedding_release_data_callback.Delegate - s_releaseDataCallback = ReleaseDataCallbackAdapter; - internal static readonly node_embedding_handle_error_callback.Delegate - s_handleErrorCallback = HandleErrorCallbackAdapter; - internal static readonly node_embedding_configure_platform_callback.Delegate - s_configurePlatformCallback = ConfigurePlatformCallbackAdapter; - internal static readonly node_embedding_configure_runtime_callback.Delegate - s_configureRuntimeCallback = ConfigureRuntimeCallbackAdapter; - internal static readonly node_embedding_get_args_callback.Delegate - s_getArgsCallback = GetArgsCallbackAdapter; - internal static readonly node_embedding_preload_callback.Delegate - s_preloadCallback = PreloadCallbackAdapter; - internal static readonly node_embedding_start_execution_callback.Delegate - s_startExecutionCallback = StartExecutionCallbackAdapter; - internal static readonly node_embedding_handle_result_callback.Delegate - s_handleResultCallback = HandleResultCallbackAdapter; - internal static readonly node_embedding_initialize_module_callback.Delegate - s_initializeModuleCallback = InitializeModuleCallbackAdapter; - internal static readonly node_embedding_run_task_callback.Delegate - s_runTaskCallback = RunTaskCallbackAdapter; - internal static readonly node_embedding_post_task_callback.Delegate - s_postTaskCallback = PostTaskCallbackAdapter; - internal static readonly node_embedding_run_node_api_callback.Delegate - s_runNodeApiCallback = RunNodeApiCallbackAdapter; -#endif - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void ReleaseDataCallbackAdapter(nint data) - { - if (data != default) - { - GCHandle.FromIntPtr(data).Free(); - } - } - - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe node_embedding_status HandleErrorCallbackAdapter( - nint cb_data, - nint messages, - nuint messages_size, - node_embedding_status status) - { - try - { - var callback = (HandleErrorCallback)GCHandle.FromIntPtr(cb_data).Target!; - return callback(Utf8StringArray.ToStringArray(messages, (int)messages_size), status); - } - catch (Exception) - { - return node_embedding_status.generic_error; - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe node_embedding_status ConfigurePlatformCallbackAdapter( - nint cb_data, - node_embedding_platform_config platform_config) - { - try - { - var callback = (ConfigurePlatformCallback)GCHandle.FromIntPtr(cb_data).Target!; - callback(platform_config); - return node_embedding_status.ok; - } - catch (Exception) - { - return node_embedding_status.generic_error; - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe node_embedding_status ConfigureRuntimeCallbackAdapter( - nint cb_data, - node_embedding_platform platform, - node_embedding_runtime_config runtime_config) - { - try - { - var callback = (ConfigureRuntimeCallback)GCHandle.FromIntPtr(cb_data).Target!; - callback(platform, runtime_config); - return node_embedding_status.ok; - } - catch (Exception) - { - return node_embedding_status.generic_error; - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void GetArgsCallbackAdapter(nint cb_data, int argc, nint argv) - { - try - { - var callback = (GetArgsCallback)GCHandle.FromIntPtr(cb_data).Target!; - callback(Utf8StringArray.ToStringArray(argv, argc)); - } - catch (Exception) - { - // TODO: Handle exception. - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void PreloadCallbackAdapter( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require) - { - try - { - var callback = (PreloadCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); - using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); - callback(embeddingRuntime, new JSValue(process), new JSValue(require)); - } - catch (Exception) - { - // TODO: Handle exception. - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe napi_value StartExecutionCallbackAdapter( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value process, - napi_value require, - napi_value run_cjs) - { - try - { - var callback = (StartExecutionCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); - using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); - return (napi_value)callback( - embeddingRuntime, new JSValue(process), new JSValue(require), new JSValue(run_cjs)); - } - catch (Exception) - { - return default; - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void HandleResultCallbackAdapter( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - napi_value value) - { - try - { - var callback = (HandleResultCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); - using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); - callback(embeddingRuntime, new JSValue(value)); - } - catch (Exception) - { - // TODO: Handle exception. - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe napi_value InitializeModuleCallbackAdapter( - nint cb_data, - node_embedding_runtime runtime, - napi_env env, - nint module_name, - napi_value exports) - { - try - { - var callback = (InitializeModuleCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); - using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); - return (napi_value)callback( - embeddingRuntime, - Utf8StringArray.PtrToStringUTF8((byte*)module_name), - new JSValue(exports)); - } - catch (Exception) - { - return default; - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void RunTaskCallbackAdapter(nint cb_data) - { - try - { - var callback = (RunTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; - callback(); - } - catch (Exception) - { - // TODO: Handle exception. - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void PostTaskCallbackAdapter( - nint cb_data, - node_embedding_run_task_functor run_task) - { - try - { - var callback = (PostTaskCallback)GCHandle.FromIntPtr(cb_data).Target!; - callback(run_task); - } - catch (Exception) - { - // TODO: Handle exception. - } - } - -#if UNMANAGED_DELEGATES - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] -#endif - internal static unsafe void RunNodeApiCallbackAdapter( - nint cb_data, - node_embedding_runtime runtime, - napi_env env) - { - try - { - var callback = (RunNodeApiCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodejsEmbeddingRuntime embeddingRuntime = NodejsEmbeddingRuntime.FromHandle(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); - using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); - callback(embeddingRuntime); - } - catch (Exception) - { - // TODO: Handle exception. - } - } -} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs b/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs deleted file mode 100644 index 44cdb2f3..00000000 --- a/src/NodeApi/Runtime/NodejsEmbeddingModuleInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using static NodejsEmbedding; - -public class NodejsEmbeddingModuleInfo -{ - public string? Name { get; set; } - public InitializeModuleCallback? OnInitialize { get; set; } - public int? NodeApiVersion { get; set; } -} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs deleted file mode 100644 index 483a5d2e..00000000 --- a/src/NodeApi/Runtime/NodejsEmbeddingPlatform.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using System; -using System.Runtime.InteropServices; -using static JSRuntime; -using static NodejsEmbedding; - -/// -/// Manages a Node.js platform instance, provided by `libnode`. -/// -/// -/// Only one Node.js platform instance can be created per process. Once the platform is disposed, -/// another platform instance cannot be re-initialized. One or more -/// instances may be created using the platform. -/// -public sealed class NodejsEmbeddingPlatform : IDisposable -{ - private node_embedding_platform _platform; - - public static implicit operator node_embedding_platform(NodejsEmbeddingPlatform platform) - => platform._platform; - - /// - /// Initializes the Node.js platform. - /// - /// Path to the `libnode` shared library, including extension. - /// Optional platform settings. - /// A Node.js platform instance has already been - /// loaded in the current process. - public unsafe NodejsEmbeddingPlatform( - string libnodePath, NodejsEmbeddingPlatformSettings? settings) - { - if (Current != null) - { - throw new InvalidOperationException( - "Only one Node.js platform instance per process is allowed."); - } - Current = this; - Initialize(libnodePath); - - if (settings?.OnError != null) - { - var handle_error_functor = new node_embedding_handle_error_functor - { - data = (nint)GCHandle.Alloc(settings.OnError), - invoke = new node_embedding_handle_error_callback(s_handleErrorCallback), - release = new node_embedding_release_data_callback(s_releaseDataCallback), - }; - JSRuntime.EmbeddingOnError(handle_error_functor).ThrowIfFailed(); - } - - JSRuntime.EmbeddingSetApiVersion(EmbeddingApiVersion, NodeApiVersion).ThrowIfFailed(); - - using node_embedding_configure_platform_functor_ref configurePlatformFunctorRef = - settings ?? new NodejsEmbeddingPlatformSettings(); - - JSRuntime.EmbeddingCreatePlatform( - settings?.Args, configurePlatformFunctorRef, out _platform) - .ThrowIfFailed(); - } - - internal NodejsEmbeddingPlatform(node_embedding_platform platform) - { - _platform = platform; - } - - /// - /// Gets a value indicating whether the current platform has been disposed. - /// - public bool IsDisposed { get; private set; } - - /// - /// Gets the Node.js platform instance for the current process, or null if not initialized. - /// - public static NodejsEmbeddingPlatform? Current { get; private set; } - - public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; - - /// - /// Disposes the platform. After disposal, another platform instance may not be initialized - /// in the current process. - /// - public void Dispose() - { - if (IsDisposed) return; - IsDisposed = true; - JSRuntime.EmbeddingDeletePlatform(_platform); - } - - /// - /// Creates a new Node.js embedding runtime with a dedicated main thread. - /// - /// Optional directory that is used as the base directory when resolving - /// imported modules, and also as the value of the global `__dirname` property. If unspecified, - /// importing modules is not enabled and `__dirname` is undefined. - /// Optional script to run in the environment. (Literal script content, - /// not a path to a script file.) - /// A new instance. - public NodejsEmbeddingThreadRuntime CreateThreadRuntime( - string? baseDir = null, - NodejsEmbeddingRuntimeSettings? settings = null) - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); - - return new NodejsEmbeddingThreadRuntime(this, baseDir, settings); - } - - public unsafe void GetParsedArgs(GetArgsCallback? getArgs, GetArgsCallback? getRuntimeArgs) - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); - - using var getArgsFunctorRef = new node_embedding_get_args_functor_ref( - getArgs, new node_embedding_get_args_callback(s_getArgsCallback)); - using var getRuntimeArgsFunctorRef = new node_embedding_get_args_functor_ref( - getRuntimeArgs, new node_embedding_get_args_callback(s_getArgsCallback)); - - JSRuntime.EmbeddingPlatformGetParsedArgs( - _platform, getArgsFunctorRef, getRuntimeArgsFunctorRef).ThrowIfFailed(); - } - - public string[] GetParsedArgs() - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); - - string[]? result = null; - GetParsedArgs((string[] args) => result = args, null); - return result ?? []; - } - - public string[] GetRuntimeParsedArgs() - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingPlatform)); - - string[]? result = null; - GetParsedArgs(null, (string[] args) => result = args); - return result ?? []; - } -} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs b/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs deleted file mode 100644 index ff197ad2..00000000 --- a/src/NodeApi/Runtime/NodejsEmbeddingPlatformSettings.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using static JSRuntime; -using static NodejsEmbedding; - -public class NodejsEmbeddingPlatformSettings -{ - public node_embedding_platform_flags? PlatformFlags { get; set; } - public string[]? Args { get; set; } - public HandleErrorCallback? OnError { get; set; } - public ConfigurePlatformCallback? ConfigurePlatform { get; set; } - - public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; - - public static unsafe implicit operator node_embedding_configure_platform_functor_ref( - NodejsEmbeddingPlatformSettings? settings) - { - var confgurePlatform = new ConfigurePlatformCallback((config) => - { - if (settings?.PlatformFlags != null) - { - JSRuntime.EmbeddingPlatformSetFlags(config, settings.PlatformFlags.Value) - .ThrowIfFailed(); - } - settings?.ConfigurePlatform?.Invoke(config); - }); - - return new node_embedding_configure_platform_functor_ref( - confgurePlatform, - new node_embedding_configure_platform_callback(s_configurePlatformCallback)); - } -} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs deleted file mode 100644 index c41172bc..00000000 --- a/src/NodeApi/Runtime/NodejsEmbeddingRuntime.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using System; -using System.Collections.Generic; -using static JSRuntime; -using static NodejsEmbedding; - -/// -/// A Node.js runtime. -/// -/// -/// Multiple Node.js environments may be created (concurrently) in the same process. -/// -public sealed class NodejsEmbeddingRuntime : IDisposable -{ - private node_embedding_runtime _runtime; - private static readonly - Dictionary s_embeddedRuntimes = new(); - - public static implicit operator node_embedding_runtime(NodejsEmbeddingRuntime runtime) - => runtime._runtime; - - public struct Module - { - public string Name { get; set; } - public InitializeModuleCallback OnInitialize { get; set; } - public int? NodeApiVersion { get; set; } - } - - public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; - - public NodejsEmbeddingRuntime( - NodejsEmbeddingPlatform platform, NodejsEmbeddingRuntimeSettings? settings = null) - { - JSRuntime.EmbeddingCreateRuntime( - platform, settings ?? new NodejsEmbeddingRuntimeSettings(), out _runtime) - .ThrowIfFailed(); - } - - private NodejsEmbeddingRuntime(node_embedding_runtime runtime) - { - _runtime = runtime; - lock (s_embeddedRuntimes) { s_embeddedRuntimes.Add(runtime, this); } - } - - public static NodejsEmbeddingRuntime? FromHandle(node_embedding_runtime runtime) - { - lock (s_embeddedRuntimes) - { - if (s_embeddedRuntimes.TryGetValue( - runtime, out NodejsEmbeddingRuntime? embeddingRuntime)) - { - return embeddingRuntime; - } - return null; - } - } - - public static NodejsEmbeddingRuntime GetOrCreate(node_embedding_runtime runtime) - { - NodejsEmbeddingRuntime? embeddingRuntime = FromHandle(runtime); - embeddingRuntime ??= new NodejsEmbeddingRuntime(runtime); - return embeddingRuntime; - } - - public static void Run(NodejsEmbeddingPlatform platform, - NodejsEmbeddingRuntimeSettings? settings = null) - { - JSRuntime.EmbeddingRunRuntime(platform, settings ?? new NodejsEmbeddingRuntimeSettings()) - .ThrowIfFailed(); - } - - /// - /// Gets a value indicating whether the Node.js environment is disposed. - /// - public bool IsDisposed { get; private set; } - - /// - /// Disposes the Node.js environment, causing its main thread to exit. - /// - public void Dispose() - { - if (IsDisposed) return; - IsDisposed = true; - - lock (s_embeddedRuntimes) { s_embeddedRuntimes.Remove(_runtime); } - JSRuntime.EmbeddingDeleteRuntime(_runtime).ThrowIfFailed(); - } - - public unsafe bool RunEventLoop(node_embedding_event_loop_run_mode runMode) - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); - - return JSRuntime.EmbeddingRunEventLoop(_runtime, runMode, out bool hasMoreWork) - .ThrowIfFailed(hasMoreWork); - } - - public unsafe void CompleteEventLoop() - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); - - JSRuntime.EmbeddingCompleteEventLoop(_runtime).ThrowIfFailed(); - } - - public unsafe void TerminateEventLoop() - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); - - JSRuntime.EmbeddingTerminateEventLoop(_runtime).ThrowIfFailed(); - } - - public unsafe void RunNodeApi(RunNodeApiCallback runNodeApi) - { - if (IsDisposed) throw new ObjectDisposedException(nameof(NodejsEmbeddingRuntime)); - - using var runNodeApiFunctorRef = new node_embedding_run_node_api_functor_ref( - runNodeApi, new node_embedding_run_node_api_callback(s_runNodeApiCallback)); - JSRuntime.EmbeddingRunNodeApi(_runtime, runNodeApiFunctorRef).ThrowIfFailed(); - } -} diff --git a/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs b/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs deleted file mode 100644 index acc3be9e..00000000 --- a/src/NodeApi/Runtime/NodejsEmbeddingRuntimeSettings.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.JavaScript.NodeApi.Runtime; - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using static JSRuntime; -using static NodejsEmbedding; - -public sealed class NodejsEmbeddingRuntimeSettings -{ - public node_embedding_runtime_flags? RuntimeFlags { get; set; } - public string[]? Args { get; set; } - public string[]? RuntimeArgs { get; set; } - public PreloadCallback? OnPreload { get; set; } - public StartExecutionCallback? StartExecution { get; set; } - public string? MainScript { get; set; } - public HandleResultCallback? HandleStartExecutionResult { get; set; } - public IEnumerable? Modules { get; set; } - public PostTaskCallback? OnPostTask { get; set; } - public ConfigureRuntimeCallback? ConfigureRuntime { get; set; } - - public static JSRuntime JSRuntime => NodejsEmbedding.JSRuntime; - - public static unsafe implicit operator node_embedding_configure_runtime_functor_ref( - NodejsEmbeddingRuntimeSettings? settings) - { - var confgureRuntime = new ConfigureRuntimeCallback((platform, config) => - { - if (settings?.RuntimeFlags != null) - { - JSRuntime.EmbeddingRuntimeSetFlags(config, settings.RuntimeFlags.Value) - .ThrowIfFailed(); - } - if (settings?.Args != null || settings?.RuntimeArgs != null) - { - JSRuntime.EmbeddingRuntimeSetArgs(config, settings.Args, settings.RuntimeArgs) - .ThrowIfFailed(); - } - if (settings?.OnPreload != null) - { - var preloadFunctor = new node_embedding_preload_functor - { - data = (nint)GCHandle.Alloc(settings.OnPreload), - invoke = new node_embedding_preload_callback(s_preloadCallback), - release = new node_embedding_release_data_callback(s_releaseDataCallback), - }; - JSRuntime.EmbeddingRuntimeOnPreload(config, preloadFunctor).ThrowIfFailed(); - } - if (settings?.StartExecution != null - || settings?.MainScript != null - || settings?.HandleStartExecutionResult != null) - { - StartExecutionCallback? startExecutionCallback = - settings?.MainScript != null - ? (NodejsEmbeddingRuntime runtime, JSValue process, JSValue require, JSValue runCommonJS) - => runCommonJS.Call(JSValue.Null, (JSValue)settings.MainScript) - : settings?.StartExecution; - node_embedding_start_execution_functor startExecutionFunctor = - startExecutionCallback != null - ? new node_embedding_start_execution_functor - { - data = (nint)GCHandle.Alloc(startExecutionCallback), - invoke = new node_embedding_start_execution_callback( - s_startExecutionCallback), - release = new node_embedding_release_data_callback(s_releaseDataCallback), - } : default; - node_embedding_handle_result_functor handleStartExecutionResultFunctor = - settings?.HandleStartExecutionResult != null - ? new node_embedding_handle_result_functor - { - data = (nint)GCHandle.Alloc(settings.HandleStartExecutionResult), - invoke = new node_embedding_handle_result_callback(s_handleResultCallback), - release = new node_embedding_release_data_callback(s_releaseDataCallback), - } : default; - JSRuntime.EmbeddingRuntimeOnStartExecution( - config, startExecutionFunctor, handleStartExecutionResultFunctor) - .ThrowIfFailed(); - } - if (settings?.Modules != null) - { - foreach (NodejsEmbeddingModuleInfo module in settings.Modules) - { - var moduleFunctor = new node_embedding_initialize_module_functor - { - data = (nint)GCHandle.Alloc(module.OnInitialize - ?? throw new ArgumentException("Module initialization is missing")), - invoke = new node_embedding_initialize_module_callback( - s_initializeModuleCallback), - release = new node_embedding_release_data_callback( - s_releaseDataCallback), - }; - - JSRuntime.EmbeddingRuntimeAddModule( - config, - module.Name ?? throw new ArgumentException("Module name is missing"), - moduleFunctor, - module.NodeApiVersion ?? NodeApiVersion) - .ThrowIfFailed(); - } - } - if (settings?.OnPostTask != null) - { - var postTaskFunctor = new node_embedding_post_task_functor - { - data = (nint)GCHandle.Alloc(settings.OnPostTask), - invoke = new node_embedding_post_task_callback(s_postTaskCallback), - release = new node_embedding_release_data_callback(s_releaseDataCallback), - }; - JSRuntime.EmbeddingRuntimeSetTaskRunner(config, postTaskFunctor).ThrowIfFailed(); - } - settings?.ConfigureRuntime?.Invoke(platform, config); - }); - - return new node_embedding_configure_runtime_functor_ref( - confgureRuntime, - new node_embedding_configure_runtime_callback(s_configureRuntimeCallback)); - } -} diff --git a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs index 76769303..a586fd2e 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs @@ -1,67 +1,416 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace Microsoft.JavaScript.NodeApi.Runtime; +using System; +using System.Runtime.InteropServices; + // Imports embedding APIs from libnode. public unsafe partial class NodejsRuntime { #pragma warning disable IDE1006 // Naming: missing prefix '_' - private delegate* unmanaged[Cdecl] - node_embedding_on_error; + //============================================================================================== + // Data types + //============================================================================================== + + public record struct node_embedding_platform(nint Handle); + public record struct node_embedding_runtime(nint Handle); + public record struct node_embedding_platform_config(nint Handle); + public record struct node_embedding_runtime_config(nint Handle); + public record struct node_embedding_node_api_scope(nint Handle); + + public enum NodeEmbeddingStatus : int + { + OK = 0, + GenericError = 1, + NullArg = 2, + BadArg = 3, + OutOfMemory = 4, + // This value is added to the exit code in cases when Node.js API returns + // an error exit code. + ErrorExitCode = 512, + } + + [Flags] + public enum NodeEmbeddingPlatformFlags : int + { + None = 0, + // Enable stdio inheritance, which is disabled by default. + // This flag is also implied by + // node_embedding_platform_flags_no_stdio_initialization. + EnableStdioInheritance = 1 << 0, + // Disable reading the NODE_OPTIONS environment variable. + DisableNodeOptionsEnv = 1 << 1, + // Do not parse CLI options. + DisableCliOptions = 1 << 2, + // Do not initialize ICU. + NoIcu = 1 << 3, + // Do not modify stdio file descriptor or TTY state. + NoStdioInitialization = 1 << 4, + // Do not register Node.js-specific signal handlers + // and reset other signal handlers to default state. + NoDefaultSignalHandling = 1 << 5, + // Do not initialize OpenSSL config. + NoInitOpenSsl = 1 << 8, + // Do not initialize Node.js debugging based on environment variables. + NoParseGlobalDebugVariables = 1 << 9, + // Do not adjust OS resource limits for this process. + NoAdjustResourceLimits = 1 << 10, + // Do not map code segments into large pages for this process. + NoUseLargePages = 1 << 11, + // Skip printing output for --help, --version, --v8-options. + NoPrintHelpOrVersionOutput = 1 << 12, + // Initialize the process for predictable snapshot generation. + GeneratePredictableSnapshot = 1 << 14, + } + + // The flags for the Node.js runtime initialization. + // They match the internal EnvironmentFlags::Flags enum. + [Flags] + public enum NodeEmbeddingRuntimeFlags : int + { + None = 0, + // Use the default behavior for Node.js instances. + DefaultFlags = 1 << 0, + // Controls whether this Environment is allowed to affect per-process state + // (e.g. cwd, process title, uid, etc.). + // This is set when using default. + OwnsProcessState = 1 << 1, + // Set if this Environment instance is associated with the global inspector + // handling code (i.e. listening on SIGUSR1). + // This is set when using default. + OwnsInspector = 1 << 2, + // Set if Node.js should not run its own esm loader. This is needed by some + // embedders, because it's possible for the Node.js esm loader to conflict + // with another one in an embedder environment, e.g. Blink's in Chromium. + NoRegisterEsmLoader = 1 << 3, + // Set this flag to make Node.js track "raw" file descriptors, i.e. managed + // by fs.open() and fs.close(), and close them during + // node_embedding_delete_runtime(). + TrackUmanagedFDs = 1 << 4, + // Set this flag to force hiding console windows when spawning child + // processes. This is usually used when embedding Node.js in GUI programs on + // Windows. + HideConsoleWindows = 1 << 5, + // Set this flag to disable loading native addons via `process.dlopen`. + // This environment flag is especially important for worker threads + // so that a worker thread can't load a native addon even if `execArgv` + // is overwritten and `--no-addons` is not specified but was specified + // for this Environment instance. + NoNativeAddons = 1 << 6, + // Set this flag to disable searching modules from global paths like + // $HOME/.node_modules and $NODE_PATH. This is used by standalone apps that + // do not expect to have their behaviors changed because of globally + // installed modules. + NoGlobalSearchPaths = 1 << 7, + // Do not export browser globals like setTimeout, console, etc. + NoBrowserGlobals = 1 << 8, + // Controls whether or not the Environment should call V8Inspector::create(). + // This control is needed by embedders who may not want to initialize the V8 + // inspector in situations where one has already been created, + // e.g. Blink's in Chromium. + NoCreateInspector = 1 << 9, + // Controls whether or not the InspectorAgent for this Environment should + // call StartDebugSignalHandler. This control is needed by embedders who may + // not want to allow other processes to start the V8 inspector. + NoStartDebugSignalHandler = 1 << 10, + // Controls whether the InspectorAgent created for this Environment waits for + // Inspector frontend events during the Environment creation. It's used to + // call node::Stop(env) on a Worker thread that is waiting for the events. + NoWaitForInspectorFrontend = 1 << 11 + } + + //============================================================================================== + // Callbacks + //============================================================================================== + + public record struct node_embedding_data_release_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_data_release_callback( + delegate* unmanaged[Cdecl] handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NodeEmbeddingStatus Delegate(nint data); + + public node_embedding_data_release_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_platform_configure_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_platform_configure_callback(delegate* unmanaged[Cdecl]< + nint, node_embedding_platform_config, NodeEmbeddingStatus> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NodeEmbeddingStatus Delegate( + nint cb_data, + node_embedding_platform_config platform_config); + + public node_embedding_platform_configure_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_runtime_configure_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_runtime_configure_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_platform, + node_embedding_runtime_config, + NodeEmbeddingStatus> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NodeEmbeddingStatus Delegate( + nint cb_data, + node_embedding_platform platform, + node_embedding_runtime_config runtime_config); + + public node_embedding_runtime_configure_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_runtime_preload_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_runtime_preload_callback(delegate* unmanaged[Cdecl]< + nint, node_embedding_runtime, napi_env, napi_value, napi_value, void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require); + + public node_embedding_runtime_preload_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_runtime_loading_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_runtime_loading_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + napi_value, + napi_value, + napi_value, + napi_value> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate napi_value Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value process, + napi_value require, + napi_value run_cjs); + + public node_embedding_runtime_loading_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_runtime_loaded_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_runtime_loaded_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + napi_value, + void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + napi_value load_result); + + public node_embedding_runtime_loaded_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } - private delegate* unmanaged[Cdecl] - node_embedding_set_api_version; + public record struct node_embedding_module_initialize_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_module_initialize_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_runtime, + napi_env, + nint, + napi_value, + napi_value> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate napi_value Delegate( + nint cb_data, + node_embedding_runtime runtime, + napi_env env, + nint module_name, + napi_value exports); + + public node_embedding_module_initialize_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_task_run_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_task_run_callback( + delegate* unmanaged[Cdecl] handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NodeEmbeddingStatus Delegate(nint cb_data); + + public node_embedding_task_run_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_task_post_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_task_post_callback(delegate* unmanaged[Cdecl]< + nint, + node_embedding_task_run_callback, + nint, + node_embedding_data_release_callback, + nint, + NodeEmbeddingStatus> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NodeEmbeddingStatus Delegate( + nint cb_data, + node_embedding_task_run_callback run_task, + nint task_data, + node_embedding_data_release_callback release_task_data, + nint is_posted); + + public node_embedding_task_post_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + public record struct node_embedding_node_api_run_callback(nint Handle) + { +#if UNMANAGED_DELEGATES + public node_embedding_node_api_run_callback(delegate* unmanaged[Cdecl]< + nint, + napi_env, + void> handle) + : this((nint)handle) { } +#endif + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void Delegate( + nint cb_data, + napi_env env); + + public node_embedding_node_api_run_callback(Delegate callback) + : this(Marshal.GetFunctionPointerForDelegate(callback)) { } + } + + //============================================================================================== + // Functions + //============================================================================================== + + //---------------------------------------------------------------------------------------------- + // Error handling functions. + //---------------------------------------------------------------------------------------------- + + private delegate* unmanaged[Cdecl] node_embedding_last_error_message_get; + + private delegate* unmanaged[Cdecl] node_embedding_last_error_message_set; + + //---------------------------------------------------------------------------------------------- + // Node.js global platform functions. + //---------------------------------------------------------------------------------------------- private delegate* unmanaged[Cdecl]< + int, int, nint, - node_embedding_configure_platform_functor_ref, - node_embedding_configure_runtime_functor_ref, - node_embedding_status> node_embedding_run_main; + node_embedding_platform_configure_callback, + nint, + node_embedding_runtime_configure_callback, + nint, + NodeEmbeddingStatus> node_embedding_main_run; private delegate* unmanaged[Cdecl]< int, + int, + nint, + node_embedding_platform_configure_callback, nint, - node_embedding_configure_platform_functor_ref, nint, - node_embedding_status> node_embedding_create_platform; + NodeEmbeddingStatus> node_embedding_platform_create; - private delegate* unmanaged[Cdecl] - node_embedding_delete_platform; + private delegate* unmanaged[Cdecl] + node_embedding_platform_delete; private delegate* unmanaged[Cdecl]< node_embedding_platform_config, - node_embedding_platform_flags, - node_embedding_status> node_embedding_platform_set_flags; + NodeEmbeddingPlatformFlags, + NodeEmbeddingStatus> node_embedding_platform_config_set_flags; private delegate* unmanaged[Cdecl]< node_embedding_platform, - node_embedding_get_args_functor_ref, - node_embedding_get_args_functor_ref, - node_embedding_status> node_embedding_platform_get_parsed_args; + nint, + nint, + nint, + nint, + NodeEmbeddingStatus> node_embedding_platform_get_parsed_args; + + //---------------------------------------------------------------------------------------------- + // Node.js runtime functions. + //---------------------------------------------------------------------------------------------- private delegate* unmanaged[Cdecl]< node_embedding_platform, - node_embedding_configure_runtime_functor_ref, - node_embedding_status> node_embedding_run_runtime; + node_embedding_runtime_configure_callback, + nint, + NodeEmbeddingStatus> node_embedding_runtime_run; private delegate* unmanaged[Cdecl]< node_embedding_platform, - node_embedding_configure_runtime_functor_ref, + node_embedding_runtime_configure_callback, + nint, nint, - node_embedding_status> node_embedding_create_runtime; + NodeEmbeddingStatus> node_embedding_runtime_create; - private delegate* unmanaged[Cdecl] - node_embedding_delete_runtime; + private delegate* unmanaged[Cdecl] + node_embedding_runtime_delete; + + private delegate* unmanaged[Cdecl] + node_embedding_runtime_config_set_node_api_version; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, - node_embedding_runtime_flags, - node_embedding_status> node_embedding_runtime_set_flags; + NodeEmbeddingRuntimeFlags, + NodeEmbeddingStatus> node_embedding_runtime_config_set_flags; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, @@ -69,232 +418,343 @@ private delegate* unmanaged[Cdecl]< nint, int, nint, - node_embedding_status> node_embedding_runtime_set_args; + NodeEmbeddingStatus> node_embedding_runtime_config_set_args; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, - node_embedding_preload_functor, - node_embedding_status> node_embedding_runtime_on_preload; + node_embedding_runtime_preload_callback, + nint, + node_embedding_data_release_callback, + NodeEmbeddingStatus> node_embedding_runtime_config_on_preload; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, - node_embedding_start_execution_functor, - node_embedding_handle_result_functor, - node_embedding_status> node_embedding_runtime_on_start_execution; + node_embedding_runtime_loading_callback, + nint, + node_embedding_data_release_callback, + NodeEmbeddingStatus> node_embedding_runtime_config_on_loading; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, + node_embedding_runtime_loaded_callback, nint, - node_embedding_initialize_module_functor, - int, - node_embedding_status> node_embedding_runtime_add_module; + node_embedding_data_release_callback, + NodeEmbeddingStatus> node_embedding_runtime_config_on_loaded; private delegate* unmanaged[Cdecl]< node_embedding_runtime_config, - node_embedding_post_task_functor, - node_embedding_status> node_embedding_runtime_set_task_runner; + nint, + node_embedding_module_initialize_callback, + nint, + node_embedding_data_release_callback, + int, + NodeEmbeddingStatus> node_embedding_runtime_config_add_module; private delegate* unmanaged[Cdecl]< node_embedding_runtime, - node_embedding_event_loop_run_mode, nint, - node_embedding_status> node_embedding_run_event_loop; + node_embedding_data_release_callback, + NodeEmbeddingStatus> node_embedding_runtime_user_data_set; - private delegate* unmanaged[Cdecl] - node_embedding_complete_event_loop; + private delegate* unmanaged[Cdecl]< + node_embedding_runtime, + nint, + NodeEmbeddingStatus> node_embedding_runtime_user_data_get; - private delegate* unmanaged[Cdecl] - node_embedding_terminate_event_loop; + //---------------------------------------------------------------------------------------------- + // Node.js runtime functions for the event loop. + //---------------------------------------------------------------------------------------------- + + private delegate* unmanaged[Cdecl]< + node_embedding_runtime_config, + node_embedding_task_post_callback, + nint, + node_embedding_data_release_callback, + NodeEmbeddingStatus> node_embedding_runtime_config_set_task_runner; + + private delegate* unmanaged[Cdecl] + node_embedding_runtime_event_loop_run; + + private delegate* unmanaged[Cdecl] + node_embedding_runtime_event_loop_terminate; + + private delegate* unmanaged[Cdecl] + node_embedding_runtime_event_loop_run_once; + + private delegate* unmanaged[Cdecl] + node_embedding_runtime_event_loop_run_no_wait; + + //---------------------------------------------------------------------------------------------- + // Node.js runtime functions for the Node-API interop. + //---------------------------------------------------------------------------------------------- private delegate* unmanaged[Cdecl]< node_embedding_runtime, - node_embedding_run_node_api_functor_ref, - node_embedding_status> node_embedding_run_node_api; + node_embedding_node_api_run_callback, + nint, + NodeEmbeddingStatus> node_embedding_runtime_node_api_run; private delegate* unmanaged[Cdecl]< node_embedding_runtime, nint, nint, - node_embedding_status> node_embedding_open_node_api_scope; + NodeEmbeddingStatus> node_embedding_runtime_node_api_scope_open; private delegate* unmanaged[Cdecl]< node_embedding_runtime, node_embedding_node_api_scope, - node_embedding_status> node_embedding_close_node_api_scope; + NodeEmbeddingStatus> node_embedding_runtime_node_api_scope_close; - public override node_embedding_status - EmbeddingOnError(node_embedding_handle_error_functor error_handler) + //============================================================================================== + // C# function wrappers + //============================================================================================== + + public override string EmbeddingGetLastErrorMessage() { - return Import(ref node_embedding_on_error)(error_handler); + nint messagePtr = Import(ref node_embedding_last_error_message_get)(); + return PtrToStringUTF8((byte*)messagePtr) ?? string.Empty; } - public override node_embedding_status EmbeddingSetApiVersion( - int embedding_api_version, - int node_api_version) + public override void EmbeddingSetLastErrorMessage(ReadOnlySpan message) { - return Import(ref node_embedding_set_api_version)( - embedding_api_version, node_api_version); + using PooledBuffer messageBuffer = PooledBuffer.FromSpanUtf8(message); + fixed (byte* messagePtr = messageBuffer) + Import(ref node_embedding_last_error_message_set)((nint)messagePtr); } - public override node_embedding_status EmbeddingRunMain( + public override NodeEmbeddingStatus EmbeddingRunMain( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime) + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) { using Utf8StringArray utf8Args = new(args); - nint argsPtr = utf8Args.Pin(); - return Import(ref node_embedding_run_main)( - args.Length, argsPtr, configure_platform, configure_runtime); + fixed (nint* argsPtr = utf8Args) + return Import(ref node_embedding_main_run)( + 1, // Embedding API version + args.Length, + (nint)argsPtr, + configure_platform, + configure_platform_data, + configure_runtime, + configure_runtime_data); } - public override node_embedding_status EmbeddingCreatePlatform( + public override NodeEmbeddingStatus EmbeddingCreatePlatform( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, out node_embedding_platform result) { using Utf8StringArray utf8Args = new(args); - fixed (nint* argsPtr = &utf8Args.Pin()) + fixed (nint* argsPtr = utf8Args) fixed (node_embedding_platform* result_ptr = &result) { - return Import(ref node_embedding_create_platform)( - args.Length, (nint)argsPtr, configure_platform, (nint)result_ptr); + return Import(ref node_embedding_platform_create)( + 1, // Embedding API version + args.Length, + (nint)argsPtr, + configure_platform, + configure_platform_data, + (nint)result_ptr); } } - public override node_embedding_status - EmbeddingDeletePlatform(node_embedding_platform platform) + public override NodeEmbeddingStatus EmbeddingDeletePlatform(node_embedding_platform platform) { - return Import(ref node_embedding_delete_platform)(platform); + return Import(ref node_embedding_platform_delete)(platform); } - public override node_embedding_status EmbeddingPlatformSetFlags( - node_embedding_platform_config platform_config, - node_embedding_platform_flags flags) + public override NodeEmbeddingStatus EmbeddingPlatformConfigSetFlags( + node_embedding_platform_config platform_config, NodeEmbeddingPlatformFlags flags) { - return Import(ref node_embedding_platform_set_flags)(platform_config, flags); + return Import(ref node_embedding_platform_config_set_flags)(platform_config, flags); } - public override node_embedding_status EmbeddingPlatformGetParsedArgs( + public override NodeEmbeddingStatus EmbeddingPlatformGetParsedArgs( node_embedding_platform platform, - node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_runtime_args) + nint args_count, + nint args, + nint runtime_args_count, + nint runtime_args) { return Import(ref node_embedding_platform_get_parsed_args)( - platform, get_args, get_runtime_args); + platform, args_count, args, runtime_args_count, runtime_args); } - public override node_embedding_status EmbeddingRunRuntime( + public override NodeEmbeddingStatus EmbeddingRunRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime) + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) { - return Import(ref node_embedding_run_runtime)(platform, configure_runtime); + return Import(ref node_embedding_runtime_run)( + platform, configure_runtime, configure_runtime_data); } - public override node_embedding_status EmbeddingCreateRuntime( + public override NodeEmbeddingStatus EmbeddingCreateRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data, out node_embedding_runtime result) { fixed (node_embedding_runtime* result_ptr = &result) { - return Import(ref node_embedding_create_runtime)( - platform, configure_runtime, (nint)result_ptr); + return Import(ref node_embedding_runtime_create)( + platform, configure_runtime, configure_runtime_data, (nint)result_ptr); } } - public override node_embedding_status + public override NodeEmbeddingStatus EmbeddingDeleteRuntime(node_embedding_runtime runtime) { - return Import(ref node_embedding_delete_runtime)(runtime); + return Import(ref node_embedding_runtime_delete)(runtime); } - public override node_embedding_status EmbeddingRuntimeSetFlags( - node_embedding_runtime_config runtime_config, - node_embedding_runtime_flags flags) + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetNodeApiVersion( + node_embedding_runtime_config runtime_config, int node_api_version) { - return Import(ref node_embedding_runtime_set_flags)(runtime_config, flags); + return Import(ref node_embedding_runtime_config_set_node_api_version)( + runtime_config, node_api_version); } - public override node_embedding_status EmbeddingRuntimeSetArgs( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetFlags( + node_embedding_runtime_config runtime_config, NodeEmbeddingRuntimeFlags flags) + { + return Import(ref node_embedding_runtime_config_set_flags)(runtime_config, flags); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetArgs( node_embedding_runtime_config runtime_config, ReadOnlySpan args, ReadOnlySpan runtime_args) { using Utf8StringArray utf8Args = new(args); - nint argsPtr = utf8Args.Pin(); using Utf8StringArray utf8RuntimeArgs = new(runtime_args); - nint runtimeArgsPtr = utf8RuntimeArgs.Pin(); - return Import(ref node_embedding_runtime_set_args)( - runtime_config, args.Length, argsPtr, runtime_args.Length, runtimeArgsPtr); + fixed (nint* argsPtr = utf8Args) + fixed (nint* runtimeArgsPtr = utf8RuntimeArgs) + { + return Import(ref node_embedding_runtime_config_set_args)( + runtime_config, + args.Length, + (nint)argsPtr, + runtime_args.Length, + (nint)runtimeArgsPtr); + } } - public override node_embedding_status EmbeddingRuntimeOnPreload( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnPreload( node_embedding_runtime_config runtime_config, - node_embedding_preload_functor run_preload) + node_embedding_runtime_preload_callback preload, + nint preload_data, + node_embedding_data_release_callback release_preload_data) { - return Import(ref node_embedding_runtime_on_preload)(runtime_config, run_preload); + return Import(ref node_embedding_runtime_config_on_preload)( + runtime_config, preload, preload_data, release_preload_data); } - public override node_embedding_status EmbeddingRuntimeOnStartExecution( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoading( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution, - node_embedding_handle_result_functor handle_result) + node_embedding_runtime_loading_callback run_load, + nint load_data, + node_embedding_data_release_callback release_load_data) { - return Import(ref node_embedding_runtime_on_start_execution)( - runtime_config, start_execution, handle_result); + return Import(ref node_embedding_runtime_config_on_loading)( + runtime_config, run_load, load_data, release_load_data); } - public override node_embedding_status EmbeddingRuntimeAddModule( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoaded( node_embedding_runtime_config runtime_config, - string moduleName, - node_embedding_initialize_module_functor init_module, - int module_node_api_version) + node_embedding_runtime_loaded_callback handle_loaded, + nint handle_loaded_data, + node_embedding_data_release_callback release_handle_loaded_data) { - using (PooledBuffer moduleNameBuffer = PooledBuffer.FromStringUtf8(moduleName)) - fixed (byte* moduleNamePtr = &moduleNameBuffer.Pin()) - return Import(ref node_embedding_runtime_add_module)( - runtime_config, (nint)moduleNamePtr, init_module, module_node_api_version); + return Import(ref node_embedding_runtime_config_on_loaded)( + runtime_config, handle_loaded, handle_loaded_data, release_handle_loaded_data); } - public override node_embedding_status EmbeddingRuntimeSetTaskRunner( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigAddModule( node_embedding_runtime_config runtime_config, - node_embedding_post_task_functor post_task) + ReadOnlySpan module_name, + node_embedding_module_initialize_callback init_module, + nint init_module_data, + node_embedding_data_release_callback release_init_module_data, + int module_node_api_version) { - return Import(ref node_embedding_runtime_set_task_runner)(runtime_config, post_task); + PooledBuffer moduleNameBuffer = PooledBuffer.FromSpanUtf8(module_name); + fixed (byte* moduleNamePtr = moduleNameBuffer) + return Import(ref node_embedding_runtime_config_add_module)( + runtime_config, + (nint)moduleNamePtr, + init_module, + init_module_data, + release_init_module_data, + module_node_api_version); } - public override node_embedding_status EmbeddingRunEventLoop( + public override NodeEmbeddingStatus EmbeddingRuntimeSetUserData( node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - out bool has_more_work) + nint user_data, + node_embedding_data_release_callback release_user_data) + { + return Import(ref node_embedding_runtime_user_data_set)( + runtime, user_data, release_user_data); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeGetUserData( + node_embedding_runtime runtime, out nint user_data) + { + fixed (nint* userDataPtr = &user_data) + return Import(ref node_embedding_runtime_user_data_get)(runtime, (nint)userDataPtr); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_task_post_callback post_task, + nint post_task_data, + node_embedding_data_release_callback release_post_task_data) + { + return Import(ref node_embedding_runtime_config_set_task_runner)( + runtime_config, post_task, post_task_data, release_post_task_data); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeRunEventLoop(node_embedding_runtime runtime) + { + return Import(ref node_embedding_runtime_event_loop_run)(runtime); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeTerminateEventLoop( + node_embedding_runtime runtime) { - c_bool resultBool = default; - c_bool* result_ptr = &resultBool; - node_embedding_status status = Import(ref node_embedding_run_event_loop)( - runtime, run_mode, (nint)result_ptr); - has_more_work = (bool)resultBool; - return status; + return Import(ref node_embedding_runtime_event_loop_terminate)(runtime); } - public override node_embedding_status EmbeddingCompleteEventLoop(node_embedding_runtime runtime) + public override NodeEmbeddingStatus EmbeddingRuntimeRunOnceEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) { - return Import(ref node_embedding_complete_event_loop)(runtime); + fixed (bool* hasMoreWorkPtr = &hasMoreWork) + return Import(ref node_embedding_runtime_event_loop_run_once)( + runtime, (nint)hasMoreWorkPtr); } - public override node_embedding_status - EmbeddingTerminateEventLoop(node_embedding_runtime runtime) + public override NodeEmbeddingStatus EmbeddingRuntimeRunNoWaitEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) { - return Import(ref node_embedding_terminate_event_loop)(runtime); + fixed (bool* hasMoreWorkPtr = &hasMoreWork) + return Import(ref node_embedding_runtime_event_loop_run_no_wait)( + runtime, (nint)hasMoreWorkPtr); } - public override node_embedding_status EmbeddingRunNodeApi( + public override NodeEmbeddingStatus EmbeddingRuntimeRunNodeApi( node_embedding_runtime runtime, - node_embedding_run_node_api_functor_ref run_node_api) + node_embedding_node_api_run_callback run_node_api, + nint run_node_api_data) { - return Import(ref node_embedding_run_node_api)(runtime, run_node_api); + return Import(ref node_embedding_runtime_node_api_run)( + runtime, run_node_api, run_node_api_data); } - public override node_embedding_status EmbeddingOpenNodeApiScope( + public override NodeEmbeddingStatus EmbeddingRuntimeOpenNodeApiScope( node_embedding_runtime runtime, out node_embedding_node_api_scope node_api_scope, out napi_env env) @@ -302,16 +762,16 @@ public override node_embedding_status EmbeddingOpenNodeApiScope( fixed (node_embedding_node_api_scope* scopePtr = &node_api_scope) fixed (napi_env* envPtr = &env) { - return Import(ref node_embedding_open_node_api_scope)( + return Import(ref node_embedding_runtime_node_api_scope_open)( runtime, (nint)scopePtr, (nint)envPtr); } } - public override node_embedding_status EmbeddingCloseNodeApiScope( + public override NodeEmbeddingStatus EmbeddingRuntimeCloseNodeApiScope( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope) { - return Import(ref node_embedding_close_node_api_scope)(runtime, node_api_scope); + return Import(ref node_embedding_runtime_node_api_scope_close)(runtime, node_api_scope); } #pragma warning restore IDE1006 diff --git a/src/NodeApi/Runtime/NodejsRuntime.JS.cs b/src/NodeApi/Runtime/NodejsRuntime.JS.cs index 062d8a96..21e3e2b4 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.JS.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.JS.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace Microsoft.JavaScript.NodeApi.Runtime; +using System; + // Imports Node.js native APIs defined in js_native_api.h public unsafe partial class NodejsRuntime { @@ -187,8 +187,8 @@ public override napi_status ThrowError(napi_env env, string? code, string msg) { using (PooledBuffer codeBuffer = PooledBuffer.FromStringUtf8(code)) using (PooledBuffer msgBuffer = PooledBuffer.FromStringUtf8(msg)) - fixed (byte* code_ptr = &codeBuffer.Pin()) - fixed (byte* msg_ptr = &codeBuffer.Pin()) + fixed (byte* code_ptr = codeBuffer) + fixed (byte* msg_ptr = msgBuffer) { return Import(ref napi_throw_error)( env, @@ -203,8 +203,8 @@ public override napi_status ThrowTypeError(napi_env env, string? code, string ms { using (PooledBuffer codeBuffer = PooledBuffer.FromStringUtf8(code)) using (PooledBuffer msgBuffer = PooledBuffer.FromStringUtf8(msg)) - fixed (byte* code_ptr = &codeBuffer.Pin()) - fixed (byte* msg_ptr = &codeBuffer.Pin()) + fixed (byte* code_ptr = codeBuffer) + fixed (byte* msg_ptr = msgBuffer) { return Import(ref napi_throw_type_error)( env, @@ -220,8 +220,8 @@ public override napi_status ThrowRangeError(napi_env env, string? code, string m { using (PooledBuffer codeBuffer = PooledBuffer.FromStringUtf8(code)) using (PooledBuffer msgBuffer = PooledBuffer.FromStringUtf8(msg)) - fixed (byte* code_ptr = &codeBuffer.Pin()) - fixed (byte* msg_ptr = &codeBuffer.Pin()) + fixed (byte* code_ptr = codeBuffer) + fixed (byte* msg_ptr = msgBuffer) { return Import(ref napi_throw_range_error)( @@ -238,8 +238,8 @@ public override napi_status ThrowSyntaxError(napi_env env, string? code, string { using (PooledBuffer codeBuffer = PooledBuffer.FromStringUtf8(code)) using (PooledBuffer msgBuffer = PooledBuffer.FromStringUtf8(msg)) - fixed (byte* code_ptr = &codeBuffer.Pin()) - fixed (byte* msg_ptr = &codeBuffer.Pin()) + fixed (byte* code_ptr = codeBuffer) + fixed (byte* msg_ptr = msgBuffer) { return Import(ref node_api_throw_syntax_error)( env, @@ -562,7 +562,7 @@ public override napi_status GetSymbolFor(napi_env env, string name, out napi_val { result = default; using (PooledBuffer nameBuffer = PooledBuffer.FromStringUtf8(name)) - fixed (byte* name_ptr = &nameBuffer.Pin()) + fixed (byte* name_ptr = nameBuffer) fixed (napi_value* result_ptr = &result) { return Import(ref node_api_symbol_for)( @@ -1047,7 +1047,7 @@ public override napi_status CreateFunction( out napi_value result) { using (PooledBuffer nameBuffer = PooledBuffer.FromStringUtf8(name)) - fixed (byte* name_ptr = &nameBuffer.Pin()) + fixed (byte* name_ptr = nameBuffer) fixed (napi_value* result_ptr = &result) { return Import(ref napi_create_function)( @@ -1591,7 +1591,7 @@ public override napi_status DefineClass( { result = default; using (PooledBuffer nameBuffer = PooledBuffer.FromStringUtf8(name)) - fixed (byte* name_ptr = &nameBuffer.Pin()) + fixed (byte* name_ptr = nameBuffer) fixed (napi_property_descriptor* properties_ptr = &properties.GetPinnableReference()) fixed (napi_value* result_ptr = &result) { diff --git a/src/NodeApi/Runtime/NodejsRuntime.Node.cs b/src/NodeApi/Runtime/NodejsRuntime.Node.cs index 07cb0c5a..406f57ac 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.Node.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.Node.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; using System.Diagnostics.CodeAnalysis; -namespace Microsoft.JavaScript.NodeApi.Runtime; - // Imports Node.js native APIs defined in node_api.h public unsafe partial class NodejsRuntime { @@ -467,8 +467,8 @@ public override void FatalError(string location, string message) { using (PooledBuffer locationBuffer = PooledBuffer.FromStringUtf8(location)) using (PooledBuffer messageBuffer = PooledBuffer.FromStringUtf8(message)) - fixed (byte* location_ptr = &locationBuffer.Pin()) - fixed (byte* message_ptr = &messageBuffer.Pin()) + fixed (byte* location_ptr = locationBuffer) + fixed (byte* message_ptr = messageBuffer) { if (napi_fatal_error == null) { diff --git a/src/NodeApi/Runtime/NodejsRuntime.Types.cs b/src/NodeApi/Runtime/NodejsRuntime.Types.cs index a0843e19..2a326165 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.Types.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.Types.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Runtime.InteropServices; - namespace Microsoft.JavaScript.NodeApi.Runtime; +using System.Runtime.InteropServices; + // Type definitions from Node.JS node_api.h and node_api_types.h public unsafe partial class NodejsRuntime { diff --git a/src/NodeApi/Runtime/PooledBuffer.cs b/src/NodeApi/Runtime/PooledBuffer.cs index a8c62e8a..d4a234db 100644 --- a/src/NodeApi/Runtime/PooledBuffer.cs +++ b/src/NodeApi/Runtime/PooledBuffer.cs @@ -1,9 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; +#if !(NETFRAMEWORK || NETSTANDARD) using System.Buffers; +#endif +using System.ComponentModel; using System.Text; -namespace Microsoft.JavaScript.NodeApi.Runtime; - internal struct PooledBuffer : IDisposable { public static readonly PooledBuffer Empty = new(); @@ -16,8 +22,8 @@ public PooledBuffer() #if NETFRAMEWORK || NETSTANDARD - // Avoid a dependency on System.Buffers with .NET Framwork. - // It is available as a nuget package, but might not be installed in the application. + // Avoid a dependency on System.Buffers with .NET Framework. + // It is available as a NuGet package, but might not be installed in the application. // In this case the buffer is not actually pooled. public PooledBuffer(int length) : this(length, length) { } @@ -61,7 +67,9 @@ public void Dispose() public readonly Span Span => Buffer; - public readonly ref byte Pin() => ref Span.GetPinnableReference(); + // To support PooledBuffer usage within a fixed statement. + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly ref byte GetPinnableReference() => ref Span.GetPinnableReference(); public static unsafe PooledBuffer FromStringUtf8(string? value) { @@ -76,4 +84,21 @@ public static unsafe PooledBuffer FromStringUtf8(string? value) return buffer; } + + public static unsafe PooledBuffer FromSpanUtf8(ReadOnlySpan value) + { + if (value.IsEmpty) + { + return Empty; + } + + fixed (char* valuePtr = value) + { + int byteLength = Encoding.UTF8.GetByteCount(valuePtr, value.Length); + PooledBuffer buffer = new(byteLength, byteLength + 1); + fixed (byte* bufferPtr = buffer.Span) + Encoding.UTF8.GetBytes(valuePtr, value.Length, bufferPtr, byteLength + 1); + return buffer; + } + } } diff --git a/src/NodeApi/Runtime/TracingJSRuntime.cs b/src/NodeApi/Runtime/TracingJSRuntime.cs index 05ad780e..27aa2534 100644 --- a/src/NodeApi/Runtime/TracingJSRuntime.cs +++ b/src/NodeApi/Runtime/TracingJSRuntime.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; using System.Collections.Generic; using System.Diagnostics; @@ -10,9 +12,6 @@ using System.Runtime.InteropServices; using System.Text; using Microsoft.JavaScript.NodeApi.Interop; - -namespace Microsoft.JavaScript.NodeApi.Runtime; - using static NodejsRuntime; /// @@ -2658,165 +2657,222 @@ public override napi_status GetNodeVersion(napi_env env, out napi_node_version r #region Embedding - public override node_embedding_status - EmbeddingOnError(node_embedding_handle_error_functor error_handler) + public override string EmbeddingGetLastErrorMessage() { - return _runtime.EmbeddingOnError(error_handler); + return _runtime.EmbeddingGetLastErrorMessage(); } - public override node_embedding_status EmbeddingSetApiVersion( - int embedding_api_version, - int node_api_version) + public override void EmbeddingSetLastErrorMessage(ReadOnlySpan message) { - return _runtime.EmbeddingSetApiVersion(embedding_api_version, node_api_version); + _runtime.EmbeddingSetLastErrorMessage(message); } - public override node_embedding_status EmbeddingRunMain( + public override NodeEmbeddingStatus EmbeddingRunMain( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, - node_embedding_configure_runtime_functor_ref configure_runtime) + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) { - return _runtime.EmbeddingRunMain(args, configure_platform, configure_runtime); + return _runtime.EmbeddingRunMain( + args, + configure_platform, + configure_platform_data, + configure_runtime, + configure_runtime_data); } - public override node_embedding_status EmbeddingCreatePlatform( + public override NodeEmbeddingStatus EmbeddingCreatePlatform( ReadOnlySpan args, - node_embedding_configure_platform_functor_ref configure_platform, + node_embedding_platform_configure_callback configure_platform, + nint configure_platform_data, out node_embedding_platform result) { - return _runtime.EmbeddingCreatePlatform(args, configure_platform, out result); + return _runtime.EmbeddingCreatePlatform( + args, configure_platform, configure_platform_data, out result); } - public override node_embedding_status - EmbeddingDeletePlatform(node_embedding_platform platform) + public override NodeEmbeddingStatus EmbeddingDeletePlatform(node_embedding_platform platform) { return _runtime.EmbeddingDeletePlatform(platform); } - public override node_embedding_status EmbeddingPlatformSetFlags( - node_embedding_platform_config platform_config, - node_embedding_platform_flags flags) + public override NodeEmbeddingStatus EmbeddingPlatformConfigSetFlags( + node_embedding_platform_config platform_config, NodeEmbeddingPlatformFlags flags) { - return _runtime.EmbeddingPlatformSetFlags(platform_config, flags); + return _runtime.EmbeddingPlatformConfigSetFlags(platform_config, flags); } - public override node_embedding_status EmbeddingPlatformGetParsedArgs( + public override NodeEmbeddingStatus EmbeddingPlatformGetParsedArgs( node_embedding_platform platform, - node_embedding_get_args_functor_ref get_args, - node_embedding_get_args_functor_ref get_runtime_args) + nint args_count, + nint args, + nint runtime_args_count, + nint runtime_args) { - return _runtime.EmbeddingPlatformGetParsedArgs(platform, get_args, get_runtime_args); + return _runtime.EmbeddingPlatformGetParsedArgs( + platform, args_count, args, runtime_args_count, runtime_args); } - public override node_embedding_status EmbeddingRunRuntime( + public override NodeEmbeddingStatus EmbeddingRunRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime) + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data) { - return _runtime.EmbeddingRunRuntime(platform, configure_runtime); + return _runtime.EmbeddingRunRuntime(platform, configure_runtime, configure_runtime_data); } - public override node_embedding_status EmbeddingCreateRuntime( + public override NodeEmbeddingStatus EmbeddingCreateRuntime( node_embedding_platform platform, - node_embedding_configure_runtime_functor_ref configure_runtime, + node_embedding_runtime_configure_callback configure_runtime, + nint configure_runtime_data, out node_embedding_runtime result) { - return _runtime.EmbeddingCreateRuntime(platform, configure_runtime, out result); + return _runtime.EmbeddingCreateRuntime( + platform, configure_runtime, configure_runtime_data, out result); } - public override node_embedding_status + public override NodeEmbeddingStatus EmbeddingDeleteRuntime(node_embedding_runtime runtime) { return _runtime.EmbeddingDeleteRuntime(runtime); } - public override node_embedding_status EmbeddingRuntimeSetFlags( - node_embedding_runtime_config runtime_config, - node_embedding_runtime_flags flags) + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetNodeApiVersion( + node_embedding_runtime_config runtime_config, int node_api_version) { - return _runtime.EmbeddingRuntimeSetFlags(runtime_config, flags); + return _runtime.EmbeddingRuntimeConfigSetNodeApiVersion(runtime_config, node_api_version); } - public override node_embedding_status EmbeddingRuntimeSetArgs( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetFlags( + node_embedding_runtime_config runtime_config, NodeEmbeddingRuntimeFlags flags) + { + return _runtime.EmbeddingRuntimeConfigSetFlags(runtime_config, flags); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetArgs( node_embedding_runtime_config runtime_config, ReadOnlySpan args, ReadOnlySpan runtime_args) { - return _runtime.EmbeddingRuntimeSetArgs(runtime_config, args, runtime_args); + return _runtime.EmbeddingRuntimeConfigSetArgs(runtime_config, args, runtime_args); } - public override node_embedding_status EmbeddingRuntimeOnPreload( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnPreload( node_embedding_runtime_config runtime_config, - node_embedding_preload_functor run_preload) + node_embedding_runtime_preload_callback preload, + nint preload_data, + node_embedding_data_release_callback release_preload_data) { - return _runtime.EmbeddingRuntimeOnPreload(runtime_config, run_preload); + return _runtime.EmbeddingRuntimeConfigOnPreload( + runtime_config, preload, preload_data, release_preload_data); } - public override node_embedding_status EmbeddingRuntimeOnStartExecution( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoading( node_embedding_runtime_config runtime_config, - node_embedding_start_execution_functor start_execution, - node_embedding_handle_result_functor handle_result) + node_embedding_runtime_loading_callback run_load, + nint load_data, + node_embedding_data_release_callback release_load_data) { - return _runtime.EmbeddingRuntimeOnStartExecution( - runtime_config, start_execution, handle_result); + return _runtime.EmbeddingRuntimeConfigOnLoading( + runtime_config, run_load, load_data, release_load_data); } - public override node_embedding_status EmbeddingRuntimeAddModule( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigOnLoaded( node_embedding_runtime_config runtime_config, - string moduleName, - node_embedding_initialize_module_functor init_module, - int module_node_api_version) + node_embedding_runtime_loaded_callback handle_loaded, + nint handle_loaded_data, + node_embedding_data_release_callback release_handle_loaded_data) { - return _runtime.EmbeddingRuntimeAddModule( - runtime_config, moduleName, init_module, module_node_api_version); + return _runtime.EmbeddingRuntimeConfigOnLoaded( + runtime_config, handle_loaded, handle_loaded_data, release_handle_loaded_data); } - public override node_embedding_status EmbeddingRuntimeSetTaskRunner( + public override NodeEmbeddingStatus EmbeddingRuntimeConfigAddModule( node_embedding_runtime_config runtime_config, - node_embedding_post_task_functor post_task) + ReadOnlySpan module_name, + node_embedding_module_initialize_callback init_module, + nint init_module_data, + node_embedding_data_release_callback release_init_module_data, + int module_node_api_version) { - return _runtime.EmbeddingRuntimeSetTaskRunner(runtime_config, post_task); + return _runtime.EmbeddingRuntimeConfigAddModule( + runtime_config, + module_name, + init_module, + init_module_data, + release_init_module_data, + module_node_api_version); } - public override node_embedding_status EmbeddingRunEventLoop( + public override NodeEmbeddingStatus EmbeddingRuntimeSetUserData( node_embedding_runtime runtime, - node_embedding_event_loop_run_mode run_mode, - out bool has_more_work) + nint user_data, + node_embedding_data_release_callback release_user_data) + { + return _runtime.EmbeddingRuntimeSetUserData(runtime, user_data, release_user_data); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeGetUserData( + node_embedding_runtime runtime, out nint user_data) + { + return _runtime.EmbeddingRuntimeGetUserData(runtime, out user_data); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeConfigSetTaskRunner( + node_embedding_runtime_config runtime_config, + node_embedding_task_post_callback post_task, + nint post_task_data, + node_embedding_data_release_callback release_post_task_data) + { + return _runtime.EmbeddingRuntimeConfigSetTaskRunner( + runtime_config, post_task, post_task_data, release_post_task_data); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeRunEventLoop(node_embedding_runtime runtime) + { + return _runtime.EmbeddingRuntimeRunEventLoop(runtime); + } + + public override NodeEmbeddingStatus EmbeddingRuntimeTerminateEventLoop( + node_embedding_runtime runtime) { - return _runtime.EmbeddingRunEventLoop(runtime, run_mode, out has_more_work); + return _runtime.EmbeddingRuntimeTerminateEventLoop(runtime); } - public override node_embedding_status - EmbeddingCompleteEventLoop(node_embedding_runtime runtime) + public override NodeEmbeddingStatus EmbeddingRuntimeRunOnceEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) { - return _runtime.EmbeddingCompleteEventLoop(runtime); + return _runtime.EmbeddingRuntimeRunOnceEventLoop(runtime, out hasMoreWork); } - public override node_embedding_status - EmbeddingTerminateEventLoop(node_embedding_runtime runtime) + public override NodeEmbeddingStatus EmbeddingRuntimeRunNoWaitEventLoop( + node_embedding_runtime runtime, out bool hasMoreWork) { - return _runtime.EmbeddingTerminateEventLoop(runtime); + return _runtime.EmbeddingRuntimeRunNoWaitEventLoop(runtime, out hasMoreWork); } - public override node_embedding_status EmbeddingRunNodeApi( + public override NodeEmbeddingStatus EmbeddingRuntimeRunNodeApi( node_embedding_runtime runtime, - node_embedding_run_node_api_functor_ref run_node_api) + node_embedding_node_api_run_callback run_node_api, + nint run_node_api_data) { - return _runtime.EmbeddingRunNodeApi(runtime, run_node_api); + return _runtime.EmbeddingRuntimeRunNodeApi(runtime, run_node_api, run_node_api_data); } - public override node_embedding_status EmbeddingOpenNodeApiScope( + public override NodeEmbeddingStatus EmbeddingRuntimeOpenNodeApiScope( node_embedding_runtime runtime, out node_embedding_node_api_scope node_api_scope, out napi_env env) { - return _runtime.EmbeddingOpenNodeApiScope(runtime, out node_api_scope, out env); + return _runtime.EmbeddingRuntimeOpenNodeApiScope(runtime, out node_api_scope, out env); } - public override node_embedding_status EmbeddingCloseNodeApiScope( + public override NodeEmbeddingStatus EmbeddingRuntimeCloseNodeApiScope( node_embedding_runtime runtime, node_embedding_node_api_scope node_api_scope) { - return _runtime.EmbeddingCloseNodeApiScope(runtime, node_api_scope); + return _runtime.EmbeddingRuntimeCloseNodeApiScope(runtime, node_api_scope); } #endregion diff --git a/src/NodeApi/Runtime/Utf8StringArray.cs b/src/NodeApi/Runtime/Utf8StringArray.cs index 6d5c4a0c..f3b7b36c 100644 --- a/src/NodeApi/Runtime/Utf8StringArray.cs +++ b/src/NodeApi/Runtime/Utf8StringArray.cs @@ -1,4 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.JavaScript.NodeApi.Runtime; + using System; +#if !(NETFRAMEWORK || NETSTANDARD) +using System.Buffers; +#endif +using System.ComponentModel; using System.Runtime.InteropServices; using System.Text; @@ -24,8 +33,8 @@ public unsafe Utf8StringArray(ReadOnlySpan strings) Utf8Strings = new nint[strings.Length]; _stringBuffer = new byte[byteLength]; #else - Utf8Strings = System.Buffers.ArrayPool.Shared.Rent(strings.Length); - _stringBuffer = System.Buffers.ArrayPool.Shared.Rent(byteLength); + Utf8Strings = ArrayPool.Shared.Rent(strings.Length); + _stringBuffer = ArrayPool.Shared.Rent(byteLength); #endif // Pin the string buffer @@ -52,18 +61,19 @@ public void Dispose() _pinnedStringBuffer.Free(); #if !(NETFRAMEWORK || NETSTANDARD) - System.Buffers.ArrayPool.Shared.Return(Utf8Strings); - System.Buffers.ArrayPool.Shared.Return(_stringBuffer); + ArrayPool.Shared.Return(Utf8Strings); + ArrayPool.Shared.Return(_stringBuffer); #endif } } - public readonly nint[] Utf8Strings { get; } public bool Disposed { get; private set; } - public readonly ref nint Pin() + // To support Utf8StringArray usage within a fixed statement. + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly ref nint GetPinnableReference() { if (Disposed) throw new ObjectDisposedException(nameof(Utf8StringArray)); Span span = Utf8Strings; diff --git a/test/GCTests.cs b/test/GCTests.cs index c4d95756..8b0cafba 100644 --- a/test/GCTests.cs +++ b/test/GCTests.cs @@ -18,7 +18,7 @@ public void GCHandles() Skip.If( NodejsEmbeddingTests.NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using NodejsEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => { @@ -69,7 +69,7 @@ public void GCObjects() Skip.If( NodejsEmbeddingTests.NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using NodejsEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => { diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index 348931c1..cdcf2f75 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -25,25 +25,25 @@ public class NodejsEmbeddingTests private static string LibnodePath { get; } = GetLibnodePath(); // The Node.js platform may only be initialized once per process. - internal static NodejsEmbeddingPlatform? NodejsPlatform { get; } = + internal static NodeEmbeddingPlatform? NodejsPlatform { get; } = File.Exists(LibnodePath) - ? new(LibnodePath, new NodejsEmbeddingPlatformSettings + ? new(LibnodePath, new NodeEmbeddingPlatformSettings { Args = new[] { "node", "--expose-gc" } }) : null; - internal static NodejsEmbeddingThreadRuntime CreateNodejsEnvironment() + internal static NodeEmbeddingThreadRuntime CreateNodejsEnvironment() { Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); return NodejsPlatform.CreateThreadRuntime( Path.Combine(GetRepoRootDirectory(), "test"), - new NodejsEmbeddingRuntimeSettings { MainScript = MainScript }); + new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); } internal static void RunInNodejsEnvironment(Action action) { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.SynchronizationContext.Run(action); } @@ -51,21 +51,21 @@ internal static void RunInNodejsEnvironment(Action action) public void LoadMainScriptNoThread() { Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using var runtime = new NodejsEmbeddingRuntime(NodejsPlatform, - new NodejsEmbeddingRuntimeSettings { MainScript = MainScript }); - runtime.CompleteEventLoop(); + using var runtime = new NodeEmbeddingRuntime(NodejsPlatform, + new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); + runtime.RunEventLoop(); } [SkippableFact] public void LoadMainScriptWithThread() { - using NodejsEmbeddingThreadRuntime runtime = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime runtime = CreateNodejsEnvironment(); } [SkippableFact] public void StartEnvironment() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -90,7 +90,7 @@ public interface IConsole { void Log(string message); } [SkippableFact] public void CallFunction() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.SynchronizationContext.Run(() => { @@ -106,7 +106,7 @@ public void CallFunction() [SkippableFact] public void ImportBuiltinModule() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -126,7 +126,7 @@ public void ImportBuiltinModule() [SkippableFact] public void ImportCommonJSModule() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -143,7 +143,7 @@ public void ImportCommonJSModule() [SkippableFact] public void ImportCommonJSPackage() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { @@ -160,7 +160,7 @@ public void ImportCommonJSPackage() [SkippableFact] public async Task ImportESModule() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { @@ -178,7 +178,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public async Task ImportESPackage() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { @@ -210,7 +210,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public void UnhandledRejection() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); string? errorMessage = null; nodejs.UnhandledPromiseRejection += (_, e) => @@ -232,7 +232,7 @@ public void UnhandledRejection() [SkippableFact] public void ErrorPropagation() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); JSException exception = Assert.Throws(() => { @@ -388,7 +388,7 @@ private static async Task TestWorker( string workerScript, Func mainRun) { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); await nodejs.RunAsync(async () => { NodeWorker.Options workerOptions = mainPrepare.Invoke(); @@ -420,7 +420,7 @@ await nodejs.RunAsync(async () => [SkippableFact] public void MarshalClass() { - using NodejsEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); + using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); nodejs.Run(() => { From 4cc059f2bf3d0038df0d5087584728de0ebf55f6 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 6 Feb 2025 10:52:13 -0800 Subject: [PATCH 05/11] Use UserData to link NodeEmbeddingRuntime with its handle --- bench/Benchmarks.cs | 6 +- src/NodeApi/Runtime/NodeEmbedding.cs | 24 ++-- src/NodeApi/Runtime/NodeEmbeddingRuntime.cs | 129 +++++++++--------- .../Runtime/NodeEmbeddingRuntimeSettings.cs | 6 +- .../Runtime/NodeEmbeddingThreadRuntime.cs | 2 +- .../Runtime/NodejsRuntime.Embedding.cs | 4 +- test/NodejsEmbeddingTests.cs | 2 +- 7 files changed, 91 insertions(+), 82 deletions(-) diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index e5e4b1f4..228f836d 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -66,6 +66,8 @@ public static void Main(string[] args) private JSFunction _jsFunctionCallMethodWithArgs; private JSReference _reference = null!; private static readonly string[] s_settings = new[] { "node", "--expose-gc" }; + private static string s_mainScript { get; } = + "globalThis.require = require('module').createRequire(process.execPath);\n"; /// /// Simple class that is exported to JS and used in some benchmarks. @@ -93,7 +95,9 @@ protected void Setup() // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on // the same thread. NodejsEmbeddingThreadRuntime creates a separate thread that would slow // down the micro-benchmarks. - _runtime = new(platform); + _runtime = NodeEmbeddingRuntime.Create(platform, + new NodeEmbeddingRuntimeSettings { MainScript = s_mainScript }); + // The nodeApiScope creates JSValueScope instance that saves itself as // the thread-local JSValueScope.Current. _nodeApiScope = new(_runtime); diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs index 57ef9294..39f63c22 100644 --- a/src/NodeApi/Runtime/NodeEmbedding.cs +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -136,6 +136,13 @@ public static unsafe Functor Callback = new node_embedding_task_post_callback(s_taskPostCallback) }; + public static unsafe FunctorRef + CreateNodeApiRunFunctorRef(RunNodeApiCallback callback) => new() + { + Data = (nint)GCHandle.Alloc(callback), + Callback = new node_embedding_node_api_run_callback(s_nodeApiRunCallback) + }; + #if UNMANAGED_DELEGATES internal static readonly unsafe delegate* unmanaged[Cdecl] s_releaseDataCallback = &ReleaseDataCallbackAdapter; @@ -264,8 +271,7 @@ internal static unsafe void RuntimePreloadCallbackAdapter( try { var callback = (PreloadCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.FromHandle(runtime); callback(embeddingRuntime, new JSValue(process), new JSValue(require)); } catch (Exception ex) @@ -289,9 +295,7 @@ internal static unsafe napi_value RuntimeLoadingCallbackAdapter( try { var callback = (LoadingCallback)GCHandle.FromIntPtr(cb_data).Target!; - //TODO: Unwrap from runtime - NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.FromHandle(runtime); return (napi_value)callback( embeddingRuntime, new JSValue(process), new JSValue(require), new JSValue(run_cjs)); } @@ -315,8 +319,7 @@ internal static unsafe void RuntimeLoadedCallbackAdapter( try { var callback = (LoadedCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.FromHandle(runtime); callback(embeddingRuntime, new JSValue(loading_result)); } catch (Exception ex) @@ -339,8 +342,7 @@ internal static unsafe napi_value ModuleInitializeCallbackAdapter( try { var callback = (InitializeModuleCallback)GCHandle.FromIntPtr(cb_data).Target!; - NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.GetOrCreate(runtime) - ?? throw new InvalidOperationException("Embedding runtime is not found"); + NodeEmbeddingRuntime embeddingRuntime = NodeEmbeddingRuntime.FromHandle(runtime); return (napi_value)callback( embeddingRuntime, Utf8StringArray.PtrToStringUTF8((byte*)module_name), @@ -402,9 +404,7 @@ internal static unsafe NodeEmbeddingStatus TaskPostCallbackAdapter( #if UNMANAGED_DELEGATES [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] #endif - internal static unsafe void NodeApiRunCallbackAdapter( - nint cb_data, - napi_env env) + internal static unsafe void NodeApiRunCallbackAdapter(nint cb_data, napi_env env) { using var jsValueScope = new JSValueScope(JSValueScopeType.Root, env, JSRuntime); try diff --git a/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs index 6327530f..e7e2f1ce 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs @@ -4,7 +4,9 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; using System; -using System.Collections.Generic; +#if UNMANAGED_DELEGATES +using System.Runtime.CompilerServices; +#endif using System.Runtime.InteropServices; using static NodeEmbedding; using static NodejsRuntime; @@ -17,16 +19,11 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; /// public sealed class NodeEmbeddingRuntime : IDisposable { - private node_embedding_runtime _runtime; - private static readonly - Dictionary s_embeddedRuntimes = new(); - - public static explicit operator node_embedding_runtime(NodeEmbeddingRuntime runtime) - => runtime._runtime; + private bool _mustDeleteRuntime; // Only delete runtime if it was created by calling Create. public static JSRuntime JSRuntime => NodeEmbedding.JSRuntime; - public unsafe NodeEmbeddingRuntime( + public static unsafe NodeEmbeddingRuntime Create( NodeEmbeddingPlatform platform, NodeEmbeddingRuntimeSettings? settings = null) { using FunctorRef functorRef = @@ -35,57 +32,44 @@ public unsafe NodeEmbeddingRuntime( platform.Handle, functorRef.Callback, functorRef.Data, - out _runtime) + out node_embedding_runtime runtime) .ThrowIfFailed(); + NodeEmbeddingRuntime result = FromHandle(runtime); + result._mustDeleteRuntime = true; + return result; } private NodeEmbeddingRuntime(node_embedding_runtime runtime) { - _runtime = runtime; - lock (s_embeddedRuntimes) { s_embeddedRuntimes.Add(runtime, this); } + Handle = runtime; } - public node_embedding_runtime Handle => _runtime; + public node_embedding_runtime Handle { get; } - public static NodeEmbeddingRuntime? FromHandle(node_embedding_runtime runtime) + public static unsafe NodeEmbeddingRuntime FromHandle(node_embedding_runtime runtime) { - lock (s_embeddedRuntimes) + JSRuntime.EmbeddingRuntimeGetUserData(runtime, out nint userData).ThrowIfFailed(); + if (userData != default) { - if (s_embeddedRuntimes.TryGetValue( - runtime, out NodeEmbeddingRuntime? embeddingRuntime)) - { - return embeddingRuntime; - } - return null; + return (NodeEmbeddingRuntime)GCHandle.FromIntPtr(userData).Target!; } - } - public static NodeEmbeddingRuntime GetOrCreate(node_embedding_runtime runtime) - { - NodeEmbeddingRuntime? embeddingRuntime = FromHandle(runtime); - embeddingRuntime ??= new NodeEmbeddingRuntime(runtime); - return embeddingRuntime; + NodeEmbeddingRuntime result = new NodeEmbeddingRuntime(runtime); + JSRuntime.EmbeddingRuntimeSetUserData( + runtime, + (nint)GCHandle.Alloc(result), + new node_embedding_data_release_callback(s_releaseRuntimeCallback)) + .ThrowIfFailed(); + return result; } - public static unsafe void Run(NodeEmbeddingPlatform platform, - NodeEmbeddingRuntimeSettings? settings = null) + public static unsafe void Run( + NodeEmbeddingPlatform platform, NodeEmbeddingRuntimeSettings? settings = null) { - ConfigureRuntimeCallback? configureRuntime = settings?.CreateConfigureRuntimeCallback(); - nint callbackData = configureRuntime != null - ? (nint)GCHandle.Alloc(configureRuntime) - : default; - try - { - JSRuntime.EmbeddingRunRuntime( - platform.Handle, - new node_embedding_runtime_configure_callback(s_runtimeConfigureCallback), - callbackData) - .ThrowIfFailed(); - } - finally - { - if (callbackData != default) GCHandle.FromIntPtr(callbackData).Free(); - } + using FunctorRef functorRef = + CreateRuntimeConfigureFunctorRef(settings?.CreateConfigureRuntimeCallback()); + JSRuntime.EmbeddingRunRuntime(platform.Handle, functorRef.Callback, functorRef.Data) + .ThrowIfFailed(); } /// @@ -98,32 +82,31 @@ public static unsafe void Run(NodeEmbeddingPlatform platform, /// public void Dispose() { - if (IsDisposed) return; + if (IsDisposed || !_mustDeleteRuntime) return; IsDisposed = true; - lock (s_embeddedRuntimes) { s_embeddedRuntimes.Remove(_runtime); } - JSRuntime.EmbeddingDeleteRuntime(_runtime).ThrowIfFailed(); + JSRuntime.EmbeddingDeleteRuntime(Handle).ThrowIfFailed(); } public unsafe void RunEventLoop() { if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); - JSRuntime.EmbeddingRuntimeRunEventLoop(_runtime).ThrowIfFailed(); + JSRuntime.EmbeddingRuntimeRunEventLoop(Handle).ThrowIfFailed(); } public unsafe void TerminateEventLoop() { if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); - JSRuntime.EmbeddingRuntimeTerminateEventLoop(_runtime).ThrowIfFailed(); + JSRuntime.EmbeddingRuntimeTerminateEventLoop(Handle).ThrowIfFailed(); } public unsafe bool RunEventLoopOnce() { if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); - JSRuntime.EmbeddingRuntimeRunOnceEventLoop(_runtime, out bool result).ThrowIfFailed(); + JSRuntime.EmbeddingRuntimeRunOnceEventLoop(Handle, out bool result).ThrowIfFailed(); return result; } @@ -131,7 +114,7 @@ public unsafe bool RunEventLoopNoWait() { if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); - JSRuntime.EmbeddingRuntimeRunNoWaitEventLoop(_runtime, out bool result).ThrowIfFailed(); + JSRuntime.EmbeddingRuntimeRunNoWaitEventLoop(Handle, out bool result).ThrowIfFailed(); return result; } @@ -139,18 +122,40 @@ public unsafe void RunNodeApi(RunNodeApiCallback runNodeApi) { if (IsDisposed) throw new ObjectDisposedException(nameof(NodeEmbeddingRuntime)); - nint callbackData = (nint)GCHandle.Alloc(runNodeApi); - try - { - JSRuntime.EmbeddingRuntimeRunNodeApi( - _runtime, - new node_embedding_node_api_run_callback(s_nodeApiRunCallback), - callbackData) - .ThrowIfFailed(); - } - finally + using FunctorRef functorRef = + CreateNodeApiRunFunctorRef(runNodeApi); + JSRuntime.EmbeddingRuntimeRunNodeApi(Handle, functorRef.Callback, functorRef.Data) + .ThrowIfFailed(); + } + +#if UNMANAGED_DELEGATES + private static readonly unsafe delegate* unmanaged[Cdecl] + s_releaseRuntimeCallback = &ReleaseRuntimeCallbackAdapter; +#else + private static readonly node_embedding_data_release_callback.Delegate + s_releaseRuntimeCallback = ReleaseRuntimeCallbackAdapter; +#endif + +#if UNMANAGED_DELEGATES + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] +#endif + private static unsafe NodeEmbeddingStatus ReleaseRuntimeCallbackAdapter(nint data) + { + if (data != default) { - if (callbackData != default) GCHandle.FromIntPtr(callbackData).Free(); + try + { + GCHandle gcHandle = GCHandle.FromIntPtr(data); + NodeEmbeddingRuntime runtime = (NodeEmbeddingRuntime)gcHandle.Target!; + gcHandle.Free(); + runtime._mustDeleteRuntime = false; + } + catch (Exception ex) + { + JSRuntime.EmbeddingSetLastErrorMessage(ex.Message.AsSpan()); + return NodeEmbeddingStatus.GenericError; + } } + return NodeEmbeddingStatus.OK; } } diff --git a/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs b/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs index 958990c0..1f4bc6ad 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingRuntimeSettings.cs @@ -57,9 +57,9 @@ public unsafe ConfigureRuntimeCallback CreateConfigureRuntimeCallback() if (MainScript != null) { JSValue onLoading(NodeEmbeddingRuntime runtime, - JSValue process, - JSValue require, - JSValue runCommonJS) + JSValue process, + JSValue require, + JSValue runCommonJS) => runCommonJS.Call(JSValue.Null, (JSValue)MainScript); Functor functor = diff --git a/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs b/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs index 9b126dbc..948a4d82 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingThreadRuntime.cs @@ -44,7 +44,7 @@ internal NodeEmbeddingThreadRuntime( _thread = new(() => { - using var runtime = new NodeEmbeddingRuntime(platform, settings); + using var runtime = NodeEmbeddingRuntime.Create(platform, settings); // The new scope instance saves itself as the thread-local JSValueScope.Current. using var nodeApiScope = new NodeEmbeddingNodeApiScope(runtime); diff --git a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs index a586fd2e..4c3c5da7 100644 --- a/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs +++ b/src/NodeApi/Runtime/NodejsRuntime.Embedding.cs @@ -532,7 +532,7 @@ public override NodeEmbeddingStatus EmbeddingRunMain( using Utf8StringArray utf8Args = new(args); fixed (nint* argsPtr = utf8Args) return Import(ref node_embedding_main_run)( - 1, // Embedding API version + NodeEmbedding.EmbeddingApiVersion, args.Length, (nint)argsPtr, configure_platform, @@ -552,7 +552,7 @@ public override NodeEmbeddingStatus EmbeddingCreatePlatform( fixed (node_embedding_platform* result_ptr = &result) { return Import(ref node_embedding_platform_create)( - 1, // Embedding API version + NodeEmbedding.EmbeddingApiVersion, args.Length, (nint)argsPtr, configure_platform, diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index cdcf2f75..f82dd882 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -51,7 +51,7 @@ internal static void RunInNodejsEnvironment(Action action) public void LoadMainScriptNoThread() { Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using var runtime = new NodeEmbeddingRuntime(NodejsPlatform, + using var runtime = NodeEmbeddingRuntime.Create(NodejsPlatform, new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); runtime.RunEventLoop(); } From b969568faae2a32e2690a2f76062b1a00c8b93d6 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 6 Feb 2025 11:21:04 -0800 Subject: [PATCH 06/11] Add Microsoft.JavaScript.LibNode package reference --- Directory.Packages.props | 1 + test/NodeApi.Test.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index ee953757..0956a246 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,7 @@ + diff --git a/test/NodeApi.Test.csproj b/test/NodeApi.Test.csproj index 8aa0a447..97da16f1 100644 --- a/test/NodeApi.Test.csproj +++ b/test/NodeApi.Test.csproj @@ -24,6 +24,7 @@ + From 00561739f90747fbafa38278d5d587bb6c8c8811 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 6 Feb 2025 20:13:14 -0800 Subject: [PATCH 07/11] Update example projects --- examples/jsdom/jsdom.csproj | 5 +---- examples/winui-fluid/winui-fluid.csproj | 5 +---- test/TestUtils.cs | 6 ++---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/jsdom/jsdom.csproj b/examples/jsdom/jsdom.csproj index 37f04217..2f26eed5 100644 --- a/examples/jsdom/jsdom.csproj +++ b/examples/jsdom/jsdom.csproj @@ -11,13 +11,10 @@ - - false - PreserveNewest - + diff --git a/examples/winui-fluid/winui-fluid.csproj b/examples/winui-fluid/winui-fluid.csproj index d8cb573c..c776d410 100644 --- a/examples/winui-fluid/winui-fluid.csproj +++ b/examples/winui-fluid/winui-fluid.csproj @@ -42,13 +42,10 @@ - - false - PreserveNewest - + diff --git a/test/TestUtils.cs b/test/TestUtils.cs index 98c44c36..49803140 100644 --- a/test/TestUtils.cs +++ b/test/TestUtils.cs @@ -73,10 +73,8 @@ public static string GetSharedLibraryExtension() else return ".so"; } - public static string GetLibnodePath() => Path.Combine( - GetRepoRootDirectory(), - "bin", - GetCurrentPlatformRuntimeIdentifier(), + public static string GetLibnodePath() => + Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "libnode" + GetSharedLibraryExtension()); public static string? LogOutput( From aed65238d93f69e0e91e56901d7431f65d09a358 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Thu, 6 Feb 2025 20:16:00 -0800 Subject: [PATCH 08/11] Format code --- src/NodeApi/Runtime/NodeEmbeddingRuntime.cs | 2 +- test/NodejsEmbeddingTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs index e7e2f1ce..0c816d5b 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingRuntime.cs @@ -54,7 +54,7 @@ public static unsafe NodeEmbeddingRuntime FromHandle(node_embedding_runtime runt return (NodeEmbeddingRuntime)GCHandle.FromIntPtr(userData).Target!; } - NodeEmbeddingRuntime result = new NodeEmbeddingRuntime(runtime); + NodeEmbeddingRuntime result = new(runtime); JSRuntime.EmbeddingRuntimeSetUserData( runtime, (nint)GCHandle.Alloc(result), diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index f82dd882..987f0284 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -51,7 +51,7 @@ internal static void RunInNodejsEnvironment(Action action) public void LoadMainScriptNoThread() { Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); - using var runtime = NodeEmbeddingRuntime.Create(NodejsPlatform, + using NodeEmbeddingRuntime runtime = NodeEmbeddingRuntime.Create(NodejsPlatform, new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); runtime.RunEventLoop(); } From df6c2a328c51fadd74623b9063293e5a6c756fb6 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Sat, 8 Feb 2025 14:46:52 -0800 Subject: [PATCH 09/11] Use fixed LibNode Nuget package --- Directory.Build.targets | 4 ++ Directory.Packages.props | 2 +- examples/jsdom/jsdom.csproj | 2 +- examples/winui-fluid/winui-fluid.csproj | 2 +- test/NodeApi.Test.csproj | 2 +- test/NodejsEmbeddingTests.cs | 94 ++++++++++++------------- test/TestUtils.cs | 18 +++-- 7 files changed, 64 insertions(+), 60 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 63e2ded0..d48eac85 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -15,6 +15,9 @@ + + %(PackageVersion.Version) + - + diff --git a/examples/jsdom/jsdom.csproj b/examples/jsdom/jsdom.csproj index 2f26eed5..492e9909 100644 --- a/examples/jsdom/jsdom.csproj +++ b/examples/jsdom/jsdom.csproj @@ -14,7 +14,7 @@ + - diff --git a/examples/winui-fluid/winui-fluid.csproj b/examples/winui-fluid/winui-fluid.csproj index c776d410..064a8fb8 100644 --- a/examples/winui-fluid/winui-fluid.csproj +++ b/examples/winui-fluid/winui-fluid.csproj @@ -45,7 +45,7 @@ - + diff --git a/test/NodeApi.Test.csproj b/test/NodeApi.Test.csproj index 97da16f1..d12e471a 100644 --- a/test/NodeApi.Test.csproj +++ b/test/NodeApi.Test.csproj @@ -20,11 +20,11 @@ + - diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index 987f0284..5980bd1f 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -25,17 +25,14 @@ public class NodejsEmbeddingTests private static string LibnodePath { get; } = GetLibnodePath(); // The Node.js platform may only be initialized once per process. - internal static NodeEmbeddingPlatform? NodejsPlatform { get; } = - File.Exists(LibnodePath) - ? new(LibnodePath, new NodeEmbeddingPlatformSettings - { - Args = new[] { "node", "--expose-gc" } - }) - : null; + internal static NodeEmbeddingPlatform NodejsPlatform { get; } = + new(LibnodePath, new NodeEmbeddingPlatformSettings + { + Args = new[] { "node", "--expose-gc" } + }); internal static NodeEmbeddingThreadRuntime CreateNodejsEnvironment() { - Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); return NodejsPlatform.CreateThreadRuntime( Path.Combine(GetRepoRootDirectory(), "test"), new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); @@ -47,22 +44,21 @@ internal static void RunInNodejsEnvironment(Action action) nodejs.SynchronizationContext.Run(action); } - [SkippableFact] + [Fact] public void LoadMainScriptNoThread() { - Skip.If(NodejsPlatform == null, "Node shared library not found at " + LibnodePath); using NodeEmbeddingRuntime runtime = NodeEmbeddingRuntime.Create(NodejsPlatform, new NodeEmbeddingRuntimeSettings { MainScript = MainScript }); runtime.RunEventLoop(); } - [SkippableFact] + [Fact] public void LoadMainScriptWithThread() { using NodeEmbeddingThreadRuntime runtime = CreateNodejsEnvironment(); } - [SkippableFact] + [Fact] public void StartEnvironment() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -77,7 +73,7 @@ public void StartEnvironment() Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public void RestartEnvironment() { // Create and destroy a Node.js environment twice, using the same platform instance. @@ -87,7 +83,7 @@ public void RestartEnvironment() public interface IConsole { void Log(string message); } - [SkippableFact] + [Fact] public void CallFunction() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -103,7 +99,7 @@ public void CallFunction() Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public void ImportBuiltinModule() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -123,7 +119,7 @@ public void ImportBuiltinModule() Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public void ImportCommonJSModule() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -140,7 +136,7 @@ public void ImportCommonJSModule() Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public void ImportCommonJSPackage() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -157,7 +153,7 @@ public void ImportCommonJSPackage() Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public async Task ImportESModule() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -175,7 +171,7 @@ await nodejs.RunAsync(async () => Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public async Task ImportESPackage() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -207,7 +203,7 @@ await nodejs.RunAsync(async () => Assert.Equal(0, nodejs.ExitCode); } - [SkippableFact] + [Fact] public void UnhandledRejection() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -229,7 +225,7 @@ public void UnhandledRejection() Assert.Equal("test", errorMessage); } - [SkippableFact] + [Fact] public void ErrorPropagation() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); @@ -265,7 +261,7 @@ public void ErrorPropagation() (line) => line.StartsWith($"at {typeof(NodejsEmbeddingTests).FullName}.")); } - [SkippableFact] + [Fact] public async Task WorkerIsMainThread() { await TestWorker( @@ -274,15 +270,15 @@ await TestWorker( Assert.True(NodeWorker.IsMainThread); return new NodeWorker.Options { Eval = true }; }, - workerScript: @" -const assert = require('node:assert'); -const { isMainThread } = require('node:worker_threads'); -assert(!isMainThread); -", + workerScript: """ + const assert = require('node:assert'); + const { isMainThread } = require('node:worker_threads'); + assert(!isMainThread); + """, mainRun: (worker) => Task.CompletedTask); } - [SkippableFact] + [Fact] public async Task WorkerArgs() { await TestWorker( @@ -297,18 +293,18 @@ await TestWorker( WorkerData = true, }; }, - workerScript: @" -const assert = require('node:assert'); -const process = require('node:process'); -const { workerData } = require('node:worker_threads'); -assert.deepStrictEqual(process.argv.slice(2), ['test1', 'test2']); -assert.strictEqual(typeof workerData, 'boolean'); -assert(workerData); -", + workerScript: """ + const assert = require('node:assert'); + const process = require('node:process'); + const { workerData } = require('node:worker_threads'); + assert.deepStrictEqual(process.argv.slice(2), ['test1', 'test2']); + assert.strictEqual(typeof workerData, 'boolean'); + assert(workerData); + """, mainRun: (worker) => Task.CompletedTask); } - [SkippableFact] + [Fact] public async Task WorkerEnv() { await TestWorker( @@ -320,15 +316,15 @@ await TestWorker( Eval = true, }; }, - workerScript: @" -const assert = require('node:assert'); -const { getEnvironmentData } = require('node:worker_threads'); -assert.strictEqual(getEnvironmentData('test'), true); -", + workerScript: """ + const assert = require('node:assert'); + const { getEnvironmentData } = require('node:worker_threads'); + assert.strictEqual(getEnvironmentData('test'), true); + """, mainRun: (worker) => Task.CompletedTask); } - [SkippableFact] + [Fact] public async Task WorkerMessages() { await TestWorker( @@ -336,10 +332,10 @@ await TestWorker( { return new NodeWorker.Options { Eval = true }; }, - workerScript: @" -const { parentPort } = require('node:worker_threads'); -parentPort.on('message', (msg) => parentPort.postMessage(msg)); // echo -", + workerScript: """ + const { parentPort } = require('node:worker_threads'); + parentPort.on('message', (msg) => parentPort.postMessage(msg)); // echo + """, mainRun: async (worker) => { TaskCompletionSource echoCompletion = new(); @@ -354,7 +350,7 @@ await TestWorker( }); } - [SkippableFact] + [Fact] public async Task WorkerStdinStdout() { await TestWorker( @@ -417,7 +413,7 @@ await nodejs.RunAsync(async () => /// Tests the functionality of dynamically exporting and marshalling a class type from .NET /// to JS (as opposed to relying on [JSExport] (compile-time code-generation) for marshalling. /// - [SkippableFact] + [Fact] public void MarshalClass() { using NodeEmbeddingThreadRuntime nodejs = CreateNodejsEnvironment(); diff --git a/test/TestUtils.cs b/test/TestUtils.cs index 49803140..46166646 100644 --- a/test/TestUtils.cs +++ b/test/TestUtils.cs @@ -12,16 +12,19 @@ namespace Microsoft.JavaScript.NodeApi.Test; public static class TestUtils { - public static string GetRepoRootDirectory() + public static string GetAssemblyLocation() { #if NETFRAMEWORK - string assemblyLocation = new Uri(typeof(TestUtils).Assembly.CodeBase).LocalPath; + return new Uri(typeof(TestUtils).Assembly.CodeBase).LocalPath; #else -#pragma warning disable IL3000 // Assembly.Location returns an empty string for assemblies embedded in a single-file app - string assemblyLocation = typeof(TestUtils).Assembly.Location!; -#pragma warning restore IL3000 + // Assembly.Location returns an empty string for assemblies embedded in a single-file app + return typeof(TestUtils).Assembly.Location; #endif + } + public static string GetRepoRootDirectory() + { + string assemblyLocation = GetAssemblyLocation(); string? solutionDir = string.IsNullOrEmpty(assemblyLocation) ? Environment.CurrentDirectory : Path.GetDirectoryName(assemblyLocation); @@ -74,8 +77,9 @@ public static string GetSharedLibraryExtension() } public static string GetLibnodePath() => - Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, - "libnode" + GetSharedLibraryExtension()); + Path.Combine( + Path.GetDirectoryName(GetAssemblyLocation()) ?? string.Empty, + "libnode" + GetSharedLibraryExtension()); public static string? LogOutput( Process process, From b61a58ec2a4703032f7906df4b208633581f80cb Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Sat, 8 Feb 2025 18:40:51 -0800 Subject: [PATCH 10/11] Task.Run does not guarantee using new thread --- .ado/publish.yml | 1 - .github/workflows/build.yml | 1 - Directory.Packages.props | 1 - test/GCTests.cs | 10 +--- test/JSReferenceTests.cs | 5 +- test/JSValueScopeTests.cs | 7 ++- test/NodeApi.Test.csproj | 1 - test/TestCases/napi-dotnet/ThreadSafety.cs | 54 +++++++++++++++++++--- test/TestUtils.cs | 22 +++++++++ 9 files changed, 77 insertions(+), 25 deletions(-) diff --git a/.ado/publish.yml b/.ado/publish.yml index 6c4505d2..07269945 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -259,7 +259,6 @@ extends: - script: > dotnet test -f ${{ MatrixEntry.DotNetVersion }} --configuration Release - --property:ParallelizeTestCollections=false --logger trx --results-directory "$(Build.StagingDirectory)/test/${{ MatrixEntry.DotNetVersion }}-Release" displayName: Run tests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f82c58d7..6d1e0ac0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,6 @@ jobs: run: > dotnet test -f ${{ matrix.dotnet-version }} --configuration ${{ matrix.configuration }} - --property:ParallelizeTestCollections=false --logger trx --results-directory "out/test/${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}" continue-on-error: true diff --git a/Directory.Packages.props b/Directory.Packages.props index 17644e56..ab77dced 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,7 +17,6 @@ - diff --git a/test/GCTests.cs b/test/GCTests.cs index 8b0cafba..25e3b367 100644 --- a/test/GCTests.cs +++ b/test/GCTests.cs @@ -12,12 +12,9 @@ public class GCTests { private static string LibnodePath { get; } = GetLibnodePath(); - [SkippableFact] + [Fact] public void GCHandles() { - Skip.If( - NodejsEmbeddingTests.NodejsPlatform == null, - "Node shared library not found at " + LibnodePath); using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => @@ -63,12 +60,9 @@ public void GCHandles() }); } - [SkippableFact] + [Fact] public void GCObjects() { - Skip.If( - NodejsEmbeddingTests.NodejsPlatform == null, - "Node shared library not found at " + LibnodePath); using NodeEmbeddingThreadRuntime nodejs = NodejsEmbeddingTests.CreateNodejsEnvironment(); nodejs.Run(() => diff --git a/test/JSReferenceTests.cs b/test/JSReferenceTests.cs index 1283cbec..6e487afc 100644 --- a/test/JSReferenceTests.cs +++ b/test/JSReferenceTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Threading.Tasks; using Xunit; using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime; @@ -52,7 +51,7 @@ public void GetReferenceFromDifferentThread() JSReference reference = new(value); // Run in a new thread which will not have any current scope. - Task.Run(() => + TestUtils.RunInThread(() => { Assert.Throws(() => reference.GetValue()); }).Wait(); @@ -67,7 +66,7 @@ public void GetReferenceFromDifferentRootScope() JSReference reference = new(value); // Run in a new thread and establish another root scope there. - Task.Run(() => + TestUtils.RunInThread(() => { using JSValueScope rootScope2 = TestScope(JSValueScopeType.Root); Assert.Throws(() => reference.GetValue()); diff --git a/test/JSValueScopeTests.cs b/test/JSValueScopeTests.cs index 2057e5ee..201d2608 100644 --- a/test/JSValueScopeTests.cs +++ b/test/JSValueScopeTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Threading.Tasks; using Xunit; using static Microsoft.JavaScript.NodeApi.Runtime.JSRuntime; @@ -325,7 +324,7 @@ public void CreateValueFromDifferentThread() using JSValueScope rootScope = TestScope(JSValueScopeType.Root); // Run in a new thread which will not have any current scope. - Task.Run(() => + TestUtils.RunInThread(() => { Assert.Throws(() => JSValueScope.Current); JSInvalidThreadAccessException ex = Assert.Throws( @@ -342,7 +341,7 @@ public void AccessValueFromDifferentThread() JSValue objectValue = JSValue.CreateObject(); // Run in a new thread which will not have any current scope. - Task.Run(() => + TestUtils.RunInThread(() => { Assert.Throws(() => JSValueScope.Current); JSInvalidThreadAccessException ex = Assert.Throws( @@ -359,7 +358,7 @@ public void AccessValueFromDifferentRootScope() JSValue objectValue = JSValue.CreateObject(); // Run in a new thread and establish another root scope there. - Task.Run(() => + TestUtils.RunInThread(() => { using JSValueScope rootScope2 = TestScope(JSValueScopeType.Root); Assert.Equal(JSValueScopeType.Root, JSValueScope.Current.ScopeType); diff --git a/test/NodeApi.Test.csproj b/test/NodeApi.Test.csproj index d12e471a..698ad30e 100644 --- a/test/NodeApi.Test.csproj +++ b/test/NodeApi.Test.csproj @@ -24,7 +24,6 @@ - diff --git a/test/TestCases/napi-dotnet/ThreadSafety.cs b/test/TestCases/napi-dotnet/ThreadSafety.cs index 48276c49..ce91c628 100644 --- a/test/TestCases/napi-dotnet/ThreadSafety.cs +++ b/test/TestCases/napi-dotnet/ThreadSafety.cs @@ -1,8 +1,9 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.JavaScript.NodeApi.TestCases; @@ -36,7 +37,7 @@ private static void ValidateNotOnJSThread() public static async Task CallDelegateFromOtherThread(Action action) { - await Task.Run(() => + await RunInThread(() => { ValidateNotOnJSThread(); @@ -48,7 +49,7 @@ public static async Task CallInterfaceMethodFromOtherThread( ISmpleInterface interfaceObj, string value) { - return await Task.Run(() => + return await RunInThread(() => { ValidateNotOnJSThread(); @@ -59,7 +60,7 @@ public static async Task CallInterfaceMethodFromOtherThread( public static async Task EnumerateCollectionFromOtherThread( IReadOnlyCollection collection) { - return await Task.Run(() => + return await RunInThread(() => { ValidateNotOnJSThread(); @@ -76,7 +77,7 @@ public static async Task EnumerateCollectionFromOtherThread( public static async Task EnumerateDictionaryFromOtherThread( IReadOnlyDictionary dictionary) { - return await Task.Run(() => + return await RunInThread(() => { ValidateNotOnJSThread(); @@ -93,11 +94,52 @@ public static async Task EnumerateDictionaryFromOtherThread( public static async Task ModifyDictionaryFromOtherThread( IDictionary dictionary, string keyToRemove) { - return await Task.Run(() => + return await RunInThread(() => { ValidateNotOnJSThread(); return dictionary.Remove(keyToRemove); }); } + + private static Task RunInThread(Action action) + { + TaskCompletionSource threadCompletion = new TaskCompletionSource(); + + Thread thread = new Thread(() => + { + try + { + action(); + threadCompletion.TrySetResult(true); + } + catch (Exception e) + { + threadCompletion.TrySetException(e); + } + }); + thread.Start(); + + return threadCompletion.Task; + } + + private static Task RunInThread(Func func) + { + TaskCompletionSource threadCompletion = new TaskCompletionSource(); + + Thread thread = new Thread(() => + { + try + { + threadCompletion.TrySetResult(func()); + } + catch (Exception e) + { + threadCompletion.TrySetException(e); + } + }); + thread.Start(); + + return threadCompletion.Task; + } } diff --git a/test/TestUtils.cs b/test/TestUtils.cs index 46166646..d038ab03 100644 --- a/test/TestUtils.cs +++ b/test/TestUtils.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; +using System.Threading.Tasks; namespace Microsoft.JavaScript.NodeApi.Test; @@ -145,4 +146,25 @@ public static void CopyIfNewer(string sourceFilePath, string targetFilePath) File.Copy(sourceFilePath, targetFilePath, overwrite: true); } } + + public static Task RunInThread(Action action) + { + TaskCompletionSource threadCompletion = new TaskCompletionSource(); + + Thread thread = new Thread(() => + { + try + { + action(); + threadCompletion.TrySetResult(true); + } + catch (Exception e) + { + threadCompletion.TrySetException(e); + } + }); + thread.Start(); + + return threadCompletion.Task; + } } From 3d50ced311a17945d56f323a817e64b844b53a61 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Sat, 8 Feb 2025 18:43:49 -0800 Subject: [PATCH 11/11] Format code --- test/TestUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestUtils.cs b/test/TestUtils.cs index d038ab03..81704549 100644 --- a/test/TestUtils.cs +++ b/test/TestUtils.cs @@ -149,9 +149,9 @@ public static void CopyIfNewer(string sourceFilePath, string targetFilePath) public static Task RunInThread(Action action) { - TaskCompletionSource threadCompletion = new TaskCompletionSource(); + TaskCompletionSource threadCompletion = new(); - Thread thread = new Thread(() => + Thread thread = new(() => { try {