Exploring the ADMHost Sample

When I first talked about AppDomainManagers, I mentioned that there were three ways to set them up.  You can either setup an environment block, use some registry keys, or use the unmanaged hosting API.  In most of my samples so far I've used the environment variables, and in fact I discourage using the registry keys.

That leaves the unmanaged hosting API as the only entry point for setting up an AppDomainManager that I have left to explore.  Since this involves a decent chunk of code, I've posted it as a downloadable MSDN sample, ADMHost rather than putting it in the blog directly.

ADMHost exists as a basic template of an application that uses managed and unmanaged hosting -- by itself it's not very useful, in fact it just prints Hello World and exits.  However, it does demonstrate how to split your host into a managed and an unmanaged half using AppDomainManagers, and you can replace the "Hello World" logic with your own application specific logic in just a couple of minutes.

A Whirlwind Tour of a CLR Host

Let's take a look at the code then.  It comes as a solution that will open in Visual Studio 2005 beta 2 or later, and a set of pre-built binaries.  (By default the output will not work on Win9x, however the project could be pretty easily tweaked to support those platforms if you need).  The solution consists of two projects, ADMHost which is the unmanaged entry point and the unmanaged half of the CLR host, and ManagedHost which is surprisingly enough the managed half of the host.

The COM interfaces that the two halves of the host use to communicate over are defined in ManagedHost as the imaginatively named IManagedHost and IUnmanagedHost.  These interfaces are then exported to a type library with tlbexp, and imported into the native host with Visual C++'s #import directive.

The entry point to the application is wmain in ADMHost\ADMHost.cpp.  The first thing that ADMHost does when it starts up is call CClrHost::BindToRuntime in order to load up the CLR.  BindToRuntime just uses ATL to create a CClrHost object (which implements IUnmanagedHost), and returns that.  As part of the construction process of CClrHost, we call CorBindToRuntimeEx telling it to bind to the latest CLR which can be hosted via the ICLRRuntimeHost interface.  In practice this means that you'll get Whidbey for now, but when later releases of the CLR come out, you'll get them instead.  v1.x of the CLR does not support ICLRRuntimeHost, so you won't ever bind to those versions.

Now that the entry point has an IUnmanagedHostPtr, it calls Start() to fire up the runtime.  The Start call translates into a call to CClrHost::raw_Start which begins by validating that we've bound to a runtime and that the runtime has not yet started.  It then asks the CLR to get it's IClrControl object which allows the host to tweak some knobs on the CLR.  Similarly, it offers the CClrHost instance to the CLR as an IHostControl, which the CLR uses when it needs to tell the host to do something.

The first knob that we set on the CLR is the AppDomainManager by calling IClrControl::SetAppDomainManagerType, and passing in the name of the ManagedHost assembly and the AppDomainManager type.  These are hard coded as constants CClrHost::AppDomainManagerAssembly and CClrHost::AppDomainManagerType on lines 4 and 5 of ClrHost.cpp, so if you rename the ManagedHost class or namespace, or rename, reversion, or sign the ManagedHost.dll with a new public key, you'll need to update the constants here as well.  Once we've told the CLR what class implements our AppDomainManager, we can go ahead and tell it to startup by calling ICLRRuntimeHost::Start.

In the process of starting up the CLR, the default AppDomain is created.  Since we told the CLR that ADMHost.ManagedHost was our AppDomainManager, as soon as this domain is created an instance of ManagedHost (ManagedHost\ManagedHost.cs) is created and InitializeNewDomain will be called.  ManagedHost simply sets its InitializationFlags to AppDomainInitilizationOptions::RegisterWithHost.  When InitializeNewDomain completes, the CLR checks the RegisterWithHost flag.  Seeing that it has been set, the CLR calls IHostControl::SetAppDomainManager passing in the AppDomain ID and a pointer to the AppDomainManager for that domain.

The implementation of SetAppDomainManager that CClrHost provides (line 84 in ClrHost.cpp) first does a QueryInterface on the AppDomainManager, to convert it to an IManagedHost.  Once it has the pointer to the IManagedHost, it calls IManagedHost::SetUnmanagedHost passing in a pointer to itself.  The supplied AppDomainManager saves away this reference in the ManagedHost.unmanagedHost field, but doesn't actually use it.  In a more complex hosting scenario, the AppDomainManager might use this reference to ask the unmanaged host to perform some service for it.

Note that we haven't returned from ICLRRuntimeHost::Start() yet.  At this point, a conceptual callstack looks something like:

wmain()
IUnmanagedHostPtr::Start()
(IUnmanagedHost) CClrHost::raw_Start()
ICLRRuntimeHost::Start()
CLR Stack Frames
(IHostControl) CClrHost::SetAppDomainManager()
IManagedHost::raw_SetUnmanagedHost()
CLR Stack Frames
ADMHost.ManagedHost.SetUnmanagedHost()

After registering the unmanaged host with the AppDomainManager, SetUnmanagedHost saves the AppDomainManager reference in a STL map, indexed by it's AppDomain ID.  When it does that, control flow will now wind back down the stack and back into the main routine.

At this point, the main function calls RunApplication located in ADMHost\ADMHost.cpp on line 10, passing it a reference to the IUnmanagedHost.  If you wanted to customize this sample quickly, this method would be the only one you need to replace since it's where all application specific code would go.

RunApplication access the AppDomainManager of the default domain by getting the DefaultManagedHost property of the IUnmanagedHost.  CClrHost::get_DefaultManagedHost simply calls IUnmanagedHost::GetManagedHost passing it an ID of 1, representing the default domain.  CClrHost::raw_GetUnmanagedHost then accesses the STL map to get the specified AppDomainManager.

RunApplication tells the default managed host to create a second AppDomain, which is implemented in ManagedHost.cs as ManagedHost::CreateAppDomain.  As you would expect this call does nothing more than call AppDomain.CreateDomain, returning the new domain's ID.  When the domain is created, the CLR will call the new ManagedHost's InitializeNewDomain, which will again set the RegisterWithHost flag.  This will cause CClrHost::raw_SetAppDomainManager to be called, and the new AppDomain will be added to the map.  RunApplication then gets a reference to the new managed host, and calls Write having it echo "Hello World".

When RunApplication finishes, wmain calls Stop on the IUnmanagedHost reference.  CClrHost::raw_Stop first iterates over each AppDomainManager in the STL map, calling Dispose on them.  Note that this Dispose method was defined in IManagedHost.  The CLR will not automatically dispose of an AppDomainManager even if it implements IDisposable.  After the Dispose method is called on the AppDomainManagers, CClrHost stops the CLR itself, and the program terminates.

Customizing ADMHost

If you wanted to use ADMHost as a starting point for your application, the first thing you would want to do would be replace the keypair in ManagedHost\ManagedHost.snk with one that you generated yourself.  (of course this means you'll also have to update the strong name of ManagedHost.dll on line 4 of ADMHost\ClrHost.cpp).

Next you'll want to customize the services that your managed and unmanaged hosts provide to each other by updating the IManagedHost and IUnmanagedHost interfaces in ManagedHost\IManagedHost.cs and ManagedHost\IUnmanagedHost.cs.  For instance, you might want your managed host to expose a method that loads up an assembly containing the bulk of your application and starts it off.  You would then implement these services by implementing IManagedHost on the ManagedHost class and implementing IUnmanagedHost on the CClrHost class.  Notice that when Visual C++ #import's the IUnmanagedHost interface, it will mangle the names, so you'll be implementing methods named raw_Method() rather than just Method().

Finally, you'll want to replace the RunApplication method in ADMHost\ADMHost.cpp to actually perform the work of your application.

Wrapping Up

Although the ADMHost sample application doesn't do very much on it's own, it can be used as a starting point for an application that wishes to host the CLR and use an AppDomainManager.  The application specific code is very isolated, and can easily be customized.  I'll also be using this sample to demonstrate some other features that require interaction between managed and unmanaged code in a hosting environment.