Skip to content

Commit f414408

Browse files
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 8ede2d4 commit f414408

File tree

14 files changed

+407
-66
lines changed

14 files changed

+407
-66
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

+91-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,26 @@
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+
17+
/// <summary>
18+
/// A delegate used to resolve native libraries via callback.
19+
/// </summary>
20+
/// <param name="libraryName">The native library to resolve</param>
21+
/// <param name="assembly">The assembly requesting the resolution</param>
22+
/// <param name="DllImportSearchPath?">
23+
/// The DllImportSearchPathsAttribute on the PInvoke, if any.
24+
/// Otherwise, the DllImportSearchPathsAttribute on the assembly, if any.
25+
/// Otherwise null.
26+
/// </param>
27+
/// <returns>The handle for the loaded native library on success, null on failure</returns>
28+
public delegate IntPtr DllImportResolver(string libraryName,
29+
Assembly assembly,
30+
DllImportSearchPath? searchPath);
31+
1532
/// <summary>
1633
/// APIs for managing Native Libraries
1734
/// </summary>
@@ -58,7 +75,9 @@ public static bool TryLoad(string libraryPath, out IntPtr handle)
5875
/// Otherwise, the flags specified by the DefaultDllImportSearchPaths attribute on the
5976
/// calling assembly (if any) are used.
6077
/// This LoadLibrary() method does not invoke the managed call-backs for native library resolution:
78+
/// * The per-assembly registered callback
6179
/// * AssemblyLoadContext.LoadUnmanagedDll()
80+
/// * AssemblyLoadContext.ResolvingUnmanagedDllEvent
6281
/// </summary>
6382
/// <param name="libraryName">The name of the native library to be loaded</param>
6483
/// <param name="assembly">The assembly loading the native library</param>
@@ -117,7 +136,6 @@ public static bool TryLoad(string libraryName, Assembly assembly, DllImportSearc
117136
/// No action if the input handle is null.
118137
/// </summary>
119138
/// <param name="handle">The native library handle to be freed</param>
120-
/// <exception cref="System.InvalidOperationException">If the operation fails</exception>
121139
public static void Free(IntPtr handle)
122140
{
123141
FreeLib(handle);
@@ -161,6 +179,78 @@ public static bool TryGetExport(IntPtr handle, string name, out IntPtr address)
161179
return address != IntPtr.Zero;
162180
}
163181

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

166256
[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)