diff --git a/src/NodeApi/Runtime/NativeLibrary.cs b/src/NodeApi/Runtime/NativeLibrary.cs index a7e59b4a..8820a7a5 100644 --- a/src/NodeApi/Runtime/NativeLibrary.cs +++ b/src/NodeApi/Runtime/NativeLibrary.cs @@ -4,6 +4,8 @@ #if !NET7_0_OR_GREATER using System; +using System.IO; +using System.Reflection; using System.Runtime.InteropServices; #if !NETFRAMEWORK using SysNativeLibrary = System.Runtime.InteropServices.NativeLibrary; @@ -50,6 +52,25 @@ public static nint Load(string libraryName) #endif } + /// + /// Loads a native library using the high-level API. + /// + /// The name of the native library to be loaded. + /// The assembly loading the native library. + /// The search path. + /// The OS handle for the loaded native library. + public static nint Load(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { +#if NETFRAMEWORK + string libraryPath = FindLibrary(libraryName, assembly, searchPath) + ?? throw new DllNotFoundException($"Could not find library: {libraryName}"); + + return LoadLibrary(libraryPath); +#else + return SysNativeLibrary.Load(libraryName, assembly, searchPath); +#endif + } + /// /// Gets the address of an exported symbol. /// @@ -75,6 +96,79 @@ public static bool TryGetExport(nint handle, string name, out nint procAddress) #endif } +#if NETFRAMEWORK + /// + /// Searches various well-known paths for a library and returns the first result. + /// + /// Name of the library to search for. + /// Assembly to search relative from. + /// The search path. + /// Library path if found, otherwise false. + private static string? FindLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (Path.IsPathRooted(libraryName) && File.Exists(libraryName)) + { + return Path.GetFullPath(libraryName); + } + + string[] tryLibraryNames; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + tryLibraryNames = + [ + libraryName, + $"{libraryName}.dll" + ]; + } + else + { + string libraryExtension = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? "dylib" + : "so"; + + tryLibraryNames = + [ + libraryName, + $"lib{libraryName}", + $"{libraryName}.{libraryExtension}", + $"lib{libraryName}.{libraryExtension}" + ]; + } + + string?[] tryDirectories = + [ + searchPath == null || (searchPath & DllImportSearchPath.AssemblyDirectory) > 0 + ? Path.GetDirectoryName(assembly.Location) + : null, + + searchPath == null || (searchPath & DllImportSearchPath.SafeDirectories) > 0 + ? Environment.SystemDirectory + : null, + ]; + + foreach (string? tryDirectory in tryDirectories) + { + if (tryDirectory == null) + { + continue; + } + + foreach (string tryLibraryName in tryLibraryNames) + { + string tryLibraryPath = Path.Combine(tryDirectory, tryLibraryName); + + if (File.Exists(tryLibraryPath)) + { + return tryLibraryPath; + } + } + } + + return null; + } +#endif + #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments [DllImport("kernel32")] diff --git a/src/NodeApi/Runtime/NodejsPlatform.cs b/src/NodeApi/Runtime/NodejsPlatform.cs index fe0c9b4a..c6d4e5b8 100644 --- a/src/NodeApi/Runtime/NodejsPlatform.cs +++ b/src/NodeApi/Runtime/NodejsPlatform.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Reflection; using System.Runtime.InteropServices; namespace Microsoft.JavaScript.NodeApi.Runtime; @@ -25,23 +26,34 @@ public sealed class NodejsPlatform : IDisposable /// /// Initializes the Node.js platform. /// - /// Path to the `libnode` shared library, including extension. + /// + /// Name of the `libnode` shared library. + /// Has to be a full file path when using .NET Framework. + /// /// Optional platform arguments. /// A Node.js platform instance has already been /// loaded in the current process. public NodejsPlatform( - string libnodePath, + string libnode, 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); + var entryAssembly = Assembly.GetAssembly(typeof(NodejsPlatform)); + + nint libnodeHandle = + entryAssembly != null + ? NativeLibrary.Load( + libnode, + entryAssembly!, + DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories + ) + : NativeLibrary.Load(libnode); + Runtime = new NodejsRuntime(libnodeHandle); Runtime.CreatePlatform(args, (error) => Console.WriteLine(error), out _platform)