Add Per-assembly Load Native Library callbacks (dotnet/coreclr#21555)
authorSwaroop Sridhar <Swaroop.Sridhar@microsoft.com>
Fri, 18 Jan 2019 17:24:41 +0000 (09:24 -0800)
committerGitHub <noreply@github.com>
Fri, 18 Jan 2019 17:24:41 +0000 (09:24 -0800)
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

Commit migrated from https://github.com/dotnet/coreclr/commit/732f892665343e84d04eea4478eccb459b385f55

15 files changed:
src/coreclr/src/System.Private.CoreLib/Resources/Strings.resx
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.cs
src/coreclr/src/vm/callhelpers.h
src/coreclr/src/vm/dllimport.cpp
src/coreclr/src/vm/dllimport.h
src/coreclr/src/vm/interoputil.cpp
src/coreclr/src/vm/interoputil.h
src/coreclr/src/vm/metasig.h
src/coreclr/src/vm/mscorlib.h
src/coreclr/tests/issues.targets
src/coreclr/tests/src/Interop/CMakeLists.txt
src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CMakeLists.txt [new file with mode: 0644]
src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.cs [new file with mode: 0644]
src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.csproj [new file with mode: 0644]
src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/ResolveLib.cpp [new file with mode: 0644]

index 906ca36..c89f942 100644 (file)
   <data name="InvalidOperation_CannotImportGlobalFromDifferentModule" xml:space="preserve">
     <value>Unable to import a global method or field from a different module.</value>
   </data>
-  <data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
+  <data name="InvalidOperation_CannotRegisterSecondResolver" xml:space="preserve">
+    <value>A resolver is already set for the assembly.</value>
+  </data>
+    <data name="InvalidOperation_CannotRemoveLastFromEmptyCollection" xml:space="preserve">
     <value>Cannot remove the last element from an empty collection.</value>
   </data>
   <data name="InvalidOperation_CannotRestoreUnsupressedFlow" xml:space="preserve">
index 38712ac..00b78d5 100644 (file)
@@ -9,10 +9,26 @@ using System.Runtime.CompilerServices;
 using System.Runtime.ConstrainedExecution;
 using Win32Native = Microsoft.Win32.Win32Native;
 using System.Diagnostics;
+using System.Threading;
 
 namespace System.Runtime.InteropServices
 {
     /// <summary>
+    /// A delegate used to resolve native libraries via callback.
+    /// </summary>
+    /// <param name="libraryName">The native library to resolve</param>
+    /// <param name="assembly">The assembly requesting the resolution</param>
+    /// <param name="searchPath">
+    ///     The DllImportSearchPathsAttribute on the PInvoke, if any. 
+    ///     Otherwise, the DllImportSearchPathsAttribute on the assembly, if any. 
+    ///     Otherwise null.
+    /// </param>
+    /// <returns>The handle for the loaded native library on success, null on failure</returns>  
+    public delegate IntPtr DllImportResolver(string libraryName,
+                                             Assembly assembly,
+                                             DllImportSearchPath? searchPath);
+
+    /// <summary>
     /// APIs for managing Native Libraries 
     /// </summary>
     public static partial class NativeLibrary
@@ -58,7 +74,9 @@ namespace System.Runtime.InteropServices
         /// Otherwise, the flags specified by the DefaultDllImportSearchPaths attribute on the 
         /// calling assembly (if any) are used. 
         /// This LoadLibrary() method does not invoke the managed call-backs for native library resolution: 
+        /// * The per-assembly registered callback 
         /// * AssemblyLoadContext.LoadUnmanagedDll()
+        /// * AssemblyLoadContext.ResolvingUnmanagedDllEvent
         /// </summary>
         /// <param name="libraryName">The name of the native library to be loaded</param>
         /// <param name="assembly">The assembly loading the native library</param>
@@ -117,7 +135,6 @@ namespace System.Runtime.InteropServices
         /// No action if the input handle is null.
         /// </summary>
         /// <param name="handle">The native library handle to be freed</param>
-        /// <exception cref="System.InvalidOperationException">If the operation fails</exception>
         public static void Free(IntPtr handle)
         {
             FreeLib(handle);
@@ -161,6 +178,78 @@ namespace System.Runtime.InteropServices
             return address != IntPtr.Zero;
         }
 
+        /// <summary>
+        /// Map from assembly to native-library resolver.
+        /// Interop specific fields and properties are generally not added to Assembly class.
+        /// Therefore, this table uses weak assembly pointers to indirectly achieve 
+        /// similar behavior.
+        /// </summary>
+        private static ConditionalWeakTable<Assembly, DllImportResolver> s_nativeDllResolveMap = null;
+
+        /// <summary>
+        /// Set a callback for resolving native library imports from an assembly.
+        /// This per-assembly resolver is the first attempt to resolve native library loads 
+        /// initiated by this assembly.
+        ///
+        /// Only one resolver can be registered per assembly. 
+        /// Trying to register a second resolver fails with InvalidOperationException.
+        /// </summary>
+        /// <param name="assembly">The assembly for which the resolver is registered</param>
+        /// <param name="resolver">The resolver callback to register</param>
+        /// <exception cref="System.ArgumentNullException">If assembly or resolver is null</exception>
+        /// <exception cref="System.ArgumentException">If a resolver is already set for this assembly</exception>
+        public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
+        {
+            if (assembly == null)
+                throw new ArgumentNullException(nameof(assembly));
+            if (resolver == null)
+                throw new ArgumentNullException(nameof(resolver));
+            if (!(assembly is RuntimeAssembly))
+                throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
+
+            if (s_nativeDllResolveMap == null)
+            {
+                Interlocked.CompareExchange(ref s_nativeDllResolveMap,
+                    new ConditionalWeakTable<Assembly, DllImportResolver>(), null);
+            }
+
+            try
+            {
+                s_nativeDllResolveMap.Add(assembly, resolver);
+            }
+            catch (ArgumentException)
+            {
+                // ConditionalWealTable throws ArgumentException if the Key already exists
+                throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondResolver);
+            }
+        }
+
+        /// <summary>
+        /// The helper function that calls the per-assembly native-library resolver 
+        /// if one is registered for this assembly.
+        /// </summary>
+        /// <param name="libraryName">The native library to load</param>
+        /// <param name="assembly">The assembly trying load the native library</param>
+        /// <param name="hasDllImportSearchPathFlags">If the pInvoke has DefaultDllImportSearchPathAttribute</param>
+        /// <param name="dllImportSearchPathFlags">If hasdllImportSearchPathFlags is true, the flags in 
+        ///                                       DefaultDllImportSearchPathAttribute; meaningless otherwise </param>
+        /// <returns>The handle for the loaded library on success. Null on failure.</returns>  
+        internal static IntPtr LoadLibraryCallbackStub(string libraryName, Assembly assembly,
+                                                       bool hasDllImportSearchPathFlags, uint dllImportSearchPathFlags)
+        {
+            if (s_nativeDllResolveMap == null)
+            {
+                return IntPtr.Zero;
+            }
+
+            if (!s_nativeDllResolveMap.TryGetValue(assembly, out DllImportResolver resolver))
+            {
+                return IntPtr.Zero;
+            }
+
+            return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
+        }
+
         /// External functions that implement the NativeLibrary interface
 
         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
index bcd553d..032cec4 100644 (file)
@@ -566,6 +566,7 @@ enum DispatchCallSimpleFlags
 #define STRINGREF_TO_ARGHOLDER(x) (LPVOID)STRINGREFToObject(x)
 #define PTR_TO_ARGHOLDER(x) (LPVOID)x
 #define DWORD_TO_ARGHOLDER(x)   (LPVOID)(SIZE_T)x
+#define BOOL_TO_ARGHOLDER(x) DWORD_TO_ARGHOLDER(!!(x))   
 
 #define INIT_VARIABLES(count)                               \
         DWORD   __numArgs = count;                          \
index 37cfda9..18e77d8 100644 (file)
 #include "clr/fs/path.h"
 using namespace clr::fs;
 
-// The Bit 0x2 has different semantics in DllImportSearchPath and LoadLibraryExA flags.
-// In DllImportSearchPath enum, bit 0x2 represents SearchAssemblyDirectory -- which is performed by CLR.
-// Unlike other bits in this enum, this bit shouldn't be directly passed on to LoadLibrary()
-#define DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY 0x2
-
 // remove when we get an updated SDK
 #define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100
 #define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
@@ -6127,6 +6122,57 @@ bool         NDirect::s_fSecureLoadLibrarySupported = false;
 #define PLATFORM_SHARED_LIB_PREFIX_W W("")
 #endif // !FEATURE_PAL
 
+// The Bit 0x2 has different semantics in DllImportSearchPath and LoadLibraryExA flags.
+// In DllImportSearchPath enum, bit 0x2 represents SearchAssemblyDirectory -- which is performed by CLR.
+// Unlike other bits in this enum, this bit shouldn't be directly passed on to LoadLibrary()
+#define DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY 0x2
+
+// DllImportSearchPathFlags is a special enumeration, whose values are tied closely with LoadLibrary flags.
+// There is no "default" value DllImportSearchPathFlags. In the absence of DllImportSearchPath attribute, 
+// CoreCLR's LoadLibrary implementation uses the following defaults.
+// Other implementations of LoadLibrary callbacks/events are free to use other default conventions.
+void GetDefaultDllImportSearchPathFlags(DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
+{
+    STANDARD_VM_CONTRACT;
+
+    *searchAssemblyDirectory = TRUE;
+    *dllImportSearchPathFlags = 0;
+}
+
+// If a module has the DefaultDllImportSearchPathsAttribute, get DllImportSearchPathFlags from it, and return true.
+// Otherwise, get CoreCLR's default value for DllImportSearchPathFlags, and return false.
+BOOL GetDllImportSearchPathFlags(Module *pModule, DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
+{
+    STANDARD_VM_CONTRACT;
+
+    if (pModule->HasDefaultDllImportSearchPathsAttribute())
+    {
+        *dllImportSearchPathFlags = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
+        *searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
+        return TRUE;
+    }
+
+    GetDefaultDllImportSearchPathFlags(dllImportSearchPathFlags, searchAssemblyDirectory);
+    return FALSE;
+}
+
+// If a pInvoke has the DefaultDllImportSearchPathsAttribute, get DllImportSearchPathFlags from it, and returns true.
+// Otherwise, if the containing assembly has the DefaultDllImportSearchPathsAttribute, get DllImportSearchPathFlags from it, and returns true.
+// Otherwise, get CoreCLR's default value for DllImportSearchPathFlags, and return false.
+BOOL GetDllImportSearchPathFlags(NDirectMethodDesc * pMD, DWORD *dllImportSearchPathFlags, BOOL *searchAssemblyDirectory)
+{
+    STANDARD_VM_CONTRACT;
+
+    if (pMD->HasDefaultDllImportSearchPathsAttribute())
+    {
+        *dllImportSearchPathFlags = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
+        *searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
+        return TRUE;
+    }
+
+    return GetDllImportSearchPathFlags(pMD->GetModule(), dllImportSearchPathFlags, searchAssemblyDirectory);
+}
+
 // static
 NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryFromPath(LPCWSTR libraryPath, BOOL throwOnError)
 {
@@ -6151,7 +6197,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryFromPath(LPCWSTR libraryPath, BOOL thr
 
 // static 
 NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *callingAssembly, 
-                                                 BOOL hasDllImportSearchFlag, DWORD dllImportSearchFlag
+                                                 BOOL hasDllImportSearchFlags, DWORD dllImportSearchFlags
                                                  BOOL throwOnError)
 {
     CONTRACTL
@@ -6164,30 +6210,26 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryByName(LPCWSTR libraryName, Assembly *
 
     LoadLibErrorTracker errorTracker;
 
-    // First checks if a default DllImportSearchPathFlag was passed in, if so, use that value.
-    // Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute. If so, use that value.
-    BOOL searchAssemblyDirectory = TRUE;
-    DWORD dllImportSearchPathFlag = 0;
+    // First checks if a default dllImportSearchPathFlags was passed in, if so, use that value.
+    // Otherwise checks if the assembly has the DefaultDllImportSearchPathsAttribute attribute. 
+    // If so, use that value.
+    BOOL searchAssemblyDirectory;
+    DWORD dllImportSearchPathFlags;
 
-    if (hasDllImportSearchFlag)
+    if (hasDllImportSearchFlags)
     {
-        dllImportSearchPathFlag = dllImportSearchFlag & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
-        searchAssemblyDirectory = dllImportSearchFlag & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
+        dllImportSearchPathFlags = dllImportSearchFlags & ~DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
+        searchAssemblyDirectory = dllImportSearchFlags & DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY;
 
     }
-    else 
+    else
     {
-        Module * pModule = callingAssembly->GetManifestModule();
-
-        if (pModule->HasDefaultDllImportSearchPathsAttribute())
-        {
-            dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
-            searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
-        }
+        GetDllImportSearchPathFlags(callingAssembly->GetManifestModule(),
+                                    &dllImportSearchPathFlags, &searchAssemblyDirectory);
     }
 
     NATIVE_LIBRARY_HANDLE hmod = 
-        LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, &errorTracker, libraryName);
+        LoadLibraryModuleBySearch(callingAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, &errorTracker, libraryName);
 
     if (throwOnError && (hmod == nullptr))
     {
@@ -6203,29 +6245,13 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(NDirectMethodDesc * pMD
 {
     STANDARD_VM_CONTRACT;
    
-    // First checks if the method has DefaultDllImportSearchPathsAttribute. If so, use that value.
-    // Otherwise checks if the assembly has the attribute. If so, use that value.
-    BOOL searchAssemblyDirectory = TRUE;
-    DWORD dllImportSearchPathFlag = 0;
+    BOOL searchAssemblyDirectory;
+    DWORD dllImportSearchPathFlags;
 
-    if (pMD->HasDefaultDllImportSearchPathsAttribute())
-    {
-        dllImportSearchPathFlag = pMD->DefaultDllImportSearchPathsAttributeCachedValue();
-        searchAssemblyDirectory = pMD->DllImportSearchAssemblyDirectory();
-    }
-    else 
-    {
-        Module * pModule = pMD->GetModule();
-
-        if (pModule->HasDefaultDllImportSearchPathsAttribute())
-        {
-            dllImportSearchPathFlag = pModule->DefaultDllImportSearchPathsAttributeCachedValue();
-            searchAssemblyDirectory = pModule->DllImportSearchAssemblyDirectory();
-        }
-    }
+    GetDllImportSearchPathFlags(pMD, &dllImportSearchPathFlags, &searchAssemblyDirectory);
 
     Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
-    return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlag, pErrorTracker, wszLibName);
+    return LoadLibraryModuleBySearch(pAssembly, searchAssemblyDirectory, dllImportSearchPathFlags, pErrorTracker, wszLibName);
 }
 
 // static
@@ -6274,6 +6300,17 @@ INT_PTR NDirect::GetNativeLibraryExport(NATIVE_LIBRARY_HANDLE handle, LPCWSTR sy
     return address;
 }
 
+#ifndef PLATFORM_UNIX
+BOOL IsWindowsAPISet(PCWSTR wszLibName)
+{
+    STANDARD_VM_CONTRACT;
+
+    // This is replicating quick check from the OS implementation of api sets.
+    return SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || 
+           SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0;
+}
+#endif // !PLATFORM_UNIX
+
 // static
 NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, PCWSTR wszLibName)
 {
@@ -6282,13 +6319,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
     //Check if we  need to provide the host a chance to provide the unmanaged dll 
 
 #ifndef PLATFORM_UNIX
-    // Prevent Overriding of Windows API sets.
-    // This is replicating quick check from the OS implementation of api sets.
-    if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
+    if (IsWindowsAPISet(wszLibName))
     {
+        // Prevent Overriding of Windows API sets.
         return NULL;
     }
-#endif
+#endif // !PLATFORM_UNIX
 
     NATIVE_LIBRARY_HANDLE hmod = NULL;
     AppDomain* pDomain = GetAppDomain();
@@ -6438,6 +6474,51 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD
     return hmod;
 }
 
+NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc * pMD, LPCWSTR wszLibName)
+{
+    STANDARD_VM_CONTRACT;
+
+    if (pMD->GetModule()->IsSystem())
+    {
+        // Don't attempt to callback on Corelib itself.
+        // The LoadLibrary callback stub is managed code that requires CoreLib 
+        return NULL;
+    }
+
+    DWORD dllImportSearchPathFlags;
+    BOOL searchAssemblyDirectory;
+    BOOL hasDllImportSearchPathFlags = GetDllImportSearchPathFlags(pMD, &dllImportSearchPathFlags, &searchAssemblyDirectory);
+    dllImportSearchPathFlags |= searchAssemblyDirectory ? DLLIMPORTSEARCHPATH_ASSEMBLYDIRECTORY : 0;
+
+    Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
+    NATIVE_LIBRARY_HANDLE handle = NULL;
+
+    GCX_COOP();
+
+    struct {
+        STRINGREF libNameRef;
+        OBJECTREF assemblyRef;
+    } gc = { NULL, NULL };
+
+    GCPROTECT_BEGIN(gc);
+
+    gc.libNameRef = StringObject::NewString(wszLibName);
+    gc.assemblyRef = pAssembly->GetExposedObject();
+
+    PREPARE_NONVIRTUAL_CALLSITE(METHOD__NATIVELIBRARY__LOADLIBRARYCALLBACKSTUB);
+    DECLARE_ARGHOLDER_ARRAY(args, 4);
+    args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.libNameRef);
+    args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.assemblyRef);
+    args[ARGNUM_2] = BOOL_TO_ARGHOLDER(hasDllImportSearchPathFlags);
+    args[ARGNUM_3] = DWORD_TO_ARGHOLDER(dllImportSearchPathFlags);
+
+     // Make the call
+    CALL_MANAGED_METHOD(handle, NATIVE_LIBRARY_HANDLE, args);
+    GCPROTECT_END();
+
+    return handle;
+}
+
 // Try to load the module alongside the assembly where the PInvoke was declared.
 NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
 {
@@ -6461,11 +6542,12 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssem
 }
 
 // Try to load the module from the native DLL search directories
-NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
+NATIVE_LIBRARY_HANDLE NDirect::LoadFromNativeDllSearchDirectories(LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
 {
     STANDARD_VM_CONTRACT;
 
     NATIVE_LIBRARY_HANDLE hmod = NULL;
+    AppDomain* pDomain = GetAppDomain();
 
     if (pDomain->HasNativeDllSearchDirectories())
     {
@@ -6587,7 +6669,7 @@ static void DetermineLibNameVariations(const WCHAR** libNameVariations, int* num
 // Search for the library and variants of its name in probing directories.
 //static 
 NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssembly, 
-                                                         BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag,
+                                                         BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlags,
                                                          LoadLibErrorTracker * pErrorTracker, LPCWSTR wszLibName)
 {
     STANDARD_VM_CONTRACT;
@@ -6597,7 +6679,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
 #if defined(FEATURE_CORESYSTEM) && !defined(PLATFORM_UNIX)
     // Try to go straight to System32 for Windows API sets. This is replicating quick check from
     // the OS implementation of api sets.
-    if (SString::_wcsnicmp(wszLibName, W("api-"), 4) == 0 || SString::_wcsnicmp(wszLibName, W("ext-"), 4) == 0)
+    if (IsWindowsAPISet(wszLibName))
     {
         hmod = LocalLoadLibraryHelper(wszLibName, LOAD_LIBRARY_SEARCH_SYSTEM32, pErrorTracker);
         if (hmod != NULL)
@@ -6625,7 +6707,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
         currLibNameVariation.Printf(prefixSuffixCombinations[i], PLATFORM_SHARED_LIB_PREFIX_W, wszLibName, PLATFORM_SHARED_LIB_SUFFIX_W);
 
         // NATIVE_DLL_SEARCH_DIRECTORIES set by host is considered well known path
-        hmod = LoadFromNativeDllSearchDirectories(pDomain, currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
+        hmod = LoadFromNativeDllSearchDirectories(currLibNameVariation, loadWithAlteredPathFlags, pErrorTracker);
         if (hmod != NULL)
         {
             return hmod;
@@ -6634,11 +6716,11 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
         if (!libNameIsRelativePath)
         {
             DWORD flags = loadWithAlteredPathFlags;
-            if ((dllImportSearchPathFlag & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
+            if ((dllImportSearchPathFlags & LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR) != 0)
             {
                 // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR is the only flag affecting absolute path. Don't OR the flags
                 // unconditionally as all absolute path P/Invokes could then lose LOAD_WITH_ALTERED_SEARCH_PATH.
-                flags |= dllImportSearchPathFlag;
+                flags |= dllImportSearchPathFlags;
             }
 
             hmod = LocalLoadLibraryHelper(currLibNameVariation, flags, pErrorTracker);
@@ -6649,14 +6731,14 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
         }
         else if ((callingAssembly != nullptr) && searchAssemblyDirectory)
         {
-            hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
+            hmod = LoadFromPInvokeAssemblyDirectory(callingAssembly, currLibNameVariation, loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
             if (hmod != NULL)
             {
                 return hmod;
             }
         }
 
-        hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlag, pErrorTracker);
+        hmod = LocalLoadLibraryHelper(currLibNameVariation, dllImportSearchPathFlags, pErrorTracker);
         if (hmod != NULL)
         {
             return hmod;
@@ -6686,7 +6768,7 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleBySearch(Assembly *callingAssemb
             Assembly *pAssembly = spec.LoadAssembly(FILE_LOADED);
             Module *pModule = pAssembly->FindModuleByName(szLibName);
 
-            hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlag, pErrorTracker);
+            hmod = LocalLoadLibraryHelper(pModule->GetPath(), loadWithAlteredPathFlags | dllImportSearchPathFlags, pErrorTracker);
         }
     }
 
@@ -6707,11 +6789,18 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
     if ( !name || !*name )
         return NULL;
     
-    ModuleHandleHolder hmod;
-
     PREFIX_ASSUME( name != NULL );
     MAKE_WIDEPTR_FROMUTF8( wszLibName, name );
 
+    ModuleHandleHolder hmod = LoadLibraryModuleViaCallback(pMD, wszLibName);
+    if (hmod != NULL)
+    {
+#ifdef FEATURE_PAL
+        hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
+#endif // FEATURE_PAL
+        return hmod.Extract();
+    }
+
     AppDomain* pDomain = GetAppDomain();
 
     // AssemblyLoadContext is not supported in AppX mode and thus,
index 1203803..b88d339 100644 (file)
@@ -76,7 +76,7 @@ public:
     static LPVOID NDirectGetEntryPoint(NDirectMethodDesc *pMD, HINSTANCE hMod);
     static NATIVE_LIBRARY_HANDLE LoadLibraryFromPath(LPCWSTR libraryPath, BOOL throwOnError);
     static NATIVE_LIBRARY_HANDLE LoadLibraryByName(LPCWSTR name, Assembly *callingAssembly, 
-                                                   BOOL hasDllImportSearchPathFlag, DWORD dllImportSearchPathFlag
+                                                   BOOL hasDllImportSearchPathFlags, DWORD dllImportSearchPathFlags
                                                    BOOL throwOnError);
     static HINSTANCE LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracker *pErrorTracker);
     static void FreeNativeLibrary(NATIVE_LIBRARY_HANDLE handle);
@@ -122,12 +122,13 @@ public:
 private:
     NDirect() {LIMITED_METHOD_CONTRACT;};     // prevent "new"'s on this class
 
-    static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
+    static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
     static NATIVE_LIBRARY_HANDLE LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
     static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
     static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
-    static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(NDirectMethodDesc * pMD, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
-    static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
+    static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaCallback(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
+    static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(NDirectMethodDesc * pMD, LoadLibErrorTracker * pErrorTracker, LPCWSTR wszLibName);
+    static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlags, LoadLibErrorTracker * pErrorTracker, LPCWSTR wszLibName);
 
 #if !defined(FEATURE_PAL)
     // Indicates if the OS supports the new secure LoadLibraryEx flags introduced in KB2533623
index b84670b..c70154d 100644 (file)
@@ -897,9 +897,9 @@ void FillExceptionData(
 #endif // CROSSGEN_COMPILE
 
 //---------------------------------------------------------------------------
-//returns true if pImport has DefaultDllImportSearchPathsAttribute
-//if true, also returns dllImportSearchPathFlag and searchAssemblyDirectory values.
-BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDllImportSearchPathFlag)
+// If pImport has the DefaultDllImportSearchPathsAttribute, 
+// set the value of the attribute in pDlImportSearchPathFlags and return true.
+BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDllImportSearchPathFlags)
 {
     CONTRACTL
     {
@@ -929,7 +929,7 @@ BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, md
     args[0].InitEnum(SERIALIZATION_TYPE_U4, (ULONG)0);
 
     ParseKnownCaArgs(ca, args, lengthof(args));
-    *pDllImportSearchPathFlag = args[0].val.u4;
+    *pDllImportSearchPathFlags = args[0].val.u4;
     return TRUE;
 }
 
index 91f6828..872848b 100644 (file)
@@ -136,9 +136,9 @@ void FillExceptionData(
     _In_opt_ IRestrictedErrorInfo* pRestrictedErrorInfo);
 
 //---------------------------------------------------------------------------
-//returns true if pImport has DefaultDllImportSearchPathsAttribute
-//if true, also returns dllImportSearchPathFlag and searchAssemblyDirectory values.
-BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDlImportSearchPathFlag);
+// If pImport has the DefaultDllImportSearchPathsAttribute, 
+// set the value of the attribute in pDlImportSearchPathFlags and return true.
+BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDlImportSearchPathFlags);
 
 //---------------------------------------------------------------------------
 // Returns the index of the LCID parameter if one exists and -1 otherwise.
index cfa8440..cbac116 100644 (file)
@@ -552,6 +552,7 @@ DEFINE_METASIG_T(IM(RefGuid_OutIntPtr_RetCustomQueryInterfaceResult, r(g(GUID))
 
 DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(ASSEMBLYBASE)))
 DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I))
+DEFINE_METASIG_T(SM(Str_AssemblyBase_Bool_UInt_RetIntPtr, s C(ASSEMBLYBASE) F K, I))
 
 // ThreadPool
 DEFINE_METASIG(SM(Obj_Bool_RetVoid, j F, v))
index 7c5802a..94acf84 100644 (file)
@@ -493,6 +493,8 @@ DEFINE_METHOD(MARSHAL,              GET_DELEGATE_FOR_FUNCTION_POINTER, GetDelega
 DEFINE_METHOD(MARSHAL,              ALLOC_CO_TASK_MEM,                 AllocCoTaskMem,                SM_Int_RetIntPtr)
 DEFINE_FIELD(MARSHAL,               SYSTEM_MAX_DBCS_CHAR_SIZE,         SystemMaxDBCSCharSize)
 
+DEFINE_CLASS(NATIVELIBRARY, Interop, NativeLibrary)
+DEFINE_METHOD(NATIVELIBRARY,        LOADLIBRARYCALLBACKSTUB, LoadLibraryCallbackStub, SM_Str_AssemblyBase_Bool_UInt_RetIntPtr)
 
 DEFINE_CLASS(MEMBER,                Reflection,             MemberInfo)
 
index 9b6286b..4d76cd1 100644 (file)
         <ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibraryResolveEvent/ResolveEventTests/*">
             <Issue>21964</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibraryResolveCallback/CallbackTests/*">
+            <Issue>21964</Issue>
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/Regressions/coreclr/16064/methodimpl/*">
             <Issue>9565</Issue>
         </ExcludeList>
index 9b67844..106ef1f 100644 (file)
@@ -57,6 +57,7 @@ add_subdirectory(MarshalAPI/FunctionPointer)
 add_subdirectory(MarshalAPI/IUnknown)
 add_subdirectory(NativeLibrary)
 add_subdirectory(NativeLibraryResolveEvent)
+add_subdirectory(NativeLibraryResolveCallback)
 add_subdirectory(SizeConst)
 add_subdirectory(DllImportAttribute/ExeFile)
 add_subdirectory(DllImportAttribute/FileNameContainDot)
diff --git a/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CMakeLists.txt b/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b3f3409
--- /dev/null
@@ -0,0 +1,13 @@
+cmake_minimum_required (VERSION 2.6)
+project (ResolveLib)
+include_directories(${INC_PLATFORM_DIR})
+set(SOURCES ResolveLib.cpp)
+
+# add the executable
+add_library (ResolveLib SHARED ${SOURCES})
+target_link_libraries(ResolveLib ${LINK_LIBRARIES_ADDITIONAL})
+
+# add the install targets
+install (TARGETS ResolveLib DESTINATION bin)
+
+
diff --git a/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.cs b/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.cs
new file mode 100644 (file)
index 0000000..0e73849
--- /dev/null
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+using Console = Internal.Console;
+
+[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+public class CallbackTests
+{
+    public static int Main()
+    {
+        try
+        {
+            Assembly assembly = Assembly.GetExecutingAssembly();
+
+            DllImportResolver resolver =
+                (string libraryName, Assembly asm, DllImportSearchPath? dllImportSearchPath) =>
+                {
+                    if (dllImportSearchPath != DllImportSearchPath.System32)
+                    {
+                        Console.WriteLine($"Unexpected dllImportSearchPath: {dllImportSearchPath.ToString()}");
+                        throw new ArgumentException();
+                    }
+
+                    return NativeLibrary.Load("ResolveLib", asm, null);
+                };
+
+            DllImportResolver anotherResolver =
+                (string libraryName, Assembly asm, DllImportSearchPath? dllImportSearchPath) =>
+                    IntPtr.Zero;
+
+            try
+            {
+                NativeSum(10, 10);
+                Console.WriteLine("Exception expected: no callback registered yet");
+                return 101;
+            }
+            catch (DllNotFoundException) {}
+
+            try
+            {
+                NativeLibrary.SetDllImportResolver(null, resolver);
+
+                Console.WriteLine("Exception expected: assembly parameter null");
+                return 102;
+            }
+            catch (ArgumentNullException) { }
+
+            try
+            {
+                NativeLibrary.SetDllImportResolver(assembly, null);
+
+                Console.WriteLine("Exception expected: resolver parameter null");
+                return 103;
+            }
+            catch (ArgumentNullException) { }
+
+            // Set a resolver callback
+            NativeLibrary.SetDllImportResolver(assembly, resolver);
+
+            try
+            {
+                // Try to set another resolver on the same assembly.
+                NativeLibrary.SetDllImportResolver(assembly, anotherResolver);
+
+                Console.WriteLine("Exception expected: Trying to register second resolver");
+                return 104;
+            }
+            catch (InvalidOperationException) { }
+
+            if (NativeSum(10, 10) != 20)
+            {
+                Console.WriteLine("Unexpected ReturnValue from NativeSum()");
+                return 105;
+            }
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Unexpected exception: {e.ToString()} {e.Message}");
+            return 106;
+        }
+
+        return 100;
+    }
+
+    [DllImport("NativeLib")]
+    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+    static extern int NativeSum(int arg1, int arg2);
+}
diff --git a/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.csproj b/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/CallbackTests.csproj
new file mode 100644 (file)
index 0000000..c947a50
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>CallbackTests</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{F1E66554-8C8E-4141-85CF-D0CD6A0CD0B0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <ReferenceSystemPrivateCoreLib>true</ReferenceSystemPrivateCoreLib>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="*.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="CMakeLists.txt" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
diff --git a/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/ResolveLib.cpp b/src/coreclr/tests/src/Interop/NativeLibraryResolveCallback/ResolveLib.cpp
new file mode 100644 (file)
index 0000000..597b549
--- /dev/null
@@ -0,0 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+#include <stdio.h>
+#include <xplatform.h>
+
+extern "C" DLL_EXPORT int NativeSum(int a, int b)
+{
+    return a + b;
+}