Sunny Ahuwanya's Blog

Mostly notes on .NET and C#

Enumerating AppDomains from a CLR Host

If you’ve ever tried to enumerate the appdomains in your application, you would have discovered that it’s not exactly an easy task. It’s even more difficult if you try to enumerate the appdomains from an unmanaged CLR host.
I was faced with this task recently and decided to blog about how circuitous it can be.

The obvious solution is to use the ICorRuntimeHost::EnumDomains method, however, the ICorRuntimeHost hosting interface was deprecated and superceded by the CLR v2 ICLRRuntimeHost interface, which does not provide a means to enumerate appdomains.

That’s okay, there are other options available, one of which is to use the ICorPublishProcess::EnumAppDomains method.
To do so, first get an ICorPublishProcess instance that represents the current process, like so:

ICorPublish* pPub = NULL;
ICorPublishProcess* pCurrProcess = NULL;

HRESULT hr = CoCreateInstance (CLSID_CorpubPublish, NULL, CLSCTX_INPROC_SERVER, IID_ICorPublish,(LPVOID *)&pPub);

DWORD cpid = GetCurrentProcessId();
hr = pPub->GetProcess(cpid, &pCurrProcess);

And then pass that instance to the following function.

void EnumAppDomains (ICorPublishProcess *pProcess)
{
    #define NAME_LEN 261

    // Enumerate the application domains.
    ICorPublishAppDomainEnum* pEnumDomains;
    HRESULT hr = pProcess->EnumAppDomains(&pEnumDomains);
    if (SUCCEEDED(hr))
    {
        ICorPublishAppDomain* appDomains[1];
        ULONG aFetched = 1;
		ULONG32 nId;
        while (aFetched > 0 && pEnumDomains->Next(1,appDomains, &aFetched) == S_OK)
        {
			// Display information about each domain.
			if (aFetched > 0)
			{
				appDomains[0]->GetID(&nId);

				WCHAR name[NAME_LEN];
				ULONG32 size=0;
				appDomains[0]->GetName(NAME_LEN, &size, name);
				if (size > 0)
				{
					wprintf(L"name = %s , id = %Iu\n", name, nId);
				} 
				appDomains[0]->Release();
			}

        }
        pEnumDomains->Release();
    }
}

So far, so good. Looks like we’re done here except that there are a few lingering issues:

You have to make sure you are instantiating the right version of ICorPublish for the version of the CLR you’re running, or else you’ll run into some issues as discussed here and here.

The more significant drawback is that you have to call CoCreateInstance as seen in the first code listing. This means you are creating a COM object and you really should call CoInitializeEx(NULL, COINIT_MULTITHREADED) when your host starts, to properly initialize the the COM library.
You’re now knee deep in COM land and have to deal with COM related issues if you choose this route.

An approach that avoids initializing COM involves using the ICLRDebugging interface which can be instantiated with the CLRCreateInstance function. Once instantiated, make a call to OpenVirtualProcess method to obtain an ICorDebugProcess interface and then call ICorDebugProcess::EnumerateAppDomains to enumerate the appdomains in the process.

I don’t like this approach because it’s a *debugging* API and seems excessive for the objective.
So what other options do we have left? Let’s revisit the ICorRuntimeHost interface. It’s deprecated but that doesn’t mean it can no longer be used.
Using this interface is pretty straightforward and there are good examples (.NET 2 version) available on how to use it. The only snag is that my application already makes use of the newer ICLRRuntimeHost interface and I don’t want to replace it with the ICorRuntimeHost interface. I need the older interface to enumerate appdomains and the newer interface for all other hosting related tasks.

Fortunately, you can obtain the two interfaces from the same runtime as shown in this MSDN forum discussion. Here’s an excerpt of the relevant code.

#include"stdafx.h"
#include<mscoree.h>
#include<metahost.h>
#pragmacomment(lib, "mscoree.lib")

int _tmain(int argc, _TCHAR* argv[])
{
	HRESULT hr;
	ICLRMetaHost *pMetaHost = NULL;
	hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
	ICLRRuntimeInfo * lpRuntimeInfo = NULL;
	hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&lpRuntimeInfo);
	ICLRRuntimeHost * lpRuntimeHost = NULL;
	hr = lpRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID *)&lpRuntimeHost);
	hr = lpRuntimeHost->Start();
	ICorRuntimeHost* lpCorRuntimeHost = NULL;
	hr = lpRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (LPVOID *)&lpCorRuntimeHost);
	// To do.
	return 0;
}

One thing I discovered while trying out the code above is that you have to start the runtime first ( lpRuntimeHost->Start() ) before obtaining the second interface. All my attempts to obtain the second interface before starting the runtime failed.

If you don’t have a pointer to the runtime, you can inspect the process using the ICLRMetaHost interface to get the loaded runtimes as shown below.

HRESULT GetV1CorRuntimeHost(ICorRuntimeHost** ppCorRuntimeHost){

    IEnumUnknown* pRuntimeEnum = NULL;
    ICLRMetaHost* pMetaHost = NULL;
    IUnknown* pClrRunTimeInfoThunk = NULL;
    ICLRRuntimeInfo* pClrRunTimeInfo = NULL;

    //Get the ICLRMetaHost interface
    HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    if (FAILED(hr))
    {
        wprintf(L"GetV1CorRuntimeHost: CLRCreateInstance CLRMetaHost failed w/hr 0x%08lx\n", hr);
		goto Cleanup;
    }

    //Get the runtime enumerator
    hr = pMetaHost->EnumerateLoadedRuntimes(GetCurrentProcess(),&pRuntimeEnum);
	if (FAILED(hr))
    {
        wprintf(L"GetV1CorRuntimeHost: EnumerateLoadedRuntimes failed w/hr 0x%08lx\n", hr);
		goto Cleanup;
    }

    //Get the first loaded runtime
    ULONG fetched = 0;
    hr = pRuntimeEnum->Next(1, &pClrRunTimeInfoThunk, &fetched);
    if (FAILED(hr))
    {
        wprintf(L"GetV1CorRuntimeHost: pRuntimeEnum->Next failed w/hr 0x%08lx\n", hr);
		goto Cleanup;
    }

    if(fetched != 1)
    {
	wprintf(L"GetV1CorRuntimeHost: Runtime could not be fetched");
	hr = E_FAIL;
	goto Cleanup;
    }

    //Get the ICLRRuntimeInfo object
    hr = pClrRunTimeInfoThunk->QueryInterface(IID_PPV_ARGS(&pClrRunTimeInfo));
    if (FAILED(hr))
    {
        wprintf(L"GetV1CorRuntimeHost: Failed to get the ICLRRuntimeInfo object w/hr 0x%08lx\n", hr);
		goto Cleanup;
    }

    //Get the ICorRuntimeHost interface
    *ppCorRuntimeHost = NULL;
    hr = pClrRunTimeInfo->GetInterface(CLSID_CorRuntimeHost, 
    IID_PPV_ARGS(ppCorRuntimeHost));
    if (FAILED(hr))
    {
        wprintf(L"GetV1CorRuntimeHost: GetInterface(CLSID_CorRuntimeHost) failed w/hr 0x%08lx\n", hr);
	goto Cleanup;
    } 

    hr = S_OK;

Cleanup:

    if(pClrRunTimeInfo) pClrRunTimeInfo->Release();
    if(pClrRunTimeInfoThunk) pClrRunTimeInfoThunk->Release();
    if(pRuntimeEnum) pRuntimeEnum->Release();
    if(pMetaHost) pMetaHost->Release();

	return hr;
	
}

Obtaining the ICorRuntimeHost interface is now as easy as

ICorRuntimeHost *pCorRuntimeHost = NULL;
HRESULT hr = GetV1CorRuntimeHost(&pCorRuntimeHost);

Henceforth, it’s easy to enumerate the appdomains as shown in this blog post.
Here’s an excerpt of the relevant code (substituting the ICorRuntimeHost variable name to match the name used in this post)

// Enumerate the AppDomains
HDOMAINENUM adEnum;
hr = pCorRuntimeHost->EnumDomains(&adEnum);
EXITONERROR(hr, "Unable to enumerate AppDomains");

// Loop thru the domains
IUnknown * pDomainUnk = NULL;
hr = pCorRuntimeHost->NextDomain(adEnum, &pDomainUnk);
while(SUCCESSFUL(hr))
{
    // Got the IUnknown* to the AppDomain - convert it to AppDomain pointer
    _AppDomain * pCurDomain = NULL;
    hr = pDomainUnk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCurDomain);
    if (SUCCESSFUL(hr))
    {
        // Display the name of the AppDomain
        BSTR str;
        if (SUCCESSFUL(pCurDomain->get_FriendlyName(&str)))
        {
        wprintf(L"AppDomain: %s\n",str);
        }
        else
        {
            printf("AppDomain: unable to get the name!\n");
        }
    } 

    // Loop onto the next Domain
    hr = pCorRuntimeHost->NextDomain(adEnum, &pDomainUnk);
}