Skip to content

Commit c041582

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 678c6dd commit c041582

File tree

13 files changed

+337
-34
lines changed

13 files changed

+337
-34
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-resolution-callback.
183+
/// Generally interop specific fields and properties are not added to assembly.
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 callback is the first attempt to resolve native library loads
192+
/// initiated by this assembly.
193+
///
194+
/// Only one resolver callback can be registered per assembly.
195+
/// Trying to register a second callback fails with InvalidOperationException.
196+
/// </summary>
197+
/// <param name="assembly">The assembly for which the callback is registered</param>
198+
/// <param name="resolver">The callback to register</param>
199+
/// <exception cref="System.ArgumentNullException">If assembly or resolver is null</exception>
200+
/// <exception cref="System.InvalidOperationException">If a callback 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

+88-26
Original file line numberDiff line numberDiff line change
@@ -6151,7 +6151,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryFromPath(LPCWSTR libraryPath, BOOL thr
61516151

61526152
// static
61536153
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *callingAssembly,
6154-
BOOL hasDllImportSearchFlag, DWORD dllImportSearchFlag,
6154+
BOOL hasDllImportSearchFlags, DWORD dllImportSearchFlags,
61556155
BOOL throwOnError)
61566156
{
61576157
CONTRACTL
@@ -6164,15 +6164,15 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *
61646164

61656165
LoadLibErrorTracker errorTracker;
61666166

6167-
// First checks if a default DllImportSearchPathFlag was passed in, if so, use that value.
6167+
// First checks if a default dllImportSearchPathFlags was passed in, if so, use that value.
61686168
// Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute. If so, use that value.
61696169
BOOL searchAssemblyDirectory = TRUE;
6170-
DWORD dllImportSearchPathFlag = 0;
6170+
DWORD dllImportSearchPathFlags = 0;
61716171

6172-
if (hasDllImportSearchFlag)
6172+
if (hasDllImportSearchFlags)
61736173
{
6174-
dllImportSearchPathFlag = dllImportSearchFlag & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
6175-
searchAssemblyDirectory = dllImportSearchFlag & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
6174+
dllImportSearchPathFlags = dllImportSearchFlags & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
6175+
searchAssemblyDirectory = dllImportSearchFlags & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
61766176

61776177
}
61786178
else
@@ -6181,13 +6181,13 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *
61816181

61826182
if (pModule->HasDefaultDllImportSearchPathsAttribute())
61836183
{
6184-
dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
6184+
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
61856185
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
61866186
}
61876187
}
61886188

61896189
NATIVE_LIBRARY_HANDLE hmod =
6190-
LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, &errorTracker, libraryName);
6190+
LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, &errorTracker, libraryName);
61916191

61926192
if (throwOnError && (hmod == nullptr))
61936193
{
@@ -6206,11 +6206,11 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD
62066206
// First checks if the method has DefaultDllImportSearchPathsAttribute. If so, use that value.
62076207
// Otherwise checks if the assembly has the attribute. If so, use that value.
62086208
BOOL searchAssemblyDirectory = TRUE;
6209-
DWORD dllImportSearchPathFlag = 0;
6209+
DWORD dllImportSearchPathFlags = 0;
62106210

62116211
if (pMD->HasDefaultDllImportSearchPathsAttribute())
62126212
{
6213-
dllImportSearchPathFlag = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
6213+
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
62146214
searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
62156215
}
62166216
else
@@ -6219,13 +6219,13 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD
62196219

62206220
if (pModule->HasDefaultDllImportSearchPathsAttribute())
62216221
{
6222-
dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
6222+
dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
62236223
searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
62246224
}
62256225
}
62266226

62276227
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
6228-
return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, pErrorTracker, wszLibName);
6228+
return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, pErrorTracker, wszLibName);
62296229
}
62306230

62316231
// static
@@ -6274,6 +6274,17 @@ INT_PTR NDirect::GetNativeLibraryExport(NATIVE_LIBRARY_HANDLE handle, LPCWSTR sy
62746274
return address;
62756275
}
62766276

6277+
#ifndef PLATFORM_UNIX
6278+
BOOL IsWindowsAPISet(PCWSTR wszLibName)
6279+
{
6280+
STANDARD_VM_CONTRACT;
6281+
6282+
// This is replicating quick check from the OS implementation of api sets.
6283+
return SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 ||
6284+
SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0;
6285+
}
6286+
#endif // !PLATFORM_UNIX
6287+
62776288
// static
62786289
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, PCWSTR wszLibName)
62796290
{
@@ -6282,13 +6293,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
62826293
//Check if we need to provide the host a chance to provide the unmanaged dll
62836294

62846295
#ifndef PLATFORM_UNIX
6285-
// Prevent Overriding of Windows API sets.
6286-
// This is replicating quick check from the OS implementation of api sets.
6287-
if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
6296+
if (IsWindowsAPISet(wszLibName))
62886297
{
6298+
// Prevent Overriding of Windows API sets.
62896299
return NULL;
62906300
}
6291-
#endif
6301+
#endif // !PLATFORM_UNIX
62926302

62936303
NATIVE_LIBRARY_HANDLE hmod = NULL;
62946304
AppDomain* pDomain = GetAppDomain();
@@ -6438,6 +6448,49 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD
64386448
return hmod;
64396449
}
64406450

6451+
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc * pMD, LPCWSTR wszLibName)
6452+
{
6453+
STANDARD_VM_CONTRACT;
6454+
6455+
NATIVE_LIBRARY_HANDLE handle = NULL;
6456+
6457+
DWORD dllImportSearchPathFlags = 0;
6458+
BOOL hasDllImportSearchPathFlags = pMD->HasDefaultDllImportSearchPathsAttribute();
6459+
if (hasDllImportSearchPathFlags)
6460+
{
6461+
dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
6462+
if (pMD->DllImportSearchAssemblyDirectory())
6463+
dllImportSearchPathFlags |= DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
6464+
}
6465+
6466+
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
6467+
6468+
GCX_COOP();
6469+
6470+
struct {
6471+
STRINGREF libNameRef;
6472+
OBJECTREF assemblyRef;
6473+
} gc = { NULL, NULL };
6474+
6475+
GCPROTECT_BEGIN(gc);
6476+
6477+
gc.libNameRef = StringObject::NewString(wszLibName);
6478+
gc.assemblyRef = pAssembly->GetExposedObject();
6479+
6480+
PREPARE_NONVIRTUAL_CALLSITE(METHOD__NATIVELIBRARY__LOADLIBRARYCALLBACKSTUB);
6481+
DECLARE_ARGHOLDER_ARRAY(args, 4);
6482+
args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.libNameRef);
6483+
args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.assemblyRef);
6484+
args[ARGNUM_2] = BOOL_TO_ARGHOLDER(hasDllImportSearchPathFlags);
6485+
args[ARGNUM_3] = DWORD_TO_ARGHOLDER(dllImportSearchPathFlags);
6486+
6487+
// Make the call
6488+
CALL_MANAGED_METHOD(handle, NATIVE_LIBRARY_HANDLE, args);
6489+
GCPROTECT_END();
6490+
6491+
return handle;
6492+
}
6493+
64416494
// Try to load the module alongside the assembly where the PInvoke was declared.
64426495
NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
64436496
{
@@ -6461,11 +6514,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssem
64616514
}
64626515

64636516
// Try to load the module from the native DLL search directories
6464-
NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
6517+
NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
64656518
{
64666519
STANDARD_VM_CONTRACT;
64676520

64686521
NATIVE_LIBRARY_HANDLE hmod = NULL;
6522+
AppDomain* pDomain = GetAppDomain();
64696523

64706524
if (pDomain->HasNativeDllSearchDirectories())
64716525
{
@@ -6587,7 +6641,7 @@ static void DetermineLibNameVariations(const WCHAR** libNameVariations, int* num
65876641
// Search for the library and variants of its name in probing directories.
65886642
//static
65896643
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssembly,
6590-
BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag,
6644+
BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlags,
65916645
LoadLibErrorTracker * pErrorTracker, LPCWSTR wszLibName)
65926646
{
65936647
STANDARD_VM_CONTRACT;
@@ -6597,7 +6651,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
65976651
#if defined(FEATURE_CORESYSTEM) && !defined(PLATFORM_UNIX)
65986652
// Try to go straight to System32 for Windows API sets. This is replicating quick check from
65996653
// the OS implementation of api sets.
6600-
if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
6654+
if (IsWindowsAPISet(wszLibName))
66016655
{
66026656
hmod = LocalLoadLibraryHelper(wszLibName, LOAD_LIBRARY_SEARCH_SYSTEM32, pErrorTracker);
66036657
if (hmod != NULL)
@@ -6625,7 +6679,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
66256679
currLibNameVariation.Printf(prefixSuffixCombinations[i], PLATFORM_SHARED_LIB_PREFIX_W, wszLibName, PLATFORM_SHARED_LIB_SUFFIX_W);
66266680

66276681
// NATIVE_DLL_SEARCH_DIRECTORIES set by host is considered well known path
6628-
hmod = LoadFromNativeDllSearchDirectories(pDomain, currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
6682+
hmod = LoadFromNativeDllSearchDirectories(currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
66296683
if (hmod != NULL)
66306684
{
66316685
return hmod;
@@ -6634,11 +6688,11 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
66346688
if (!libNameIsRelativePath)
66356689
{
66366690
DWORD flags = loadWithAlteredPathFlags;
6637-
if ((dllImportSearchPathFlag & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
6691+
if ((dllImportSearchPathFlags & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
66386692
{
66396693
// LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR is the only flag affecting absolute path. Don't OR the flags
66406694
// unconditionally as all absolute path P/Invokes could then lose LOAD_WITH_ALTERED_SEARCH_PATH.
6641-
flags |= dllImportSearchPathFlag;
6695+
flags |= dllImportSearchPathFlags;
66426696
}
66436697

66446698
hmod = LocalLoadLibraryHelper(currLibNameVariation, flags, pErrorTracker);
@@ -6649,14 +6703,14 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
66496703
}
66506704
else if ((callingAssembly != nullptr) && searchAssemblyDirectory)
66516705
{
6652-
hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
6706+
hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
66536707
if (hmod != NULL)
66546708
{
66556709
return hmod;
66566710
}
66576711
}
66586712

6659-
hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlag, pErrorTracker);
6713+
hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlags, pErrorTracker);
66606714
if (hmod != NULL)
66616715
{
66626716
return hmod;
@@ -6686,7 +6740,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
66866740
Assembly *pAssembly = spec.LoadAssembly(FILE_LOADED);
66876741
Module *pModule = pAssembly->FindModuleByName(szLibName);
66886742

6689-
hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
6743+
hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
66906744
}
66916745
}
66926746

@@ -6707,11 +6761,19 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
67076761
if ( !name || !*name )
67086762
return NULL;
67096763

6710-
ModuleHandleHolder hmod;
67116764

67126765
PREFIX_ASSUME( name != NULL );
67136766
MAKE_WIDEPTR_FROMUTF8( wszLibName, name );
67146767

6768+
ModuleHandleHolder hmod = LoadLibraryModuleViaCallback(pMD, wszLibName);
6769+
if (hmod != NULL)
6770+
{
6771+
#ifdef FEATURE_PAL
6772+
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
6773+
#endif // FEATURE_PAL
6774+
return hmod.Extract();
6775+
}
6776+
67156777
AppDomain* pDomain = GetAppDomain();
67166778

67176779
// AssemblyLoadContext is not supported in AppX mode and thus,

0 commit comments

Comments
 (0)