Adding a Custom Host for the Common Language Runtime

The first thing that needs to happen when writing a custom CLR host is to load and initialize the CLR. This happens through a small piece of code that takes a version and other startup information and starts the CLR. This code is known as a “shim” and it is implemented in a file called mscoree.dll. On startup, a process will get a pointer to an interface in this shim and through that interface initialize and start the CLR.

To get an interface to this shim, call CorBindToRuntimeEx. Listing C.1 shows how to make a call to this function. The complete source that is associated with this listing and the following code snippets are available in the CustomCLR directory for this Appendix.

Listing C.1. Loading the CLR into the Process
LPWSTR pszVer = L"v1.0.2204";
LPWSTR pszFlavor = L"wks";
ICorRuntimeHost *pHost = NULL;

//
// CorBindToRuntime is the primary api host
// used to load the CLR into a process.
// In addition to version and "svr" vs "wks"
// concurrent gc, and "no
// domain neutral code ("single domain host")
// is selected.
HRESULT hr = CorBindToRuntimeEx(
                  //version
                  pszVer,
                  // svr or wks
                  pszFlavor,
                  //domain-neutral"ness" and gc settings
                  STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN |
                      STARTUP_CONCURRENT_GC,
                  CLSID_CorRuntimeHost,
                  IID_ICorRuntimeHost,
                  (void **)&pHost);

The end result of calling this function is a pointer to an interface that can control the running of the CLR. Of course, if the HRESULT returned FAILED (or !(SUCCEEDED)), then the pointer is not modified. If it succeeds, then an ICorRuntimeHost interface is returned. This function takes three arguments that control the CLR that is loaded, the version, the CLR type, and the optimization and GC flags.

The version string is a UNICODE (two bytes per character) string specifying the version of the CLR that is to be loaded. It is of the form “vmajor.minor.build”. An example of the CLR for the released version would be “v1.0.3705”. This is just a suggestion to the loader as to what version should be loaded. To get the version that was actually loaded, call GetCORVersion. Null can be passed to CorBindToRuntimeEx, in which case the host is delegating the decision about which version to load (normally, this is the latest version of the CLR).

The next argument is also a UNICODE string that can have two values: “wks” or “svr,” indicating a workstation or server build respectively. If “wks” is specified, it is a request to use the CLR that is implemented in mscorwks.dll. If “svr” is specified, it is a request to use the CLR that is implemented in mscorsvr.dll. These two DLLs are loaded from the directory that is specified by the version string or the version that is loaded. If NULL is passed as an argument, the workstation build is loaded. The workstation build is optimized for client applications. The server build is tuned to a server type of environment. For example, the server build takes advantage of multiple processors on a single machine and performs garbage collection on a separate processor in parallel with the mainline code. If only one processor exists on the machine, then specifying “svr” doesn't always revert to “wks”.

The last flag specifies the mode in which the garbage collector is to run. These modes might or might not be concurrent. The flag STARTUP_CONCURRENT_GC indicates that CLR should perform garbage collection on a background thread rather than on any one of the user threads. If the flag is missing, then the CLR garbage collection is run in series with the user code and on threads that the user code has allocated. This mode of operation is less responsive than the concurrent mode, but the overall performance of the GC is better.

In addition to the GC flags, three optimization flags determine how assemblies are loaded into the application domain. These three flags are as follows:

  • STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN—This flag specifies single domain mode. It is assumed that every domain will have a single assembly.

  • STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN—This flag specifies domain-neutral mode. If multiple assemblies are loaded into a domain, this flag allows for the static and read-only portions of the code to be shared among the assemblies loaded into the GC.

  • STARTUP_LOADER_OPTIMIZATION_—This flag specifies that strong named assemblies (shared assemblies) should be loaded domain neutral.

Note that these flags are not flags in the same sense of the term used for enums. Each of these flags is mutually exclusive and cannot be combined.

The last two input arguments specify the CLSID for the class that is implementing the host interface and the IID for the host interface respectively.

When CorBindToRuntimeEx completes successfully, you have an ICorRuntimeHost interface that has the following methods you can call:

  • Start— Starts the runtime. In general, it is not necessary to call this method because the CLR is automatically started when the first application domain is created or the first request to run managed code is received.

  • Stop— Unloads the CLR from the process. In general, it is not necessary to call this method because the CLR is automatically shut down when the process exits. Note that when the CLR is unloaded using Stop(), it cannot be reinitialized into the same process.

  • CreateDomain— Creates an application domain. The caller receives an interface pointer to an instance of System.AppDomain that can be used to further control the domain. The type of this interface pointer is _AppDomain. Compare this to the static function to create an application domain in managed space, that is, AppDomain.CreateDomain.

  • CreateDomainEx— Extends the functionality of CreateDomain by allowing callers to pass in an IAppDomainSetup interface that contains properties used to configure the domain. Compare this to the static function to create an application domain in managed space, that is, AppDomain.CreateDomain.

  • CreateDomainSetup— Returns an interface pointer of type IAppDomainSetup. IAppDomainSetup contains methods that allow a host to configure various aspects of a domain before it is created. The pointer returned from this method is typically passed to CreateDomainEx. Compare this function with the AppDomainSetup class with its associated members and constructors.

  • CreateEvidence— Returns an interface pointer of type IIdentity. IIdentity allows you to create security evidence that is passed to CreateDomain(Ex). Compare this method with the implementations of IIdentity in the WindowsIdentity, PassportIdentity, GenericIdentity, and FormsIdentity. Also look at the Evidence class in the System.Security.Policy namespace.

  • GetConfiguration— Returns an ICorConfiguration interface that has the methods to control how the process and AppDomain interact with a debugger and customize the size of the GC heap and callbacks for thread control and AppDomain loading events.

  • GetDefaultDomain— Returns an interface pointer to the default domain for the process. Compare this with the AppDomain.CurrentDomain property.

  • UnloadDomain— Unloads the domain. Compare this with the AppDomain.Unload static method.

  • EnumDomains— Returns an enumerator that a host can use to enumerate the domains in the process.

  • NextDomain— Returns the next domain in the enumeration. This method returns S_FALSE when the enumeration has no more domains.

  • ResetDomain— Resets the enumerator back to the beginning of the list.

After you have an ICorRuntimeHost interface, the CLR has been loaded in this process. You might want to see just what version of the CLR has been loaded. The version string in CorBindToRuntimeEx was just a suggestion. You can get the version of the CLR that is loaded in your process as follows:

wchar_t buffer[128];
DWORD dwBytes;
hr = GetCORVersion(buffer, sizeof(buffer), &dwBytes);
wprintf(L"Version %ls of the CLR loaded...
", buffer);

To start running managed code, you need to get a pointer to an AppDomain. You can use the methods on the ICorRuntimeHost to create new AppDomains and load and run assemblies into these AppDomains. This involves many transitions between managed and unmanaged space. In addition, you need to keep track of the last AppDomain that unloads because unloading the last AppDomain also implicitly calls Stop and unloads the CLR from the process. After the CLR is unloaded from the process, it cannot be reloaded. For those reasons, you typically want to build a small stub assembly that starts and stops the AppDomains to be associated with your process. To get a pointer to the default AppDomain for the process, you need to execute code that looks like Listing C.2.

Listing C.2. Getting a Pointer to the Default AppDomain
//
// Get a pointer to the default domain in the process.
//
AppDomain *pDefaultDomain = NULL;
IUnknown   *pAppDomainPunk = NULL;
hr = pHost->GetDefaultDomain(&pAppDomainPunk);
hr = pAppDomainPunk->QueryInterface(__uuidof(_AppDomain),
                                    (void**) &pDefaultDomain);

These lines of code return an interface to the default AppDomain for the process. Listing C.3 shows how to load and run the managed hosting code.

Listing C.3. Loading and Running the Managed Hosting Code
//
// Load the managed portion of our host into the default domain.
//
ICorRun *pMgdHost = NULL;
ObjectHandle *pObjHandle = NULL;

hr = pDefaultDomain->CreateInstance(_bstr_t("MgdHost"),
        bstr_t("ClrHost.MgdHost.HostProcessRequest"),
        &pObjHandle);
if(FAILED(hr))
    return;
printf("Managed hosting code successfully created...
");

VARIANT v;
VariantInit(&v);
hr = pObjHandle->Unwrap(&v);

hr = v.pdispVal->QueryInterface(__uuidof(ICorRun),
                                (void**) &pMgdHost);
if(FAILED(hr))
    return;
pMgdHost->Run(_bstr_t("DiningPhilosophers"),
              _bstr_t("DiningPhilosophers.exe "));

The CreateInstance method is looking for an assembly; therefore, that assembly needs to be located so that the AppDomain can find it. An AppDomain first starts out looking for an assembly by using its base directory. This is also one of the first steps in securing your application. You should put the assembly in the same directory as specified by the BaseDirectory or ApplicationBase so that the AppDomain can find it.

After the host is inside managed code (in the preceding example, you enter managed code by executing the Run method), it must ensure that the ApplicationBase and the ConfigurationFile properties are set. The ApplicationBase is the first place that the AppDomain looks for assemblies. The ConfigurationFile property is a path to an XML configuration file that specifies settings for assembly versioning and locating types that the application accesses remotely.

If the CLR cannot find a given assembly, it raises an event AssemblyResolve. If the assembly for the particular host is in memory (in the case of Reflection.Emit) or otherwise hidden, then this event handler resolves and returns an Assembly. The simplicity of hooking up to this event handler is shown in Listing C.4.

Listing C.4. Resolving an Assembly Reference
private Assembly AssemblyResolveHandler(object sender, ResolveEventArgs e)
{
    // resolve the assembly
}
. . .
ad.AssemblyResolve += new ResolveEventHandler(AssemblyResolveHandler);

Another possibly interesting event that can be listened to would be the event that is raised any time an Assembly is loaded. Listing C.5 shows hooking up to this event.

Listing C.5. Loading an Assembly Event
private void AssemblyLoadHandler(object sender, AssemblyLoadEventArgs e)
{
    Console.WriteLine("Loading: {0} ", e.LoadedAssembly.GetName().Name);
    return;
}
. . .
// Hook up a load handler
ad.AssemblyLoad += new AssemblyLoadEventHandler(AssemblyLoadHandler);

All of this is customization. You can (and should) add a specific security policy for a custom CLR host, special configuration, and so on. Now that you are in managed code, you can specialize many different settings.

Note

This Appendix briefly showed how you can add a custom host for the CLR. Each of the existing hosts for the CLR follows this same procedure to start up the CLR. This Appendix has shown where it is appropriate to add security and configuration information for a custom host to increase isolation and security. Now you can build your own or at least have a better idea of what is going on when you execute your next managed application. Feel free to modify and enhance the code in the CustomCLR directory.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.111.183