Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibNode Discovery #425

Merged
merged 18 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" /><!-- 4.1.0 is compatible with .NET Standard -->
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.202" />
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.203" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />
Expand Down
7 changes: 0 additions & 7 deletions bench/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,7 +83,6 @@ public static void Method() { }
protected void Setup()
{
NodeEmbeddingPlatform platform = new(
LibnodePath,
new NodeEmbeddingPlatformSettings { Args = s_settings });

// This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on
Expand Down
2 changes: 1 addition & 1 deletion src/NodeApi.DotNetHost/JSMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ public Expression<JSCallback> BuildFromJSConstructorExpression(ConstructorInfo c

ParameterExpression resultVariable = Expression.Variable(
constructor.DeclaringType!, "__result");
variables = new List<ParameterExpression>(argVariables.Append(resultVariable));
variables = [.. argVariables.Append(resultVariable)];
statements.Add(Expression.Assign(resultVariable,
Expression.New(constructor, argVariables)));

Expand Down
7 changes: 3 additions & 4 deletions src/NodeApi.Generator/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(
(variables ?? Enumerable.Empty<string>()).Union(
lambda.Parameters.Select((p) => p.Name!)))),
ToCS(lambda.Body, path, [.. (variables ?? Enumerable.Empty<string>()).Union(
lambda.Parameters.Select((p) => p.Name!))]),

ParameterExpression parameter =>
(parameter.IsByRef && parameter.Name?.StartsWith(OutParameterPrefix) == true) ?
Expand Down Expand Up @@ -285,7 +284,7 @@ private static string FormatStatement(
if (assignment.Left is ParameterExpression variable &&
!variables.Contains(variable.Name!))
{
variables = new HashSet<string>(variables.Union(new[] { variable.Name! }));
variables = [.. variables.Union(new[] { variable.Name! })];
s += FormatType(variable.Type) + " " + s;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/NodeApi.Generator/TypeDefinitionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ private static IEnumerable<string> MergeSystemReferenceAssemblies(

private static Version InferReferenceAssemblyVersionFromPath(string assemblyPath)
{
var pathParts = assemblyPath.Split(
List<string> pathParts = assemblyPath.Split(
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToList();

// Infer the version from a system reference assembly path such as
Expand Down Expand Up @@ -1230,7 +1230,7 @@ private void BeginNamespace(ref SourceBuilder s, Type type)
return;
}

List<string> namespaceParts = new(type.Namespace?.Split('.') ?? Enumerable.Empty<string>());
List<string> namespaceParts = [.. type.Namespace?.Split('.') ?? Enumerable.Empty<string>()];

int namespacePartsCount = namespaceParts.Count;
Type? declaringType = type.DeclaringType;
Expand Down
217 changes: 184 additions & 33 deletions src/NodeApi/Runtime/NativeLibrary.cs
Original file line number Diff line number Diff line change
@@ -1,13 +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;

Expand Down Expand Up @@ -39,15 +37,53 @@ public static nint GetMainProgramHandle()
/// <summary>
/// Loads a native library using default flags.
/// </summary>
/// <param name="libraryName">The name of the native library to be loaded.</param>
/// <param name="libraryPath">The name of the native library to be loaded.</param>
/// <returns>The OS handle for the loaded native library.</returns>
public static nint Load(string libraryName)
public static nint Load(string libraryPath)
{
#if NETFRAMEWORK || NETSTANDARD
return LoadLibrary(libraryName);
#else
return SysNativeLibrary.Load(libraryName);
#endif
return LoadFromPath(libraryPath, throwOnError: true);
}

/// <summary>
/// Provides a simple API for loading a native library and returns a value that indicates whether the operation succeeded.
/// </summary>
/// <param name="libraryPath">The name of the native library to be loaded.</param>
/// <param name="handle">When the method returns, the OS handle of the loaded native library.</param>
/// <returns><c>true</c> if the native library was loaded successfully; otherwise, <c>false</c>.</returns>
public static bool TryLoad(string libraryPath, out nint handle)
{
handle = LoadFromPath(libraryPath, throwOnError: false);
return handle != 0;
}

static nint LoadFromPath(string libraryPath, bool throwOnError)
{
if (libraryPath is null)
throw new ArgumentNullException(nameof(libraryPath));

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
nint handle = LoadLibrary(libraryPath);
if (handle == 0 && throwOnError)
throw new DllNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message);

return handle;
}
else
{
dlerror();
nint handle = dlopen(libraryPath, RTLD_LAZY);
nint error = dlerror();
if (error != 0)
{
if (throwOnError)
throw new DllNotFoundException(Marshal.PtrToStringAuto(error));

handle = 0;
}

return handle;
}
}

/// <summary>
Expand All @@ -58,53 +94,168 @@ public static nint Load(string libraryName)
/// <returns>The address of the symbol.</returns>
public static nint GetExport(nint handle, string name)
{
#if NETFRAMEWORK || NETSTANDARD
return GetProcAddress(handle, name);
#else
return SysNativeLibrary.GetExport(handle, name);
#endif
return GetSymbol(handle, name, throwOnError: true);
}

public static bool TryGetExport(nint handle, string name, out nint procAddress)
{
#if NETFRAMEWORK || NETSTANDARD
procAddress = GetProcAddress(handle, name);
return procAddress != default;
#else
return SysNativeLibrary.TryGetExport(handle, name, out procAddress);
#endif
procAddress = GetSymbol(handle, name, throwOnError: false);
return procAddress != 0;
}

static nint GetSymbol(nint handle, string name, bool throwOnError)
{
if (handle == 0)
throw new ArgumentNullException(nameof(handle));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
nint procAddress = GetProcAddress(handle, name);
if (procAddress == 0 && throwOnError)
throw new EntryPointNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message);

return procAddress;
}
else
{
dlerror();
nint procAddress = dlsym(handle, name);
nint error = dlerror();
if (error != 0)
{
if (throwOnError)
throw new EntryPointNotFoundException(Marshal.PtrToStringAuto(error));

procAddress = 0;
}

return procAddress;
}
}

#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments

[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 dlopen(nint fileName, int flags)
private delegate nint DlErrorDelegate();
private static DlErrorDelegate? s_dlerror;

private static nint dlerror()
{
// Some Linux distros / versions have libdl version 2 only.
// Mac OS only has the unversioned library.
// 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 dlopen2(fileName, flags);
return dlerror0();
}
catch (DllNotFoundException)
catch (EntryPointNotFoundException)
{
return dlopen1(fileName, flags);
try
{
return (s_dlerror = dlerror1)();
}
catch (DllNotFoundException)
{
return (s_dlerror = dlerror2)();
}
}
}

[DllImport("libdl", EntryPoint = "dlopen")]
private static extern nint dlopen1(nint fileName, int flags);
[DllImport("c", EntryPoint = "dlerror")]
private static extern nint dlerror0();

[DllImport("dl", EntryPoint = "dlerror")]
private static extern nint dlerror1();

[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 (EntryPointNotFoundException)
{
try
{
return (s_dlopen = dlopen1)(fileName, flags);
}
catch (DllNotFoundException)
{
return (s_dlopen = dlopen2)(fileName, flags);
}
}
}

[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")]
private static extern nint dlopen2(nint fileName, int flags);
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 (EntryPointNotFoundException)
{
try
{
return (s_dlsym = dlsym1)(handle, symbol);
}
catch (DllNotFoundException)
{
return (s_dlsym = dlsym2)(handle, symbol);
}
}
}

[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")]
private static extern nint dlsym2(nint handle, string symbol);

private const int RTLD_LAZY = 1;

Expand Down
Loading