ASP.NET Internals – Managed runtime loader
Introduction
There is a file called aspnet.config in the .Net framework installation. This file is used to specify startup flags for both ASP.NET and CLR for those settings that are needed very early in the worker process lifetime, when the config system is not yet present.
In IIS 7.5 we added an attribute to the <applicationPools> configuration collection called CLRConfigFile, on the applicationHost.config file, which allows you to specify your own aspnet.config, for a particular application pool.
There is another attribute on the <applicationPools> called managedRuntimeLoader, which specify the library in charge of loading CLR. By default, this task is executed by ASP.NET native layer, in the webengine4.dll. If no loader is specified, or if an error occurs, the loading falls back to IIS itself.
IIS 7.5 issue.
There was a problem in IIS 7.5, in which the managedRuntimeLoader was never provided with the path to this CLRConfigFile, so it was forced to use the one in the framework directory. So if you wanted to use the CLRConfigFile, to setup different CLR flags for an application pool, you will need to set the attribute AND explicitly clear the managedRuntimeLoader attribute on the application pool definition on applicationHost.config:
...
<system.applicationHost>
...
<applicationPools>
<!-- Set the CLRConfigFile and clear managedRuntimeLoader -->
<add name="MyAppPool" CLRConfigFile="c:\myconfigs\myapp1\aspnet.config" managedRuntimeLoader="" />
...
The second step shouldn’t be necessary and is confusing. The clearing of the attributes basically forces the fall back to IIS runtime loader, which uses the correct CLRConfigFile.
This has been fixed on IIS 8, where you only need to set the CLRConfigFile attribute.
To verify this is working, you can change one parameter on the aspnet.config such as disabling the server garbage collector. (Note: Disabling the server garbage collector is a good thing if you want to save memory, at the expense of throughput. Typical use of this is hosting scenarios, where web sites has low traffic and higher site density is desirable.)
<configuration>
<runtime>
...
<gcServer enabled="false"/>
Attaching a debugger to the worker process and using SOS command !eeversion will show the garbage collector mode being used:
C:\>cdb.exe -pn w3wp.exe
Microsoft (R) Windows Debugger Version 6.13.0009.1140 X86
Copyright (c) Microsoft Corporation. All rights reserved.
…
0:044> .loadby sos clr
0:044> !eeversion
4.0.30319.239 retail
Workstation mode
SOS Version: 4.0.30319.239 retail build
Make sure your box has more than one logical core; otherwise workstation mode will always be used.
Implementing a loader
Implementing a loader is probably not needed, but if you want to do it, you will need a native DLL that exports a function named LoadManagedRuntime. In IIS 8 you can also export LoadManagedRuntimeEx. The function signature is shown below:
HRESULT __stdcall
LoadManagedRuntime(
_In_ PCWSTR pwszRuntimeVersion,
__deref_out IUnknown ** ppManagedRuntimeHost)
{
return LoadRuntimeInternal(pwszRuntimeVersion, ppManagedRuntimeHost);
}
HRESULT __stdcall
LoadManagedRuntimeEx(
_In_ PCWSTR pwszRuntimeVersion,
_In_ PCWSTR pszClrConfigFile,
__deref_out IUnknown ** ppManagedRuntimeHost)
{
return LoadRuntimeInternal(pwszRuntimeVersion, ppManagedRuntimeHost, pszClrConfigFile);
}
HRESULT LoadRuntimeInternal(_In_ PCWSTR pwszRuntimeVersion, __deref_out IUnknown** ppHost /*= NULL*/, _In_ PCWSTR pwszAspNetHostConfig /*= NULL*/) {
HMODULE hModule = NULL;
HRESULT hr = S_OK;
IUnknown *pHost = NULL;
PFNCorBindToRuntimeHost pCorBindToRuntimeHost;
TCHAR szPath[MAX_PATH + 1];
TCHAR szAspConfig[MAX_PATH + 1];
ExpandEnvironmentStrings(L"%windir%\\system32\\mscoree.dll", szPath, MAX_PATH);
if(pwszAspNetHostConfig == NULL) {
ExpandEnvironmentStrings(ASPNET_CONFIG, szAspConfig, MAX_PATH);
pwszAspNetHostConfig = szAspConfig;
}
hModule = LoadLibrary(szPath);
ON_NULL_EXIT(hModule);
pCorBindToRuntimeHost = (PFNCorBindToRuntimeHost)GetProcAddress(hModule, "CorBindToRuntimeHost");
ON_NULL_EXIT(pCorBindToRuntimeHost);
hr = pCorBindToRuntimeHost(
L"v4.0.30319",
L"svr",
pwszAspNetHostConfig,
NULL, // reserved
STARTUP_CONCURRENT_GC|STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST|STARTUP_LOADER_SAFEMODE|STARTUP_DISABLE_COMMITTHREADSTACK|STARTUP_HOARD_GC_VM,
CLSID_CLRRuntimeHost,
IID_IUnknown,
(VOID**)&pHost);
Cleanup:
if (pHost != NULL && ppHost != NULL) {
pHost->AddRef();
*ppHost = pHost;
}
if(hModule != NULL) {
FreeLibrary(hModule);
}
return hr;
}
See the attached project for the full sample code. The sample can be compiled into a native DLL and the managedRuntimeLoader attribute can be set to point to that DLL.
...
<system.applicationHost>
...
<applicationPools>
<!-- Set the CLRConfigFile and managedRuntimeLoader -->
<add name="MyAppPool" CLRConfigFile="c:\myconfigs\myapp1\aspnet.config" managedRuntimeLoader="c:\mynativedlls\myruntimeloader.dll" />
...
Note that debugging such a code on IIS will require the use of “Image File Execution Options”, since the code is executed very early on the worker process lifecycle. An easier way to test might be to load the library yourself and call the functions, the way IIS would (see the test project).
Conclusion
IIS 7.5 required for you to clean the managedRuntimeLoader attribute, if you wanted to change CLR parameters on your own CLRConfigFile.
Implementation of a managed runtime loader requires a native DLL which exports a LoadManagedRuntime() and/or LoadManagedRuntimeEx().
Thanks for reading.
Comments
Anonymous
March 04, 2012
Shouldn't pwszRuntimeVersion be used somewhere within LoadRuntimeInternal? I'd have expected it to be used (or can you call out why it's not used/relevant?)Anonymous
March 05, 2012
Yes, it can be used to determine the first parameter for CorBindToRuntimeHost(). We hardcoded the version on the sample for simplicity. I believe it doesn't have the same format though. So you might need to add some logic there.