托管clr和捕获线程exception

我正在尝试编写一个可以加载托管插件的插件系统。 如果有任何exception,主机应该能够卸载插件。 对于我的poc,我在C#中有一个示例代码库,它会抛出这样的exception……

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),那么主机进程将崩溃。

可以在非托管主机中完成任何事情,它可以优雅地从运行时删除错误的应用程序并继续工作?

您可以专门为每个给定的插件启动一个新的AppDomain并在其中启动它。 请参阅http://msdn.microsoft.com/en-us/library/ms164323.aspx

每个AppDomain都是一个可以执行代码的隔离环境。 在一个AppDomain中发生的exception可以与其余的隔离。 请参阅: 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,可以轻松捕获所有未处理的exception。 您可以在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派生
  • 插件方法一定不能是静态的(静态方法调用不要经过AppDomainfilter)

所以你的课将这样写:

 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 +exception/