2011-10-25 69 views
12

我正在尝试编写一个可以加载托管插件的插件系统。如果有任何例外,主机应该能够卸载插件。 我POC我在C#中抛出这样一个异常的示例代码库...托管clr和捕捉线程异常

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

您的代码启用了.NET 1.x异常处理策略。这只是终止线程。不是你想要的,你还需要调用ICLRPolicyManager :: SetDefaultAction()来告诉它在线程中止时卸载应用程序域。你仍然有一个死线程,使用__try/__ catch来捕捉异常。 –

+0

我添加了以下行clrPolicyManager-> SetDefaultAction(OPR_ThreadAbort,eUnloadAppDomain);到代码,我已经更新了代码,但效果是一样的,主机进程仍然崩溃 –

+0

您可能错过了评论的“死线程”部分。你必须抓住SEH异常。异常代码是0xe0434f4d。 http://msdn.microsoft.com/en-us/library/s58ftw19%28v=VS.100%29.aspx –

回答

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

+0

域提供了一个内存/安全沙箱,但它们不提供线程隔离,即在CLR级别创建线程,并且它们可以在任何域中执行,因此如果线程上发生未处理的异常,则整个CLR将崩溃。 。 –

+0

@ np-hard - 请参阅msdn:“使用应用程序域隔离可能会导致进程的任务。如果执行任务的AppDomain的状态变得不稳定,则可以在不影响进程的情况下卸载AppDomain。当一个进程必须长时间运行而不重新启动时,您还可以使用应用程序域来隔离不应该共享数据的任务。“ (http://msdn.microsoft.com/en-us/library/system.appdomain.aspx) – Polity

+0

@ np-hard - 请阅读:http://ikickandibite.blogspot.com/2010/04/appdomains-and- true-isolation.html,它处理你的问题。我不确定我们是否可以使用CLR-Hosting API复制此内容。如果没有,你可以为插件dll开发一个托管引导程序,它可以很好地处理一个unhandeled异常 – Polity

1

貌似添加具有SetDefaultAction一起以下解决崩溃:

clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy); 
+0

正如问题中提到的“问题是,如果我使用上面的代码,主机将完成运行如果我删除了clrPolicyManager-> SetUnhandledExceptionPolicy(eHostDeterminedPolicy),那么主机进程就会崩溃。“ –

0
+3

裸露的链接不能提供良好的答案。请您**总结**这里的文章。如果链接的内容移动,这个答案变得比无用的更糟糕。也没有必要签署所有的答案,他们有你的天赋,这是你的签名。 – ChrisF

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

对于这个能够然而工作,你需要做两名更改您的插件类:

  • 他们必须从MarshalByRefObject的
  • 派生
  • 插件方法不能是静态(static方法调用不通过的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)。