From dcc0551136ce3883383c9799c0e05f0b2633008c Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Sat, 8 Feb 2025 18:49:41 -0600 Subject: [PATCH 01/18] Allow null to be passed as libpath. The result is that a series of procedures for locating the libnode library is done. a) On Core+ platforms, NativeLibrary is used directly with a relative path off of the assembly. This causes a search across the applications .deps.json. This is the ideal scenario as long as the LibNode packages deliver the library into runtimes/{rid}/native. b) On Framework platforms a search procedure is used. First, libnode is attempted to be loaded by relative name directly. This handles the situation where the library is directly available in some way. Such as relative to the executable. Second runtimes/{rid}/native is examined with a known RID based on current platform. A few local NativeLibrary methods were improved. Load was updated to support non-Windows platforms. This handles the scenario of Framework on ~Windows. Such as Mono. TryLoad was introduced to probe paths during discovery. GetExport/TryGetExport was updated to support non-Windows platforms. This handles the scenario of Framework on ~Windows. Such as Mono. dlerror() was introduced to handle the situation of null exports. Which should not reaaaaly happen in libnode. But is technically correct. The pinvokes to libdl are technically out of date. dl was merged into libc on Linux. And was already present in libc on OS X. But these are left. I made changes to NuGet.config, removing the react stuff and the disabled sources. I don't know what the goal was here, but this was required to get the code building for me. Expects to use Microsoft.JavaScript.LibNode version that deposit native libraries properly. Change tests to not need a hard coded libnode path. --- Directory.Packages.props | 2 +- bench/Benchmarks.cs | 8 +- src/NodeApi/Runtime/NativeLibrary.cs | 124 +++++++++++++++++-- src/NodeApi/Runtime/NodeEmbedding.cs | 98 ++++++++++++++- src/NodeApi/Runtime/NodeEmbeddingPlatform.cs | 2 +- test/GCTests.cs | 1 - test/NodejsEmbeddingTests.cs | 4 +- test/TestUtils.cs | 5 - 8 files changed, 216 insertions(+), 28 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ab77dced..d03ba344 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index 228f836d..6c0a1e7c 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -44,12 +44,6 @@ public static void Main(string[] args) .WithOptions(ConfigOptions.JoinSummary)); } - private static string LibnodePath { get; } = Path.Combine( - GetRepoRootDirectory(), - "bin", - GetCurrentPlatformRuntimeIdentifier(), - "libnode" + GetSharedLibraryExtension()); - private NodeEmbeddingRuntime? _runtime; private NodeEmbeddingNodeApiScope? _nodeApiScope; private JSValue _jsString; @@ -89,7 +83,7 @@ public static void Method() { } protected void Setup() { NodeEmbeddingPlatform platform = new( - LibnodePath, + null, new NodeEmbeddingPlatformSettings { Args = s_settings }); // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 6ab1a4bd..5ac657d5 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -44,12 +44,50 @@ public static nint GetMainProgramHandle() public static nint Load(string libraryName) { #if NETFRAMEWORK || NETSTANDARD - return LoadLibrary(libraryName); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return LoadLibrary(libraryName); + } + else + { + var h = dlopen(libraryName, RTLD_LAZY); + if (h == 0) + throw new DllNotFoundException(); + + return h; + } #else return SysNativeLibrary.Load(libraryName); #endif } + /// + /// Provides a simple API for loading a native library and returns a value that indicates whether the operation succeeded. + /// + /// The name of the native library to be loaded. + /// When the method returns, the OS handle of the loaded native library. + /// true if the native library was loaded successfully; otherwise, false. + public static bool TryLoad(string libraryName, out nint handle) + { + if (libraryName is null) + throw new ArgumentNullException(nameof(libraryName)); + +#if NETFRAMEWORK || NETSTANDARD + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + handle = LoadLibrary(libraryName); + return handle != 0; + } + else + { + handle = dlopen(libraryName, RTLD_LAZY); + return handle != 0; + } +#else + return SysNativeLibrary.TryLoad(libraryName); +#endif + } + /// /// Gets the address of an exported symbol. /// @@ -58,8 +96,28 @@ public static nint Load(string libraryName) /// The address of the symbol. public static nint GetExport(nint handle, string name) { + if (handle == 0) + throw new ArgumentNullException(nameof(handle)); + if (name is null) + throw new ArgumentNullException(nameof(name)); + #if NETFRAMEWORK || NETSTANDARD - return GetProcAddress(handle, name); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return GetProcAddress(handle, name); + } + else + { + // clear any existing errors + dlerror(); + + var address = dlsym(handle, name); + var error = dlerror(); + if (error != 0) + throw new EntryPointNotFoundException(Marshal.PtrToStringAuto(error)); + + return address; + } #else return SysNativeLibrary.GetExport(handle, name); #endif @@ -68,8 +126,20 @@ public static nint GetExport(nint handle, string name) public static bool TryGetExport(nint handle, string name, out nint procAddress) { #if NETFRAMEWORK || NETSTANDARD - procAddress = GetProcAddress(handle, name); - return procAddress != default; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + procAddress = GetProcAddress(handle, name); + return procAddress != default; + } + else + { + // clear any existing errors + dlerror(); + + procAddress = dlsym(handle, name); + var error = dlerror(); + return error != 0; + } #else return SysNativeLibrary.TryGetExport(handle, name, out procAddress); #endif @@ -86,7 +156,27 @@ public static bool TryGetExport(nint handle, string name, out nint procAddress) [DllImport("kernel32")] private static extern nint GetProcAddress(nint hModule, string procName); - private static nint dlopen(nint fileName, int flags) + private static nint dlerror() + { + // Some Linux distros / versions have libdl version 2 only. + // Mac OS only has the unversioned library. + try + { + return dlerror2(); + } + catch (DllNotFoundException) + { + return dlerror1(); + } + } + + [DllImport("libdl", EntryPoint = "dlerror")] + private static extern nint dlerror1(); + + [DllImport("libdl.so.2", EntryPoint = "dlerror")] + private static extern nint dlerror2(); + + private static nint dlopen(string fileName, int flags) { // Some Linux distros / versions have libdl version 2 only. // Mac OS only has the unversioned library. @@ -101,10 +191,30 @@ private static nint dlopen(nint fileName, int flags) } [DllImport("libdl", EntryPoint = "dlopen")] - private static extern nint dlopen1(nint fileName, int flags); + private static extern nint dlopen1(string fileName, int flags); [DllImport("libdl.so.2", EntryPoint = "dlopen")] - private static extern nint dlopen2(nint fileName, int flags); + private static extern nint dlopen2(string fileName, int flags); + + private static nint dlsym(nint handle, string name) + { + // Some Linux distros / versions have libdl version 2 only. + // Mac OS only has the unversioned library. + try + { + return dlsym2(handle, name); + } + catch (DllNotFoundException) + { + return dlsym1(handle, name); + } + } + + [DllImport("libdl", EntryPoint = "dlsym")] + private static extern nint dlsym1(nint fileName, string flags); + + [DllImport("libdl.so.2", EntryPoint = "dlsym")] + private static extern nint dlsym2(nint fileName, string flags); private const int RTLD_LAZY = 1; diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs index 39f63c22..66131922 100644 --- a/src/NodeApi/Runtime/NodeEmbedding.cs +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -4,10 +4,12 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; using System; +using System.IO; #if UNMANAGED_DELEGATES using System.Runtime.CompilerServices; #endif using System.Runtime.InteropServices; + using static JSRuntime; using static NodejsRuntime; @@ -33,15 +35,105 @@ public static JSRuntime JSRuntime } } - public static void Initialize(string libNodePath) +#if NETFRAMEWORK || NETSTANDARD + + /// + /// Discovers the fallback RID of the current platform. + /// + /// + static string? GetFallbackRuntimeIdentifier() + { + var arch = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X86 => "x86", + Architecture.X64 => "x64", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm64", + _ => null, + }; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return $"win-{arch}"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return $"linux-{arch}"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return $"osx-{arch}"; + + return null; + } + + /// + /// Returns a version of the library name with the OS specific prefix and suffix. + /// + /// + /// + static string? MapLibraryName(string name) + { + if (name == null) + return null; + + if (Path.HasExtension(name)) + return name; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return name + ".dll"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return name + ".dylib"; + else + return name + ".so"; + } + + /// + /// Scans the runtimes/{rid}/native directory relative to the application base directory for the native library. + /// + /// + static string? FindFallbackLibNode() + { + if (GetFallbackRuntimeIdentifier() is string rid) + if (MapLibraryName("libnode") is string fileName) + if (Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native", fileName) is string libPath) + if (File.Exists(libPath)) + return libPath; + + return null; + } + +#endif + + /// + /// Attempts to load the libnode library using the default discovery logic as appropriate for the platform. + /// + /// + /// + static nint LoadDefaultLibNode() + { +#if NETFRAMEWORK || NETSTANDARD + if (NativeLibrary.TryLoad("libnode", out var handle)) + return handle; +#else + if (NativeLibrary.TryLoad("libnode", typeof(NodeEmbedding).Assembly, null, out var handle)) + return handle; +#endif + +#if NETFRAMEWORK || NETSTANDARD + var path = FindFallbackLibNode(); + if (path is not null) + return NativeLibrary.Load(path); +#endif + + throw new DllNotFoundException("The JSRuntime cannot locate the libnode shared library."); + } + + 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); + nint libnodeHandle = libNodePath is null ? LoadDefaultLibNode() : NativeLibrary.Load(libNodePath); s_jsRuntime = new NodejsRuntime(libnodeHandle); } diff --git a/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs index ccf69f02..d91b4bcd 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs @@ -29,7 +29,7 @@ public static explicit operator node_embedding_platform(NodeEmbeddingPlatform pl /// Optional platform settings. /// A Node.js platform instance has already been /// loaded in the current process. - public NodeEmbeddingPlatform(string libNodePath, NodeEmbeddingPlatformSettings? settings) + public NodeEmbeddingPlatform(string? libNodePath, NodeEmbeddingPlatformSettings? settings) { if (Current != null) { diff --git a/test/GCTests.cs b/test/GCTests.cs index 3d1ddedc..441d21e9 100644 --- a/test/GCTests.cs +++ b/test/GCTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.JavaScript.NodeApi.Test; public class GCTests { - private static string LibnodePath { get; } = GetLibnodePath(); [Fact] public void GCHandles() diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index 660ac683..ef9c085f 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -22,11 +22,9 @@ 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 NodeEmbeddingPlatform NodejsPlatform { get; } = - new(LibnodePath, new NodeEmbeddingPlatformSettings + new(null, new NodeEmbeddingPlatformSettings { Args = new[] { "node", "--expose-gc" } }); diff --git a/test/TestUtils.cs b/test/TestUtils.cs index 81704549..b52c09cf 100644 --- a/test/TestUtils.cs +++ b/test/TestUtils.cs @@ -77,11 +77,6 @@ public static string GetSharedLibraryExtension() else return ".so"; } - public static string GetLibnodePath() => - Path.Combine( - Path.GetDirectoryName(GetAssemblyLocation()) ?? string.Empty, - "libnode" + GetSharedLibraryExtension()); - public static string? LogOutput( Process process, StreamWriter logWriter) From cf83d9acee8c19dbefcb0073243b5fe4bebcfabc Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 13:11:22 -0600 Subject: [PATCH 02/18] Full variable name. Remove LibNodePath parameter and replace with property on settings. Fix dlerror check. Should be zero. --- bench/Benchmarks.cs | 1 - src/NodeApi/Runtime/NativeLibrary.cs | 37 ++++++++++--------- src/NodeApi/Runtime/NodeEmbeddingPlatform.cs | 4 +- .../Runtime/NodeEmbeddingPlatformSettings.cs | 1 + test/NodejsEmbeddingTests.cs | 2 +- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/bench/Benchmarks.cs b/bench/Benchmarks.cs index 6c0a1e7c..c3461ab5 100644 --- a/bench/Benchmarks.cs +++ b/bench/Benchmarks.cs @@ -83,7 +83,6 @@ public static void Method() { } protected void Setup() { NodeEmbeddingPlatform platform = new( - null, new NodeEmbeddingPlatformSettings { Args = s_settings }); // This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 5ac657d5..60fb7467 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -4,6 +4,7 @@ #if !NET7_0_OR_GREATER using System; +using System.ComponentModel; using System.Runtime.InteropServices; #if !(NETFRAMEWORK || NETSTANDARD) using SysNativeLibrary = System.Runtime.InteropServices.NativeLibrary; @@ -46,15 +47,19 @@ public static nint Load(string libraryName) #if NETFRAMEWORK || NETSTANDARD if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return LoadLibrary(libraryName); + nint handle = LoadLibrary(libraryName); + if (handle == 0) + throw new DllNotFoundException(); + + return handle; } else { - var h = dlopen(libraryName, RTLD_LAZY); - if (h == 0) + nint handle = dlopen(libraryName, RTLD_LAZY); + if (handle == 0) throw new DllNotFoundException(); - return h; + return handle; } #else return SysNativeLibrary.Load(libraryName); @@ -104,19 +109,20 @@ public static nint GetExport(nint handle, string name) #if NETFRAMEWORK || NETSTANDARD if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return GetProcAddress(handle, name); + nint procAddress = GetProcAddress(handle, name); + if (procAddress == 0) + throw new EntryPointNotFoundException(); + + return procAddress; } else { - // clear any existing errors dlerror(); + nint procAddress = dlsym(handle, name); + if (dlerror() != 0) + throw new EntryPointNotFoundException(); - var address = dlsym(handle, name); - var error = dlerror(); - if (error != 0) - throw new EntryPointNotFoundException(Marshal.PtrToStringAuto(error)); - - return address; + return procAddress; } #else return SysNativeLibrary.GetExport(handle, name); @@ -129,16 +135,13 @@ public static bool TryGetExport(nint handle, string name, out nint procAddress) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { procAddress = GetProcAddress(handle, name); - return procAddress != default; + return procAddress != 0; } else { - // clear any existing errors dlerror(); - procAddress = dlsym(handle, name); - var error = dlerror(); - return error != 0; + return dlerror() != 0; } #else return SysNativeLibrary.TryGetExport(handle, name, out procAddress); diff --git a/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs index d91b4bcd..4cb13be0 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingPlatform.cs @@ -29,7 +29,7 @@ public static explicit operator node_embedding_platform(NodeEmbeddingPlatform pl /// Optional platform settings. /// A Node.js platform instance has already been /// loaded in the current process. - public NodeEmbeddingPlatform(string? libNodePath, NodeEmbeddingPlatformSettings? settings) + public NodeEmbeddingPlatform(NodeEmbeddingPlatformSettings? settings) { if (Current != null) { @@ -37,7 +37,7 @@ public NodeEmbeddingPlatform(string? libNodePath, NodeEmbeddingPlatformSettings? "Only one Node.js platform instance per process is allowed."); } Current = this; - Initialize(libNodePath); + Initialize(settings?.LibNodePath); using FunctorRef functorRef = CreatePlatformConfigureFunctorRef(settings?.CreateConfigurePlatformCallback()); diff --git a/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs b/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs index 9bf6d580..d64477ab 100644 --- a/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs +++ b/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs @@ -8,6 +8,7 @@ namespace Microsoft.JavaScript.NodeApi.Runtime; public class NodeEmbeddingPlatformSettings { + public string? LibNodePath { get; set; } public NodeEmbeddingPlatformFlags? PlatformFlags { get; set; } public string[]? Args { get; set; } public ConfigurePlatformCallback? ConfigurePlatform { get; set; } diff --git a/test/NodejsEmbeddingTests.cs b/test/NodejsEmbeddingTests.cs index ef9c085f..b2703916 100644 --- a/test/NodejsEmbeddingTests.cs +++ b/test/NodejsEmbeddingTests.cs @@ -24,7 +24,7 @@ public class NodejsEmbeddingTests // The Node.js platform may only be initialized once per process. internal static NodeEmbeddingPlatform NodejsPlatform { get; } = - new(null, new NodeEmbeddingPlatformSettings + new(new NodeEmbeddingPlatformSettings { Args = new[] { "node", "--expose-gc" } }); From a06e92b380d714b6ba8ff39f10fd2d3c98877585 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 13:22:25 -0600 Subject: [PATCH 03/18] No error means success. --- src/NodeApi/Runtime/NativeLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 60fb7467..d8799bc9 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -141,7 +141,7 @@ public static bool TryGetExport(nint handle, string name, out nint procAddress) { dlerror(); procAddress = dlsym(handle, name); - return dlerror() != 0; + return dlerror() == 0; } #else return SysNativeLibrary.TryGetExport(handle, name, out procAddress); From a540fc66ecd5eafaf78c49fe49c7db1537560b1c Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 14:32:05 -0600 Subject: [PATCH 04/18] Move duplicated code into GetSymbol and LoadFromPath. Same names as used by the runtime's version of NativeLibrary. --- src/NodeApi/Runtime/NativeLibrary.cs | 109 ++++++++++++++------------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index d8799bc9..b4a18927 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -45,22 +45,7 @@ public static nint GetMainProgramHandle() public static nint Load(string libraryName) { #if NETFRAMEWORK || NETSTANDARD - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - nint handle = LoadLibrary(libraryName); - if (handle == 0) - throw new DllNotFoundException(); - - return handle; - } - else - { - nint handle = dlopen(libraryName, RTLD_LAZY); - if (handle == 0) - throw new DllNotFoundException(); - - return handle; - } + return LoadFromPath(libraryName, throwOnError: true); #else return SysNativeLibrary.Load(libraryName); #endif @@ -72,25 +57,37 @@ public static nint Load(string libraryName) /// The name of the native library to be loaded. /// When the method returns, the OS handle of the loaded native library. /// true if the native library was loaded successfully; otherwise, false. - public static bool TryLoad(string libraryName, out nint handle) + public static bool TryLoad(string libraryPath, out nint handle) { - if (libraryName is null) - throw new ArgumentNullException(nameof(libraryName)); - #if NETFRAMEWORK || NETSTANDARD + handle = LoadFromPath(libraryPath, throwOnError: false); + return handle != 0; +#else + return SysNativeLibrary.TryLoad(libraryName); +#endif + } + + static nint LoadFromPath(string libraryPath, bool throwOnError) + { + if (libraryPath is null) + throw new ArgumentNullException(nameof(libraryPath)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - handle = LoadLibrary(libraryName); - return handle != 0; + nint handle = LoadLibrary(libraryPath); + if (handle == 0) + throw new DllNotFoundException(); + + return handle; } else { - handle = dlopen(libraryName, RTLD_LAZY); - return handle != 0; + nint handle = dlopen(libraryPath, RTLD_LAZY); + if (handle == 0) + throw new DllNotFoundException(); + + return handle; } -#else - return SysNativeLibrary.TryLoad(libraryName); -#endif } /// @@ -100,18 +97,41 @@ public static bool TryLoad(string libraryName, out nint handle) /// The name of the exported symbol. /// The address of the symbol. public static nint GetExport(nint handle, string name) + { +#if NETFRAMEWORK || NETSTANDARD + return GetSymbol(handle, name, throwOnError: true); +#else + return SysNativeLibrary.GetExport(handle, name); +#endif + } + + public static bool TryGetExport(nint handle, string name, out nint procAddress) + { +#if NETFRAMEWORK || NETSTANDARD + procAddress = GetSymbol(handle, name, throwOnError: false); + return procAddress != 0; +#else + return SysNativeLibrary.TryGetExport(handle, name, out procAddress); +#endif + } + + static nint GetSymbol(nint handle, string name, bool throwOnError) { if (handle == 0) throw new ArgumentNullException(nameof(handle)); - if (name is null) + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); -#if NETFRAMEWORK || NETSTANDARD if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { nint procAddress = GetProcAddress(handle, name); if (procAddress == 0) - throw new EntryPointNotFoundException(); + { + if (throwOnError) + throw new EntryPointNotFoundException(); + + procAddress = 0; + } return procAddress; } @@ -120,32 +140,15 @@ public static nint GetExport(nint handle, string name) dlerror(); nint procAddress = dlsym(handle, name); if (dlerror() != 0) - throw new EntryPointNotFoundException(); + { + if (throwOnError) + throw new EntryPointNotFoundException(); - return procAddress; - } -#else - return SysNativeLibrary.GetExport(handle, name); -#endif - } + procAddress = 0; + } - public static bool TryGetExport(nint handle, string name, out nint procAddress) - { -#if NETFRAMEWORK || NETSTANDARD - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - procAddress = GetProcAddress(handle, name); - return procAddress != 0; - } - else - { - dlerror(); - procAddress = dlsym(handle, name); - return dlerror() == 0; + return procAddress; } -#else - return SysNativeLibrary.TryGetExport(handle, name, out procAddress); -#endif } #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments From c4d2273adc149854d0a9e9f1ef7a2e0460efc039 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 16:43:59 -0600 Subject: [PATCH 05/18] Check null arch dirs in case, instead of just silently failing. Doens't matter but might in the future. --- src/NodeApi/Runtime/NodeEmbedding.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs index 66131922..fe86f930 100644 --- a/src/NodeApi/Runtime/NodeEmbedding.cs +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -43,7 +43,7 @@ public static JSRuntime JSRuntime /// static string? GetFallbackRuntimeIdentifier() { - var arch = RuntimeInformation.ProcessArchitecture switch + string? arch = RuntimeInformation.ProcessArchitecture switch { Architecture.X86 => "x86", Architecture.X64 => "x64", @@ -53,13 +53,13 @@ public static JSRuntime JSRuntime }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return $"win-{arch}"; + return arch is not null ? $"win-{arch}" : "win"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return $"linux-{arch}"; + return arch is not null ? $"linux-{arch}" : "linux"; if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return $"osx-{arch}"; + return arch is not null ? $"osx-{arch}" : "osx"; return null; } @@ -79,10 +79,11 @@ public static JSRuntime JSRuntime if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return name + ".dll"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return name + ".dylib"; - else - return name + ".so"; + + return name + ".so"; } /// From a889ec909b308b3410bf8b6f318b3f62bf0bd51e Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 16:45:47 -0600 Subject: [PATCH 06/18] Change to typed. --- src/NodeApi/Runtime/NodeEmbedding.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs index fe86f930..f813066c 100644 --- a/src/NodeApi/Runtime/NodeEmbedding.cs +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -111,15 +111,15 @@ public static JSRuntime JSRuntime static nint LoadDefaultLibNode() { #if NETFRAMEWORK || NETSTANDARD - if (NativeLibrary.TryLoad("libnode", out var handle)) + if (NativeLibrary.TryLoad("libnode", out nint handle)) return handle; #else - if (NativeLibrary.TryLoad("libnode", typeof(NodeEmbedding).Assembly, null, out var handle)) + if (NativeLibrary.TryLoad("libnode", typeof(NodeEmbedding).Assembly, null, out nint handle)) return handle; #endif #if NETFRAMEWORK || NETSTANDARD - var path = FindFallbackLibNode(); + string? path = FindFallbackLibNode(); if (path is not null) return NativeLibrary.Load(path); #endif From 9aa5a455b7b2311bd200f5334854011c46663b86 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 16:53:23 -0600 Subject: [PATCH 07/18] Search local paths first. --- src/NodeApi/Runtime/NodeEmbedding.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/NodeApi/Runtime/NodeEmbedding.cs b/src/NodeApi/Runtime/NodeEmbedding.cs index f813066c..7c9b74d1 100644 --- a/src/NodeApi/Runtime/NodeEmbedding.cs +++ b/src/NodeApi/Runtime/NodeEmbedding.cs @@ -71,7 +71,7 @@ public static JSRuntime JSRuntime /// static string? MapLibraryName(string name) { - if (name == null) + if (name is null) return null; if (Path.HasExtension(name)) @@ -90,7 +90,7 @@ public static JSRuntime JSRuntime /// Scans the runtimes/{rid}/native directory relative to the application base directory for the native library. /// /// - static string? FindFallbackLibNode() + static string? FindLocalLibNode() { if (GetFallbackRuntimeIdentifier() is string rid) if (MapLibraryName("libnode") is string fileName) @@ -104,25 +104,27 @@ public static JSRuntime JSRuntime #endif /// - /// Attempts to load the libnode library using the default discovery logic as appropriate for the platform. + /// Attempts to load the libnode library using the discovery logic as appropriate for the platform. /// /// - /// + /// static nint LoadDefaultLibNode() { #if NETFRAMEWORK || NETSTANDARD - if (NativeLibrary.TryLoad("libnode", out nint handle)) - return handle; + // search local paths that would be provided by LibNode packages + string? path = FindLocalLibNode(); + if (path is not null) + if (NativeLibrary.TryLoad(path, out nint handle)) + return handle; #else + // search using default dependency context if (NativeLibrary.TryLoad("libnode", typeof(NodeEmbedding).Assembly, null, out nint handle)) return handle; #endif -#if NETFRAMEWORK || NETSTANDARD - string? path = FindFallbackLibNode(); - if (path is not null) - return NativeLibrary.Load(path); -#endif + // attempt to load from default OS search paths + if (NativeLibrary.TryLoad("libnode", out nint defaultHandle)) + return defaultHandle; throw new DllNotFoundException("The JSRuntime cannot locate the libnode shared library."); } From feb55a662baa3e8078a02e7b515f2f8fe2a8d741 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 17:17:34 -0600 Subject: [PATCH 08/18] Handle error messages. --- src/NodeApi/Runtime/NativeLibrary.cs | 41 +++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index b4a18927..b3c613ba 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -40,12 +40,12 @@ public static nint GetMainProgramHandle() /// /// Loads a native library using default flags. /// - /// The name of the native library to be loaded. + /// The name of the native library to be loaded. /// The OS handle for the loaded native library. - public static nint Load(string libraryName) + public static nint Load(string libraryPath) { #if NETFRAMEWORK || NETSTANDARD - return LoadFromPath(libraryName, throwOnError: true); + return LoadFromPath(libraryPath, throwOnError: true); #else return SysNativeLibrary.Load(libraryName); #endif @@ -54,7 +54,7 @@ public static nint Load(string libraryName) /// /// Provides a simple API for loading a native library and returns a value that indicates whether the operation succeeded. /// - /// The name of the native library to be loaded. + /// The name of the native library to be loaded. /// When the method returns, the OS handle of the loaded native library. /// true if the native library was loaded successfully; otherwise, false. public static bool TryLoad(string libraryPath, out nint handle) @@ -75,16 +75,23 @@ static nint LoadFromPath(string libraryPath, bool throwOnError) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { nint handle = LoadLibrary(libraryPath); - if (handle == 0) - throw new DllNotFoundException(); + if (handle == 0 && throwOnError) + throw new DllNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message); return handle; } else { + dlerror(); nint handle = dlopen(libraryPath, RTLD_LAZY); - if (handle == 0) - throw new DllNotFoundException(); + nint error = dlerror(); + if (error != 0) + { + if (throwOnError) + throw new DllNotFoundException(Marshal.PtrToStringAuto(error)); + + handle = 0; + } return handle; } @@ -125,13 +132,8 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { nint procAddress = GetProcAddress(handle, name); - if (procAddress == 0) - { - if (throwOnError) - throw new EntryPointNotFoundException(); - - procAddress = 0; - } + if (procAddress == 0 && throwOnError) + throw new DllNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message); return procAddress; } @@ -139,10 +141,11 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) { dlerror(); nint procAddress = dlsym(handle, name); - if (dlerror() != 0) + nint error = dlerror(); + if (error != 0) { if (throwOnError) - throw new EntryPointNotFoundException(); + throw new EntryPointNotFoundException(Marshal.PtrToStringAuto(error)); procAddress = 0; } @@ -156,10 +159,10 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) [DllImport("kernel32")] private static extern nint GetModuleHandle(string? moduleName); - [DllImport("kernel32")] + [DllImport("kernel32", SetLastError = true)] private static extern nint LoadLibrary(string moduleName); - [DllImport("kernel32")] + [DllImport("kernel32", SetLastError = true)] private static extern nint GetProcAddress(nint hModule, string procName); private static nint dlerror() From 0ef4cdb2dfd166dc185b843f2c354c60402a0622 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Tue, 11 Feb 2025 17:48:22 -0600 Subject: [PATCH 09/18] Not sure this matters, since there is no way to build this, but this was missing. --- src/NodeApi/Runtime/NativeLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index b3c613ba..05b29d87 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -63,7 +63,7 @@ public static bool TryLoad(string libraryPath, out nint handle) handle = LoadFromPath(libraryPath, throwOnError: false); return handle != 0; #else - return SysNativeLibrary.TryLoad(libraryName); + return SysNativeLibrary.TryLoad(libraryName, out handle); #endif } From bfbc5f83ce2383b3bd88b5027ce861cb1620f651 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 09:50:31 -0600 Subject: [PATCH 10/18] Fix parameter names. --- src/NodeApi/Runtime/NativeLibrary.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 05b29d87..71d01c65 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -205,25 +205,25 @@ private static nint dlopen(string fileName, int flags) [DllImport("libdl.so.2", EntryPoint = "dlopen")] private static extern nint dlopen2(string fileName, int flags); - private static nint dlsym(nint handle, string name) + private static nint dlsym(nint handle, string symbol) { // Some Linux distros / versions have libdl version 2 only. // Mac OS only has the unversioned library. try { - return dlsym2(handle, name); + return dlsym2(handle, symbol); } catch (DllNotFoundException) { - return dlsym1(handle, name); + return dlsym1(handle, symbol); } } [DllImport("libdl", EntryPoint = "dlsym")] - private static extern nint dlsym1(nint fileName, string flags); + private static extern nint dlsym1(nint handle, string symbol); [DllImport("libdl.so.2", EntryPoint = "dlsym")] - private static extern nint dlsym2(nint fileName, string flags); + private static extern nint dlsym2(nint handle, string symbol); private const int RTLD_LAZY = 1; From d2d30ec0f57e5314d5e30b7ef9da214bb8785ae3 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 09:53:01 -0600 Subject: [PATCH 11/18] dlopen takes null fileName. --- src/NodeApi/Runtime/NativeLibrary.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 71d01c65..1a6d1f9a 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -185,7 +185,7 @@ private static nint dlerror() [DllImport("libdl.so.2", EntryPoint = "dlerror")] private static extern nint dlerror2(); - private static nint dlopen(string fileName, int flags) + private static nint dlopen(string? fileName, int flags) { // Some Linux distros / versions have libdl version 2 only. // Mac OS only has the unversioned library. @@ -200,10 +200,10 @@ private static nint dlopen(string fileName, int flags) } [DllImport("libdl", EntryPoint = "dlopen")] - private static extern nint dlopen1(string fileName, int flags); + private static extern nint dlopen1(string? fileName, int flags); [DllImport("libdl.so.2", EntryPoint = "dlopen")] - private static extern nint dlopen2(string fileName, int flags); + private static extern nint dlopen2(string? fileName, int flags); private static nint dlsym(nint handle, string symbol) { From 7fde8a6ffcfbdfabfc9765429a0975d14139e155 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 09:54:38 -0600 Subject: [PATCH 12/18] Wrong exception. --- src/NodeApi/Runtime/NativeLibrary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 1a6d1f9a..a047ea49 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -133,7 +133,7 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) { nint procAddress = GetProcAddress(handle, name); if (procAddress == 0 && throwOnError) - throw new DllNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message); + throw new EntryPointNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message); return procAddress; } From 7686c4a6a0fe0f0cb8d58d1d20ef951dd075b2ba Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 10:05:08 -0600 Subject: [PATCH 13/18] Change order. libdl.so.2 really should be the fallback. My view here is that anything that has dl.so.2 as the proper library should have moved it to libc anyways. So I'm not sure what OS this actually applies to, but maybe there is something. --- src/NodeApi/Runtime/NativeLibrary.cs | 60 +++++++++++++++++++++------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index a047ea49..9122719e 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -167,19 +167,29 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) private static nint dlerror() { - // Some Linux distros / versions have libdl version 2 only. - // Mac OS only has the unversioned library. + // some operating systems have dlerror in libc, some in libdl, some in libdl.so.2 + // attempt in that order try { - return dlerror2(); + return dlerror0(); } catch (DllNotFoundException) { - return dlerror1(); + try + { + return dlerror1(); + } + catch (DllNotFoundException) + { + return dlerror2(); + } } } - [DllImport("libdl", EntryPoint = "dlerror")] + [DllImport("c", EntryPoint = "dlerror")] + private static extern nint dlerror0(); + + [DllImport("dl", EntryPoint = "dlerror")] private static extern nint dlerror1(); [DllImport("libdl.so.2", EntryPoint = "dlerror")] @@ -187,19 +197,29 @@ private static nint dlerror() private static nint dlopen(string? fileName, int flags) { - // Some Linux distros / versions have libdl version 2 only. - // Mac OS only has the unversioned library. + // some operating systems have dlopen in libc, some in libdl, some in libdl.so.2 + // attempt in that order try { - return dlopen2(fileName, flags); + return dlopen0(fileName, flags); } catch (DllNotFoundException) { - return dlopen1(fileName, flags); + try + { + return dlopen1(fileName, flags); + } + catch (DllNotFoundException) + { + return dlopen2(fileName, flags); + } } } - [DllImport("libdl", EntryPoint = "dlopen")] + [DllImport("c", EntryPoint = "dlopen")] + private static extern nint dlopen0(string? fileName, int flags); + + [DllImport("dl", EntryPoint = "dlopen")] private static extern nint dlopen1(string? fileName, int flags); [DllImport("libdl.so.2", EntryPoint = "dlopen")] @@ -207,19 +227,29 @@ private static nint dlopen(string? fileName, int flags) private static nint dlsym(nint handle, string symbol) { - // Some Linux distros / versions have libdl version 2 only. - // Mac OS only has the unversioned library. + // some operating systems have dlsym in libc, some in libdl, some in libdl.so.2 + // attempt in that order try { - return dlsym2(handle, symbol); + return dlsym0(handle, symbol); } catch (DllNotFoundException) { - return dlsym1(handle, symbol); + try + { + return dlsym1(handle, symbol); + } + catch (DllNotFoundException) + { + return dlsym2(handle, symbol); + } } } - [DllImport("libdl", EntryPoint = "dlsym")] + [DllImport("c", EntryPoint = "dlsym")] + private static extern nint dlsym0(nint handle, string symbol); + + [DllImport("dl", EntryPoint = "dlsym")] private static extern nint dlsym1(nint handle, string symbol); [DllImport("libdl.so.2", EntryPoint = "dlsym")] From 18e1cbed216e33626e53433f7f193326664862ec Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 12:21:30 -0600 Subject: [PATCH 14/18] Remove alternate path to real impl. Change #ifdef to represent the real CLR version at which NativeLibrary appeared (future proofs change in compatibility). --- src/NodeApi/Runtime/NativeLibrary.cs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 9122719e..ee966406 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#if !NET7_0_OR_GREATER +#if !NETCOREAPP3_0_OR_GREATER using System; using System.ComponentModel; using System.Runtime.InteropServices; -#if !(NETFRAMEWORK || NETSTANDARD) -using SysNativeLibrary = System.Runtime.InteropServices.NativeLibrary; -#endif namespace Microsoft.JavaScript.NodeApi.Runtime; @@ -44,11 +41,7 @@ public static nint GetMainProgramHandle() /// The OS handle for the loaded native library. public static nint Load(string libraryPath) { -#if NETFRAMEWORK || NETSTANDARD return LoadFromPath(libraryPath, throwOnError: true); -#else - return SysNativeLibrary.Load(libraryName); -#endif } /// @@ -59,12 +52,8 @@ public static nint Load(string libraryPath) /// true if the native library was loaded successfully; otherwise, false. public static bool TryLoad(string libraryPath, out nint handle) { -#if NETFRAMEWORK || NETSTANDARD handle = LoadFromPath(libraryPath, throwOnError: false); return handle != 0; -#else - return SysNativeLibrary.TryLoad(libraryName, out handle); -#endif } static nint LoadFromPath(string libraryPath, bool throwOnError) @@ -105,21 +94,13 @@ static nint LoadFromPath(string libraryPath, bool throwOnError) /// The address of the symbol. public static nint GetExport(nint handle, string name) { -#if NETFRAMEWORK || NETSTANDARD return GetSymbol(handle, name, throwOnError: true); -#else - return SysNativeLibrary.GetExport(handle, name); -#endif } public static bool TryGetExport(nint handle, string name, out nint procAddress) { -#if NETFRAMEWORK || NETSTANDARD procAddress = GetSymbol(handle, name, throwOnError: false); return procAddress != 0; -#else - return SysNativeLibrary.TryGetExport(handle, name, out procAddress); -#endif } static nint GetSymbol(nint handle, string name, bool throwOnError) From 16c56bc773784a31551efa3a73f6b65bc207b35f Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 16:56:34 -0600 Subject: [PATCH 15/18] Cache dl* function as delegate. Don't bother for dl*0 which is at the top. --- src/NodeApi/Runtime/NativeLibrary.cs | 40 +++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index ee966406..27381caf 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -6,6 +6,7 @@ using System; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; namespace Microsoft.JavaScript.NodeApi.Runtime; @@ -146,23 +147,30 @@ static nint GetSymbol(nint handle, string name, bool throwOnError) [DllImport("kernel32", SetLastError = true)] private static extern nint GetProcAddress(nint hModule, string procName); + private delegate nint DlErrorDelegate(); + private static DlErrorDelegate? s_dlerror; + private static nint dlerror() { + // cache dlerror function + if (s_dlerror is not null) + return s_dlerror(); + // some operating systems have dlerror in libc, some in libdl, some in libdl.so.2 // attempt in that order try { return dlerror0(); } - catch (DllNotFoundException) + catch (EntryPointNotFoundException) { try { - return dlerror1(); + return (s_dlerror = dlerror1)(); } catch (DllNotFoundException) { - return dlerror2(); + return (s_dlerror = dlerror2)(); } } } @@ -176,23 +184,30 @@ private static nint dlerror() [DllImport("libdl.so.2", EntryPoint = "dlerror")] private static extern nint dlerror2(); + private delegate nint DlOpenDelegate(string? fileName, int flags); + private static DlOpenDelegate? s_dlopen; + private static nint dlopen(string? fileName, int flags) { + // cache dlopen function + if (s_dlopen is not null) + return s_dlopen(fileName, flags); + // some operating systems have dlopen in libc, some in libdl, some in libdl.so.2 // attempt in that order try { return dlopen0(fileName, flags); } - catch (DllNotFoundException) + catch (EntryPointNotFoundException) { try { - return dlopen1(fileName, flags); + return (s_dlopen = dlopen1)(fileName, flags); } catch (DllNotFoundException) { - return dlopen2(fileName, flags); + return (s_dlopen = dlopen2)(fileName, flags); } } } @@ -206,23 +221,30 @@ private static nint dlopen(string? fileName, int flags) [DllImport("libdl.so.2", EntryPoint = "dlopen")] private static extern nint dlopen2(string? fileName, int flags); + private delegate nint DlSymDelegate(nint handle, string symbol); + private static DlSymDelegate? s_dlsym; + private static nint dlsym(nint handle, string symbol) { + // cache dlsym function + if (s_dlsym is not null) + return s_dlsym(handle, symbol); + // some operating systems have dlsym in libc, some in libdl, some in libdl.so.2 // attempt in that order try { return dlsym0(handle, symbol); } - catch (DllNotFoundException) + catch (EntryPointNotFoundException) { try { - return dlsym1(handle, symbol); + return (s_dlsym = dlsym1)(handle, symbol); } catch (DllNotFoundException) { - return dlsym2(handle, symbol); + return (s_dlsym = dlsym2)(handle, symbol); } } } From a74daac9aa9bda7dac7a0755202f0ac4e9661906 Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Wed, 12 Feb 2025 17:27:04 -0600 Subject: [PATCH 16/18] Why does that keep appearing? --- src/NodeApi/Runtime/NativeLibrary.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index 27381caf..2628bb4a 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -6,7 +6,6 @@ using System; using System.ComponentModel; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; namespace Microsoft.JavaScript.NodeApi.Runtime; From 32d3f3a4e3bc15cd927a17af6a3f2809ef3e99cc Mon Sep 17 00:00:00 2001 From: Jerome Haltom Date: Sat, 15 Feb 2025 20:22:58 -0600 Subject: [PATCH 17/18] Use new official package version. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d03ba344..3fdb20c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + From e59e193201084668689c1bf18e96067e681de9d4 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Sun, 16 Feb 2025 13:36:05 -0800 Subject: [PATCH 18/18] Fix formatting issues --- src/NodeApi.DotNetHost/JSMarshaller.cs | 2 +- src/NodeApi.Generator/ExpressionExtensions.cs | 7 +++---- src/NodeApi.Generator/TypeDefinitionsGenerator.cs | 4 ++-- src/NodeApi/Runtime/TracingJSRuntime.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/NodeApi.DotNetHost/JSMarshaller.cs b/src/NodeApi.DotNetHost/JSMarshaller.cs index baa2122b..aa8ec644 100644 --- a/src/NodeApi.DotNetHost/JSMarshaller.cs +++ b/src/NodeApi.DotNetHost/JSMarshaller.cs @@ -475,7 +475,7 @@ public Expression BuildFromJSConstructorExpression(ConstructorInfo c ParameterExpression resultVariable = Expression.Variable( constructor.DeclaringType!, "__result"); - variables = new List(argVariables.Append(resultVariable)); + variables = [.. argVariables.Append(resultVariable)]; statements.Add(Expression.Assign(resultVariable, Expression.New(constructor, argVariables))); diff --git a/src/NodeApi.Generator/ExpressionExtensions.cs b/src/NodeApi.Generator/ExpressionExtensions.cs index 32d9aaf8..96fa26f3 100644 --- a/src/NodeApi.Generator/ExpressionExtensions.cs +++ b/src/NodeApi.Generator/ExpressionExtensions.cs @@ -59,9 +59,8 @@ private static string ToCS( (variables is null ? FormatType(lambda.ReturnType) + " " + lambda.Name + "(" + string.Join(", ", lambda.Parameters.Select((p) => p.ToCS())) + ")\n" : "(" + string.Join(", ", lambda.Parameters.Select((p) => p.ToCS())) + ") =>\n") + - ToCS(lambda.Body, path, new HashSet( - (variables ?? Enumerable.Empty()).Union( - lambda.Parameters.Select((p) => p.Name!)))), + ToCS(lambda.Body, path, [.. (variables ?? Enumerable.Empty()).Union( + lambda.Parameters.Select((p) => p.Name!))]), ParameterExpression parameter => (parameter.IsByRef && parameter.Name?.StartsWith(OutParameterPrefix) == true) ? @@ -285,7 +284,7 @@ private static string FormatStatement( if (assignment.Left is ParameterExpression variable && !variables.Contains(variable.Name!)) { - variables = new HashSet(variables.Union(new[] { variable.Name! })); + variables = [.. variables.Union(new[] { variable.Name! })]; s += FormatType(variable.Type) + " " + s; } } diff --git a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs index f0c32532..80e4405d 100644 --- a/src/NodeApi.Generator/TypeDefinitionsGenerator.cs +++ b/src/NodeApi.Generator/TypeDefinitionsGenerator.cs @@ -333,7 +333,7 @@ private static IEnumerable MergeSystemReferenceAssemblies( private static Version InferReferenceAssemblyVersionFromPath(string assemblyPath) { - var pathParts = assemblyPath.Split( + List pathParts = assemblyPath.Split( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToList(); // Infer the version from a system reference assembly path such as @@ -1230,7 +1230,7 @@ private void BeginNamespace(ref SourceBuilder s, Type type) return; } - List namespaceParts = new(type.Namespace?.Split('.') ?? Enumerable.Empty()); + List namespaceParts = [.. type.Namespace?.Split('.') ?? Enumerable.Empty()]; int namespacePartsCount = namespaceParts.Count; Type? declaringType = type.DeclaringType; diff --git a/src/NodeApi/Runtime/TracingJSRuntime.cs b/src/NodeApi/Runtime/TracingJSRuntime.cs index 27aa2534..e78de8df 100644 --- a/src/NodeApi/Runtime/TracingJSRuntime.cs +++ b/src/NodeApi/Runtime/TracingJSRuntime.cs @@ -178,7 +178,7 @@ private string Format(napi_env env, napi_value value) valueString = $" {GetValueString(env, functionName)}()"; } break; - }; + } return $"{value.Handle:X16} {valueType.ToString().Substring(5)}{valueString}"; }