Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating a host application for the .NET Common Language Runtime.

0.00/5 (No votes)
22 Oct 2001 1  
This article explains how easy it is to write custom host applications for the .NET Common Language Runtime to run managed code.

Creating a Host to the .NET Common Language Runtime

We have all seen the different type of applications the .NET Common Language Runtime (CLR) caters for, Web applications (ASP.NET), GUI-based ones (WinForms), simple console-based ones and managed ActiveX controls hosted by Internet Explorer

When we write code targeted for the 'managed world' in the languages that .NET currently supports, .NET aware language compilers emit assemblies that contain the CPU agnostic IL in them. These assemblies are executed by the CLR

One could guess that in Windows' unmanaged world, when one invokes an executable from the Windows shell, it would have to be a WIN32 process with its own 4GB address spaces. Hence one could also guess that when one invokes a .NET application, before the managed code starts to execute on the CLR, a small amount of unmanaged code must run to creates the environment necessary for the CLR. This piece of code is known as the 'host' for the CLR

It is this host that initializes entities called AppDomains within the managed world of the .NET CLR. AppDomains are CLR's way of representing isolated application services (much like operating system tasks). One or more AppDomains can coexist within the CLR without interfering with each other, just as WIN32 processes share computing resources without interference

Some standard CLR hosts that are shipped as a part of the .NET runtime are

ASP.NET

Loads the runtime into the process that handles Web requests. ASP.NET creates an AppDomain for each Web application that will run on a Web server. ASP.NET applications are actually loaded and executed by an ISAPI filter DLL extension to the IIS web server, that accepts HTTP requests for the .aspx files associated with the application.

Internet Explorer

Creates AppDomains to run managed controls. The .NET framework supports the downloading and execution of browser-based controls. It uses the extensibility mechanism of Internet Explorer through a MIME filter to create AppDomains for any managed controls hosted on a web-site. By default, one AppDomain is created for each web-site visited.

Shell Executables

Invokes runtime hosting code to transfer control to the runtime each time an executable is launched from the shell. (Note: A .NET Portable Executable (PE) file contains unmanaged code at the entrypoint to set up the runtime and transfer control to it and thereby the rest of the managed code. Naturally, this runs first when the executable is launched from the Windows shell.)

The .NET framework allows easy usage of managed components running under the CLR in the unmanaged world: it creates COM Callable Wrappers (CCW) silently. Consider the following example for a component written in C#.

using System;
using System.Reflection;
using System.Windows.Forms;
namespace Ranjeet.SimpleCLRHost
{
    public class HelloHostDemo
    {
        public void Hi() 
        {
            MessageBox.Show("YOO HOO from the Managed World!", 
                            "And now for this message");
        }
    }
}

To use this (not very useful) C# component in an unmanaged application as a COM component, one would:

  1. compile the component as a .NET assembly (a DLL)
  2. use the command-line tool regasm.exe to register the DLL. This generates an equivalent type library for use in COM environments, and creates the traditional COM registry entries (HKCR/CLSID) for CoCreateInstance/CreateObject to locate these types.

But, have you ever wondered what exactly happens when this tool is run? Well, it generates an equivalent type-library for use in the COM environment and creates the traditional COM registry entries under "HKCR\CLSID" for CoCreateInstance or CreateObject to locate the types in it. Here's what the registry entries look like:

Notice that the InprocServer32 section's default key points to mscoree.dll? Aha!! The other keys show that there is information available to the CLR that indicate the assembly name and the type name, among other attributes of the managed component.

This begs the question: 'What is mscoree.dll doing under that InprocServer32 section?' Mscoree.dll is the Microsoft .NET Runtime Execution Engine, which hosts the .NET CLR in an unmanaged environment, and exposes a generic-hosting API for the same.

Here is the TypeLibrary generated by RegAsm.

When one wants to use the type-library generated through the RegAsm or TlbExp tools:

  1. A call is made to CoCreateInstance with the GUID of the CoClass within the generated type-library that loads mscoree.dll.

  2. The method DllGetClassObject (internally called by CoCreateInstance, traditional COM) is exported by the mscoree.dll. The COM Interoperability portion of .NET provides a standard class-factory implementation to create an instance of any .NET framework class. This class-factory is returned to the caller, who makes a call to the CreateInstance method, passing in the type requested. In our sample above, HelloHostDemo is a class with a single method Hi in it. Since no explicit interfaces is declared nor any advanced type-library overriding attributes are used, the type-library generates types for the ClassInterface, after exposing NO dispids(by default). Hence IDispatch is the default interface.

    This interface pointer is what is returned back by the call to CreateInstance from the class factory, which is the COM Callable Wrapper (known as CCW for short) that's fabricated by the CLR at runtime.

This is how .NET uses mscoree.dll to add a layer above a managed component, so that COM is totally unaware of any such thing as a managed environment.

Now that we've finished explaining (whew!) how a CCW is used to make calls in to any .NET component within an unmanaged environment, let's talk about what this article is all about, how mscoree.dll actually loads the CLR within the process address-space of the callee and how we can write a very simple host for the CLR.

Here is what our simple CLR host would do:

  1. host the Common Language Runtime using CLR's Hosting API's.
  2. load a type from a .NET assembly - HelloHostDemo, in our example, from the HelloMsgBox.dll assembly. (See instructions on how to build the sample for the .NET Framework SDK, Beta2.)
  3. retrieve this type as a IDispatch pointer and use that to locate the dispid of the method (Hi, in our example) through a call to GetIDsOfNames.
  4. Use Invoke on the IDispatch pointer with the dispid retrieved, and that's all!

The most important hosting API is CorBindToRuntimeEx as described by Steven Pratschner from Microsoft, is more like a 'shim' code that exports the above mentioned API.

CorBindToRuntimeEx accepts version numbers of CLR's and other configurable parameters based on which it returns an instance of the ICorRuntimeHost interface pointer.

The Hosting API lets a custom host fine tune some of the CLR settings like:

  1. Version of the CLR to be loaded
  2. Build of the CLR to be loaded, Workstation or Server for client applications and multiprocessor server scenarios respectively. In case of multi-processor machines the server build allows garbage collection to be done on each processor in parallel.
  3. Specifying the garbage collection settings as concurrent or non-concurrent. Concurrent means garbage collections happens on background threads rather than the thread running user code, hence beneficial for UI apps. Non-concurrent means garbage collection happens on the same thread as the thread running user code, mostly used for server/UI-less Apps. Anyway, on single processor machines non-concurrent GC happens only.
  4. Domain Neutrality settings, that specify the way in which copies of frequently used Assemblies in multiple AppDomains are loaded, either different copies for all AppDomains or Shared copy across all AppDomains, the latter being AppDomain neutral.

The call to this function is made as follows:

// Retrieve a pointer to the ICorRuntimeHost interface

HRESULT hr = CorBindToRuntimeEx(
                 NULL,   //Retrieve latest version by default

                 L"wks", //Request a WorkStation build of the CLR

                 STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | 
                 STARTUP_CONCURRENT_GC,
                 CLSID_CorRuntimeHost,
                 IID_ICorRuntimeHost,
                 (void**)&spRuntimeHost
                 );

The ICorRuntimeHost allows the host to specify additional configurable parameters like

  1. start/stop control over CLR, e.g.:

    spRuntimeHost->Start();
  2. retrieve a pointer to the default AppDomain of the managed code, e.g.:

    spRuntimeHost->GetDefaultDomain(&pUnk);
  3. retrieve a pointer to the ICorConfiguration through which additional fine tuning can be achieved (see the documentation for CLR issues involved.)

There! Notice that in the SimpleCLRHost example, we find that no call is made to to the traditional CoInitialize function before retrieving the COM interface, so no apartment is specified explicitly. I have a clue to this, but need more ideas to confirm/clarify my suspicions.

To run the sample, first run buildmanaged.bat before building and executing the SimpleCLRHost.

There are a couple of things to note before running the sample:

  1. The sample uses the header file mscoree.h which is located typically in the C:\PROGRAM FILES\ MICROSOFT.NET\FRAMEWORKSDK\INCLUDE directory, and so I chose to add that in my list of include paths through Tools -> Options in Visual Studio.
  2. It uses the MsCorLib.Tlb which provides interface definitions used within the program, which is located typically in the path C:\WINNT\Microsoft.NET\Framework\v1.0.2914 for beta2 versions of the CLR, so I just hardcoded the path.

References:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here