Skip to content

Commit e47524b

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 e47524b

File tree

13 files changed

+243
-90
lines changed

13 files changed

+243
-90
lines changed

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

+79-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,67 @@ 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.ArgumentException">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+
// ConditionalWealTable throws ArgumentException if the Key already exists
216+
s_nativeDllResolveMap.Add(assembly, resolver);
217+
}
218+
219+
/// <summary>
220+
/// The helper function that calls the per-assembly native-library resolver
221+
/// if one is registered for this assembly.
222+
/// </summary>
223+
/// <param name="libraryName">The native library to load</param>
224+
/// <param name="assembly">The assembly trying load the native library</param>
225+
/// <param name="hasDllImportSearchPathFlags">If the pInvoke has DefaultDllImportSearchPathAttribute</param>
226+
/// <param name="dllImportSearchPathFlags">If hasdllImportSearchPathFlags is true, the flags in
227+
/// DefaultDllImportSearchPathAttribute; meaningless otherwise </param>
228+
/// <returns>The handle for the loaded library on success. Null on failure.</returns>
229+
internal static IntPtr LoadLibraryCallbackStub(string libraryName, Assembly assembly,
230+
bool hasDllImportSearchPathFlags, uint dllImportSearchPathFlags)
231+
{
232+
DllImportResolver resolver;
233+
234+
if (!s_nativeDllResolveMap.TryGetValue(assembly, out resolver))
235+
{
236+
return IntPtr.Zero;
237+
}
238+
239+
return resolver(libraryName, assembly, hasDllImportSearchPathFlags ? (DllImportSearchPath?)dllImportSearchPathFlags : null);
240+
}
241+
164242
/// External functions that implement the NativeLibrary interface
165243

166244
[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

+1-82
Original file line numberDiff line numberDiff line change
@@ -6491,87 +6491,6 @@ NATIVE_LIBRARY_HANDLE NDirect::LoadLibraryModuleViaCallback(NDirectMethodDesc *
64916491
return handle;
64926492
}
64936493

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-
65756494
// Try to load the module alongside the assembly where the PInvoke was declared.
65766495
NATIVE_LIBRARY_HANDLE NDirect::LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker)
65776496
{
@@ -6842,7 +6761,7 @@ HINSTANCE NDirect::LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracke
68426761
if ( !name || !*name )
68436762
return NULL;
68446763

6845-
PREFIX_ASSUME( name != NULL );
6764+
PREFIX_ASSUME( name != NULL );
68466765
MAKE_WIDEPTR_FROMUTF8( wszLibName, name );
68476766

68486767
ModuleHandleHolder hmod = LoadLibraryModuleViaCallback(pMD, wszLibName);

src/vm/dllimport.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class NDirect
7676
static LPVOID NDirectGetEntryPoint(NDirectMethodDesc *pMD, HINSTANCE hMod);
7777
static NATIVE_LIBRARY_HANDLE LoadLibraryFromPath(LPCWSTR libraryPath, BOOL throwOnError);
7878
static NATIVE_LIBRARY_HANDLE LoadLibraryByName(LPCWSTR name, Assembly *callingAssembly,
79-
BOOL hasDllImportSearchPathFlag, DWORD dllImportSearchPathFlag,
79+
BOOL hasDllImportSearchPathFlags, DWORD dllImportSearchPathFlags,
8080
BOOL throwOnError);
8181
static HINSTANCE LoadLibraryModule(NDirectMethodDesc * pMD, LoadLibErrorTracker *pErrorTracker);
8282
static void FreeNativeLibrary(NATIVE_LIBRARY_HANDLE handle);
@@ -122,12 +122,13 @@ class NDirect
122122
private:
123123
NDirect() {LIMITED_METHOD_CONTRACT;}; // prevent "new"'s on this class
124124

125-
static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(AppDomain* pDomain, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
125+
static NATIVE_LIBRARY_HANDLE LoadFromNativeDllSearchDirectories(LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
126126
static NATIVE_LIBRARY_HANDLE LoadFromPInvokeAssemblyDirectory(Assembly *pAssembly, LPCWSTR libName, DWORD flags, LoadLibErrorTracker *pErrorTracker);
127127
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaHost(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
128128
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaEvent(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
129+
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleViaCallback(NDirectMethodDesc * pMD, LPCWSTR wszLibName);
129130
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(NDirectMethodDesc * pMD, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
130-
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlag, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
131+
static NATIVE_LIBRARY_HANDLE LoadLibraryModuleBySearch(Assembly *callingAssembly, BOOL searchAssemblyDirectory, DWORD dllImportSearchPathFlags, LoadLibErrorTracker * pErrorTracker, const wchar_t* wszLibName);
131132

132133
#if !defined(FEATURE_PAL)
133134
// Indicates if the OS supports the new secure LoadLibraryEx flags introduced in KB2533623

src/vm/interoputil.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -898,8 +898,8 @@ void FillExceptionData(
898898

899899
//---------------------------------------------------------------------------
900900
//returns true if pImport has DefaultDllImportSearchPathsAttribute
901-
//if true, also returns dllImportSearchPathFlag and searchAssemblyDirectory values.
902-
BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDllImportSearchPathFlag)
901+
//if true, also returns dllImportSearchPathFlags and searchAssemblyDirectory values.
902+
BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDllImportSearchPathFlags)
903903
{
904904
CONTRACTL
905905
{
@@ -929,7 +929,7 @@ BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, md
929929
args[0].InitEnum(SERIALIZATION_TYPE_U4, (ULONG)0);
930930

931931
ParseKnownCaArgs(ca, args, lengthof(args));
932-
*pDllImportSearchPathFlag = args[0].val.u4;
932+
*pDllImportSearchPathFlags = args[0].val.u4;
933933
return TRUE;
934934
}
935935

src/vm/interoputil.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ void FillExceptionData(
137137

138138
//---------------------------------------------------------------------------
139139
//returns true if pImport has DefaultDllImportSearchPathsAttribute
140-
//if true, also returns dllImportSearchPathFlag and searchAssemblyDirectory values.
140+
//if true, also returns dllImportSearchPathFlags and searchAssemblyDirectory values.
141141
BOOL GetDefaultDllImportSearchPathsAttributeValue(IMDInternalImport *pImport, mdToken token, DWORD * pDlImportSearchPathFlag);
142142

143143
//---------------------------------------------------------------------------

src/vm/metasig.h

+1
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ DEFINE_METASIG_T(IM(RefGuid_OutIntPtr_RetCustomQueryInterfaceResult, r(g(GUID))
552552

553553
DEFINE_METASIG_T(SM(IntPtr_AssemblyName_RetAssemblyBase, I C(ASSEMBLY_NAME), C(ASSEMBLYBASE)))
554554
DEFINE_METASIG_T(SM(Str_AssemblyBase_IntPtr_RetIntPtr, s C(ASSEMBLYBASE) I, I))
555+
DEFINE_METASIG_T(SM(Str_AssemblyBase_Bool_UInt_RetIntPtr, s C(ASSEMBLYBASE) F K, I))
555556

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

src/vm/mscorlib.h

+2
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ DEFINE_METHOD(MARSHAL, GET_DELEGATE_FOR_FUNCTION_POINTER, GetDelega
487487
DEFINE_METHOD(MARSHAL, ALLOC_CO_TASK_MEM, AllocCoTaskMem, SM_Int_RetIntPtr)
488488
DEFINE_FIELD(MARSHAL, SYSTEM_MAX_DBCS_CHAR_SIZE, SystemMaxDBCSCharSize)
489489

490+
DEFINE_CLASS(NATIVELIBRARY, Interop, NativeLibrary)
491+
DEFINE_METHOD(NATIVELIBRARY, LOADLIBRARYCALLBACKSTUB, LoadLibraryCallbackStub, SM_Str_AssemblyBase_Bool_UInt_RetIntPtr)
490492

491493
DEFINE_CLASS(MEMBER, Reflection, MemberInfo)
492494

tests/src/Interop/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ add_subdirectory(MarshalAPI/FunctionPointer)
5757
add_subdirectory(MarshalAPI/IUnknown)
5858
add_subdirectory(NativeLibrary)
5959
add_subdirectory(NativeLibraryResolveEvent)
60+
add_subdirectory(NativeLibraryResolveCallBack)
6061
add_subdirectory(SizeConst)
6162
add_subdirectory(DllImportAttribute/ExeFile)
6263
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 (ResolveLib)
3+
include_directories(${INC_PLATFORM_DIR})
4+
set(SOURCES ResolveLib.cpp)
5+
6+
# add the executable
7+
add_library (ResolveLib SHARED ${SOURCES})
8+
target_link_libraries(ResolveLib ${LINK_LIBRARIES_ADDITIONAL})
9+
10+
# add the install targets
11+
install (TARGETS ResolveLib DESTINATION bin)
12+
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
using System;
5+
using System.IO;
6+
using System.Reflection;
7+
using System.Runtime.InteropServices;
8+
using TestLibrary;
9+
10+
using Console = Internal.Console;
11+
12+
public class CallBackTests
13+
{
14+
public static int Main()
15+
{
16+
try
17+
{
18+
Assembly assembly = Assembly.GetExecutingAssembly();
19+
string testBinDir = Path.GetDirectoryName(assembly.Location);
20+
21+
DllImportResolver resolver =
22+
(string libraryName, Assembly asm, DllImportSearchPath? dllImportSearchPath) =>
23+
NativeLibrary.Load("ResolveLib", asm, dllImportSearchPath);
24+
25+
DllImportResolver anotherResolver =
26+
(string libraryName, Assembly asm, DllImportSearchPath? dllImportSearchPath) =>
27+
IntPtr.Zero;
28+
29+
Console.WriteLine("0 OK");
30+
try
31+
{
32+
NativeLibrary.SetDllImportResolver(null, resolver);
33+
34+
Console.WriteLine("Expected exception not thrown");
35+
return 101;
36+
}
37+
catch (ArgumentNullException e){}
38+
Console.WriteLine("1 OK");
39+
40+
try
41+
{
42+
NativeLibrary.SetDllImportResolver(assembly, null);
43+
44+
Console.WriteLine("Expected exception not thrown");
45+
return 102;
46+
}
47+
catch (ArgumentNullException e){}
48+
Console.WriteLine("2 OK");
49+
50+
// Set a resolver callback
51+
NativeLibrary.SetDllImportResolver(assembly, resolver);
52+
53+
Console.WriteLine("3 OK");
54+
55+
try
56+
{
57+
// Try to set another resolver on the same assembly.
58+
NativeLibrary.SetDllImportResolver(assembly, anotherResolver);
59+
60+
Console.WriteLine("Expected Exception not thrown");
61+
return 103;
62+
}
63+
catch (InvalidOperationException e)
64+
{
65+
Console.WriteLine($"Exception: {e.Message}");
66+
}
67+
Console.WriteLine("4 OK");
68+
69+
if (NativeSum(10, 10) != 20)
70+
{
71+
Console.WriteLine("Unexpected ReturnValue from NativeSum()");
72+
return 104;
73+
}
74+
Console.WriteLine("5 OK");
75+
}
76+
catch (Exception e)
77+
{
78+
Console.WriteLine($"Unexpected exception: {e.ToString()} {e.Message}");
79+
return 105;
80+
}
81+
82+
return 100;
83+
}
84+
85+
[DllImport("NativeLib")]
86+
static extern int NativeSum(int arg1, int arg2);
87+
}

0 commit comments

Comments
 (0)