我正在尝试编写一个可以加载托管插件的插件系统。如果有任何异常,主机应该能够卸载插件。 对于我的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),那么主机进程将崩溃。
可以在非托管主机中完成任何事情,它可以优雅地从运行时删除错误的应用程序并继续工作吗?
答案 0 :(得分:3)
首先,如果您想使用上面的代码阻止应用程序崩溃,您需要使用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);
}
}
为了能够工作,您需要对插件类进行两处更改:
所以你的课将这样写:
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)。
答案 1 :(得分:1)
看起来像SetDefaultAction一起添加以下内容可以解决崩溃问题:
clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);
答案 2 :(得分:1)
您可以专门为每个给定的插件启动一个新的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
答案 3 :(得分:0)