Skip to content

Commit 82c8738

Browse files
swaroop-sridharswaroop-sridhar
swaroop-sridhar
authored andcommitted
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 60557a9 commit 82c8738

File tree

13 files changed

+319
-133
lines changed

13 files changed

+319
-133
lines changed

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

+86-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212

1313
namespace System.Runtime.InteropServices
1414
{
15+
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="DllImportSearchPath?">
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,74 @@ 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+
public 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+
s_nativeDllResolveMap = new ConditionalWeakTable<Assembly, DllImportResolver>();
213+
}
214+
215+
try
216+
{
217+
s_nativeDllResolveMap.Add(assembly, resolver);
218+
}
219+
catch (ArgumentException e)
220+
{
221+
// ConditionalWealTable throws ArgumentException if the Key already exists
222+
throw new InvalidOperationException("Resolver is alerady Set for the Assembly");
223+
}
224+
}
225+
226+
/// <summary>
227+
/// The helper function that calls the per-assembly native-library resolver
228+
/// if one is registered for this assembly.
229+
/// </summary>
230+
/// <param name="libraryName">The native library to load</param>
231+
/// <param name="assembly">The assembly trying load the native library</param>
232+
/// <param name="hasDllImportSearchPathFlags">If the pInvoke has DefaultDllImportSearchPathAttribute</param>
233+
/// <param name="dllImportSearchPathFlags">If hasdllImportSearchPathFlags is true, the flags in
234+
/// DefaultDllImportSearchPathAttribute; meaningless otherwise </param>
235+
/// <returns>The handle for the loaded library on success. Null on failure.</returns>
236+
internal static IntPtr LoadLibraryCallbackStub(string libraryName, Assembly assembly,
237+
bool hasDllImportSearchPathFlags, uint dllImportSearchPathFlags)
238+
{
239+
DllImportResolver resolver;
240+
241+
if (!s_nativeDllResolveMap.TryGetValue(assembly, out resolver))
242+
{
243+
return IntPtr.Zero;
244+
}
245+
246+
return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
247+
}
248+
164249
/// External functions that implement the NativeLibrary interface
165250

166251
[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; \

src/vm/dllimport.cpp

+72-125
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@
5151
#include "clr/fs/path.h"
5252
using namespace clr::fs;
5353

54-
// The Bit 0x2 has different semantics in DllImportSearchPath and LoadLibraryExA flags.
55-
// In DllImportSearchPath enum, bit 0x2 represents SearchAssemblyDirectory -- which is performed by CLR.
56-
// Unlike other bits in this enum, this bit shouldn't be directly passed on to LoadLibrary()
57-
#define DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY 0x2
58-
5954
// remove when we get an updated SDK
6055
#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100
6156
#define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
@@ -6127,6 +6122,57 @@ bool NDirect::s_fSecureLoadLibrarySupported = false;
61276122
#define PLATFORM_SHARED_LIB_PREFIX_W W("")
61286123
#endif // !FEATURE_PAL
61296124

6125+
// The Bit 0x2 has different semantics in DllImportSearchPath and LoadLibraryExA flags.
6126+
// In DllImportSearchPath enum, bit 0x2 represents SearchAssemblyDirectory -- which is performed by CLR.
6127+
// Unlike other bits in this enum, this bit shouldn't be directly passed on to LoadLibrary()
6128+
#define DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY 0x2
6129+
6130+
// DllImportSearchPathFlags is a special enumeration, whose values are tied closely with LoadLibrary flags.
6131+
// There is no "default" value DllImportSearchPathFlags. In the absence of DllImportSearchPath attribute,
6132+
// CoreCLR's LoadLibrary implementation uses the following defaults.
6133+
// Other implementations of LoadLibrary callbacks/events are free to use other default conventions.
6134+
void GetDefaultDllImportSearchPathFlags(DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
6135+
{
6136+
STANDARD_VM_CONTRACT;
6137+
6138+
*searchAssemblyDirectory = TRUE;
6139+
*dllImportSearchPathFlags = 0;
6140+
}
6141+
6142+
// If a module has the DllImportSearchPathAttribute, get DllImportSearchPathFlags from it, and return true.
6143+
// Otherwise, get the default value for the flags, and return false.
6144+
BOOL GetDllImportSearchPathFlags(Module *pModule, DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
6145+
{
6146+
STANDARD_VM_CONTRACT;
6147+
6148+
if (pModule->HasDefaultDllImportSearchPathsAttribute())
6149+
{
6150+
*dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
6151+
*searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
6152+
return true;
6153+
}
6154+
6155+
GetDefaultDllImportSearchPathFlags(dllImportSearchPathFlags, searchAssemblyDirectory);
6156+
return false;
6157+
}
6158+
6159+
// If a pInvoke has DllImportSearchPathAttribute, get DllImportSearchPathFlags from it, and returns true.
6160+
// Otherwise, if the containing assembly has the DllImportSearchPathAttribute, get DllImportSearchPathFlags from it, and returns true.
6161+
// Otherwise, return false (out parameters are untouched).
6162+
BOOL GetDllImportSearchPathFlags(NDirectMethodDesc * pMD, DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
6163+
{
6164+
STANDARD_VM_CONTRACT;
6165+
6166+
if (pMD->HasDefaultDllImportSearchPathsAttribute())
6167+
{
6168+
*dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
6169+
*searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
6170+
return true;
6171+
}
6172+
6173+
return GetDllImportSearchPathFlags(pMD->GetModule(), dllImportSearchPathFlags, searchAssemblyDirectory);
6174+
}
6175+
61306176
// static
61316177
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryFromPath(LPCWSTR libraryPath, BOOL throwOnError)
61326178
{
@@ -6165,25 +6211,21 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *
61656211
LoadLibErrorTracker errorTracker;
61666212

61676213
// First checks if a default dllImportSearchPathFlags was passed in, if so, use that value.
6168-
// Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute. If so, use that value.
6169-
BOOL searchAssemblyDirectory = TRUE;
6170-
DWORD dllImportSearchPathFlags = 0;
6214+
// Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute.
6215+
// If so, use that value.
6216+
BOOL searchAssemblyDirectory;
6217+
DWORD dllImportSearchPathFlags;
61716218

61726219
if (hasDllImportSearchFlags)
61736220
{
61746221
dllImportSearchPathFlags = dllImportSearchFlags & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
61756222
searchAssemblyDirectory = dllImportSearchFlags & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
61766223

61776224
}
6178-
else
6225+
else
61796226
{
6180-
Module * pModule = callingAssembly->GetManifestModule();
6181-
6182-
if (pModule->HasDefaultDllImportSearchPathsAttribute())
6183-
{
6184-
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
6185-
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
6186-
}
6227+
GetDllImportSearchPathFlags(callingAssembly->GetManifestModule(),
6228+
&dllImportSearchPathFlags, &searchAssemblyDirectory);
61876229
}
61886230

61896231
NATIVE_LIBRARY_HANDLE hmod =
@@ -6203,26 +6245,10 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD
62036245
{
62046246
STANDARD_VM_CONTRACT;
62056247

6206-
// First checks if the method has DefaultDllImportSearchPathsAttribute. If so, use that value.
6207-
// Otherwise checks if the assembly has the attribute. If so, use that value.
6208-
BOOL searchAssemblyDirectory = TRUE;
6209-
DWORD dllImportSearchPathFlags = 0;
6248+
BOOL searchAssemblyDirectory;
6249+
DWORD dllImportSearchPathFlags;
62106250

6211-
if (pMD->HasDefaultDllImportSearchPathsAttribute())
6212-
{
6213-
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
6214-
searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
6215-
}
6216-
else
6217-
{
6218-
Module * pModule = pMD->GetModule();
6219-
6220-
if (pModule->HasDefaultDllImportSearchPathsAttribute())
6221-
{
6222-
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
6223-
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
6224-
}
6225-
}
6251+
GetDllImportSearchPathFlags(pMD, &dllImportSearchPathFlags, &searchAssemblyDirectory);
62266252

62276253
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
62286254
return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, pErrorTracker, wszLibName);
@@ -6452,18 +6478,20 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc *
64526478
{
64536479
STANDARD_VM_CONTRACT;
64546480

6455-
NATIVE_LIBRARY_HANDLE handle = NULL;
6456-
6457-
DWORD dllImportSearchPathFlags = 0;
6458-
BOOL hasDllImportSearchPathFlags = pMD->HasDefaultDllImportSearchPathsAttribute();
6459-
if (hasDllImportSearchPathFlags)
6481+
if (pMD->GetModule()->IsSystem())
64606482
{
6461-
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
6462-
if (pMD->DllImportSearchAssemblyDirectory())
6463-
dllImportSearchPathFlags |= DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
6483+
// Don't attempt to callback on Corelib itself.
6484+
// The LoadLibrary callback stub is managed code that requires CoreLib
6485+
return NULL;
64646486
}
64656487

6488+
DWORD dllImportSearchPathFlags;
6489+
BOOL searchAssemblyDirectory;
6490+
BOOL hasDllImportSearchPathFlags = GetDllImportSearchPathFlags(pMD, &dllImportSearchPathFlags, &searchAssemblyDirectory);
6491+
dllImportSearchPathFlags |= searchAssemblyDirectory ? DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY : 0;
6492+
64666493
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
6494+
NATIVE_LIBRARY_HANDLE handle = NULL;
64676495

64686496
GCX_COOP();
64696497

@@ -6491,87 +6519,6 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc *
64916519
return handle;
64926520
}
64936521

6494-
// Return the AssemblyLoadContext for an assembly
6495-
INT_PTR GetManagedAssemblyLoadContext(Assembly* pAssembly)
6496-
{
6497-
STANDARD_VM_CONTRACT;
6498-
6499-
PTR_ICLRPrivBinder pBindingContext = pAssembly->GetManifestFile()->GetBindingContext();
6500-
if (pBindingContext == NULL)
6501-
{
6502-
// GetBindingContext() returns NULL for System.Private.CoreLib
6503-
return NULL;
6504-
}
6505-
6506-
UINT_PTR assemblyBinderID = 0;
6507-
IfFailThrow(pBindingContext->GetBinderID(&assemblyBinderID));
6508-
6509-
AppDomain *pDomain = GetAppDomain();
6510-
ICLRPrivBinder *pCurrentBinder = reinterpret_cast<ICLRPrivBinder *>(assemblyBinderID);
6511-
6512-
#ifdef FEATURE_COMINTEROP
6513-
if (AreSameBinderInstance(pCurrentBinder, pDomain->GetWinRtBinder()))
6514-
{
6515-
// No ALC associated handle with WinRT Binders.
6516-
return NULL;
6517-
}
6518-
#endif // FEATURE_COMINTEROP
6519-
6520-
// The code here deals with two implementations of ICLRPrivBinder interface:
6521-
// - CLRPrivBinderCoreCLR for the TPA binder in the default ALC, and
6522-
// - CLRPrivBinderAssemblyLoadContext for custom ALCs.
6523-
// in order obtain the associated ALC handle.
6524-
INT_PTR ptrManagedAssemblyLoadContext = AreSameBinderInstance(pCurrentBinder, pDomain->GetTPABinderContext())
6525-
? ((CLRPrivBinderCoreCLR *)pCurrentBinder)->GetManagedAssemblyLoadContext()
6526-
: ((CLRPrivBinderAssemblyLoadContext *)pCurrentBinder)->GetManagedAssemblyLoadContext();
6527-
6528-
return ptrManagedAssemblyLoadContext;
6529-
}
6530-
6531-
// static
6532-
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, PCWSTR wszLibName)
6533-
{
6534-
STANDARD_VM_CONTRACT;
6535-
6536-
NATIVE_LIBRARY_HANDLE hmod = NULL;
6537-
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
6538-
INT_PTR ptrManagedAssemblyLoadContext = GetManagedAssemblyLoadContext(pAssembly);
6539-
6540-
if (ptrManagedAssemblyLoadContext == NULL)
6541-
{
6542-
return NULL;
6543-
}
6544-
6545-
GCX_COOP();
6546-
6547-
struct {
6548-
STRINGREF DllName;
6549-
OBJECTREF AssemblyRef;
6550-
} gc = { NULL, NULL };
6551-
6552-
GCPROTECT_BEGIN(gc);
6553-
6554-
gc.DllName = StringObject::NewString(wszLibName);
6555-
gc.AssemblyRef = pAssembly->GetExposedObject();
6556-
6557-
// Prepare to invoke System.Runtime.Loader.AssemblyLoadContext.ResolveUnmanagedDllUsingEvent method
6558-
// While ResolveUnmanagedDllUsingEvent() could compute the AssemblyLoadContext using the AssemblyRef
6559-
// argument, it will involve another pInvoke to the runtime. So AssemblyLoadContext is passed in
6560-
// as an additional argument.
6561-
PREPARE_NONVIRTUAL_CALLSITE(METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUNMANAGEDDLLUSINGEVENT);
6562-
DECLARE_ARGHOLDER_ARRAY(args, 3);
6563-
args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.DllName);
6564-
args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.AssemblyRef);
6565-
args[ARGNUM_2] = PTR_TO_ARGHOLDER(ptrManagedAssemblyLoadContext);
6566-
6567-
// Make the call
6568-
CALL_MANAGED_METHOD(hmod, NATIVE_LIBRARY_HANDLE, args);
6569-
6570-
GCPROTECT_END();
6571-
6572-
return hmod;
6573-
}
6574-
65756522
// Try to load the module alongside the assembly where the PInvoke was declared.
65766523
NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
65776524
{
@@ -6842,7 +6789,7 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
68426789
if ( !name || !*name )
68436790
return NULL;
68446791

6845-
PREFIX_ASSUME( name != NULL );
6792+
PREFIX_ASSUME( name != NULL );
68466793
MAKE_WIDEPTR_FROMUTF8( wszLibName, name );
68476794

68486795
ModuleHandleHolder hmod = LoadLibraryModuleViaCallback(pMD, wszLibName);

0 commit comments

Comments
 (0)