0

I'm studing C#. I read books of Andrew Troelsen "C# and the .NET Platform" and Jeffrey Richter's "CLR via C#". Now, I'm trying to make application, which will load assemblies from some directory, push them to AppDomain's and run the method's included (the application which supports plug-ins). Here is the DLL where is the common interface. I add it to my application, and in all DLLs with plugins. MainLib.DLL

namespace MainLib
{
public interface ICommonInterface
{
    void ShowDllName();
}
}

Here is plugins: PluginWithOutException

namespace PluginWithOutException
{
public class WithOutException : MarshalByRefObject, ICommonInterface
{
    public void ShowDllName()
    {
        MessageBox.Show("PluginWithOutException");
    }

    public WithOutException()
    {

    }
}
}

and another one: PluginWithException

namespace PluginWithException
{
public class WithException : MarshalByRefObject, ICommonInterface
{
    public void ShowDllName()
    {
        MessageBox.Show("WithException");
        throw new NotImplementedException();
    }
}
}

And here is an application, which loads DLLs and runs them in another AppDomain's

namespace Plug_inApp
{   
class Program
{

    static void Main(string[] args)
    {

        ThreadPool.QueueUserWorkItem(CreateDomainAndLoadAssebly, @"E:\Plugins\PluginWithException.dll");

        Console.ReadKey();
    }
    public static void CreateDomainAndLoadAssebly(object name)
    {
        string assemblyName = (string)name;
        Assembly assemblyToLoad = null;
        AppDomain domain = AppDomain.CreateDomain(string.Format("{0} Domain", assemblyName));
        domain.FirstChanceException += domain_FirstChanceException;

        try
        {
            assemblyToLoad = Assembly.LoadFrom(assemblyName);
        }
        catch (FileNotFoundException)
        {
            MessageBox.Show("Can't find assembly!");
            throw;
        }

        var theClassTypes = from t in assemblyToLoad.GetTypes()
                            where t.IsClass &&
                                  (t.GetInterface("ICommonInterface") != null)
                            select t;
        foreach (Type type in theClassTypes)
        {
            ICommonInterface instance = (ICommonInterface)domain.CreateInstanceFromAndUnwrap(assemblyName, type.FullName);
            instance.ShowDllName();
        }

    }

    static void domain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
    {
        MessageBox.Show(e.Exception.Message);
    }
}
}

I expect, that if I run instance.ShowDllName(); in another domain (maybe I do it wrong?) the unhandled exception will drop the domain where it runs, but the default domain will work. But in my case - default domain crashes after exception occurs in another domain. Please, tell me what I'm doing wrong?

Dima Serdechnyi
  • 707
  • 2
  • 10
  • 19
  • 1
    You're mistaken. An unhandled exception in *any appdomain* will bring down the *entire process*. – dlev May 02 '13 at 21:30
  • You either need to handle the exception or it will bring down the entire process. http://stackoverflow.com/questions/7071957/appdomain-handling-the-exceptions – Dave May 02 '13 at 21:34
  • Ok, is there any ways to catch this exception, show MessageBox with something like "PluginsWithException crashed" and the app will not crash? – Dima Serdechnyi May 02 '13 at 21:34
  • AS you can see, I tried to use "domain.FirstChanceException += domain_FirstChanceException;", but it doesn't helps( – Dima Serdechnyi May 02 '13 at 21:36
  • If you don't want it to crash you need to handle the exception. Try catching the NotImplementedException – Dave May 02 '13 at 21:36
  • Please read the link i posted above. FirstChanceException is simply an event handler is not handling the exception in any way. – Dave May 02 '13 at 21:37
  • @Dave so there is no way to catch this exception and prevent application crash? – Dima Serdechnyi May 02 '13 at 21:43
  • @DimaSerdechnyi, see my answer for how to do this. – James Thurley May 03 '13 at 13:44

2 Answers2

5

There is a way to get some control over this if you really need to. We do this because our add-ins can be written outside of our team, and as much as possible we try to prevent our application from crashing because of other people's add-ins.

So our application will take down the AppDomain that threw the exception, inform the user, and carry on. Or it will simply FailFast if the exception came from the main AppDomain.

In your App.config you need the following:

<configuration>
 <runtime>
  <legacyUnhandledExceptionPolicy enabled="true" />
 </runtime>
</configuration>

This will revert to the legacy behaviour for unhandled exceptions and allow you to decide for yourself whether to kill the entire process or just the AppDomain.

You will still have some other issues to deal with, such as working out which exceptions came from which AppDomain.

Another issue is that not all exceptions are serializable, which means some will turn into a SerializationException when they cross the AppDomain boundary.

Because our add-ins implement a common base class we got around these issues by putting unhandled exception handlers in the add-ins themselves. We then hook into AppDomain.CurrentDomain.UnhandledException and TaskScheduler.UnobservedTaskException and call AppDomain.Unload(AppDomain.CurrentDomain) to terminate the add-in.

This isn't perfect, but it works very well for our project.

James Thurley
  • 2,650
  • 26
  • 38
1

The unhandled exception from a child AppDomain will bring down the child AppDomain and then it will be thrown in your main AppDomain. If you don't handle it there, the main AppDomain will also go down. The FirstChanceException does not handle unhandled exceptions. Check the documentation on the FirstChanceException event. It is raised for all execptions thrown by your application even for the ones that you are handling. It gives you a chance to examine all exceptions thrown (handled or unhandled).

All calls to add-ins should be in try/catch blocks. Catch all exceptions there and log them. You can even mark a plug-in as unreliable (because it is not stable) and do not load it by default the next time your app starts. Or let the user decide what to do. MS Office application used to disable unstable plug-ins (the ones that crashed the app) and then the user had to enable them again from the about dialog (its been a while since I developed MS Office add-ins and I don't know if they follow the same approach in Office 2010 and later versions). Have a look at this example by the System.AddIn team on how to detect add-in failures. It also mentions that unhandled exceptions from child threads of the child AppDomain will bring the whole process down no matter what you do.

Panos Rontogiannis
  • 4,154
  • 1
  • 24
  • 29