хостинг clr и перехват исключений потоков - PullRequest
13 голосов
/ 26 октября 2011

Я пытаюсь написать систему плагинов, которая может загружать управляемые плагины.Хост должен иметь возможность выгружать плагины, если есть какие-либо исключения.для моего 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 ]

3 голосов
/ 04 ноября 2011

Прежде всего, если вы хотите предотвратить сбой приложения с помощью приведенного выше кода, вам нужно использовать 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).

1 голос
/ 03 ноября 2011

Вы можете запустить новый 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

1 голос
/ 30 октября 2011

Похоже, что добавление следующего вместе с SetDefaultAction устраняет сбой:

clrPolicyManager->SetUnhandledExceptionPolicy(EClrUnhandledException::eHostDeterminedPolicy);
0 голосов
/ 04 ноября 2011

Вы подняли очень интересный вопрос, спасибо за это.

Полагаю, эта статья будет достаточно полезна: http://etutorials.org/Programming/programming+microsoft+visual+c+sharp+2005/Part+III+More+C+Language/Chapter+9+Exception+Handling/Unhandled+Exceptions/

...