Skip to content

Commit 678c6dd

Browse files
Add NativeLibrary Resolve Event
This change adds the Native library resolving event, to be raised as the last attempt to resolve a native DLL in an AssemblyLoadContext. With this change, the DllImport resolution sequence is as follows (stopping at any step with successful resolution): * If the invoking-assembly is not in the default load context, call AssemblyLoadContext.LoadUnmanagedDll() * Run the default load logic, try loading from: * AppDomain cache * NATIVE_DLL_SEARCH_DIRECTORIES * Invoking-assembly directory, System32, etc. based on DllImportSearchPaths * Raise the ResolvingUnmanagedDll event API Review: https://github.com/dotnet/corefx/issues/32850 The ResolveEventTests triggered a pre-existing bug in the exception handling code (#21964). Disabling the test on ARM64 Windows until the issue is fixed.
1 parent 4bda36a commit 678c6dd

File tree

14 files changed

+398
-20
lines changed

14 files changed

+398
-20
lines changed

src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs

+48-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ internal AssemblyLoadContext(bool fRepresentsTPALoadContext, bool isCollectible)
7878
m_pNativeAssemblyLoadContext = InitializeAssemblyLoadContext(thisHandlePtr, fRepresentsTPALoadContext, isCollectible);
7979

8080
// Initialize event handlers to be null by default
81+
ResolvingUnmanagedDll = null;
8182
Resolving = null;
8283
Unloading = null;
8384

@@ -304,12 +305,12 @@ private Assembly GetFirstResolvedAssembly(AssemblyName assemblyName)
304305
resolvedAssembly = handler(this, assemblyName);
305306
if (resolvedAssembly != null)
306307
{
307-
break;
308+
return resolvedAssembly;
308309
}
309310
}
310311
}
311312

312-
return resolvedAssembly;
313+
return null;
313314
}
314315

315316
private Assembly ValidateAssemblyNameWithSimpleName(Assembly assembly, string requestedSimpleName)
@@ -413,6 +414,36 @@ private static IntPtr ResolveUnmanagedDll(string unmanagedDllName, IntPtr gchMan
413414
return context.LoadUnmanagedDll(unmanagedDllName);
414415
}
415416

417+
// This method is invoked by the VM to resolve a native library using the ResolvingUnmanagedDll event
418+
// after trying all other means of resolution.
419+
private static IntPtr ResolveUnmanagedDllUsingEvent(string unmanagedDllName, Assembly assembly, IntPtr gchManagedAssemblyLoadContext)
420+
{
421+
AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target);
422+
return context.GetResolvedUnmanagedDll(assembly, unmanagedDllName);
423+
}
424+
425+
private IntPtr GetResolvedUnmanagedDll(Assembly assembly, string unmanagedDllName)
426+
{
427+
IntPtr resolvedDll = IntPtr.Zero;
428+
429+
Func<Assembly, string, IntPtr> dllResolveHandler = ResolvingUnmanagedDll;
430+
431+
if (dllResolveHandler != null)
432+
{
433+
// Loop through the event subscribers and return the first non-null native library handle
434+
foreach (Func<Assembly, string, IntPtr> handler in dllResolveHandler.GetInvocationList())
435+
{
436+
resolvedDll = handler(assembly, unmanagedDllName);
437+
if (resolvedDll != IntPtr.Zero)
438+
{
439+
return resolvedDll;
440+
}
441+
}
442+
}
443+
444+
return IntPtr.Zero;
445+
}
446+
416447
public static AssemblyLoadContext Default
417448
{
418449
get
@@ -509,7 +540,22 @@ internal static void OnProcessExit()
509540
}
510541
}
511542

543+
// Event handler for resolving native libraries.
544+
// This event is raised if the native library could not be resolved via
545+
// the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()]
546+
//
547+
// Inputs: Invoking assembly, and library name to resolve
548+
// Returns: A handle to the loaded native library
549+
public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll;
550+
551+
// Event handler for resolving managed assemblies.
552+
// This event is raised if the managed assembly could not be resolved via
553+
// the default resolution logic [including AssemblyLoadContext.Load()]
554+
//
555+
// Inputs: The AssemblyLoadContext and AssemblyName to be loaded
556+
// Returns: The Loaded assembly object.
512557
public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving;
558+
513559
public event Action<AssemblyLoadContext> Unloading;
514560

515561
// Contains the reference to VM's representation of the AssemblyLoadContext

src/vm/dllimport.cpp

+107-17
Original file line numberDiff line numberDiff line change
@@ -6275,7 +6275,7 @@ INT_PTR NDirect::GetNativeLibraryExport(NATIVE_LIBRARY_HANDLE handle, LPCWSTR sy
62756275
}
62766276

62776277
// static
6278-
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, AppDomain* pDomain, PCWSTR wszLibName)
6278+
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, PCWSTR wszLibName)
62796279
{
62806280
STANDARD_VM_CONTRACT;
62816281
//Dynamic Pinvoke Support:
@@ -6290,7 +6290,8 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
62906290
}
62916291
#endif
62926292

6293-
LPVOID hmod = NULL;
6293+
NATIVE_LIBRARY_HANDLE hmod = NULL;
6294+
AppDomain* pDomain = GetAppDomain();
62946295
CLRPrivBinderCoreCLR *pTPABinder = pDomain->GetTPABinderContext();
62956296
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
62966297

@@ -6349,11 +6350,92 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaHost(NDirectMethodDesc * pMD,
63496350
args[ARGNUM_1] = PTR_TO_ARGHOLDER(ptrManagedAssemblyLoadContext);
63506351

63516352
// Make the call
6352-
CALL_MANAGED_METHOD(hmod,LPVOID,args);
6353+
CALL_MANAGED_METHOD(hmod, NATIVE_LIBRARY_HANDLE, args);
63536354

63546355
GCPROTECT_END();
63556356

6356-
return (NATIVE_LIBRARY_HANDLE)hmod;
6357+
return hmod;
6358+
}
6359+
6360+
// Return the AssemblyLoadContext for an assembly
6361+
INT_PTR GetManagedAssemblyLoadContext(Assembly* pAssembly)
6362+
{
6363+
STANDARD_VM_CONTRACT;
6364+
6365+
PTR_ICLRPrivBinder pBindingContext = pAssembly->GetManifestFile()->GetBindingContext();
6366+
if (pBindingContext == NULL)
6367+
{
6368+
// GetBindingContext() returns NULL for System.Private.CoreLib
6369+
return NULL;
6370+
}
6371+
6372+
UINT_PTR assemblyBinderID = 0;
6373+
IfFailThrow(pBindingContext->GetBinderID(&assemblyBinderID));
6374+
6375+
AppDomain *pDomain = GetAppDomain();
6376+
ICLRPrivBinder *pCurrentBinder = reinterpret_cast<ICLRPrivBinder *>(assemblyBinderID);
6377+
6378+
#ifdef FEATURE_COMINTEROP
6379+
if (AreSameBinderInstance(pCurrentBinder, pDomain->GetWinRtBinder()))
6380+
{
6381+
// No ALC associated handle with WinRT Binders.
6382+
return NULL;
6383+
}
6384+
#endif // FEATURE_COMINTEROP
6385+
6386+
// The code here deals with two implementations of ICLRPrivBinder interface:
6387+
// - CLRPrivBinderCoreCLR for the TPA binder in the default ALC, and
6388+
// - CLRPrivBinderAssemblyLoadContext for custom ALCs.
6389+
// in order obtain the associated ALC handle.
6390+
INT_PTR ptrManagedAssemblyLoadContext = AreSameBinderInstance(pCurrentBinder, pDomain->GetTPABinderContext())
6391+
? ((CLRPrivBinderCoreCLR *)pCurrentBinder)->GetManagedAssemblyLoadContext()
6392+
: ((CLRPrivBinderAssemblyLoadContext *)pCurrentBinder)->GetManagedAssemblyLoadContext();
6393+
6394+
return ptrManagedAssemblyLoadContext;
6395+
}
6396+
6397+
// static
6398+
NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, PCWSTR wszLibName)
6399+
{
6400+
STANDARD_VM_CONTRACT;
6401+
6402+
NATIVE_LIBRARY_HANDLE hmod = NULL;
6403+
Assembly* pAssembly = pMD->GetMethodTable()->GetAssembly();
6404+
INT_PTR ptrManagedAssemblyLoadContext = GetManagedAssemblyLoadContext(pAssembly);
6405+
6406+
if (ptrManagedAssemblyLoadContext == NULL)
6407+
{
6408+
return NULL;
6409+
}
6410+
6411+
GCX_COOP();
6412+
6413+
struct {
6414+
STRINGREF DllName;
6415+
OBJECTREF AssemblyRef;
6416+
} gc = { NULL, NULL };
6417+
6418+
GCPROTECT_BEGIN(gc);
6419+
6420+
gc.DllName = StringObject::NewString(wszLibName);
6421+
gc.AssemblyRef = pAssembly->GetExposedObject();
6422+
6423+
// Prepare to invoke System.Runtime.Loader.AssemblyLoadContext.ResolveUnmanagedDllUsingEvent method
6424+
// While ResolveUnmanagedDllUsingEvent() could compute the AssemblyLoadContext using the AssemblyRef
6425+
// argument, it will involve another pInvoke to the runtime. So AssemblyLoadContext is passed in
6426+
// as an additional argument.
6427+
PREPARE_NONVIRTUAL_CALLSITE(METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUNMANAGEDDLLUSINGEVENT);
6428+
DECLARE_ARGHOLDER_ARRAY(args, 3);
6429+
args[ARGNUM_0] = STRINGREF_TO_ARGHOLDER(gc.DllName);
6430+
args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(gc.AssemblyRef);
6431+
args[ARGNUM_2] = PTR_TO_ARGHOLDER(ptrManagedAssemblyLoadContext);
6432+
6433+
// Make the call
6434+
CALL_MANAGED_METHOD(hmod, NATIVE_LIBRARY_HANDLE, args);
6435+
6436+
GCPROTECT_END();
6437+
6438+
return hmod;
63576439
}
63586440

63596441
// Try to load the module alongside the assembly where the PInvoke was declared.
@@ -6633,15 +6715,13 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
66336715
AppDomain* pDomain = GetAppDomain();
66346716

66356717
// AssemblyLoadContext is not supported in AppX mode and thus,
6636-
// we should not perform PInvoke resolution via it when operating in
6637-
// AppX mode.
6718+
// we should not perform PInvoke resolution via it when operating in AppX mode.
66386719
if (!AppX::IsAppXProcess())
66396720
{
6640-
hmod = LoadLibraryModuleViaHost(pMD, pDomain, wszLibName);
6721+
hmod = LoadLibraryModuleViaHost(pMD, wszLibName);
66416722
if (hmod != NULL)
66426723
{
66436724
#ifdef FEATURE_PAL
6644-
// Register the system library handle with PAL and get a PAL library handle
66456725
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
66466726
#endif // FEATURE_PAL
66476727
return hmod.Extract();
@@ -6663,27 +6743,37 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
66636743
if (SString::_wcsicmp(wszLibName, MAIN_CLR_MODULE_NAME_W) == 0)
66646744
{
66656745
hmod = GetCLRModule();
6746+
if (hmod != NULL)
6747+
{
6748+
return hmod.Extract();
6749+
}
66666750
}
66676751
#endif // FEATURE_PAL
66686752

6669-
if (hmod == NULL)
6753+
hmod = LoadLibraryModuleBySearch(pMD, pErrorTracker, wszLibName);
6754+
if (hmod != NULL)
66706755
{
6671-
hmod = LoadLibraryModuleBySearch(pMD, pErrorTracker, wszLibName);
6756+
#ifdef FEATURE_PAL
6757+
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
6758+
#endif // FEATURE_PAL
6759+
6760+
// If we have a handle add it to the cache.
6761+
pDomain->AddUnmanagedImageToCache(wszLibName, hmod);
6762+
return hmod.Extract();
6763+
}
6764+
6765+
if (!AppX::IsAppXProcess())
6766+
{
6767+
hmod = LoadLibraryModuleViaEvent(pMD, wszLibName);
66726768
if (hmod != NULL)
66736769
{
66746770
#ifdef FEATURE_PAL
6675-
// Register the system library handle with PAL and get a PAL library handle
66766771
hmod = PAL_RegisterLibraryDirect(hmod, wszLibName);
66776772
#endif // FEATURE_PAL
6773+
return hmod.Extract();
66786774
}
66796775
}
66806776

6681-
if (hmod != NULL)
6682-
{
6683-
// If we have a handle add it to the cache.
6684-
pDomain->AddUnmanagedImageToCache(wszLibName, hmod);
6685-
}
6686-
66876777
return hmod.Extract();
66886778
}
66896779

src/vm/dllimport.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ class NDirect
124124

125125
static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
126126
static NATIVE_LIBRARY_HANDLE LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
127-
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, AppDomain* pDomain, const wchar_t* wszLibName);
127+
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
128+
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
128129
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(NDirectMethodDesc * pMD, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
129130
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
130131

src/vm/metasig.h

+1
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ DEFINE_METASIG_T(IM(RefGuid_OutIntPtr_RetCustomQueryInterfaceResult, r(g(GUID))
551551
#endif //FEATURE_COMINTEROP
552552

553553
DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(ASSEMBLYBASE)))
554+
DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I))
554555

555556
// ThreadPool
556557
DEFINE_METASIG(SM(Obj_Bool_RetVoid, j F, v))

src/vm/mscorlib.h

+1
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ DEFINE_METHOD(FIRSTCHANCE_EVENTARGS, CTOR, .ctor,
864864
DEFINE_CLASS(ASSEMBLYLOADCONTEXT, Loader, AssemblyLoadContext)
865865
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVE, Resolve, SM_IntPtr_AssemblyName_RetAssemblyBase)
866866
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
867+
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLLUSINGEVENT, ResolveUnmanagedDllUsingEvent, SM_Str_AssemblyBase_IntPtr_RetIntPtr)
867868
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
868869
DEFINE_FIELD(ASSEMBLYLOADCONTEXT, ASSEMBLY_LOAD, AssemblyLoad)
869870
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_ASSEMBLY_LOAD, OnAssemblyLoad, SM_Assembly_RetVoid)

tests/issues.targets

+6
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@
388388
<ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/DefaultInterfaceMethods/methodimpl/methodimpl/*">
389389
<Issue>9565</Issue>
390390
</ExcludeList>
391+
<ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibraryResolveEvent/ResolveEventTests/*">
392+
<Issue>21964</Issue>
393+
</ExcludeList>
391394
<ExcludeList Include="$(XunitTestBinBase)/Regressions/coreclr/16064/methodimpl/*">
392395
<Issue>9565</Issue>
393396
</ExcludeList>
@@ -819,6 +822,9 @@
819822
<ExcludeList Include="$(XunitTestBinBase)/Interop/NativeLibrary/NativeLibraryTests/*">
820823
<Issue>Issue building native components for the test. Since tests are currently built on Windows, the native components end up at Core_Root instead of Test directory, which is not suitable for the test.</Issue>
821824
</ExcludeList>
825+
<ExcludeList Include="$(XunitTestBinBase)/Interop/MarshalAPI/ResolveEvent/ResolveEventTests/*">
826+
<Issue>Issue building native components for the test. Since tests are currently built on Windows, the native components end up at Core_Root instead of Test directory, which is not suitable for the test.</Issue>
827+
</ExcludeList>
822828
<ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/byte/ByteToString3/*">
823829
<Issue>needs triage</Issue>
824830
</ExcludeList>

tests/src/Interop/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ add_subdirectory(StringMarshalling/VBByRefStr)
5656
add_subdirectory(MarshalAPI/FunctionPointer)
5757
add_subdirectory(MarshalAPI/IUnknown)
5858
add_subdirectory(NativeLibrary)
59+
add_subdirectory(NativeLibraryResolveEvent)
5960
add_subdirectory(SizeConst)
6061
add_subdirectory(DllImportAttribute/ExeFile)
6162
add_subdirectory(DllImportAttribute/FileNameContainDot)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cmake_minimum_required (VERSION 2.6)
2+
project (ResolvedLib)
3+
include_directories(${INC_PLATFORM_DIR})
4+
set(SOURCES ResolvedLib.cpp)
5+
6+
# add the executable
7+
add_library (ResolvedLib SHARED ${SOURCES})
8+
target_link_libraries(ResolvedLib ${LINK_LIBRARIES_ADDITIONAL})
9+
10+
# add the install targets
11+
install (TARGETS ResolvedLib DESTINATION bin)
12+
13+

0 commit comments

Comments
 (0)