Customizing the Default Host Using Configuration Files
The default behavior described previously can be customized for the version, build type, and garbage collection settings using application configuration files. The only setting you cannot change using a configuration file is the specification for which assemblies should be loaded domain neutral. The
Application configuration files are Extensible Markup Language (XML) files that are associated with an application by naming convention and directory. When using the default host, configuration files must be placed in the same directory as the application and are named by simply appending
.config
to the
Configuration files can be used to customize the settings for both the managed executable scenario and the COM interoperability scenario described earlier. It might seem odd that a configuration file can be used to customize the CLR in the COM interoperability scenario. After all, the configuration file has settings that are relevant only to managed programs, yet the main executable for the application in this scenario is unmanaged. This works because the shim looks for the configuration file based on the name of the executable that started the process (as obtained by calling the Microsoft Win32 API GetModuleFileName ). The fact that the primary executable does not consist of managed code is irrelevant. There are two ways to edit configuration files: by hand or with the .NET Framework configuration tool. For purposes of this discussion, the configuration tool is somewhat limited in that it doesn't enable you to set the CLR version that should be used to run the application. For this reason, and for a better understanding of what's going on under the covers, I stick to showing the XML itself. However, I point out the scenarios in which the tool can be used instead. In the following sections, I describe how to use configuration files to customize the garbage collection, build type, and version settings. Concurrent Garbage Collection
Recall that the default setting for concurrent garbage collection is on. That is, collections will happen on background threads while the application is running if the application is running on a multiprocessor machine. As described, the default for concurrent garbage collection and that of the build type (workstation) work
The value for concurrent garbage collection is set using the <gcConcurrent> element in the configuration file. This element must always be nested inside the <runtime> element. (See the .NET Framework SDK documentation for a description of the complete configuration file schema.) The element <gcConcurrent> has a single attribute called enabled whose value is either the string " true " (the default) or " false ". The following configuration file shows how to disable concurrent garbage collection for a given application:
<configuration>
<runtime>
<gcConcurrent enabled="false" />
</runtime>
</configuration
Concurrent garbage collection is one of the settings that can be changed using the .NET Framework configuration tool as well. You can find a shortcut to the tool in Control Panel under Administrative Tools. I don't go into detail on using the tool here, but if you've never used the tool, an overview can be found in the SDK guide. The options for concurrent garbage collection are shown in the Properties dialog box for an application, as shown in Figure 4-5. Figure 4-5. Setting concurrent garbage collection using the .NET Framework configuration tool
The Properties dialog box
Be careful not to confuse the
Build TypeYou can change the default build type of workstation to server using the gcServer element in an application configuration file. The gcServer element has an attribute called enabled that you set to true if you'd like to run the server build. Keep in mind that the server build is loaded only on multiprocessor machines, however. Even if you set gcServer to true on a single-processor machine, the workstation build will be loaded. The following configuration file uses the gcServer element to specify that the server build of the CLR should be used:
<configuration>
<runtime>
<gcServer enabled="true" />
</runtime>
</configuration
Unlike the settings for concurrent garbage collection, there is no support in the .NET configuration tool for setting the build typeyou must edit the XML directly. As I explained in Chapter 3, you can also specify a build type using the pwszBuildFlavor parameter to CorBindToRuntimeEx . If you specify a build type using both CorBindToRuntimeEx and the gcServer element in the application's configuration file, the setting passed to CorBindToRuntimeEx takes precedence. Changing the Build Type on Older Versions of the CLRThe ability to specify a build type using an application configuration file is new in .NET Framework 2.0. If you want to load the server build for executables running with either .NET Framework 1.0 or .NET Framework 1.1, you must write a small CLR host that uses CorBindToRuntimeEx to specify the build type. Fortunately, it's very easy to write an application launcher that calls CorBindToRuntimeEx to set the server build and then runs the original application.
The following sample does just that. svrhost.exe is a CLR host that can be used to run managed executables from the command line using the server build of the CLR. svrhost.exe takes the managed executable to run as a command-line parameter. In addition to the executable, you can also pass any command-line arguments for the managed executable to svrhost.exe as well. For example, say you have an application called paystub.exe that you normally invoke as
C:\> paystub stevenpr 013103 To run paystub.exe with the server build of the CLR, you'd invoke it using svrhost.exe as follows: C:\> svrHost paystub stevenpr 013103
The code for svrhost.exe is very straightforward, as shown in Listing 4-1. svrhost.exe uses
CorBindToRuntimeEx
to load the server build of the CLR. After the CLR has started, svrhost.exe gathers the name of the program to execute along with any arguments from the command line. It then uses the
ExecuteAssembly
method on the
System.AppDomain
class to run the specified program in the default application domain. Notice that svrhost.exe uses an interface called
ICorRuntimeHost
instead of the
ICLRRuntimeHost
interface I introduced in Chapter 2.
ICorRuntimeHost
is the primary hosting interface in .NET Framework 1.0 and .NET Framework 1.1. In .NET Framework 2.0, that interface is
Listing 4-1. svrhost.cpp
#include "stdafx.h"
// needed for CorBindToRuntimeEx
#include <mscoree.h>
// include the typelib for mscorlib for access to the default AppDomain through COM Interop
#import <mscorlib.tlb> raw_interfaces_only high_property_prefixes("_get","_put","_putref")
using namespace mscorlib;
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
printf("Usage: SvrHost <Managed Exe> [Arguments for Managed Exe]\n");
return 0;
}
ICorRuntimeHost *pCLR = NULL;
// Initialize the CLR. Specify the server build. Note that I'm
// loading the CLR from .NET Framework 1.1.
HRESULT hr = CorBindToRuntimeEx(
L"v1.1.4322",
L"svr",
NULL,
CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(PVOID*) &pCLR);
assert(SUCCEEDED(hr));
// Start the CLR
pCLR->Start();
// Get a pointer to the default AppDomain
_AppDomain *pDefaultDomain = NULL;
IUnknown *pAppDomainPunk = NULL;
hr = pCLR->GetDefaultDomain(&pAppDomainPunk);
assert(pAppDomainPunk);
hr = pAppDomainPunk->QueryInterface(__uuidof(_AppDomain),
(PVOID*)&pDefaultDomain);
assert(pDefaultDomain);
// get the name of the exe to run
long retCode = 0;
BSTR asmName = SysAllocString(argv[1]);
// Collect the command-line arguments to the managed exe. These must be
// packaged as a SAFEARRAY to pass to ExecuteAssembly.
SAFEARRAY *psa = NULL;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = (argc - 2);
psa = SafeArrayCreate(VT_BSTR, 1, rgsabound);
assert(psa);
for (int i = 2; i < argc; i++)
{
long idx[1];
idx[0] = i-2;
SafeArrayPutElement(psa, idx, SysAllocString(argv[i]));
}
// Run the managed exe in the default AppDomain
hr = pDefaultDomain->ExecuteAssembly_3(asmName, NULL, psa, &retCode);
assert(SUCCEEDED(hr));
// clean up
SafeArrayDestroy(psa);
SysFreeString(asmName);
pAppDomainPunk->Release();
pDefaultDomain->Release();
_tprintf(L"\nReturn Code: %d\n", retCode);
return retCode; }
Version
Microsoft's goal is to keep each release of the CLR as backward compatible as possible with all previous releases. (I say
as possible
because we all know that complete compatibility can never be
The default behavior of the shim is to run an application with the version of the CLR used to build it (minus the upgrades scenarios I discussed). However, it might be the case that you as an application author want to be able to take advantage of a new CLR when it is released without having to rebuild and re-release your application. Each new version of the CLR will undoubtedly contain
The < supportedRuntime > Element
The default host enables you to state your
You might also specify more than one <supportedRuntime> element. This enables your application to run on any machine that has at least one of your supported versions. If, through testing, you determine that several different versions of the CLR are acceptable, supplying the full list can dramatically broaden the number of machines your application can run on without you having to worry about redistributing a given version of the .NET Framework.
Clearly, the safest way to determine which CLR versions your application can run on is through testing. As discussed earlier, backward compatibility cannot be
If you've specified multiple <supportedRuntime> elements, the order in which the elements appear in the configuration file is the order in which the shim will try to load those versions of the CLR. If none of the versions you specify can be found, the shim will display an error and your application won't run. All <supportedRuntime> elements must be nested inside the <startup> tag of the runtime section of your configuration file. The value of the version attribute is a string in the following format:
"v + <major number> + <minor number> + <build number>"
For example, setting version to "v1.1.4322" indicates that your application can be run with the version of the CLR that shipped with .NET Framework 1.1. Recall from the previous discussion in Chapter 3 of how the shim
Here's an example configuration file that causes the shim to attempt to run the application with the last two
<configuration>
<startup>
<supportedRuntime version="v2.0.41013" />
<supportedRuntime version="v1.1.4322" />
</startup>
</configuration
By design, the
<supportedRuntime>
element enables you to run your application with a version of the CLR that is different from the one used to develop the application. So it's important to use this element with caution. The whole
The < requiredRuntime > Element and .NET Framework 1.0
The
To support this scenario, you need a configuration file that contains a <requiredRuntime> element for the original version of the CLR and a <supportedRuntime> element for .NET Framework 1.1. Here's an example:
<configuration>
<startup>
<supportedRuntime version="v1.1.4322" />
<requiredRuntime version="v1.0.3705" />
</startup>
</configuration>
This configuration file enables an application built with either .NET Framework 1.0 or .NET Framework 1.1 regardless of which version is installed on the machine.
Note
As described earlier, the order of these elements in the configuration file determines their priority. Hence, if both versions 1.1.4322 and 1.0.3705 are installed, 1.1.4322 will be used. If
Unfortunately, your configuration file needs more work if you want to run an application built with .NET Framework 1.1 on .NET Framework 1.0. (Keep in mind, too, that these scenarios are restricted to the cases in which you're sure you don't rely on any features that aren't available in .NET Framework 1.0and that you've tested your application on that version.) Recall that in Chapter 3 I said that when you pick a version of the CLR to run your application, you also get the matching set of class library assemblies by default. Recall also that this behavior occurs only when either .NET Framework 1.1 or .NET Framework 2.0 is installed on the machine. If only .NET Framework 1.0 is available, you must include additional statements in the configuration file to redirect all assembly references back down to the versions that shipped with .NET Framework 1.0. You do this using the <bindingRedirect> tag in your application configuration file. You must include one <bindingRedirect> tag for each assembly that ships in the .NET Framework redistributable (unless you're sure there are assemblies you don't use). I know this sounds cumbersome, but fortunately, the same configuration file works for all applications, so once you get it right, you're set. Here's the configuration file used earlier with <bindingRedirect> entries for the .NET Framework assemblies. (I haven't included them all for the sake of brevity but the entire configuration file is available from the Microsoft Press download site for this book. See the Introduction of this book for the URL.)
<configuration>
<startup>
<supportedRuntime version="v1.1.4322" />
<requiredRuntime version="v1.0.3705" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security"
publicKeyToken="b03f5f7f11d50a3a" culture=""/>
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535"
newVersion="1.0.3300.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Remoting"
publicKeyToken="b77a5c561934e089" culture=""/>
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535"
newVersion="1.0.3300.0"/>
</dependentAssembly>
<--! Lots of others need to be included here.....
</assemblyBinding>
</runtime>
</configuration>
If you've been following along closely and are familiar with how
<bindingRedirect>
statements work, you're probably thinking that the configuration file I've just shown works great if my application runs with .NET Framework 1.0, but what if my application runs with .NET Framework 1.1 or 2.0? Won't all my assembly references get redirected down to the versions of the class library assemblies that shipped with .NET Framework 1.0? Well, the answer is yesthe configuration file as I've defined it doesn't work with all versions of the .NET Framework. What I'd really like is for the
<bindingRedirect>
statements to apply as specified if my application runs with .NET Framework 1.0 but to be ignored if my application runs with a later version (because later versions will ensure that my references to system assemblies automatically get redirected to the version that matches the CLR I've
To accommodate this scenario, you must add the appliesTo attribute to the <assemblyBinding> element (under which all of your <bindingRedirect> statements lie) in your application configuration file. The value of the appliesTo attribute is the version of the CLR to which the statements in the <assemblyBinding> section of the configuration file apply. In other words, the <bindingRedirect> statements take effect only if the version of the CLR identified by the appliesTo attribute is loaded. In this case, I clearly want the <bindingRedirect> statements to apply only if version 1.0.3705 (also known as .NET Framework 1.0) is loaded. As a result, the final configuration file looks like this:
<configuration>
<startup>
<supportedRuntime version="v2.0.1111" />
<supportedRuntime version="v1.1.4322" />
<requiredRuntime version="v1.0.3705" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"
appliesTo="v1.0.3705">
<dependentAssembly>
<assemblyIdentity name="System.Security"
publicKeyToken="b03f5f7f11d50a3a" culture=""/>
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535"
newVersion="1.0.3300.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Remoting"
publicKeyToken="b77a5c561934e089" culture=""/>
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535"
newVersion="1.0.3300.0"/>
</dependentAssembly>
<--! Lots of others need to be included here...
</assemblyBinding>
</runtime>
</configuration>
|