EternalWindows
ホスト API / AppDomainの作成通知

今回は、IHostControl::SetAppDomainManagerについて説明します。 このメソッドはAppDomainが作成されたことを通知するために存在し、 AppDomainを列挙するホストを開発する場合などで役立ちます。 ただし、実際にSetAppDomainManagerが呼ばれるためには、 予めICLRControl::SetAppDomainManagerTypeを呼び出しておく必要があり、 このためにはホストがAppDomainManagerクラスをオーバーライドしたアセンブリを用意しておかなければなりません。 簡単に言えば、次のようなコードを持ったマネージDLLが必要になるということです。

using System;
using System.Threading;

public class Class1 : AppDomainManager
{
    public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
    {
        InitializationFlags = AppDomainManagerInitializationOptions.RegisterWithHost;
    }
}

AppDomainManagerクラスをオーバーライドすると、マネージコード内でAppDomainが初期化されようとする瞬間を特定できます。 そして、このクラスのInitializationFlagsプロパティにRegisterWithHostを指定すると、 AppDomainの作成がホストのIHostControl::SetAppDomainManagerに通知されるようになります。

AppDomainの作成を検出することに対し、AppDomainがアンロードされたことを検出するにはどうすればよいのでしょうか。 これにはまず、IActionOnCLREventを継承したクラスを定義し、そのインスタンスを作成します。 そして、ICLROnEventManager::RegisterActionOnEventに、Event_DomainUnloadと共にインスタンスを指定します。

pActionOnCLREvent = new CActionOnCLREvent;
pControl->GetCLRManager(IID_PPV_ARGS(&pEventManager));
pEventManager->RegisterActionOnEvent(Event_DomainUnload, pActionOnCLREvent);

ICLROnEventManagerは、ICLRControl::GetCLRManagerで取得できます。 Event_DomainUnloadを指定したことにより、AppDomainがアンロードされた際にIActionOnCLREvent::OnEventが呼ばれます。

今回のプログラムは、AppDomainを列挙する例を示します。 まず、マネージDLLのコードを示します。

using System;
using System.Threading;

public class Class1 : AppDomainManager
{
    public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
    {
        InitializationFlags = AppDomainManagerInitializationOptions.RegisterWithHost;
    }
   
    public static int Method1(String arg)
    {
        Thread thread = new Thread(new ThreadStart(Run));
        thread.Start();

        return 0;
    }

    public static void Run()
    {
        AppDomain ad2;
            
        ad2 = AppDomain.CreateDomain("ad2");
    
        try
        {
            ad2.CreateInstanceFromAndUnwrap("C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\System.Windows.Forms.dll",
                    "System.Windows.Forms.Button");
        }
        catch (System.Exception)
        {
        }

        Thread.Sleep(5000);

        AppDomain.Unload(ad2);
    }
}

AppDomainManagerのコードの他にメソッドも用意しているのは、このメソッドを通じてAppDomainの作成を行えるようにしたいからです。 メソッドは専用のスレッドを作成し、スレッド内ではAppDomainクラスのstaticメソッドで新しいAppDomainを作成します。 このとき、ホストのIHostControl::SetAppDomainManagerが呼ばれることになります。 CreateInstanceFromAndUnwrapを呼び出しているのは、作成したAppDomainにアセンブリをロードするためです。 作成したButtonクラスを受け取っていないため不要といえば不要ですが、 AppDomainにアセンブリがロードされることを証明するために行っています。 間違って存在しないアセンブリを指定したことを考えて、例外処理は必ず記述しておきます。 これがないと例外の発生時にホストのIHostPolicyManager::OnFailureが呼ばれ、ホストが強制終了してしまうことになります。 Sleepでしばらく待機した後は、UnloadメソッドによってAppDomainをアンロードします。 このとき、ホストのIActionOnCLREvent::OnEventが呼ばれるようになります。 続いて、ホストのコードを示します。

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

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

#import "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\mscorlib.tlb" raw_interfaces_only	
using namespace mscorlib;

#define ID_METHOD 100
#define WM_ENUMDOMAIN WM_APP

HWND g_hwnd;

void EnumAppDomain(HWND hwndListBox, ICorRuntimeHost *pCorRuntimeHost);
void EnumAssemblies(HWND hwndListBox, ICorRuntimeHost *pCorRuntimeHost, int nIndex);
void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

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;
};

class CActionOnCLREvent : public IActionOnCLREvent
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP OnEvent(EClrEvent event, PVOID data);

	CActionOnCLREvent();

private:
	LONG m_cRef;
};

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND              hwndListBoxLeft = NULL;
	static HWND              hwndListBoxRight = NULL;
	static ICLRRuntimeHost   *pClrRuntimeHost = NULL;
	static ICorRuntimeHost   *pCorRuntimeHost = NULL;
	static IHostControl      *pHostControl = NULL;
	static IActionOnCLREvent *pActionOnCLREvent = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		HRESULT            hr;
		HMENU              hmenu;
		ICLRControl        *pControl;
		ICLROnEventManager *pEventManager;
		ICLRPolicyManager  *pPolicyManager;

		g_hwnd = hwnd;
		
		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		hr = CorBindToRuntimeEx(L"v2.0.50727", L"wks", 0, CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));
		if (FAILED(hr))
			return -1;
		
		pHostControl = new CHostControl;
		pClrRuntimeHost->SetHostControl(pHostControl);

		pClrRuntimeHost->GetCLRControl(&pControl);

		pControl->SetAppDomainManagerType(L"ClassLibrary1", L"Class1");

		pActionOnCLREvent = new CActionOnCLREvent;
		pControl->GetCLRManager(IID_PPV_ARGS(&pEventManager));
		pEventManager->RegisterActionOnEvent(Event_DomainUnload, pActionOnCLREvent);
		pEventManager->Release();

		pControl->GetCLRManager(IID_PPV_ARGS(&pPolicyManager));
		pPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy);
		pPolicyManager->Release();

		pControl->Release();

		pClrRuntimeHost->Start();

		hr = CorBindToRuntimeEx(L"v2.0.50727", L"wks", 0, CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
		if (FAILED(hr))
			return -1;

		hmenu = CreateMenu();
		InitializeMenuItem(hmenu, TEXT("メソッドの実行"), ID_METHOD);
		SetMenu(hwnd, hmenu);
		
		g_hwnd = hwnd;
		EnumAppDomain(hwndListBoxLeft, pCorRuntimeHost);
		
		return 0;
	}

	case WM_ENUMDOMAIN:
		EnumAppDomain(hwndListBoxLeft, pCorRuntimeHost);
		return 0;

	case WM_COMMAND: {
		int nId = LOWORD(wParam);

		if (nId == ID_METHOD) {
			HRESULT hr;
			hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(L"ClassLibrary1.dll", L"Class1", L"Method1", NULL, NULL);
			if (FAILED(hr))
				MessageBox(NULL, TEXT("メソッドの呼び出しに失敗しました。"), NULL, MB_ICONWARNING);
		}
		else {
			int nIndex;

			if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
				return 0;
			SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
			nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
			EnumAssemblies(hwndListBoxRight, pCorRuntimeHost, nIndex);
		}
		
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (pActionOnCLREvent != NULL)
			pActionOnCLREvent->Release();

		if (pHostControl != NULL)
			pHostControl->Release();
		
		if (pCorRuntimeHost != NULL)
			pCorRuntimeHost->Release();

		if (pClrRuntimeHost != NULL) {
			pClrRuntimeHost->Stop();
			pClrRuntimeHost->Release();
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void EnumAppDomain(HWND hwndListBox, ICorRuntimeHost *pCorRuntimeHost)
{
	_AppDomain  *pAppDomain;
	IUnknown    *pUnknown;
	BSTR        bstr;
	HDOMAINENUM hEnum;
	
	SendMessage(hwndListBox, LB_RESETCONTENT, 0, 0);

	pCorRuntimeHost->EnumDomains(&hEnum);
	
	while (pCorRuntimeHost->NextDomain(hEnum, &pUnknown) == S_OK) {
		pUnknown->QueryInterface(IID_PPV_ARGS(&pAppDomain));
		pAppDomain->get_FriendlyName(&bstr);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)bstr);
		SysFreeString(bstr);
		pUnknown->Release();
		pAppDomain->Release();
	}

	pCorRuntimeHost->CloseEnum(hEnum);
}

void EnumAssemblies(HWND hwndListBox, ICorRuntimeHost *pCorRuntimeHost, int nIndex)
{
	_AppDomain  *pAppDomain;
	_Assembly   *pAssembly;
	IUnknown    *pUnknown, *pElement;
	LONG        lIndexMin, lIndexMax;
	SAFEARRAY   *pArray;
	HDOMAINENUM hEnum;
	LONG        i, j;
	BSTR        bstr;

	SendMessage(hwndListBox, LB_RESETCONTENT, 0, 0);

	pCorRuntimeHost->EnumDomains(&hEnum);
	
	i = 0;
	while (pCorRuntimeHost->NextDomain(hEnum, &pUnknown) == S_OK) {
		if (i == nIndex) {
			pUnknown->QueryInterface(IID_PPV_ARGS(&pAppDomain));
			pUnknown->Release();
			pAppDomain->GetAssemblies(&pArray);
			if (pArray == NULL)
				break;
			
			SafeArrayGetLBound(pArray, 1, &lIndexMin);
			SafeArrayGetUBound(pArray, 1, &lIndexMax);

			for (j = lIndexMin; j <= lIndexMax; j++) {
				SafeArrayGetElement(pArray, &j, (void *)&pElement);
				pElement->QueryInterface(IID_PPV_ARGS(&pAssembly));

				pAssembly->get_ToString(&bstr);
				SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)bstr);

				SysFreeString(bstr);
				pElement->Release();
				pAssembly->Release();
			}
			SafeArrayDestroy(pArray);
			pAppDomain->Release();
		}
		else {
			pUnknown->Release();
			i++;
		}
	}

	pCorRuntimeHost->CloseEnum(hEnum);
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}


// CHostControl


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

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;

	return E_NOINTERFACE;
}

STDMETHODIMP CHostControl::SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager)
{
	if (g_hwnd != NULL)
		PostMessage(g_hwnd, WM_ENUMDOMAIN, 0, 0);

	return S_OK;
}


// CActionOnCLREvent


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

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

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

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CActionOnCLREvent::OnEvent(EClrEvent event, PVOID data)
{
	PostMessage(g_hwnd, WM_ENUMDOMAIN, 0, 0);

	return 0;
}

このプログラムでは、CorBindToRuntimeExを2回呼び出すことで、 ICLRRuntimeHostとICorRuntimeHostを作成しています。 前者はIHostControlの設定やメソッドの呼び出しに必要ですし、 後者はAppDomainの列挙に必要です。 このときの注意点として、ICLRRuntimeHostはICorRuntimeHostよりも先に作成しておきます。 ICLRControlを使用して次の処理が行われます。

pClrRuntimeHost->GetCLRControl(&pControl);

pControl->SetAppDomainManagerType(L"ClassLibrary1", L"Class1");

pActionOnCLREvent = new CActionOnCLREvent;
pControl->GetCLRManager(IID_PPV_ARGS(&pEventManager));
pEventManager->RegisterActionOnEvent(Event_DomainUnload, pActionOnCLREvent);
pEventManager->Release();

pControl->GetCLRManager(IID_PPV_ARGS(&pPolicyManager));
pPolicyManager->SetUnhandledExceptionPolicy(eHostDeterminedPolicy);
pPolicyManager->Release();

pControl->Release();

SetAppDomainManagerTypeにAppDomainManagerのアセンブリを指定することによって、 AppDomainが作成された際に通知を受け取れるようになります。 ICLROnEventManager::RegisterActionOnEventには、Event_DomainUnloadとIActionOnCLREventを実装したオブジェクトを指定します。 これにより、AppDomainのアンロード時にIActionOnCLREvent::OnEventが呼ばれます。 SetUnhandledExceptionPolicyは必須ではありませんが、今回のようなにマネージコードがスレッドを作成する場合は呼び出したほうがよいでしょう。 メソッド呼び出しで未処理の例外が発生した場合はホストが強制終了することはありませんが、 独自に作成したスレッドで未処理の例外が発生した場合はホストが強制終了します。 SetUnhandledExceptionPolicyにeHostDeterminedPolicyを指定すれば、これを防ぐことができます。

EnumAppDomainによって、左のリストボックスにAppDomainの名前が列挙されます。

void EnumAppDomain(HWND hwndListBox, ICorRuntimeHost *pCorRuntimeHost)
{
	_AppDomain  *pAppDomain;
	IUnknown    *pUnknown;
	BSTR        bstr;
	HDOMAINENUM hEnum;
	
	SendMessage(hwndListBox, LB_RESETCONTENT, 0, 0);

	pCorRuntimeHost->EnumDomains(&hEnum);
	
	while (pCorRuntimeHost->NextDomain(hEnum, &pUnknown) == S_OK) {
		pUnknown->QueryInterface(IID_PPV_ARGS(&pAppDomain));
		pAppDomain->get_FriendlyName(&bstr);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)bstr);
		SysFreeString(bstr);
		pUnknown->Release();
		pAppDomain->Release();
	}

	pCorRuntimeHost->CloseEnum(hEnum);
}

ICorRuntimeHost::NextDomainで取得したIUnknownは、_AppDomainで表すことができます。 get_FriendlyNameは、このAppDomainが作成される際に設定された名前であり、 マネージコードにおけるAppDomain.CreateDomainの第1引数に相当します。 既定で存在するドメインには、mscorlib.dllがロードされているはずです。

AppDomainが作成された際には、IHostControl::SetAppDomainManagerが呼ばれます。

STDMETHODIMP CHostControl::SetAppDomainManager(DWORD dwAppDomainID, IUnknown *pUnkAppDomainManager)
{
	if (g_hwnd != NULL)
		PostMessage(g_hwnd, WM_ENUMDOMAIN, 0, 0);

	return S_OK;
}

新しく作成されたAppDomainがリストボックスに表示されるために、WM_ENUMDOMAINでAppDomainの列挙を促します。 g_hwndがNULLかどうかを確認しているのは、このメソッドが既定のAppDomainが作成される際にも呼ばれるからであり、 この時点ではホストが初期化されている途中であるため、避けるようにしています。

AppDomainがアンロードされる際には、IActionOnCLREvent::OnEventが呼ばれます。

STDMETHODIMP CActionOnCLREvent::OnEvent(EClrEvent event, PVOID data)
{
	PostMessage(g_hwnd, WM_ENUMDOMAIN, 0, 0);
	
	return 0;
}

eventには通知されたイベントを表す値が格納されますが、 RegisterActionOnEventにEvent_DomainUnloadを指定していたため、eventはEvent_DomainUnloadになるはずです。 このときには、dataがAppDomainのIDになります。 WM_ENUMDOMAINをポストすることで、リストボックスが更新されてAppDomainが表示されなくなります。

IHostPolicyManagerについて

今回はIActionOnCLREventでAppDomainのアンロードを検出しましたが、 これはIHostPolicyManager::OnDefaultActionでも可能です。 ただし、このメソッドは正確にはAppDomainがアンロードされた際ではなく、 AppDomainがアンロードされようとしている際であるため、 やや使いにくいところがあるかもしれません。 次に例を示します。

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

	STDMETHODIMP OnDefaultAction(EClrOperation operation, EPolicyAction action);
	STDMETHODIMP OnTimeout(EClrOperation operation, EPolicyAction action);
	STDMETHODIMP OnFailure(EClrFailure failure, EPolicyAction action);

	CHostPolicyManager();

private:
	LONG m_cRef;
};

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IHostPolicyManager)) {
		*ppObject = static_cast<IHostPolicyManager *>(m_pPolicyManager);
		return S_OK;
	}
	
	return E_NOINTERFACE;
}

IHostPolicyManagerを継承したクラスを定義し、その型のインスタンスをGetHostManagerで返すようにします。 CHostPolicyManagerの実装例を次に示します。

STDMETHODIMP CHostPolicyManager::OnDefaultAction(EClrOperation operation, EPolicyAction action)
{
	if (operation == OPR_AppDomainUnload && action == eUnloadAppDomain) {
		// AppDomainがアンロードされようとしている。
	}

	return S_OK;
}

STDMETHODIMP CHostPolicyManager::OnTimeout(EClrOperation operation, EPolicyAction action)
{
	return S_OK;
}

STDMETHODIMP CHostPolicyManager::OnFailure(EClrFailure failure, EPolicyAction action)
{
	return S_OK;
}

OnDefaultActionは、第1引数のイベントに対して第2引数のアクションが行われようとしている際に呼ばれます。 operationがOPR_AppDomainUnloadである場合は、actionがeUnloadAppDomainになっているはずなので、 この際にAppDomainがアンロードされると考えることができます。 OnFailureは、マネージコードでエラーが発生した場合に呼ばれます。 たとえば、未処理の例外が発生した場合はfailureがFAIL_FatalRuntimeになり、actioneがRudeExitProcesであるため、 メソッドが制御を返した後にはホストが強制終了することになります。



戻る