Instantiation of CLR Classes in Win32 Native Applications
COM-visible .Net objects are instantiated by a standard COM call, for example CoCreateInstance. COM finds the CLSID either in the registry or in the activation context, loads and calls mscoree.dll which continues the process of activating the CLR and creating the object. During this process you may get error 0x80131700 "Failed to load the runtime" or 0x8013101b "This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded".
Both messages are the result of runtime version mismatch. 0x80131700 does not necessarily mean any corruption, it simply means that the runtime cannot be loaded. Possibly because it is not installed, or, the needed version is not installed. 0x8013101b means that a runtime is loaded but turns out to not match the version required to proceed with processing.
Let us consider the Visual Studio example included with Manifest Maker. The native host program here is cmdEXE.exe, the .Net object is implemented in clrDLL.dll. For the purpose of this illustration we rebuilt the project without embedding the CLR manifest in the DLL. As a result we have clrDLL.manifest file and the DLL in a sub-folder clrDLL\clrDLL.dll. As we now execute "cmdEXE.exe clr" the program instantiates the CLR object using CoCreateInstance:
clrDLL::IClrSampleClassPtr scp; HRESULT hr = scp.CreateInstance(__uuidof(clrDLL::ClrSampleClass), nullptr, CLSCTX_INPROC_SERVER); if (S_OK != hr) { wprintf(L"\nCLR class instantiation failed, hresult = 0x%08X\n", hr); return hr; }
The clrDLL.manifest includes this entry:
<clrClass name="clrDLL.ClrSampleClass" clsid="{A1B62C33-E218-469D-81C4-10D4F2C608F7}" progid="clrDLL.ClrSampleClass" threadingModel="Both"/>
The sequence of events is:
- We run "cmdEXE.exe clr"
- CSRSS creates the process, finds the application manifest and creates the activation context which includes clrDLL.manifest.
- The program calls COM to create the object.
- COM finds CLSID in the activation context, notes that this is <clrClass>, loads and calls mscoree.dll.
- mscoree.dll loads the actual .Net runtime and initializes it.
- The runtime (currently mscoreei.dll) reads configuration from system and cmdEXE.exe.config
- The runtime creates the requested object.
Excerpt from Process Monitor log:
1:38:32.0379228 PM csrss.exe 500 ReadFile C:\Projects\Examples\vs2022\_Debug-Win32\clrDLL.Manifest 1:38:32.0913937 PM cmdEXE.exe 3320 Load Image C:\Windows\SysWOW64\mscoree.dll 1:38:32.0980055 PM cmdEXE.exe 3320 Load Image C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll 1:38:32.1078790 PM cmdEXE.exe 3320 CreateFile C:\Projects\Examples\vs2022\_Debug-Win32\cmdEXE.exe.config
As can be seen from the above, while mscoree decides which version of .Net runtime to load, it does not have the contents of cmdEXE.exe.config. This means that this file will not help in resolving any runtime version problem. By the time this file is processed the runtime is already loaded.
If the <clrClass> is written as shown above, mscoree will decide that this class needs .Net 2.0 and will attempt to load this version of the runtime. If this computer does not have .Net 2.0, the call fails with error 0x80131700 and a dialog is displayed with an option to install .Net 2.0:
If runtime is 2.0 is available and loaded but the DLL is built with 4.0, we get error 0x8013101b. So there is no way to successfully instantiate the .Net class defined as shown above. The <clrClass> entry must be modified to include "runtimeVersion" attribute:
<clrClass name="clrDLL.ClrSampleClass" clsid="{A1B62C33-E218-469D-81C4-10D4F2C608F7}" progid="clrDLL.ClrSampleClass" runtimeVersion="v4.0.30319" threadingModel="Both"/>
Note that "runtimeVersion" is different from supportedRuntime.
.Net expects "supportedRuntime" to contain "v4.0" while "runtimeVersion" must contain the actual, full, runtime version. This means that to choose
.Net 4.0 runtime you must specify runtimeVersion="v4.0.30319"
. As of this writing the following runtime versions can be specified:
- v1.0.3705
- v1.1.4322
- v2.0.50727
- v4.0.30319
Additionally .Net seems to accept "v3.0" and "v3.5" as aliases for version 2.0.
A quick way to determine the actual version number is to look at subfolder names in C:\Windows\Microsoft.NET\Framework and C:\Windows\Microsoft.NET\Framework64 folders. The ultimate way to find out the installed versions is to run the following code:
ICLRMetaHost *pMetaHost; HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void **)&pMetaHost); IEnumUnknown* pEnum = 0; if (S_OK == pMetaHost->EnumerateInstalledRuntimes(&pEnum)) { ULONG celt = 0; ICLRRuntimeInfo *pRuntime; while (S_OK == pEnum->Next(1, (IUnknown**)&pRuntime, &celt)) { WCHAR version[256] = { 0 }; DWORD chars = _countof(version); if (S_OK == pRuntime->GetVersionString(version, &chars)) wprintf(L"Found: %s\n", version); pRuntime->Release(); } pEnum->Release(); }
Manifest Maker uses this technique to populate the CLR Version property page in Project Options dialog.
Sanity check: Windows is caching manifest information for each program. If you are experimenting with manifests you must change timestamp of every participating file, including executables, to make Windows use fresh, not cached, information.