хостинг clr и перехват исключений потоков
Я пытаюсь написать систему плагинов, которая может загружать управляемые Плагины. Хост должен иметь возможность выгружать Плагины, если есть какие-либо исключения. для моего poc у меня есть образец библиотеки кода на C#, который выдает исключение, подобное этому ...
public static int StartUp(string arguments)
{
Console.WriteLine("Started exception thrower with args {0}", arguments);
Thread workerThread = new Thread(() =>
{
Console.WriteLine("Starting a thread, doing some important work");
Thread.Sleep(1000);
throw new ApplicationException();
}
);
workerThread.Start();
workerThread.Join();
Console.WriteLine("this should never print");
return 11;
}
тогда у меня есть собственное консольное приложение win32, подобное этому ..
int _tmain(int argc, _TCHAR* argv[])
{
ICLRMetaHost *pMetaHost = NULL;
HRESULT hr;
ICLRRuntimeInfo *runtimeInfo = NULL;
__try
{
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
hr = pMetaHost->GetRuntime(L"v4.0.30319",IID_ICLRRuntimeInfo,(LPVOID*)&runtimeInfo);
ICLRRuntimeHost *runtimeHost = NULL;
hr = runtimeInfo->GetInterface(CLSID_CLRRuntimeHost,IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
ICLRControl* clrControl = NULL;
hr = runtimeHost->GetCLRControl(&clrControl);
ICLRPolicyManager *clrPolicyManager = NULL;
clrControl->GetCLRManager(IID_ICLRPolicyManager, (LPVOID*)&clrPolicyManager);
clrPolicyManager->SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain);
hr = runtimeHost->Start();
DWORD returnVal = NULL;
hr = runtimeHost->ExecuteInDefaultAppDomain(L"ExceptionThrower.dll",L"ExceptionThrower.MainExceptionThrower",L"StartUp",L"test",&returnVal);
runtimeHost->Release();
}
__except(1)
{
wprintf(L"n Error thrown %d",e);
}
return 0;
}
проблема в том, что если я использую приведенный выше код, узел завершит выполнение управляемого кода (строка "это никогда не должно печатать" закончится печать) Если я удалите clrPolicyManager - >SetUnhandledExceptionPolicy (eHostDeterminedPolicy), тогда процесс хоста завершит работу.
можно ли что-нибудь сделать в неуправляемом хосте, чтобы он мог изящно удалить ошибочное приложение из среды выполнения и продолжить работу ?
4 ответов
вы можете запустить новый AppDomain специально для каждого данного плагина и запустить его внутри. См.http://msdn.microsoft.com/en-us/library/ms164323.aspx
каждый AppDomain-это изолированная среда, в которой может выполняться код. Исключения, возникающие в одном AppDomain могут быть изолированы от остальных. Смотри:http://msdn.microsoft.com/en-us/library/system.appdomain (v=VS.100).aspx
прежде всего, если вы хотите предотвратить сбой приложения с помощью кода выше, вам нужно будет использовать SetUnhandledExceptionFilter, например:
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionInfo)
{
// do something useful
return EXCEPTION_EXECUTE_HANDLER; // prevent crash
}
int _tmain(int argc, _TCHAR* argv[])
{
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
...
}
но это может быть не то, что вы действительно хотите. Одно из решений (как я полагаю, предложенное Polity) - создать промежуточный AppDomain, который может легко поймать все необработанные исключения. Вы можете сделать это на C#, например:
public class PluginVerifier
{
public static int CheckPlugin(string arguments)
{
AppDomain appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());
appDomain.UnhandledException += AppDomainUnhandledException;
object obj = appDomain.CreateInstanceAndUnwrap("ExceptionThrower", "ExceptionThrower.MainExceptionThrower");
object ret = obj.GetType().InvokeMember("Startup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, obj, new object[] { arguments });
AppDomain.Unload(appDomain);
return (int)ret;
}
private static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
AppDomain appDomain = (AppDomain)sender;
// the following will prevent "this should never print" to happen
AppDomain.Unload(appDomain);
}
}
для этого, чтобы иметь возможность работать, вам нужно сделать два изменения в классы плагинов:
- они должны быть получены из MarshalByRefObject
- метод плагина не должен быть статическим (вызов статических методов не проходит через фильтр AppDomain)
таким образом, ваш класс будет написан так:
public class MainExceptionThrower: MarshalByRefObject
{
public int StartUp(string arguments)
{
...
}
}
если вы это сделаете, вы можете удалить вызовы SetUnhandledExceptionPolicy, SetActionOnFailure или SetDefaultAction и просто заменить код начальной загрузки следующим образом:
hr = runtimeHost->ExecuteInDefaultAppDomain(L"PluginSystem.dll", L"PluginSystem.PluginVerifier", L"CheckPlugin", L"test", &returnVal);
если вы попробуете это с вашим кодом запуска выше этот вызов вернет hr=0x80131604, который является COR_E_TARGETINVOCATION (TargetInvocationException).
похоже, что добавление следующего вместе с SetDefaultAction разрешает сбой:
clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);
вы подняли очень интересный вопрос, спасибо.
Я полагаю, что эта статья будет достаточно полезной: http://etutorials.org/Programming/programming+microsoft+visual+c+sharp+2005/Part+III+More+C+Language/Chapter+9+Exception+Handling/Unhandled+Exceptions/