Skip to content

Commit e99e1d6

Browse files
authored
LibNode Discovery (#425)
Thank you for submitting this PR!
1 parent bda132b commit e99e1d6

13 files changed

+294
-63
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
88
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" /><!-- 4.1.0 is compatible with .NET Standard -->
99
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
10-
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.202" />
10+
<PackageVersion Include="Microsoft.JavaScript.LibNode" Version="20.1800.203" />
1111
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
1212
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
1313
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.133" />

bench/Benchmarks.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ public static void Main(string[] args)
4444
.WithOptions(ConfigOptions.JoinSummary));
4545
}
4646

47-
private static string LibnodePath { get; } = Path.Combine(
48-
GetRepoRootDirectory(),
49-
"bin",
50-
GetCurrentPlatformRuntimeIdentifier(),
51-
"libnode" + GetSharedLibraryExtension());
52-
5347
private NodeEmbeddingRuntime? _runtime;
5448
private NodeEmbeddingNodeApiScope? _nodeApiScope;
5549
private JSValue _jsString;
@@ -89,7 +83,6 @@ public static void Method() { }
8983
protected void Setup()
9084
{
9185
NodeEmbeddingPlatform platform = new(
92-
LibnodePath,
9386
new NodeEmbeddingPlatformSettings { Args = s_settings });
9487

9588
// This setup avoids using NodejsEmbeddingThreadRuntime so benchmarks can run on

src/NodeApi.DotNetHost/JSMarshaller.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ public Expression<JSCallback> BuildFromJSConstructorExpression(ConstructorInfo c
475475

476476
ParameterExpression resultVariable = Expression.Variable(
477477
constructor.DeclaringType!, "__result");
478-
variables = new List<ParameterExpression>(argVariables.Append(resultVariable));
478+
variables = [.. argVariables.Append(resultVariable)];
479479
statements.Add(Expression.Assign(resultVariable,
480480
Expression.New(constructor, argVariables)));
481481

src/NodeApi.Generator/ExpressionExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,8 @@ private static string ToCS(
5959
(variables is null ? FormatType(lambda.ReturnType) + " " + lambda.Name + "(" +
6060
string.Join(", ", lambda.Parameters.Select((p) => p.ToCS())) + ")\n" :
6161
"(" + string.Join(", ", lambda.Parameters.Select((p) => p.ToCS())) + ") =>\n") +
62-
ToCS(lambda.Body, path, new HashSet<string>(
63-
(variables ?? Enumerable.Empty<string>()).Union(
64-
lambda.Parameters.Select((p) => p.Name!)))),
62+
ToCS(lambda.Body, path, [.. (variables ?? Enumerable.Empty<string>()).Union(
63+
lambda.Parameters.Select((p) => p.Name!))]),
6564

6665
ParameterExpression parameter =>
6766
(parameter.IsByRef && parameter.Name?.StartsWith(OutParameterPrefix) == true) ?
@@ -285,7 +284,7 @@ private static string FormatStatement(
285284
if (assignment.Left is ParameterExpression variable &&
286285
!variables.Contains(variable.Name!))
287286
{
288-
variables = new HashSet<string>(variables.Union(new[] { variable.Name! }));
287+
variables = [.. variables.Union(new[] { variable.Name! })];
289288
s += FormatType(variable.Type) + " " + s;
290289
}
291290
}

src/NodeApi.Generator/TypeDefinitionsGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ private static IEnumerable<string> MergeSystemReferenceAssemblies(
333333

334334
private static Version InferReferenceAssemblyVersionFromPath(string assemblyPath)
335335
{
336-
var pathParts = assemblyPath.Split(
336+
List<string> pathParts = assemblyPath.Split(
337337
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).ToList();
338338

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

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

12351235
int namespacePartsCount = namespaceParts.Count;
12361236
Type? declaringType = type.DeclaringType;

src/NodeApi/Runtime/NativeLibrary.cs

Lines changed: 184 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
#if !NET7_0_OR_GREATER
4+
#if !NETCOREAPP3_0_OR_GREATER
55

66
using System;
7+
using System.ComponentModel;
78
using System.Runtime.InteropServices;
8-
#if !(NETFRAMEWORK || NETSTANDARD)
9-
using SysNativeLibrary = System.Runtime.InteropServices.NativeLibrary;
10-
#endif
119

1210
namespace Microsoft.JavaScript.NodeApi.Runtime;
1311

@@ -39,15 +37,53 @@ public static nint GetMainProgramHandle()
3937
/// <summary>
4038
/// Loads a native library using default flags.
4139
/// </summary>
42-
/// <param name="libraryName">The name of the native library to be loaded.</param>
40+
/// <param name="libraryPath">The name of the native library to be loaded.</param>
4341
/// <returns>The OS handle for the loaded native library.</returns>
44-
public static nint Load(string libraryName)
42+
public static nint Load(string libraryPath)
4543
{
46-
#if NETFRAMEWORK || NETSTANDARD
47-
return LoadLibrary(libraryName);
48-
#else
49-
return SysNativeLibrary.Load(libraryName);
50-
#endif
44+
return LoadFromPath(libraryPath, throwOnError: true);
45+
}
46+
47+
/// <summary>
48+
/// Provides a simple API for loading a native library and returns a value that indicates whether the operation succeeded.
49+
/// </summary>
50+
/// <param name="libraryPath">The name of the native library to be loaded.</param>
51+
/// <param name="handle">When the method returns, the OS handle of the loaded native library.</param>
52+
/// <returns><c>true</c> if the native library was loaded successfully; otherwise, <c>false</c>.</returns>
53+
public static bool TryLoad(string libraryPath, out nint handle)
54+
{
55+
handle = LoadFromPath(libraryPath, throwOnError: false);
56+
return handle != 0;
57+
}
58+
59+
static nint LoadFromPath(string libraryPath, bool throwOnError)
60+
{
61+
if (libraryPath is null)
62+
throw new ArgumentNullException(nameof(libraryPath));
63+
64+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
65+
{
66+
nint handle = LoadLibrary(libraryPath);
67+
if (handle == 0 && throwOnError)
68+
throw new DllNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message);
69+
70+
return handle;
71+
}
72+
else
73+
{
74+
dlerror();
75+
nint handle = dlopen(libraryPath, RTLD_LAZY);
76+
nint error = dlerror();
77+
if (error != 0)
78+
{
79+
if (throwOnError)
80+
throw new DllNotFoundException(Marshal.PtrToStringAuto(error));
81+
82+
handle = 0;
83+
}
84+
85+
return handle;
86+
}
5187
}
5288

5389
/// <summary>
@@ -58,53 +94,168 @@ public static nint Load(string libraryName)
5894
/// <returns>The address of the symbol.</returns>
5995
public static nint GetExport(nint handle, string name)
6096
{
61-
#if NETFRAMEWORK || NETSTANDARD
62-
return GetProcAddress(handle, name);
63-
#else
64-
return SysNativeLibrary.GetExport(handle, name);
65-
#endif
97+
return GetSymbol(handle, name, throwOnError: true);
6698
}
6799

68100
public static bool TryGetExport(nint handle, string name, out nint procAddress)
69101
{
70-
#if NETFRAMEWORK || NETSTANDARD
71-
procAddress = GetProcAddress(handle, name);
72-
return procAddress != default;
73-
#else
74-
return SysNativeLibrary.TryGetExport(handle, name, out procAddress);
75-
#endif
102+
procAddress = GetSymbol(handle, name, throwOnError: false);
103+
return procAddress != 0;
104+
}
105+
106+
static nint GetSymbol(nint handle, string name, bool throwOnError)
107+
{
108+
if (handle == 0)
109+
throw new ArgumentNullException(nameof(handle));
110+
if (string.IsNullOrEmpty(name))
111+
throw new ArgumentNullException(nameof(name));
112+
113+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
114+
{
115+
nint procAddress = GetProcAddress(handle, name);
116+
if (procAddress == 0 && throwOnError)
117+
throw new EntryPointNotFoundException(new Win32Exception(Marshal.GetLastWin32Error()).Message);
118+
119+
return procAddress;
120+
}
121+
else
122+
{
123+
dlerror();
124+
nint procAddress = dlsym(handle, name);
125+
nint error = dlerror();
126+
if (error != 0)
127+
{
128+
if (throwOnError)
129+
throw new EntryPointNotFoundException(Marshal.PtrToStringAuto(error));
130+
131+
procAddress = 0;
132+
}
133+
134+
return procAddress;
135+
}
76136
}
77137

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

80140
[DllImport("kernel32")]
81141
private static extern nint GetModuleHandle(string? moduleName);
82142

83-
[DllImport("kernel32")]
143+
[DllImport("kernel32", SetLastError = true)]
84144
private static extern nint LoadLibrary(string moduleName);
85145

86-
[DllImport("kernel32")]
146+
[DllImport("kernel32", SetLastError = true)]
87147
private static extern nint GetProcAddress(nint hModule, string procName);
88148

89-
private static nint dlopen(nint fileName, int flags)
149+
private delegate nint DlErrorDelegate();
150+
private static DlErrorDelegate? s_dlerror;
151+
152+
private static nint dlerror()
90153
{
91-
// Some Linux distros / versions have libdl version 2 only.
92-
// Mac OS only has the unversioned library.
154+
// cache dlerror function
155+
if (s_dlerror is not null)
156+
return s_dlerror();
157+
158+
// some operating systems have dlerror in libc, some in libdl, some in libdl.so.2
159+
// attempt in that order
93160
try
94161
{
95-
return dlopen2(fileName, flags);
162+
return dlerror0();
96163
}
97-
catch (DllNotFoundException)
164+
catch (EntryPointNotFoundException)
98165
{
99-
return dlopen1(fileName, flags);
166+
try
167+
{
168+
return (s_dlerror = dlerror1)();
169+
}
170+
catch (DllNotFoundException)
171+
{
172+
return (s_dlerror = dlerror2)();
173+
}
100174
}
101175
}
102176

103-
[DllImport("libdl", EntryPoint = "dlopen")]
104-
private static extern nint dlopen1(nint fileName, int flags);
177+
[DllImport("c", EntryPoint = "dlerror")]
178+
private static extern nint dlerror0();
179+
180+
[DllImport("dl", EntryPoint = "dlerror")]
181+
private static extern nint dlerror1();
182+
183+
[DllImport("libdl.so.2", EntryPoint = "dlerror")]
184+
private static extern nint dlerror2();
185+
186+
private delegate nint DlOpenDelegate(string? fileName, int flags);
187+
private static DlOpenDelegate? s_dlopen;
188+
189+
private static nint dlopen(string? fileName, int flags)
190+
{
191+
// cache dlopen function
192+
if (s_dlopen is not null)
193+
return s_dlopen(fileName, flags);
194+
195+
// some operating systems have dlopen in libc, some in libdl, some in libdl.so.2
196+
// attempt in that order
197+
try
198+
{
199+
return dlopen0(fileName, flags);
200+
}
201+
catch (EntryPointNotFoundException)
202+
{
203+
try
204+
{
205+
return (s_dlopen = dlopen1)(fileName, flags);
206+
}
207+
catch (DllNotFoundException)
208+
{
209+
return (s_dlopen = dlopen2)(fileName, flags);
210+
}
211+
}
212+
}
213+
214+
[DllImport("c", EntryPoint = "dlopen")]
215+
private static extern nint dlopen0(string? fileName, int flags);
216+
217+
[DllImport("dl", EntryPoint = "dlopen")]
218+
private static extern nint dlopen1(string? fileName, int flags);
105219

106220
[DllImport("libdl.so.2", EntryPoint = "dlopen")]
107-
private static extern nint dlopen2(nint fileName, int flags);
221+
private static extern nint dlopen2(string? fileName, int flags);
222+
223+
private delegate nint DlSymDelegate(nint handle, string symbol);
224+
private static DlSymDelegate? s_dlsym;
225+
226+
private static nint dlsym(nint handle, string symbol)
227+
{
228+
// cache dlsym function
229+
if (s_dlsym is not null)
230+
return s_dlsym(handle, symbol);
231+
232+
// some operating systems have dlsym in libc, some in libdl, some in libdl.so.2
233+
// attempt in that order
234+
try
235+
{
236+
return dlsym0(handle, symbol);
237+
}
238+
catch (EntryPointNotFoundException)
239+
{
240+
try
241+
{
242+
return (s_dlsym = dlsym1)(handle, symbol);
243+
}
244+
catch (DllNotFoundException)
245+
{
246+
return (s_dlsym = dlsym2)(handle, symbol);
247+
}
248+
}
249+
}
250+
251+
[DllImport("c", EntryPoint = "dlsym")]
252+
private static extern nint dlsym0(nint handle, string symbol);
253+
254+
[DllImport("dl", EntryPoint = "dlsym")]
255+
private static extern nint dlsym1(nint handle, string symbol);
256+
257+
[DllImport("libdl.so.2", EntryPoint = "dlsym")]
258+
private static extern nint dlsym2(nint handle, string symbol);
108259

109260
private const int RTLD_LAZY = 1;
110261

0 commit comments

Comments
 (0)