2011-10-25 66 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)。