I was playing with .NET 2.0 unmanaged hosting API. I am trying to customize assembly loading and “fix” some assemblies on the fly. I found an interesting gotcha, that I want to write down.
In one line: if you return a non-null value from IHostAssemblyManager::GetNonHostStoreAssemblies
, then ICLRRuntimeHost::ExecuteInDefaultAppDomain
returns error code 0x80131051 or FUSION_E_LOADFROM_BLOCKED
, without ever consulting your assembly store, or the non-host store assemblies list.
The CLR hosting API is very well structured. You start with CorBindToRuntimeEx
call, followed by ICLRRuntimeHost::SetHostControl
, and then CLR will ask your host to provide a number of “managers”, such as memory manager, assembly manager, etc. All managers are optional: you may return E_NOINTERFACE to let CLR do the default thing.
Naturally, I was interested in the assembly manager that implements IHostAssemblyManager
interface. This interface returns two objects to the CLR: the assembly store, that knows how to get assemblies, and the list of “non host store assemblies” that the CLR should be able to load by itself. The documentation says that the CLR behaves differently depending on whether non host store assemblies list is NULL or not, but it describes only the differences in assembly loading order.
What it does not describe, is that if you return a non-NULL for “non host store assemblies list”, an internal variable g_bFusionHosted
is set to true
, and the method CorHost2::IsLoadFromBlocked
returns true
. This means that whenever you try to load an assembly from a particular path, you will get return code FUSION_E_LOADFROM_BLOCKED
, or 0x80131051.
In particular, ExecuteInDefaultAppDomain
will fail with this error code, because it loads assembly from a path. Even if you don’t specify the path, and don’t specify the extension, it still fails.
Note, that all that matters is a non-null value of the “non host store assemblies list”. If you call ExecuteInDefaultAppDomain
the CLR never actually consults this list. You may even pass any garbage value instead of the real list, the result will still be the same.