* Fix Satellite Assembly loading
When loading satellite assemblies, we should probe next to the parent
assembly and load into the same AssemblyLoadContext as the parent
assembly.
Disable fallback probing for satellite assemblies.
Add AssemblyLoadContext.Resolving handler to probe for satellite
assemblies next to parent
Fixes dotnet/coreclr#20979
* Call ResolveSatelliteAssembly from native
Only call ResolveSatelliteAssembly from native when
resolving a satellite assembly
* PR Feedback
Minimize string creation
Remove unnecessary if null checks
Eliminate corner cases by only allowing one case insensitive matching directory.
* ResolveSatelliteAssembly should ...
ResolveSatelliteAssembly should always be called on the ALC which loaded parentAssembly
Simplify code.
Add Debug.Assert
* Remove case insensitive culture search
* PR Feedback
* Fix parentAssembly logic
* Fixes from initial testing
* Add probe for lower case culture name
* PR feedback
Commit migrated from https://github.com/dotnet/coreclr/commit/
a65e2b9f1fe36d9b4dd0c850ec71984919547e18
// This method is invoked by the VM to resolve an assembly reference using the Resolving event
// after trying assembly resolution via Load override and TPA load context without success.
- private static Assembly ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
+ private static Assembly? ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
{
AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target)!;
return context.ResolveUsingEvent(assemblyName);
}
+ // This method is invoked by the VM to resolve a satellite assembly reference
+ // after trying assembly resolution via Load override without success.
+ private static Assembly? ResolveSatelliteAssembly(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)
+ {
+ AssemblyLoadContext context = (AssemblyLoadContext)(GCHandle.FromIntPtr(gchManagedAssemblyLoadContext).Target)!;
+
+ // Invoke the ResolveSatelliteAssembly method
+ return context.ResolveSatelliteAssembly(assemblyName);
+ }
+
private Assembly? GetFirstResolvedAssembly(AssemblyName assemblyName)
{
Assembly? resolvedAssembly = null;
//
// 1) Lookup the assembly within the LoadContext itself. If assembly is found, use it.
// 2) Invoke the LoadContext's Load method implementation. If assembly is found, use it.
- // 3) Lookup the assembly within TPABinder. If assembly is found, use it.
- // 4) Invoke the LoadContext's Resolving event. If assembly is found, use it.
- // 5) Raise exception.
+ // 3) Lookup the assembly within TPABinder (except for satellite requests). If assembly is found, use it.
+ // 4) Invoke the LoadContext's ResolveSatelliteAssembly method (for satellite requests). If assembly is found, use it.
+ // 5) Invoke the LoadContext's Resolving event. If assembly is found, use it.
+ // 6) Raise exception.
//
// This approach enables a LoadContext to override assemblies that have been loaded in TPA context by loading
// a different (or even the same!) version.
// Initialize the AssemblyName object from the AssemblySpec
spec.AssemblyNameInit(&_gcRefs.oRefAssemblyName, NULL);
+ bool isSatelliteAssemblyRequest = !spec.IsNeutralCulture();
+
if (!fInvokedForTPABinder)
{
// Step 2 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName) - Invoke Load method
{
fResolvedAssembly = true;
}
-
+
// Step 3 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
- if (!fResolvedAssembly)
+ if (!fResolvedAssembly && !isSatelliteAssemblyRequest)
{
// If we could not resolve the assembly using Load method, then attempt fallback with TPA Binder.
// Since TPA binder cannot fallback to itself, this fallback does not happen for binds within TPA binder.
}
}
}
-
- if (!fResolvedAssembly)
+
+ if (!fResolvedAssembly && isSatelliteAssemblyRequest)
{
// Step 4 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
//
+ // Attempt to resolve it using the ResolveSatelliteAssembly method.
+ // Finally, setup arguments for invocation
+ BinderMethodID idHAR_ResolveSatelitteAssembly = METHOD__ASSEMBLYLOADCONTEXT__RESOLVESATELLITEASSEMBLY;
+ MethodDescCallSite methResolveSatelitteAssembly(idHAR_ResolveSatelitteAssembly);
+
+ // Setup the arguments for the call
+ ARG_SLOT args[2] =
+ {
+ PtrToArgSlot(pManagedAssemblyLoadContextToBindWithin), // IntPtr for managed assembly load context instance
+ ObjToArgSlot(_gcRefs.oRefAssemblyName), // AssemblyName instance
+ };
+
+ // Make the call
+ _gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveSatelitteAssembly.Call_RetOBJECTREF(args);
+ if (_gcRefs.oRefLoadedAssembly != NULL)
+ {
+ // Set the flag indicating we found the assembly
+ fResolvedAssembly = true;
+ }
+ }
+
+ if (!fResolvedAssembly)
+ {
+ // Step 5 (of CLRPrivBinderAssemblyLoadContext::BindUsingAssemblyName)
+ //
// If we couldnt resolve the assembly using TPA LoadContext as well, then
// attempt to resolve it using the Resolving event.
// Finally, setup arguments for invocation
BinderMethodID idHAR_ResolveUsingEvent = METHOD__ASSEMBLYLOADCONTEXT__RESOLVEUSINGEVENT;
- MethodDescCallSite methLoadAssembly(idHAR_ResolveUsingEvent);
+ MethodDescCallSite methResolveUsingEvent(idHAR_ResolveUsingEvent);
// Setup the arguments for the call
ARG_SLOT args[2] =
};
// Make the call
- _gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methLoadAssembly.Call_RetOBJECTREF(args);
+ _gcRefs.oRefLoadedAssembly = (ASSEMBLYREF) methResolveUsingEvent.Call_RetOBJECTREF(args);
if (_gcRefs.oRefLoadedAssembly != NULL)
{
// Set the flag indicating we found the assembly
void SetCodeBase(LPCWSTR szCodeBase);
VOID SetCulture(LPCSTR szCulture);
+ bool IsNeutralCulture();
VOID ConvertPublicKeyToToken();
m_context.szLocale=szCulture;
}
+inline bool BaseAssemblySpec::IsNeutralCulture()
+{
+ return strcmp(m_context.szLocale,"")==0;
+}
+
inline void BaseAssemblySpec::SetContext(ASSEMBLYMETADATA* assemblyData)
{
LIMITED_METHOD_CONTRACT;
DEFINE_FIELD_U(_isCollectible, AssemblyLoadContextBaseObject, _isCollectible)
DEFINE_CLASS(ASSEMBLYLOADCONTEXT, Loader, AssemblyLoadContext)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVE, Resolve, SM_IntPtr_AssemblyName_RetAssemblyBase)
-DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
+DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLL, ResolveUnmanagedDll, SM_Str_IntPtr_RetIntPtr)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUNMANAGEDDLLUSINGEVENT, ResolveUnmanagedDllUsingEvent, SM_Str_AssemblyBase_IntPtr_RetIntPtr)
-DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
+DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVEUSINGEVENT, ResolveUsingResolvingEvent, SM_IntPtr_AssemblyName_RetAssemblyBase)
+DEFINE_METHOD(ASSEMBLYLOADCONTEXT, RESOLVESATELLITEASSEMBLY, ResolveSatelliteAssembly, SM_IntPtr_AssemblyName_RetAssemblyBase)
DEFINE_FIELD(ASSEMBLYLOADCONTEXT, ASSEMBLY_LOAD, AssemblyLoad)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_ASSEMBLY_LOAD, OnAssemblyLoad, SM_Assembly_RetVoid)
DEFINE_METHOD(ASSEMBLYLOADCONTEXT, ON_RESOURCE_RESOLVE, OnResourceResolve, SM_Assembly_Str_RetAssembly)
//
// Inputs: The AssemblyLoadContext and AssemblyName to be loaded
// Returns: The Loaded assembly object.
- public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving
+ public event Func<AssemblyLoadContext, AssemblyName, Assembly?> Resolving
{
add
{
}
}
}
+
+ private Assembly? ResolveSatelliteAssembly(AssemblyName assemblyName)
+ {
+ // Called by native runtime when CultureName is not empty
+ Debug.Assert(assemblyName.CultureName?.Length > 0);
+
+ string satelliteSuffix = ".resources";
+
+ if (assemblyName.Name == null || !assemblyName.Name.EndsWith(satelliteSuffix, StringComparison.Ordinal))
+ return null;
+
+ string parentAssemblyName = assemblyName.Name.Substring(0, assemblyName.Name.Length - satelliteSuffix.Length);
+
+ Assembly parentAssembly = LoadFromAssemblyName(new AssemblyName(parentAssemblyName));
+
+ AssemblyLoadContext parentALC = GetLoadContext(parentAssembly)!;
+
+ string parentDirectory = Path.GetDirectoryName(parentAssembly.Location)!;
+
+ string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");
+
+ if (Internal.IO.File.InternalExists(assemblyPath))
+ {
+ return parentALC.LoadFromAssemblyPath(assemblyPath);
+ }
+ else if (Path.IsCaseSensitive)
+ {
+ assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!.ToLowerInvariant(), $"{assemblyName.Name}.dll");
+
+ if (Internal.IO.File.InternalExists(assemblyPath))
+ {
+ return parentALC.LoadFromAssemblyPath(assemblyPath);
+ }
+ }
+
+ return null;
+ }
}
internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext