EternalWindows
ホスト API / ガベージコレクションの検出

CLRをホストすることによって可能になる操作は、 これまで述べてきたようなマネージコードの実行だけではありません。 たとえば、CLRは不要になったメモリを開放するガベージコレクションという機能を実装していますが、 これをホストから実行したり、実行のタイミングを検出したりする仕組みが用意されています。 ホストは、CLRを制御する場合にICLRControlを使用し、 CLRから通知を受け取りたい場合にIHostControlを使用します。

ICLRControlは、ICLRRuntimeHost::GetCLRControlで取得できます。 これを取得した後は、ICLRControl::GetCLRManagerを呼び出して、 CLRに対して支持を与える具体的なインターフェースを取得します。 GetCLRManagerは、ICLRRuntimeHost::Startの前に呼び出さないと失敗することが多い点に注意してください。

ICLRControl   *pControl;
ICLRGCManager *pGCManager;

pRuntimeHost->GetCLRControl(&pControl);
pControl->GetCLRManager(IID_PPV_ARGS(&pGCManager));

上記では、GetCLRManagerにICLRGCManager型の変数を指定しているため、 ICLRGCManagerを取得できることになります。 これは、ホストがガベージコレクションを実行したり、 ガベージコレクションの情報を取得したりする場合に使用します。 たとえば、ガベージコレクションを実行はICLRGCManager::Collectによって可能です。

CLRから通知を受け取るために使用するインターフェースは、IHostControlです。 ホストはこのインターフェースを実装したオブジェクトを用意し、 それをICLRRuntimeHost::SetHostControlに指定することで、 通知を受け取れるようになります。

class CHostControl : public IHostControl
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP GetHostManager(REFIID riid, void **ppObject);
	STDMETHODIMP SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager);

	CHostControl();

private:
	LONG           m_cRef;
	CHostGCManager *m_pHostGCManager;
};

上記のように、IHostControlを実装したクラスを定義します。 そして、この型のオブジェクトを作成し、ICLRRuntimeHost::SetHostControlに指定します。

pHostControl = new CHostControl;
pRuntimeHost->SetHostControl(pHostControl);

オブジェクトを受け取ったCLRは、IHostControl::GetHostManagerを呼び出します。

STDMETHODIMP CHostControl::GetHostManager(REFIID riid, void **ppObject)
{
	*ppObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IHostGCManager)) {
		*ppObject = static_cast<IHostGCManager *>(m_pHostGCManager);
		return S_OK;
	}
	
	return E_NOINTERFACE;
}

ここでは様々なインターフェースのIIDが渡されることになりますが、 その中でホストがサポートできるインターフェースがある場合は、 そのインターフェースを実装したオブジェクトを返します。 たとえば、上記ではIHostGCManagerのIIDが渡された場合は、 IHostGCManagerを実装したオブジェクトであるm_pHostGCManagerを返しています。 つまり、ホストはIHostGCManagerをサポートするということになります。 m_pHostGCManagerは、CHostControlのコンストラクタで作成されていると仮定します。

今回のプログラムは、ガベージコレクションが行われるタイミングを検出します。 まず、マネージDLLのコードを示します。

using System;

public class Class1
{
    public static int Method1(String arg)
    {
        GC.Collect(0);
        
        return 0;
    }
}

GCクラスのメソッドを呼び出すと、ガベージコレクションを強制的に実行できます。 本来ガベージコレクションは、CLRによって適切なタイミングで行われるべきですが、 今回はホストのことを考慮して明示的に行っています。 続いて、host.hを示します。

#include <windows.h>
#include <mscoree.h>
#include <corerror.h>

#pragma comment (lib, "mscoree.lib")

class CHostGCManager;

class CHostControl : public IHostControl
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP GetHostManager(REFIID riid, void **ppObject);
	STDMETHODIMP SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager);

	CHostControl();

private:
	LONG           m_cRef;
	CHostGCManager *m_pHostGCManager;
};

class CHostGCManager : public IHostGCManager
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP ThreadIsBlockingForSuspension();
	STDMETHODIMP SuspensionEnding(DWORD generation);
	STDMETHODIMP SuspensionStarting();

	CHostGCManager();

private:
	LONG m_cRef;
};

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT         hr;
	ICLRRuntimeHost *pRuntimeHost;
	IHostControl    *pHostControl;

	hr = CorBindToRuntimeEx(L"v2.0.50727", L"wks", 0, CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost));
	if (FAILED(hr))
		return 0;
	
	pHostControl = new CHostControl;
	pRuntimeHost->SetHostControl(pHostControl);
	
	pRuntimeHost->Start();

	hr = pRuntimeHost->ExecuteInDefaultAppDomain(L"ClassLibrary1.dll", L"Class1", L"Method1", NULL, NULL);
	if (FAILED(hr)){
		TCHAR szBuf[256];
		wsprintf(szBuf, TEXT("%x"), hr);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
		return 0;
	}

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	pHostControl->Release();
	pRuntimeHost->Stop();
	pRuntimeHost->Release();

	return 0;
}


// CHostControl


CHostControl::CHostControl()
{
	m_cRef = 1;
	m_pHostGCManager = new CHostGCManager;
}

STDMETHODIMP CHostControl::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IHostControl))
		*ppvObject = static_cast<IHostControl *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CHostControl::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CHostControl::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {	
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CHostControl::GetHostManager(REFIID riid, void **ppObject)
{
	*ppObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IHostGCManager)) {
		*ppObject = static_cast<IHostGCManager *>(m_pHostGCManager);
		return S_OK;
	}
	
	return E_NOINTERFACE;
}

STDMETHODIMP CHostControl::SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager)
{
	return E_NOTIMPL;
}


// CHostGCManager


CHostGCManager::CHostGCManager()
{
	m_cRef = 1;
}

STDMETHODIMP CHostGCManager::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IHostGCManager))
		*ppvObject = static_cast<IHostGCManager *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CHostGCManager::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CHostGCManager::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {	
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CHostGCManager::ThreadIsBlockingForSuspension()
{
	MessageBox(NULL, NULL, TEXT("ThreadIsBlockingForSuspension"), MB_OK);

	return S_OK;
}

STDMETHODIMP CHostGCManager::SuspensionEnding(DWORD generation)
{
	TCHAR szBuf[256];

	wsprintf(szBuf, TEXT("generation %d"), generation);
	MessageBox(NULL, szBuf, TEXT("SuspensionEnding"), MB_OK);

	return S_OK;
}

STDMETHODIMP CHostGCManager::SuspensionStarting()
{
	MessageBox(NULL, NULL, TEXT("SuspensionStarting"), MB_OK);

	return S_OK;
}

今回のメソッドはGC.Collectを呼び出していたため、 ガベージコレクションが行われることになります。 この際には、スレッドがメモリにアクセスしないように一時停止状態になりますが、 この際にIHostGCManager::SuspensionStartingが呼ばれます。

STDMETHODIMP CHostGCManager::SuspensionStarting()
{
	return S_OK;
}

特に行うことがない場合は0を返すだけで構いません。

ガベージコレクションが完了し、スレッドが動作を再開できるようになった場合はIHostGCManager::SuspensionEndingが呼ばれます。 基本的には、SuspensionStartingの直後にSuspensionEndingが呼ばれます。

STDMETHODIMP CHostGCManager::SuspensionEnding(DWORD generation)
{
	TCHAR szBuf[256];

	wsprintf(szBuf, TEXT("generation %d"), generation);
	MessageBox(NULL, szBuf, TEXT("SuspensionEnding"), MB_OK);

	return S_OK;
}

generationは、ガベージコレクションの対象となったジェネレーションが格納されます。 今回のマネージコードには0を指定していましたから、は0になっているはずです。 ホストが終了する際にもガベージコレクションは行われますが、その際には-1になります。

-1を指定したガベージコレクションはWinMainから制御を返した後に実行されます。 この時点では、既にIHostControl::Releaseが呼び出されており、 オブジェクトが開放されているのではないかと思えますが、実際には問題ありません。 理由は、ICLRRuntimeHost::SetHostControlで参照カウントが1つ増加しているからです。 ちなみに、SetHostControlにNULLを指定しても参照カウントが下がることはなく、 通知が送られないようになることもありません。


戻る