Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 732f892

Browse files
Add Per-assembly Load Native Library callbacks (#21555)
Add Per-assembly Load Native Library callbacks This Change implements the Native Library resolution Call-backs proposed in https://github.com/dotnet/corefx/issues/32015
1 parent ec53bfd commit 732f892

File tree

15 files changed

+412
-70
lines changed

15 files changed

+412
-70
lines changed

src/System.Private.CoreLib/Resources/Strings.resx

+4-1
Original file line numberDiff line numberDiff line change
@@ -2428,7 +2428,10 @@
24282428
<data name="InvalidOperation_CannotImportGlobalFromDifferentModule" xml:space="preserve">
24292429
<value>Unable to import a global method or field from a different module.</value>
24302430
</data>
2431-
<data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
2431+
<data name="InvalidOperation_CannotRegisterSecondResolver" xml:space="preserve">
2432+
<value>A resolver is already set for the assembly.</value>
2433+
</data>
2434+
<data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
24322435
<value>Cannot remove the last element from an empty collection.</value>
24332436
</data>
24342437
<data name="InvalidOperation_CannotRestoreUnsupressedFlow" xml:space="preserve">

src/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.cs

+90-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,25 @@
99
using System.Runtime.ConstrainedExecution;
1010
using Win32Native = Microsoft.Win32.Win32Native;
1111
using System.Diagnostics;
12+
using System.Threading;
1213

1314
namespace System.Runtime.InteropServices
1415
{
16+
/// <summary>
17+
/// A delegate used to resolve native libraries via callback.
18+
/// </summary>
19+
/// <param name="libraryName">The native library to resolve</param>
20+
/// <param name="assembly">The assembly requesting the resolution</param>
21+
/// <param name="searchPath">
22+
/// The DllImportSearchPathsAttribute on the PInvoke, if any.
23+
/// Otherwise, the DllImportSearchPathsAttribute on the assembly, if any.
24+
/// Otherwise null.
25+
/// </param>
26+
/// <returns>The handle for the loaded native library on success, null on failure</returns>
27+
public delegate IntPtr DllImportResolver(string libraryName,
28+
Assembly assembly,
29+
DllImportSearchPath? searchPath);
30+
1531
/// <summary>
1632
/// APIs for managing Native Libraries
1733
/// </summary>
@@ -58,7 +74,9 @@ public static bool TryLoad(string libraryPath, out IntPtr handle)
5874
/// Otherwise, the flags specified by the DefaultDllImportSearchPaths attribute on the
5975
/// calling assembly (if any) are used.
6076
/// This LoadLibrary() method does not invoke the managed call-backs for native library resolution:
77+
/// * The per-assembly registered callback
6178
/// * AssemblyLoadContext.LoadUnmanagedDll()
79+
/// * AssemblyLoadContext.ResolvingUnmanagedDllEvent
6280
/// </summary>
6381
/// <param name="libraryName">The name of the native library to be loaded</param>
6482
/// <param name="assembly">The assembly loading the native library</param>
@@ -117,7 +135,6 @@ public static bool TryLoad(string libraryName, Assembly assembly, DllImportSearc
117135
/// No action if the input handle is null.
118136
/// </summary>
119137
/// <param name="handle">The native library handle to be freed</param>
120-
/// <exception cref="System.InvalidOperationException">If the operation fails</exception>
121138
public static void Free(IntPtr handle)
122139
{
123140
FreeLib(handle);
@@ -161,6 +178,78 @@ public static bool TryGetExport(IntPtr handle, string name, out IntPtr address)
161178
return address != IntPtr.Zero;
162179
}
163180

181+
/// <summary>
182+
/// Map from assembly to native-library resolver.
183+
/// Interop specific fields and properties are generally not added to Assembly class.
184+
/// Therefore, this table uses weak assembly pointers to indirectly achieve
185+
/// similar behavior.
186+
/// </summary>
187+
private static ConditionalWeakTable<Assembly, DllImportResolver> s_nativeDllResolveMap = null;
188+
189+
/// <summary>
190+
/// Set a callback for resolving native library imports from an assembly.
191+
/// This per-assembly resolver is the first attempt to resolve native library loads
192+
/// initiated by this assembly.
193+
///
194+
/// Only one resolver can be registered per assembly.
195+
/// Trying to register a second resolver fails with InvalidOperationException.
196+
/// </summary>
197+
/// <param name="assembly">The assembly for which the resolver is registered</param>
198+
/// <param name="resolver">The resolver callback to register</param>
199+
/// <exception cref="System.ArgumentNullException">If assembly or resolver is null</exception>
200+
/// <exception cref="System.ArgumentException">If a resolver is already set for this assembly</exception>
201+
public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
202+
{
203+
if (assembly == null)
204+
throw new ArgumentNullException(nameof(assembly));
205+
if (resolver == null)
206+
throw new ArgumentNullException(nameof(resolver));
207+
if (!(assembly is RuntimeAssembly))
208+
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
209+
210+
if (s_nativeDllResolveMap == null)
211+
{
212+
Interlocked.CompareExchange(ref s_nativeDllResolveMap,
213+
new ConditionalWeakTable<Assembly, DllImportResolver>(), null);
214+
}
215+
216+
try
217+
{
218+
s_nativeDllResolveMap.Add(assembly, resolver);
219+
}
220+
catch (ArgumentException)
221+
{
222+
// ConditionalWealTable throws ArgumentException if the Key already exists
223+
throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondResolver);
224+
}
225+
}
226+
227+
/// <summary>
228+
/// The helper function that calls the per-assembly native-library resolver
229+
/// if one is registered for this assembly.
230+
/// </summary>
231+
/// <param name="libraryName">The native library to load</param>
232+
/// <param name="assembly">The assembly trying load the native library</param>
233+
/// <param name="hasDllImportSearchPathFlags">If the pInvoke has DefaultDllImportSearchPathAttribute</param>
234+
/// <param name="dllImportSearchPathFlags">If hasdllImportSearchPathFlags is true, the flags in
235+
/// DefaultDllImportSearchPathAttribute; meaningless otherwise </param>
236+
/// <returns>The handle for the loaded library on success. Null on failure.</returns>
237+
internal static IntPtr LoadLibraryCallbackStub(string libraryName, Assembly assembly,
238+
bool hasDllImportSearchPathFlags, uint dllImportSearchPathFlags)
239+
{
240+
if (s_nativeDllResolveMap == null)
241+
{
242+
return IntPtr.Zero;
243+
}
244+
245+
if (!s_nativeDllResolveMap.TryGetValue(assembly, out DllImportResolver resolver))
246+
{
247+
return IntPtr.Zero;
248+
}
249+
250+
return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
251+
}
252+
164253
/// External functions that implement the NativeLibrary interface
165254

166255
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]

src/vm/callhelpers.h

+1
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ enum DispatchCallSimpleFlags
566566
#define STRINGREF_TO_ARGHOLDER(x) (LPVOID)STRINGREFToObject(x)
567567
#define PTR_TO_ARGHOLDER(x) (LPVOID)x
568568
#define DWORD_TO_ARGHOLDER(x) (LPVOID)(SIZE_T)x
569+
#define BOOL_TO_ARGHOLDER(x) DWORD_TO_ARGHOLDER(!!(x))
569570

570571
#define INIT_VARIABLES(count) \
571572
DWORD __numArgs = count; \

0 commit comments

Comments
 (0)