[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
internal static extern bool TraceAssemblyLoadFromResolveHandlerInvoked(string assemblyName, bool isTrackedAssembly, string requestingAssemblyPath, string? requestedAssemblyPath);
+ [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
+ internal static extern bool TraceSatelliteSubdirectoryPathProbed(string filePath, int hResult);
+
private Assembly InternalLoadFromPath(string? assemblyPath, string? nativeImagePath)
{
RuntimeAssembly? loadedAssembly = null;
#include "assembly.hpp"
#include "applicationcontext.hpp"
+#include "bindertracing.h"
#include "loadcontext.hpp"
#include "bindresult.inl"
#include "failurecache.hpp"
namespace
{
+ typedef void (*OnPathProbed)(const WCHAR *path, HRESULT hr);
+
HRESULT BindSatelliteResourceByProbingPaths(
const StringArrayList *pResourceRoots,
AssemblyName *pRequestedAssemblyName,
- BindResult *pBindResult)
+ BindResult *pBindResult,
+ OnPathProbed onPathProbed)
{
HRESULT hr = S_OK;
FALSE /* fIsInGAC */,
FALSE /* fExplicitBindToNativeImage */,
&pAssembly);
+ onPathProbed(fileName, hr);
// Missing files are okay and expected when probing
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
{
SString &simpleName = pRequestedAssemblyName->GetSimpleName();
+ BinderTracing::PathSource pathSource = useNativeImages ? BinderTracing::PathSource::AppNativeImagePaths : BinderTracing::PathSource::AppPaths;
// Loop through the binding paths looking for a matching assembly
for (DWORD i = 0; i < pBindingPaths->GetCount(); i++)
{
FALSE, // fIsInGAC
useNativeImages, // fExplicitBindToNativeImage
&pAssembly);
+ BinderTracing::PathProbed(fileName, pathSource, hr);
if (FAILED(hr))
{
FALSE, // fIsInGAC
useNativeImages, // fExplicitBindToNativeImage
&pAssembly);
+ BinderTracing::PathProbed(fileName, pathSource, hr);
}
// Since we're probing, file not founds are ok and we should just try another
hr = BindSatelliteResourceByProbingPaths(pApplicationContext->GetPlatformResourceRoots(),
pRequestedAssemblyName,
- pBindResult);
+ pBindResult,
+ [](const WCHAR *path, HRESULT res) { BinderTracing::PathProbed(path, BinderTracing::PathSource::PlatformResourceRoots, res); });
// We found a platform resource file with matching file name, but whose ref-def didn't match. Fall
// back to application resource lookup to handle case where a user creates resources with the same
{
IF_FAIL_GO(BindSatelliteResourceByProbingPaths(pApplicationContext->GetAppPaths(),
pRequestedAssemblyName,
- pBindResult));
+ pBindResult,
+ [](const WCHAR *path, HRESULT res) { BinderTracing::PathProbed(path, BinderTracing::PathSource::AppPaths, res); }));
}
}
else
TRUE, // fIsInGAC
TRUE, // fExplicitBindToNativeImage
&pTPAAssembly);
+ BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr);
}
else
{
TRUE, // fIsInGAC
FALSE, // fExplicitBindToNativeImage
&pTPAAssembly);
+ BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr);
}
// On file not found, simply fall back to app path probing
PopulateBindRequest(m_bindRequest);
FireAssemblyLoadStop(m_bindRequest, m_resultAssembly, m_cached);
+
+ if (m_resultAssembly != nullptr)
+ m_resultAssembly->Release();
}
void AssemblyBindOperation::SetResult(PEAssembly *assembly, bool cached)
return m_ignoreBind;
}
}
+
+void BinderTracing::PathProbed(const WCHAR *path, BinderTracing::PathSource source, HRESULT hr)
+{
+ FireEtwKnownPathProbed(GetClrInstanceId(), path, source, hr);
+}
\ No newline at end of file
namespace BINDER_SPACE
{
- typedef enum
- {
- kBindingStoreGAC = 0x01,
- kBindingStoreManifest = 0x02,
- kBindingStoreHost = 0x04,
- kBindingStoreContext = 0x08
- } BindingStore;
-
class AssemblyBinder
{
public:
bool m_checkedIgnoreBind;
bool m_ignoreBind;
- ReleaseHolder<PEAssembly> m_resultAssembly;
+ PEAssembly *m_resultAssembly;
bool m_cached;
};
+
+ // This must match the BindingPathSource value map in ClrEtwAll.man
+ enum PathSource
+ {
+ ApplicationAssemblies,
+ AppNativeImagePaths,
+ AppPaths,
+ PlatformResourceRoots,
+ SatelliteSubdirectory
+ };
+
+ void PathProbed(const WCHAR *path, PathSource source, HRESULT hr);
};
#endif // __BINDER_TRACING_H__
<opcode name="AssemblyLoadContextResolvingHandlerInvoked" message="$(string.RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedOpcodeMessage)" symbol="CLR_ALC_RESOLVING_HANDLER_INVOKED_OPCODE" value="12"/>
<opcode name="AppDomainAssemblyResolveHandlerInvoked" message="$(string.RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedOpcodeMessage)" symbol="CLR_APPDOMAIN_ASSEMBLY_RESOLVE_HANDLER_INVOKED_OPCODE" value="13"/>
<opcode name="AssemblyLoadFromResolveHandlerInvoked" message="$(string.RuntimePublisher.AssemblyLoadFromResolveHandlerInvokedOpcodeMessage)" symbol="CLR_ASSEMBLY_LOAD_FROM_RESOLVE_HANDLER_INVOKED_OPCODE" value="14"/>
+ <opcode name="KnownPathProbed" message="$(string.RuntimePublisher.KnownPathProbedOpcodeMessage)" symbol="CLR_BINDING_PATH_PROBED_OPCODE" value="15"/>
</opcodes>
</task>
<!--Next available ID is 33-->
<map value="5" message="$(string.RuntimePublisher.GCRootKind.Overflow)"/>
</valueMap>
<valueMap name="GCHandleKindMap">
- <map value="0x0" message="$(string.RuntimePublisher.GCHandleKind.WeakShortMessage)"/>
- <map value="0x1" message="$(string.RuntimePublisher.GCHandleKind.WeakLongMessage)"/>
- <map value="0x2" message="$(string.RuntimePublisher.GCHandleKind.StrongMessage)"/>
- <map value="0x3" message="$(string.RuntimePublisher.GCHandleKind.PinnedMessage)"/>
- <map value="0x4" message="$(string.RuntimePublisher.GCHandleKind.VariableMessage)"/>
- <map value="0x5" message="$(string.RuntimePublisher.GCHandleKind.RefCountedMessage)"/>
- <map value="0x6" message="$(string.RuntimePublisher.GCHandleKind.DependentMessage)"/>
- <map value="0x7" message="$(string.RuntimePublisher.GCHandleKind.AsyncPinnedMessage)"/>
- <map value="0x8" message="$(string.RuntimePublisher.GCHandleKind.SizedRefMessage)"/>
+ <map value="0x0" message="$(string.RuntimePublisher.GCHandleKind.WeakShortMessage)"/>
+ <map value="0x1" message="$(string.RuntimePublisher.GCHandleKind.WeakLongMessage)"/>
+ <map value="0x2" message="$(string.RuntimePublisher.GCHandleKind.StrongMessage)"/>
+ <map value="0x3" message="$(string.RuntimePublisher.GCHandleKind.PinnedMessage)"/>
+ <map value="0x4" message="$(string.RuntimePublisher.GCHandleKind.VariableMessage)"/>
+ <map value="0x5" message="$(string.RuntimePublisher.GCHandleKind.RefCountedMessage)"/>
+ <map value="0x6" message="$(string.RuntimePublisher.GCHandleKind.DependentMessage)"/>
+ <map value="0x7" message="$(string.RuntimePublisher.GCHandleKind.AsyncPinnedMessage)"/>
+ <map value="0x8" message="$(string.RuntimePublisher.GCHandleKind.SizedRefMessage)"/>
+ </valueMap>
+ <valueMap name="KnownPathSourceMap">
+ <map value="0x0" message="$(string.RuntimePublisher.KnownPathSource.ApplicationAssembliesMessage)"/>
+ <map value="0x1" message="$(string.RuntimePublisher.KnownPathSource.AppNativeImagePathsMessage)"/>
+ <map value="0x2" message="$(string.RuntimePublisher.KnownPathSource.AppPathsMessage)"/>
+ <map value="0x3" message="$(string.RuntimePublisher.KnownPathSource.PlatformResourceRootsMessage)"/>
+ <map value="0x4" message="$(string.RuntimePublisher.KnownPathSource.SatelliteSubdirectoryMessage)"/>
</valueMap>
<!-- BitMaps -->
</UserData>
</template>
- <template tid="MethodDetails">
- <data name="MethodID" inType="win:UInt64" outType="win:HexInt64" />
- <data name="TypeID" inType="win:UInt64" outType="win:HexInt64" />
- <data name="MethodToken" inType="win:UInt32" outType="win:HexInt32" />
- <data name="TypeParameterCount" inType="win:UInt32" />
- <data name="LoaderModuleID" inType="win:UInt64" outType="win:HexInt64" />
- <data name="TypeParameters" count="TypeParameterCount" inType="win:UInt64" outType="win:HexInt64" />
- <UserData>
- <MethodDetails xmlns="myNs">
- <MethodID> %1 </MethodID>
- <TypeID> %2 </TypeID>
- <MethodToken> %3 </MethodToken>
- <TypeParameterCount> %4 </TypeParameterCount>
- <LoaderModuleID> %5 </LoaderModuleID>
- </MethodDetails>
- </UserData>
- </template>
-
<template tid="AssemblyLoadContextResolvingHandlerInvoked">
<data name="ClrInstanceID" inType="win:UInt16" />
<data name="AssemblyName" inType="win:UnicodeString" />
</UserData>
</template>
+ <template tid="KnownPathProbed">
+ <data name="ClrInstanceID" inType="win:UInt16" />
+ <data name="FilePath" inType="win:UnicodeString" />
+ <data name="Source" inType="win:UInt16" map="KnownPathSourceMap" />
+ <data name="Result" inType="win:Int32" />
+ <UserData>
+ <KnownPathProbed xmlns="myNs">
+ <ClrInstanceID> %1 </ClrInstanceID>
+ <FilePath> %2 </FilePath>
+ <Source> %3 </Source>
+ <Result> %4 </Result>
+ </KnownPathProbed>
+ </UserData>
+ </template>
+
+ <template tid="MethodDetails">
+ <data name="MethodID" inType="win:UInt64" outType="win:HexInt64" />
+ <data name="TypeID" inType="win:UInt64" outType="win:HexInt64" />
+ <data name="MethodToken" inType="win:UInt32" outType="win:HexInt32" />
+ <data name="TypeParameterCount" inType="win:UInt32" />
+ <data name="LoaderModuleID" inType="win:UInt64" outType="win:HexInt64" />
+ <data name="TypeParameters" count="TypeParameterCount" inType="win:UInt64" outType="win:HexInt64" />
+ <UserData>
+ <MethodDetails xmlns="myNs">
+ <MethodID> %1 </MethodID>
+ <TypeID> %2 </TypeID>
+ <MethodToken> %3 </MethodToken>
+ <TypeParameterCount> %4 </TypeParameterCount>
+ <LoaderModuleID> %5 </LoaderModuleID>
+ </MethodDetails>
+ </UserData>
+ </template>
+
<template tid="MethodLoadUnload">
<data name="MethodID" inType="win:UInt64" outType="win:HexInt64" />
<data name="ModuleID" inType="win:UInt64" outType="win:HexInt64" />
task="AssemblyLoader"
symbol="AssemblyLoadFromResolveHandlerInvoked" message="$(string.RuntimePublisher.AssemblyLoadFromResolveHandlerInvokedEventMessage)"/>
+ <event value="296" version="0" level="win:Informational" template="KnownPathProbed"
+ keywords ="AssemblyLoaderKeyword" opcode="KnownPathProbed"
+ task="AssemblyLoader"
+ symbol="KnownPathProbed" message="$(string.RuntimePublisher.KnownPathProbedEventMessage)"/>
+
</events>
</provider>
<string id="RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nHandlerName=%3;%nAssemblyLoadContext=%4;%nResultAssemblyName=%5;%nResultAssemblyPath=%6" />
<string id="RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nHandlerName=%3;%nResultAssemblyName=%4;%nResultAssemblyPath=%5" />
<string id="RuntimePublisher.AssemblyLoadFromResolveHandlerInvokedEventMessage" value="ClrInstanceID=%1;%nAssemblyName=%2;%nIsTrackedLoad=%3;%nRequestingAssemblyPath=%4;%nComputedRequestedAssemblyPath=%5" />
+ <string id="RuntimePublisher.KnownPathProbedEventMessage" value="ClrInstanceID=%1;%nFilePath=%2;%nSource=%3;%nResult=%4" />
<string id="RuntimePublisher.StackEventMessage" value="ClrInstanceID=%1;%nReserved1=%2;%nReserved2=%3;%nFrameCount=%4;%nStack=%5" />
<string id="RuntimePublisher.AppDomainMemAllocatedEventMessage" value="AppDomainID=%1;%nAllocated=%2;%nClrInstanceID=%3" />
<string id="RuntimePublisher.AppDomainMemSurvivedEventMessage" value="AppDomainID=%1;%nSurvived=%2;%nProcessSurvived=%3;%nClrInstanceID=%4" />
<string id="RuntimePublisher.TieredCompilationSettingsFlags.NoneMapMessage" value="None" />
<string id="RuntimePublisher.TieredCompilationSettingsFlags.QuickJitMapMessage" value="QuickJit" />
<string id="RuntimePublisher.TieredCompilationSettingsFlags.QuickJitForLoopsMapMessage" value="QuickJitForLoops" />
+ <string id="RuntimePublisher.KnownPathSource.ApplicationAssembliesMessage" value="ApplicationAssemblies" />
+ <string id="RuntimePublisher.KnownPathSource.AppNativeImagePathsMessage" value="AppNativeImagePaths" />
+ <string id="RuntimePublisher.KnownPathSource.AppPathsMessage" value="AppPaths" />
+ <string id="RuntimePublisher.KnownPathSource.PlatformResourceRootsMessage" value="PlatformResourceRoots" />
+ <string id="RuntimePublisher.KnownPathSource.SatelliteSubdirectoryMessage" value="SatelliteSubdirectory" />
<string id="RundownPublisher.AppDomain.ExecutableMapMessage" value="Executable" />
<string id="RundownPublisher.AppDomain.SharedMapMessage" value="Shared" />
<string id="RuntimePublisher.AssemblyLoadContextResolvingHandlerInvokedOpcodeMessage" value="AssemblyLoadContextResolvingHandlerInvoked" />
<string id="RuntimePublisher.AppDomainAssemblyResolveHandlerInvokedOpcodeMessage" value="AppDomainAssemblyResolveHandlerInvoked" />
<string id="RuntimePublisher.AssemblyLoadFromResolveHandlerInvokedOpcodeMessage" value="AssemblyLoadFromResolveHandlerInvoked" />
+ <string id="RuntimePublisher.KnownPathProbedOpcodeMessage" value="KnownPathProbed" />
<string id="RundownPublisher.MethodDCStartOpcodeMessage" value="DCStart" />
<string id="RundownPublisher.MethodDCEndOpcodeMessage" value="DCStop" />
FireEtwAssemblyLoadFromResolveHandlerInvoked(GetClrInstanceId(), assemblyName, isTrackedAssembly, requestingAssemblyPath, requestedAssemblyPath);
END_QCALL;
+}
+
+// static
+void QCALLTYPE AssemblyNative::TraceSatelliteSubdirectoryPathProbed(LPCWSTR filePath, HRESULT hr)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ BinderTracing::PathProbed(filePath, BinderTracing::PathSource::SatelliteSubdirectory, hr);
+
+ END_QCALL;
}
\ No newline at end of file
static void QCALLTYPE TraceResolvingHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR alcName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
static void QCALLTYPE TraceAssemblyResolveHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
static void QCALLTYPE TraceAssemblyLoadFromResolveHandlerInvoked(LPCWSTR assemblyName, bool isTrackedAssembly, LPCWSTR requestingAssemblyPath, LPCWSTR requestedAssemblyPath);
+ static void QCALLTYPE TraceSatelliteSubdirectoryPathProbed(LPCWSTR filePath, HRESULT hr);
};
#endif
QCFuncElement("TraceResolvingHandlerInvoked", AssemblyNative::TraceResolvingHandlerInvoked)
QCFuncElement("TraceAssemblyResolveHandlerInvoked", AssemblyNative::TraceAssemblyResolveHandlerInvoked)
QCFuncElement("TraceAssemblyLoadFromResolveHandlerInvoked", AssemblyNative::TraceAssemblyLoadFromResolveHandlerInvoked)
+ QCFuncElement("TraceSatelliteSubdirectoryPathProbed", AssemblyNative::TraceSatelliteSubdirectoryPathProbed)
FCFuncEnd()
FCFuncStart(gAssemblyNameFuncs)
<ItemGroup>
<ProjectReference Include="AssemblyToLoadDependency.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Resource.fr-FR.resx" />
+ </ItemGroup>
</Project>
public List<HandlerInvocation> AppDomainAssemblyResolveHandlers { get; internal set; }
public LoadFromHandlerInvocation AssemblyLoadFromHandler { get; internal set; }
+ public List<ProbedPath> ProbedPaths { get; internal set; }
+
public List<BindOperation> NestedBinds { get; internal set; }
public BindOperation()
{
AssemblyLoadContextResolvingHandlers = new List<HandlerInvocation>();
AppDomainAssemblyResolveHandlers = new List<HandlerInvocation>();
+ ProbedPaths = new List<ProbedPath>();
NestedBinds = new List<BindOperation>();
}
public string ComputedRequestedAssemblyPath { get; internal set; }
}
+ internal class ProbedPath
+ {
+ public enum PathSource : ushort
+ {
+ ApplicationAssemblies,
+ AppNativeImagePaths,
+ AppPaths,
+ PlatformResourceRoots,
+ SatelliteSubdirectory
+ }
+
+ public string FilePath { get; internal set; }
+ public PathSource Source { get; internal set; }
+ public int Result { get; internal set; }
+
+ public override string ToString()
+ {
+ return $"{FilePath} - Source={Source}, Result={Result}";
+ }
+ }
+
internal sealed class BinderEventListener : EventListener
{
private const EventKeywords TasksFlowActivityIds = (EventKeywords)0x80;
}
break;
}
+ case "KnownPathProbed":
+ {
+ ProbedPath probedPath = ParseKnownPathProbedEvent(GetData, GetDataString);
+ lock (eventsLock)
+ {
+ Assert.IsTrue(bindOperations.ContainsKey(data.ActivityId), $"{data.EventName} should have a matching AssemblyBindStart");
+ BindOperation bind = bindOperations[data.ActivityId];
+ bind.ProbedPaths.Add(probedPath);
+ }
+ break;
+ }
}
}
};
return loadFrom;
}
+
+ private ProbedPath ParseKnownPathProbedEvent(Func<string, object> getData, Func<string, string> getDataString)
+ {
+ var probedPath = new ProbedPath()
+ {
+ FilePath = getDataString("FilePath"),
+ Source = (ProbedPath.PathSource)getData("Source"),
+ Result = (int)getData("Result"),
+ };
+ return probedPath;
+ }
}
}
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Runtime.InteropServices;
+
+using TestLibrary;
+
+namespace BinderTracingTests
+{
+ partial class BinderTracingTest
+ {
+ private static CultureInfo SatelliteCulture = CultureInfo.CreateSpecificCulture("fr-FR");
+
+ // Assembly found in app path:
+ // KnownPathProbed : AppNativeImagePaths (DLL) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : AppNativeImagePaths (EXE) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : AppPaths (DLL) [S_OK]
+ // Note: corerun always sets APP_PATH and APP_NI_PATH. In regular use cases,
+ // the customer would have to explicitly configure the app to set those.
+ [BinderTest]
+ public static BindOperation AssemblyInAppPath()
+ {
+ AssemblyName assemblyName = new AssemblyName(DependentAssemblyName);
+ CustomALC alc = new CustomALC(nameof(AssemblyInAppPath));
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppNativeImagePaths, assemblyName.Name, isExe: false),
+ Source = ProbedPath.PathSource.AppNativeImagePaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppNativeImagePaths, assemblyName.Name, isExe: true),
+ Source = ProbedPath.PathSource.AppNativeImagePaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppPaths, assemblyName.Name, isExe: false),
+ Source = ProbedPath.PathSource.AppPaths,
+ Result = S_OK
+ }
+ }
+ };
+ }
+
+ // Assembly not found:
+ // KnownPathProbed : AppNativeImagePaths (DLL) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : AppNativeImagePaths (EXE) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : AppPaths (DLL) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : AppPaths (EXE) [COR_E_FILENOTFOUND]
+ // Note: corerun always sets APP_PATH and APP_NI_PATH. In regular use cases,
+ // the customer would have to explicitly configure the app to set those.
+ [BinderTest]
+ public static BindOperation NonExistentAssembly()
+ {
+ string assemblyName = "DoesNotExist";
+ try
+ {
+ Assembly.Load(assemblyName);
+ }
+ catch { }
+
+ return new BindOperation()
+ {
+ AssemblyName = new AssemblyName(assemblyName),
+ AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
+ Success = false,
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppNativeImagePaths, assemblyName, isExe: false),
+ Source = ProbedPath.PathSource.AppNativeImagePaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppNativeImagePaths, assemblyName, isExe: true),
+ Source = ProbedPath.PathSource.AppNativeImagePaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppPaths, assemblyName, isExe: false),
+ Source = ProbedPath.PathSource.AppPaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppPaths, assemblyName, isExe: true),
+ Source = ProbedPath.PathSource.AppPaths,
+ Result = COR_E_FILENOTFOUND
+ }
+ }
+ };
+ }
+
+ // Satellite assembly found in app path:
+ // KnownPathProbed : AppPaths (DLL) [S_OK]
+ // Note: corerun always sets APP_PATH and APP_NI_PATH. In regular use cases,
+ // the customer would have to explicitly configure the app to set those.
+ [BinderTest(isolate: true)]
+ public static BindOperation SatelliteAssembly_AppPath()
+ {
+ AssemblyName assemblyName = new AssemblyName($"{DependentAssemblyName}.resources");
+ assemblyName.CultureInfo = SatelliteCulture;
+ Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = DefaultALC,
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppPaths, assemblyName.Name, SatelliteCulture.Name),
+ Source = ProbedPath.PathSource.AppPaths,
+ Result = S_OK
+ }
+ }
+ };
+ }
+
+ // Satellite assembly found in culture subdirectory (custom ALC):
+ // KnownPathProbed : SatelliteSubdirectory [S_OK]
+ [BinderTest]
+ public static BindOperation SatelliteAssembly_CultureSubdirectory()
+ {
+ AssemblyName assemblyName = new AssemblyName($"{SubdirectoryAssemblyName}.resources");
+ assemblyName.CultureInfo = SatelliteCulture;
+ CustomALC alc = new CustomALC(nameof(SatelliteAssembly_CultureSubdirectory));
+ alc.LoadFromAssemblyPath(Helpers.GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName));
+ Assembly asm = alc.LoadFromAssemblyName(assemblyName);
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, SatelliteCulture.Name, Helpers.GetSubdirectoryPath()),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = S_OK
+ }
+ }
+ };
+ }
+
+ // Satellite assembly found in culture subdirectory (default ALC):
+ // KnownPathProbed : AppPaths [COR_E_FILENOTFOUND]
+ // KnownPathProbed : SatelliteSubdirectory [S_OK]
+ // Note: corerun always sets APP_PATH and APP_NI_PATH. In regular use cases,
+ // the customer would have to explicitly configure the app to set those.
+ [BinderTest(isolate: true)]
+ public static BindOperation SatelliteAssembly_CultureSubdirectory_DefaultALC()
+ {
+ AssemblyName assemblyName = new AssemblyName($"{SubdirectoryAssemblyName}.resources");
+ assemblyName.CultureInfo = SatelliteCulture;
+
+ // https://github.com/dotnet/corefx/issues/42477
+ _ = AssemblyLoadContext.Default;
+
+ Assembly OnAppDomainAssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ AssemblyName requested = new AssemblyName(args.Name);
+ return requested.Name == SubdirectoryAssemblyName
+ ? Assembly.LoadFile(Helpers.GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName))
+ : null;
+ };
+
+ AppDomain.CurrentDomain.AssemblyResolve += OnAppDomainAssemblyResolve;
+ Assembly asm = Assembly.Load(assemblyName);
+ AppDomain.CurrentDomain.AssemblyResolve -= OnAppDomainAssemblyResolve;
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = DefaultALC,
+ RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
+ RequestingAssemblyLoadContext = DefaultALC,
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.AppPaths, assemblyName.Name, SatelliteCulture.Name),
+ Source = ProbedPath.PathSource.AppPaths,
+ Result = COR_E_FILENOTFOUND
+ },
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, SatelliteCulture.Name, Helpers.GetSubdirectoryPath()),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = S_OK
+ }
+ }
+ };
+ }
+
+ // Satellite assembly found in lower-case culture subdirectory
+ // On non-Linux:
+ // KnownPathProbed : SatelliteSubdirectory (exact case) [S_OK]
+ // On Linux (case-sensitive):
+ // KnownPathProbed : SatelliteSubdirectory (exact case) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : SatelliteSubdirectory (lower-case) [S_OK]
+ [BinderTest]
+ public static BindOperation SatelliteAssembly_CultureSubdirectory_Lowercase()
+ {
+ AssemblyName assemblyName = new AssemblyName($"{SubdirectoryAssemblyName}.resources");
+ assemblyName.CultureInfo = SatelliteCulture;
+ CustomALC alc = new CustomALC(nameof(SatelliteAssembly_CultureSubdirectory));
+ alc.LoadFromAssemblyPath(Helpers.GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName));
+
+ Assembly asm;
+ string subdirectoryPath = Helpers.GetSubdirectoryPath();
+ string cultureSubdirectory = Path.Combine(subdirectoryPath, SatelliteCulture.Name);
+ string cultureSubdirectoryLower = Path.Combine(subdirectoryPath, SatelliteCulture.Name.ToLowerInvariant());
+ try
+ {
+ Directory.Move(cultureSubdirectory, cultureSubdirectoryLower);
+ asm = alc.LoadFromAssemblyName(assemblyName);
+ }
+ finally
+ {
+ Directory.Move(cultureSubdirectoryLower, cultureSubdirectory);
+ }
+
+ var probedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, SatelliteCulture.Name, subdirectoryPath),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? COR_E_FILENOTFOUND : S_OK
+ }
+ };
+
+ // On Linux, the path with a lower-case culture name should also be probed
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ probedPaths.Add(new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, SatelliteCulture.Name.ToLowerInvariant(), subdirectoryPath),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = S_OK
+ });
+ }
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = true,
+ ResultAssemblyName = asm.GetName(),
+ ResultAssemblyPath = asm.Location,
+ Cached = false,
+ ProbedPaths = probedPaths
+ };
+ }
+
+ // Satellite assembly not found
+ // On non-Linux:
+ // KnownPathProbed : SatelliteSubdirectory (exact case) [COR_E_FILENOTFOUND]
+ // On Linux (case-sensitive):
+ // KnownPathProbed : SatelliteSubdirectory (exact case) [COR_E_FILENOTFOUND]
+ // KnownPathProbed : SatelliteSubdirectory (lower-case) [COR_E_FILENOTFOUND]
+ [BinderTest]
+ public static BindOperation SatelliteAssembly_NotFound()
+ {
+ string cultureName = "en-GB";
+ AssemblyName assemblyName = new AssemblyName($"{SubdirectoryAssemblyName}.resources");
+ assemblyName.CultureInfo = new CultureInfo(cultureName);
+ CustomALC alc = new CustomALC(nameof(SatelliteAssembly_CultureSubdirectory));
+ alc.LoadFromAssemblyPath(Helpers.GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName));
+ Assert.Throws<FileNotFoundException>(() => alc.LoadFromAssemblyName(assemblyName));
+
+ var probedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, cultureName, Helpers.GetSubdirectoryPath()),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = COR_E_FILENOTFOUND
+ }
+ };
+
+ // On Linux (case-sensitive), the path with a lower-case culture name should also be probed
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ probedPaths.Add(new ProbedPath()
+ {
+ FilePath = Helpers.GetProbingFilePath(ProbedPath.PathSource.SatelliteSubdirectory, assemblyName.Name, cultureName.ToLowerInvariant(), Helpers.GetSubdirectoryPath()),
+ Source = ProbedPath.PathSource.SatelliteSubdirectory,
+ Result = COR_E_FILENOTFOUND
+ });
+ }
+
+ return new BindOperation()
+ {
+ AssemblyName = assemblyName,
+ AssemblyLoadContext = alc.ToString(),
+ Success = false,
+ Cached = false,
+ ProbedPaths = probedPaths
+ };
+ }
+ }
+}
[BinderTest(isolate: true)]
public static BindOperation AssemblyLoadFromResolveHandler_LoadDependency()
{
- string assemblyPath = GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName);
+ string assemblyPath = Helpers.GetAssemblyInSubdirectoryPath(SubdirectoryAssemblyName);
Assembly asm = Assembly.LoadFrom(assemblyPath);
Type t = asm.GetType(DependentAssemblyTypeName);
MethodInfo method = t.GetMethod("UseDependentAssembly", BindingFlags.Public | BindingFlags.Static);
};
}
- private static string GetAssemblyInSubdirectoryPath(string assemblyName)
- {
- string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
- return Path.Combine(appPath, "DependentAssemblies", $"{assemblyName}.dll");
- }
-
private enum HandlerReturn
{
Null,
return null;
string name = handlerReturn == HandlerReturn.RequestedAssembly ? assemblyName.Name : $"{assemblyName.Name}Mismatch";
- string assemblyPath = GetAssemblyInSubdirectoryPath(name);
+ string assemblyPath = Helpers.GetAssemblyInSubdirectoryPath(name);
if (!File.Exists(assemblyPath))
return null;
private const string DependentAssemblyTypeName = "AssemblyToLoad.Program";
private const string SubdirectoryAssemblyName = "AssemblyToLoad_Subdirectory";
+ private const int S_OK = unchecked((int)0);
+ private const int COR_E_FILENOTFOUND = unchecked((int)0x80070002);
+
private static readonly AssemblyName CoreLibName = typeof(object).Assembly.GetName();
[BinderTest]
Success = true,
ResultAssemblyName = asm.GetName(),
ResultAssemblyPath = asm.Location,
- Cached = false
+ Cached = false,
+ ProbedPaths = new List<ProbedPath>()
+ {
+ new ProbedPath()
+ {
+ FilePath = asm.Location,
+ Source = ProbedPath.PathSource.ApplicationAssemblies,
+ Result = S_OK
+ }
+ }
};
}
{
BindOperation bind = PlatformAssembly();
bind.Cached = true;
+ bind.ProbedPaths.Clear();
return bind;
}
- [BinderTest]
- public static BindOperation NonExistentAssembly()
- {
- string assemblyName = "DoesNotExist";
- try
- {
- Assembly.Load(assemblyName);
- }
- catch { }
-
- return new BindOperation()
- {
- AssemblyName = new AssemblyName(assemblyName),
- AssemblyLoadContext = DefaultALC,
- RequestingAssembly = Assembly.GetExecutingAssembly().GetName(),
- RequestingAssemblyLoadContext = DefaultALC,
- Success = false,
- Cached = false
- };
- }
-
[BinderTest(isolate: true)]
public static BindOperation Reflection()
{
Assert.IsTrue(binds.Length == 1, $"Bind event count for {assemblyName} - expected: 1, actual: {binds.Length}");
BindOperation actual = binds[0];
- ValidateBindOperation(expected, actual);
- }
-
- private static void ValidateBindOperation(BindOperation expected, BindOperation actual)
- {
- ValidateAssemblyName(expected.AssemblyName, actual.AssemblyName, nameof(BindOperation.AssemblyName));
- Assert.AreEqual(expected.AssemblyPath ?? string.Empty, actual.AssemblyPath, $"Unexpected value for {nameof(BindOperation.AssemblyPath)} on event");
- Assert.AreEqual(expected.AssemblyLoadContext, actual.AssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.AssemblyLoadContext)} on event");
- Assert.AreEqual(expected.RequestingAssemblyLoadContext ?? string.Empty, actual.RequestingAssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.RequestingAssemblyLoadContext)} on event");
- ValidateAssemblyName(expected.RequestingAssembly, actual.RequestingAssembly, nameof(BindOperation.RequestingAssembly));
-
- Assert.AreEqual(expected.Success, actual.Success, $"Unexpected value for {nameof(BindOperation.Success)} on event");
- Assert.AreEqual(expected.ResultAssemblyPath ?? string.Empty, actual.ResultAssemblyPath, $"Unexpected value for {nameof(BindOperation.ResultAssemblyPath)} on event");
- Assert.AreEqual(expected.Cached, actual.Cached, $"Unexpected value for {nameof(BindOperation.Cached)} on event");
- ValidateAssemblyName(expected.ResultAssemblyName, actual.ResultAssemblyName, nameof(BindOperation.ResultAssemblyName));
-
- ValidateHandlerInvocations(expected.AssemblyLoadContextResolvingHandlers, actual.AssemblyLoadContextResolvingHandlers, "AssemblyLoadContextResolving");
- ValidateHandlerInvocations(expected.AppDomainAssemblyResolveHandlers, actual.AppDomainAssemblyResolveHandlers, "AppDomainAssemblyResolve");
- ValidateLoadFromHandlerInvocation(expected.AssemblyLoadFromHandler, actual.AssemblyLoadFromHandler);
-
- ValidateNestedBinds(expected.NestedBinds, actual.NestedBinds);
- }
-
- private static bool AssemblyNamesMatch(AssemblyName name1, AssemblyName name2)
- {
- if (name1 == null || name2 == null)
- return name1 == null && name2 == null;
-
- return name1.Name == name2.Name
- && ((name1.Version == null && name2.Version == null) || name1.Version == name2.Version)
- && ((string.IsNullOrEmpty(name1.CultureName) && string.IsNullOrEmpty(name1.CultureName)) || name1.CultureName == name2.CultureName);
- }
-
- private static void ValidateAssemblyName(AssemblyName expected, AssemblyName actual, string propertyName)
- {
- Assert.IsTrue(AssemblyNamesMatch(expected, actual), $"Unexpected value for {propertyName} on event - expected: {expected}, actual: {actual}");
- }
-
- private static void ValidateHandlerInvocations(List<HandlerInvocation> expected, List<HandlerInvocation> actual, string eventName)
- {
- Assert.AreEqual(expected.Count, actual.Count, $"Unexpected handler invocation count for {eventName}");
-
- foreach (var match in expected)
- {
- Predicate<HandlerInvocation> pred = h =>
- AssemblyNamesMatch(h.AssemblyName, match.AssemblyName)
- && h.HandlerName == match.HandlerName
- && h.AssemblyLoadContext == match.AssemblyLoadContext
- && AssemblyNamesMatch(h.ResultAssemblyName, match.ResultAssemblyName)
- && h.ResultAssemblyPath == match.ResultAssemblyPath;
- Assert.IsTrue(actual.Exists(pred), $"Handler invocation not found: {match.ToString()}");
- }
- }
-
- private static void ValidateLoadFromHandlerInvocation(LoadFromHandlerInvocation expected, LoadFromHandlerInvocation actual)
- {
- if (expected == null || actual == null)
- {
- Assert.IsNull(expected);
- Assert.IsNull(actual);
- return;
- }
-
- ValidateAssemblyName(expected.AssemblyName, actual.AssemblyName, nameof(LoadFromHandlerInvocation.AssemblyName));
- Assert.AreEqual(expected.IsTrackedLoad, actual.IsTrackedLoad, $"Unexpected value for {nameof(LoadFromHandlerInvocation.IsTrackedLoad)} on event");
- Assert.AreEqual(expected.RequestingAssemblyPath, actual.RequestingAssemblyPath, $"Unexpected value for {nameof(LoadFromHandlerInvocation.RequestingAssemblyPath)} on event");
- Assert.AreEqual(expected.ComputedRequestedAssemblyPath, actual.ComputedRequestedAssemblyPath, $"Unexpected value for {nameof(LoadFromHandlerInvocation.ComputedRequestedAssemblyPath)} on event");
- }
-
- private static bool BindOperationsMatch(BindOperation bind1, BindOperation bind2)
- {
- try
- {
- ValidateBindOperation(bind1, bind2);
- }
- catch (AssertTestException e)
- {
- return false;
- }
-
- return true;
- }
-
- private static void ValidateNestedBinds(List<BindOperation> expected, List<BindOperation> actual)
- {
- foreach (var match in expected)
- {
- Predicate<BindOperation> pred = b => BindOperationsMatch(match, b);
- Assert.IsTrue(actual.Exists(pred), $"Nested bind operation not found: {match.ToString()}");
- }
+ Helpers.ValidateBindOperation(expected, actual);
}
}
}
</PropertyGroup>
<ItemGroup>
<Compile Include="BinderTracingTest.cs" />
+ <Compile Include="BinderTracingTest.DefaultProbing.cs" />
<Compile Include="BinderTracingTest.EventHandlers.cs" />
<Compile Include="BinderEventListener.cs" />
+ <Compile Include="Helpers.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Content</OutputItemType>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ <Targets>Build;SatelliteDllsProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="AssemblyToLoad.csproj">
<Properties>AssemblyNameSuffix=SubdirectoryMismatch</Properties>
</ItemGroup>
<Target Name="MoveDependentAssembliesToSubdirectory" AfterTargets="CopyFilesToOutputDirectory">
+ <PropertyGroup>
+ <AssemblyToLoadResourceCulture>fr-FR</AssemblyToLoadResourceCulture>
+ </PropertyGroup>
<ItemGroup>
- <AssembliesToMove Include="$(OutDir)/AssemblyToLoad_*.*" />
+ <SatelliteAssembliesToMove Include="$(OutDir)/AssemblyToLoad_*.resources.*" />
+ <AssembliesToMove Include="$(OutDir)/AssemblyToLoad_*.*" Exclude="@(SatelliteAssembliesToMove)" />
<AssembliesToMove Include="$(OutDir)/AssemblyToLoadDependency.*" />
</ItemGroup>
<Move SourceFiles="@(AssembliesToMove)" DestinationFolder="$(OutDir)/DependentAssemblies" />
+ <Move SourceFiles="@(SatelliteAssembliesToMove)" DestinationFolder="$(OutDir)/DependentAssemblies/$(AssemblyToLoadResourceCulture)" />
</Target>
</Project>
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+
+using TestLibrary;
+
+namespace BinderTracingTests
+{
+ internal class Helpers
+ {
+ public static void ValidateBindOperation(BindOperation expected, BindOperation actual)
+ {
+ ValidateAssemblyName(expected.AssemblyName, actual.AssemblyName, nameof(BindOperation.AssemblyName));
+ Assert.AreEqual(expected.AssemblyPath ?? string.Empty, actual.AssemblyPath, $"Unexpected value for {nameof(BindOperation.AssemblyPath)} on event");
+ Assert.AreEqual(expected.AssemblyLoadContext, actual.AssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.AssemblyLoadContext)} on event");
+ Assert.AreEqual(expected.RequestingAssemblyLoadContext ?? string.Empty, actual.RequestingAssemblyLoadContext, $"Unexpected value for {nameof(BindOperation.RequestingAssemblyLoadContext)} on event");
+ ValidateAssemblyName(expected.RequestingAssembly, actual.RequestingAssembly, nameof(BindOperation.RequestingAssembly));
+
+ Assert.AreEqual(expected.Success, actual.Success, $"Unexpected value for {nameof(BindOperation.Success)} on event");
+ Assert.AreEqual(expected.ResultAssemblyPath ?? string.Empty, actual.ResultAssemblyPath, $"Unexpected value for {nameof(BindOperation.ResultAssemblyPath)} on event");
+ Assert.AreEqual(expected.Cached, actual.Cached, $"Unexpected value for {nameof(BindOperation.Cached)} on event");
+ ValidateAssemblyName(expected.ResultAssemblyName, actual.ResultAssemblyName, nameof(BindOperation.ResultAssemblyName));
+
+ ValidateHandlerInvocations(expected.AssemblyLoadContextResolvingHandlers, actual.AssemblyLoadContextResolvingHandlers, "AssemblyLoadContextResolving");
+ ValidateHandlerInvocations(expected.AppDomainAssemblyResolveHandlers, actual.AppDomainAssemblyResolveHandlers, "AppDomainAssemblyResolve");
+ ValidateLoadFromHandlerInvocation(expected.AssemblyLoadFromHandler, actual.AssemblyLoadFromHandler);
+
+ ValidateProbedPaths(expected.ProbedPaths, actual.ProbedPaths);
+
+ ValidateNestedBinds(expected.NestedBinds, actual.NestedBinds);
+ }
+
+ public static string GetAssemblyInSubdirectoryPath(string assemblyName)
+ {
+ return Path.Combine(GetSubdirectoryPath(), $"{assemblyName}.dll");
+ }
+
+ public static string GetSubdirectoryPath()
+ {
+ string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ return Path.Combine(appPath, "DependentAssemblies");
+ }
+
+ public static string GetProbingFilePath(ProbedPath.PathSource pathSource, string assemblyName, string culture, string baseAssemblyDirectory = null)
+ {
+ return GetProbingFilePath(pathSource, assemblyName, false, culture, baseAssemblyDirectory);
+ }
+
+ public static string GetProbingFilePath(ProbedPath.PathSource pathSource, string assemblyName, bool isExe)
+ {
+ return GetProbingFilePath(pathSource, assemblyName, isExe, null, null);
+ }
+
+ private static string GetProbingFilePath(ProbedPath.PathSource pathSource, string assemblyName, bool isExe, string culture, string baseAssemblyDirectory)
+ {
+ string baseDirectory = baseAssemblyDirectory ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ if (!string.IsNullOrEmpty(culture))
+ baseDirectory = Path.Combine(baseDirectory, culture);
+
+ string extension = isExe ? "exe" : "dll";
+ switch (pathSource)
+ {
+ case ProbedPath.PathSource.AppNativeImagePaths:
+ return Path.Combine(baseDirectory, $"{assemblyName}.ni.{extension}");
+ case ProbedPath.PathSource.AppPaths:
+ case ProbedPath.PathSource.SatelliteSubdirectory:
+ return Path.Combine(baseDirectory, $"{assemblyName}.{extension}");
+ default:
+ throw new ArgumentOutOfRangeException(nameof(pathSource));
+ }
+ }
+
+ private static bool AssemblyNamesMatch(AssemblyName name1, AssemblyName name2)
+ {
+ if (name1 == null || name2 == null)
+ return name1 == null && name2 == null;
+
+ return name1.Name == name2.Name
+ && ((name1.Version == null && name2.Version == null) || name1.Version == name2.Version)
+ && ((string.IsNullOrEmpty(name1.CultureName) && string.IsNullOrEmpty(name2.CultureName)) || name1.CultureName == name2.CultureName);
+ }
+
+ private static void ValidateAssemblyName(AssemblyName expected, AssemblyName actual, string propertyName)
+ {
+ Assert.IsTrue(AssemblyNamesMatch(expected, actual), $"Unexpected value for {propertyName} on event - expected: {expected}, actual: {actual}");
+ }
+
+ private static void ValidateHandlerInvocations(List<HandlerInvocation> expected, List<HandlerInvocation> actual, string eventName)
+ {
+ Assert.AreEqual(expected.Count, actual.Count, $"Unexpected handler invocation count for {eventName}");
+
+ foreach (var match in expected)
+ {
+ Predicate<HandlerInvocation> pred = h =>
+ AssemblyNamesMatch(h.AssemblyName, match.AssemblyName)
+ && h.HandlerName == match.HandlerName
+ && h.AssemblyLoadContext == match.AssemblyLoadContext
+ && AssemblyNamesMatch(h.ResultAssemblyName, match.ResultAssemblyName)
+ && h.ResultAssemblyPath == match.ResultAssemblyPath;
+ Assert.IsTrue(actual.Exists(pred), $"Handler invocation not found: {match.ToString()}");
+ }
+ }
+
+ private static void ValidateLoadFromHandlerInvocation(LoadFromHandlerInvocation expected, LoadFromHandlerInvocation actual)
+ {
+ if (expected == null || actual == null)
+ {
+ Assert.IsNull(expected);
+ Assert.IsNull(actual);
+ return;
+ }
+
+ ValidateAssemblyName(expected.AssemblyName, actual.AssemblyName, nameof(LoadFromHandlerInvocation.AssemblyName));
+ Assert.AreEqual(expected.IsTrackedLoad, actual.IsTrackedLoad, $"Unexpected value for {nameof(LoadFromHandlerInvocation.IsTrackedLoad)} on event");
+ Assert.AreEqual(expected.RequestingAssemblyPath, actual.RequestingAssemblyPath, $"Unexpected value for {nameof(LoadFromHandlerInvocation.RequestingAssemblyPath)} on event");
+ Assert.AreEqual(expected.ComputedRequestedAssemblyPath, actual.ComputedRequestedAssemblyPath, $"Unexpected value for {nameof(LoadFromHandlerInvocation.ComputedRequestedAssemblyPath)} on event");
+ }
+
+ private static void ValidateProbedPaths(List<ProbedPath> expected, List<ProbedPath> actual)
+ {
+ foreach (var match in expected)
+ {
+ Predicate<ProbedPath> pred = p => p.FilePath == match.FilePath
+ && p.Source == match.Source
+ && p.Result == match.Result;
+ Assert.IsTrue(actual.Exists(pred), $"Probed path not found: {match.ToString()}");
+ }
+ }
+
+ private static bool BindOperationsMatch(BindOperation bind1, BindOperation bind2)
+ {
+ try
+ {
+ ValidateBindOperation(bind1, bind2);
+ }
+ catch (AssertTestException e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static void ValidateNestedBinds(List<BindOperation> expected, List<BindOperation> actual)
+ {
+ foreach (var match in expected)
+ {
+ Predicate<BindOperation> pred = b => BindOperationsMatch(match, b);
+ Assert.IsTrue(actual.Exists(pred), $"Nested bind operation not found: {match.ToString()}");
+ }
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="Message" xml:space="preserve">
+ <value>FR-FR</value>
+ </data>
+</root>
string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");
- if (Internal.IO.File.InternalExists(assemblyPath))
+ bool exists = Internal.IO.File.InternalExists(assemblyPath);
+ if (!exists && Path.IsCaseSensitive)
{
- return parentALC.LoadFromAssemblyPath(assemblyPath);
- }
- else if (Path.IsCaseSensitive)
- {
- assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!.ToLowerInvariant(), $"{assemblyName.Name}.dll");
-
- if (Internal.IO.File.InternalExists(assemblyPath))
+#if CORECLR
+ if (AssemblyLoadContext.IsTracingEnabled())
{
- return parentALC.LoadFromAssemblyPath(assemblyPath);
+ AssemblyLoadContext.TraceSatelliteSubdirectoryPathProbed(assemblyPath, HResults.COR_E_FILENOTFOUND);
}
+#endif // CORECLR
+ assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!.ToLowerInvariant(), $"{assemblyName.Name}.dll");
+ exists = Internal.IO.File.InternalExists(assemblyPath);
}
- return null;
+ Assembly? asm = exists ? parentALC.LoadFromAssemblyPath(assemblyPath) : null;
+#if CORECLR
+ if (AssemblyLoadContext.IsTracingEnabled())
+ {
+ AssemblyLoadContext.TraceSatelliteSubdirectoryPathProbed(assemblyPath, exists ? HResults.S_OK : HResults.COR_E_FILENOTFOUND);
+ }
+#endif // CORECLR
+
+ return asm;
}
internal IntPtr GetResolvedUnmanagedDll(Assembly assembly, string unmanagedDllName)