Infosys Microsoft Alliance and Solutions blog

« Silverlight 2 RTM | Main | Language "M" »

Working with Application Domains in WPF

In a few of my earlier blogs, I have written about how one can work with Applications domains (or AppDomain) in a Windows Presentation Foundation (WPF) applications. You can find these

  1. Creating and working with Applications in AppDomains
  2. WPF and AppDomains
  3. WPF – AppDomain – Cannot find type

The information however is scattered across these blogs. I hence decided to consolidate the learning from all of these in a single place so that it is easy for the reader to follow through. You can still go ahead and read the above blogs, or read this one, where I will capture all that I have captured in the earlier blogs with more explanation where possible. Special thanks to Hua Wang from Microsoft for clearing doubts around these concepts.

AppDomains is a concept that was introduced along with .NET. Earlier when one talked about applications and executions, one used to deal with processes, which offer isolation boundaries between two executing applications. On a Win32 box, each process gets a 2 GB address to work with and manages all the resources like threads, addresses, executing code, stack, heap etc. Each process however is heavy weight. So in .NET the concept of light weight process was introduced in the form of AppDomains. A Win32 process can host multiple AppDomains, each of which can execute managed code. AppDomains continued to provide same benefits as that of a process, but being light weight, they incur less cost in their creation and destruction. If you want to understand the details around this, you should definitely read Chris Brumme’s blog.

To understand the various concepts, I created a sample application. The same can be downloaded from here. There are four projects in the code

  1. WPFApp: WPF application that will be hosted inside of an appdomain by another application
  2. WPFControlLib: WPF user control library used by WPFApp
  3. HostApp: WPF application that hosts WPFApp inside of an appdomain
  4. Unloader: Class library project to help unloading of appdomain. I will talk about this towards the end of this post

I will cover key aspects of these applications and explain relevant sections of the code here. You can build something fresh or use the code shared earlier to walk along.

Let’s start with WPFControlLib and WPFApp. The intention of these projects is to have some application to host inside of appdomain, so I will keep the contents simple. The WPFControlLib project will control a TextBlock to show that this is user control and also a button that shows a message box, just to show interaction as well. Similarly the WPFApp is also trivial application and it has a TextBlock to show that it is the main window and it also creates an instance of the user control from the WPFControlLib project.

With this in place, let’s now get on with the appdomain work. For this we will now modify the HostApp to add two buttons to the UI, one which is used to create the appdomain and load the WPFApp and another to unload this appdomain. An appdomain can be created by using any of the various overloads the static CreateDomain method. For our case right now, we will use the simplest of these and pass a string that specifies the friendly name

            AppDomain ad = AppDomain.CreateDomain("testDomain");

Our requirement is to run the WPFApp application inside of this newly created appdomain. The easiest way to do this is to use the ExecuteAssembly method and provide the path of this application. This method loads the assembly and executes the entry point. The complete method will look something like below. Note that I am working from D:\Temp\WPFApDTest folder. 

        private void btnLoadAppD_Click(object sender, RoutedEventArgs e)

        {

            AppDomain ad = AppDomain.CreateDomain("testDomain");

            ad.ExecuteAssembly(@"D:\Temp\WPFAppDTest\WPFApp\bin\Debug\WPFApp.exe");

        }

The documentation says that ExecuteAssembly method uses LoadFile context. If you want to understand what the various load contexts are and what they mean, you can read this blog by Suzanne Cook. What load file context essentially means that I can specify the name of the assembly with full path and it will be loaded from that path and not from the current executing assembly’s path or GAC. The end result is the same, though the documentation here isn’t correct. If you use Reflector you will see that ExecuteAssembly actually uses LoadFrom and not LoadFile. See here if you want to know the differences between the two. Alternatively, if we have the WPFApp in the probing path used by Load method (or in GAC), we can then use ExecuteAssemblyByName method as well.

If we build and run now, we expect WPFApp to come up when we hit the Load AppDomain button, but instead we see this error – “Cannot find type 'WPFControlLib.UserControl1'. The assembly used when compiling might be different than that used when loading and the type is missing.  Error at object 'System.Windows.Controls.Grid' in markup file 'WPFApp;component/mainwindow.xaml' Line 8 Position 10.”

You can make out easily from this exception that while loading the WPFApp’s main window, the type UserControl1 failed to load and this is actually inside of the WPFControlLib assembly. This assembly is present in the path from where we loaded WPFApp, but still it fails to load when WPFApp is loaded inside of this other AppDomain.

This happens since the new appdomain inherits the probing path for loading types from the base appdomain. In our case, it means that same as that used by HostApp and hence the WPFControlLib isn’t found and loaded. When creating a new domain, hence we need to also set the ApplicationBase property of AppDomainSetup and pass it as parameter. See this documented here. Let’s make appropriate code change (as below) and run the application again.

        private void btnLoadAppD_Click(object sender, RoutedEventArgs e)

        {

            //need to set the applicationbase appropriately, else any additional assemblies required

            //by WPFApp won't load correctly

            AppDomainSetup setup = new AppDomainSetup();

            setup.ApplicationBase = @"D:\Temp\WPFAppDTest\WPFApp\bin\Debug";

 

            AppDomain ad = AppDomain.CreateDomain("testDomain", null, setup);

            ad.ExecuteAssembly(@"D:\Temp\WPFAppDTest\WPFApp\bin\Debug\WPFApp.exe");

        }

This time it runs fine. Note however that if I had put the WPFApp and WPFControlLib in the same path as HostApp and then used ExecuteAssembly with only WPFApp.exe (instead of full path), this would have  worked without the need to set the ApplicationBase. So far, so good! However if you tried to write any code after the ExecuteAssembly call, like say a MessageBox to say that WPFApp is loaded, it won’t display till the time you actually exit from WPFApp. And in that case the message really has no meaning, does it? What this means is that ExecuteAssembly is pretty much a blocking call since it is actually executing the entry point of WPFApp and that is the main method. The main method will exit only when the application terminates and hence this behavior. How do we handle it? Threading comes to our rescue.

However just threading isn’t sufficient for you may end in an error like this – “Cannot create instance of 'MainWindow' defined in assembly 'WPFApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Exception has been thrown by the target of an invocation.  Error in markup file 'MainWindow.xaml' Line 1 Position 9”. The inner exception gives more details and states – “{"The calling thread must be STA, because many UI components require this."}”. The final working code will look as below.

        private void btnLoadAppD_Click(object sender, RoutedEventArgs e)

        {

            Thread th = new Thread(new ThreadStart(CreateDomainAndLoadApp));

            //The calling thread must be STA, because many UI components require this

            th.SetApartmentState(ApartmentState.STA);

            th.Start();

            MessageBox.Show("WPFApp loading");

        }

 

        private void CreateDomainAndLoadApp()

        {

            //need to set the applicationbase appropriately, else any additional assemblies required

            //by WPFApp won't load correctly

            AppDomainSetup setup = new AppDomainSetup();

            setup.ApplicationBase = @"D:\Temp\WPFAppDTest\WPFApp\bin\Debug";

 

            AppDomain ad = AppDomain.CreateDomain("testDomain", null, setup);

            ad.ExecuteAssembly(@"D:\Temp\WPFAppDTest\WPFApp\bin\Debug\WPFApp.exe");

        }

Build and execute and it will work fine and you will also see the message box also. Needless to say that the message box will appear much before you see WPFApp load up since appdomain creation and launching WPFApp in it takes much longer. However with threading it does, now allow us to keep our HostApp responsive while WPFApp runs in this other appdomain. However this does brings up an interesting aspect. Is there something we can do to speed up the loading of the other appdomain and WPFApp inside of it? It turns out that yes, we can. The primary issue is that when the loader loads assemblies in an appdomain, it is by default not sharable. So even .NET framework files like System.dll, PresentationCore.dll, PresentationFramework.dll, etc will also get loaded again in the new appdomain that we create and this slows down the loading. To control this behavior, we can tag our HostApp with a LoaderOptimization attribute.

This attribute can however be set only on the entry point function. The issue we face now is that WPF applications typically don’t have their entry points exposed and are auto generated during build and this is controlled by configuring the App.xaml file as ApplicationManifest. See more details on how to address this here. In the HostApp, for the App.xaml file, change the Build Action in Properties explorer to Page. Then add the main method to App.xaml.cs and set the appropriate loader optimization attribute.

    public partial class App : Application

    {

        [STAThread]

        [LoaderOptimization(LoaderOptimization.MultiDomainHost)]

        static void Main()

        {

            App app = new App();

            app.InitializeComponent();

            app.Run();

        }

    }

I did a simple time check with this in place. In the HostApp, in the click of Load AppDomain button, I recorded the time and then in the WPFApp, in the window loaded event I again record the time and compared the difference. With the optimization in place the time lag to the WPFApp to start up in the new domain was about 600 milliseconds, while without optimization is was more than 4 seconds. With this, the creation of a new appdomain and loading a WPF application inside of it is pretty much done. Let’s now focus on how to close the application and unload the domain.

The simplest case in this is when the user directly closes WPFApp. As explained earlier, the call to ExecuteAssembly will return. But does that close the appdomain also? How do we find that out? Let’s write some logic to count the total appdomains. That will help us know how many are there at any given time. We will add a Utility class with two static methods to help achieve this. Iterating over the appdomains requires us to use interop services and use methods found in mscoree library. When adding reference, we need to add reference to mscoree.tlb found in C:\Windows\Microsoft.NET\Framework\v2.0.50727.

    class Utility

    {

        public static List<AppDomain> GetProcessAppDomains()

        {

            List<AppDomain> result = new List<AppDomain>();

            IntPtr enumHandle = IntPtr.Zero;

            mscoree.CorRuntimeHostClass host = null;

            try

            {

                host = new mscoree.CorRuntimeHostClass();

                host.EnumDomains(out enumHandle);

                object domain = null;

                host.NextDomain(enumHandle, out domain);

                while (domain != null)

                {

                    result.Add((AppDomain)domain);

                    host.NextDomain(enumHandle, out domain);

                }

            }

            finally

            {

                if (enumHandle != IntPtr.Zero)

                {

                    host.CloseEnum(enumHandle);

                }

                if (host != null)

                {

                    Marshal.ReleaseComObject(host);

                }

            }

 

            return result;

        }

 

        public static AppDomain GetAppDomain(string friendlyName)

        {

            IntPtr enumHandle = IntPtr.Zero;

            mscoree.CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass();

            try

            {

                host.EnumDomains(out enumHandle);

 

                object domain = null;

                while (true)

                {

                    host.NextDomain(enumHandle, out domain);

                    if (domain == null)

                    {

                        break;

                    }

                    AppDomain appDomain = (AppDomain)domain;

                    if (appDomain.FriendlyName.Equals(friendlyName))

                    {

                        return appDomain;

                    }

                }

            }

            finally

            {

                host.CloseEnum(enumHandle);

                Marshal.ReleaseComObject(host);

                host = null;

            }

            return null;

        }

    }


We can use the method that returns the list of appdomains just after the call to ExecuteAssembly to see the total appdomains. If you debug, you will realize that there are still 2 appdomains (as seen in the figure below)

countdomain.jpg

The appdomain listed as transparent proxy is the one that we created in our application by invoking CreateAppDomain earlier. Hence we need to unload this appdomain explicitly. The modified method is as shown below

        private void CreateDomainAndLoadApp()

        {

            //need to set the applicationbase appropriately, else any additional assemblies required

            //by WPFApp won't load correctly. If however I copy WPFApp and WPFControlLib to the same

            //folder as HostApp, I won't need this

            AppDomainSetup setup = new AppDomainSetup();

            setup.ApplicationBase = @"D:\Temp\WPFAppDTest\WPFApp\bin\Debug";

 

            AppDomain ad = AppDomain.CreateDomain("testDomain", null, setup);

            ad.ExecuteAssembly(@"D:\Temp\WPFAppDTest\WPFApp\bin\Debug\WPFApp.exe");

 

            AppDomain.Unload(ad);

            //only for debugging. once verified, can comment/remove the following line

            List<AppDomain> list = Utility.GetProcessAppDomains();

        }

The call to Utility.GetProcessAppDomains is for debugging purposes only and helps us quickly verify that now the appdomain count is 1, pointing to the primary appdomain inside of which HostApp is running. This is fine, but what if we want to explicitly unload the appdomain from our HostApp? We had already added a button to HostApp earlier for this purpose so let’s now add some code to it. As we saw earlier, we need to pass the handle to the appdomain to the Unload method. This hence calls for a slight change in the overall code to make the variable ad global.

        private void btnUnloadAppD_Click(object sender, RoutedEventArgs e)

        {

            if (ad != null)

            {

                AppDomain.Unload(ad);

            }

        }

In the button click event handler, we will then write simple logic to unload the appdomain. If you build and run this and try and unload, you will get an exception – “Error while unloading appdomain. (Exception from HRESULT: 0x80131015)”.

When a WPF application is created, a dispatcher thread is also created. If you have written applications in Visual C++/MFC you can possibly relate this to message loop, dispatch message, wndproc etc. The AppDomain.Unload call causes a thread abort exception in the threads currently running in the appdomain. When all the threads are aborted, the appdomain unloads. However in our current case, the dispatcher is in native mode, i.e. executing unmanaged code. This results in it not being aborted and results in CannotUnloadAppDomainException and this is what we saw above. So we want to successfully unload the appdomain, we will need to first kill the dispatcher. Unfortunately, this can’t be done from outside the appdomain, but has to done from within the same appdomain. We will need to make a cross appdomain call and for that we can use CrossAppDomainDelegate. The modified code looks as below.

        private void btnUnloadAppD_Click(object sender, RoutedEventArgs e)

        {

            if (ad != null)

            {

                ad.DoCallBack(new CrossAppDomainDelegate(Shutdown));

                AppDomain.Unload(ad);

                //only for debugging. once verified, can comment/remove the following line

                List<AppDomain> list = Utility.GetProcessAppDomains();

            }

        }

 

        static void Shutdown()

        {

            Application.Current.Dispatcher.InvokeShutdown();

        }

If only life was easier! The DoCallBack code added above fails at runtime with exception – “Could not load file or assembly 'HostApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.”

At first this looks intriguing. The code that is running is HostApp itself, so why can’t it be found. If you however go back a bit in this article, you will notice that when creating the appdomain, we had set the ApplicationBase to point to the directory where the WPFApp is. The cross appdomain delegate is a call that executes inside the appdomain we created, so it will try to load assemblies using the ApplicationBase path and needless to say, HostApp assembly is not present in that path. There are various ways in which we can address this

  1. Ensure that HostApp, WPFApp, WPFControlLib are all in same folder
  2. Make a copy of HostApp in the folder where WPFApp is
  3. Externalize the unloading code in another assembly and copy it in WPFApp folder
  4. Write unloading code in WPFApp itself and invoke it

The third approach will be pretty much same as what we did just now, apart from writing this in another assembly. Also second and third approaches are pretty much the same. Let’s see how we can handle the forth approach. For this we will add another class to WPFApp project.

    class Terminator : MarshalByRefObject

    {

        public Terminator()

        {

            System.Windows.Application.Current.Dispatcher.InvokeShutdown();

        }

    }

This class will then be invoked from the HostApp and will cause the dispatcher to shutdown and eventually allow the appdomain to unload.  This class is derived from MarshalByRefObject since it has to be invoked from cross domain. The code changes are as below

        private void btnUnloadAppD_Click(object sender, RoutedEventArgs e)

        {

            if (ad != null)

            {

                ad.CreateInstanceAndUnwrap("WPFApp", "WPFApp.Terminator");

 

                AppDomain.Unload(ad);

                //only for debugging. once verified, can comment/remove the following line

                List<AppDomain> list = Utility.GetProcessAppDomains();

            }

        }

If only unloading of appdomain was as easy as loading of it was? You may now end up getting a MDA Message – “RaceOnRCWCleanup () was detected. Message: An attempt has been made to free an RCW that is in use.  The RCW is in use on the active thread or another thread.  Attempting to free an in-use RCW can cause corruption or data loss.” I got this message on my dual core Vista machine. Another colleague didn’t get it on her single core XP SP2 machine. It turns out that when you invoke dispatcher shut down, you actually don’t need to call AppDomain.Unload anymore. The appdomain get’s unloaded, but there is some delay. So if you check the count immediate after invoking the shut down, you will still see the count as 2, but if you check this in say few seconds, it will be down to 1. To summarize, the method that finally worked is

        private void btnUnloadAppD_Click(object sender, RoutedEventArgs e)

        {

            //only this call is sufficient

            ad.CreateInstanceAndUnwrap("WPFApp", "WPFApp.Terminator");

        }

This approach hence works, but requires change in WPFApp. If we don’t have access to the code, then this isn’t going to work. The approach 1 I stated above can work, but requires additional housekeeping of copying all assemblies to same folder. If I miss on any 1 assembly, it will cause runtime exceptions. Hence personally I think approach 3 is the best. The terminator class that we added to WPFApp will actually be added to another assembly. At runtime we will copy this assembly to the path from where we loaded WPFApp. Copying 1 assembly is better than copying a whole bunch of assemblies.

In the demo code that I have uploaded, I have added an Unloader assembly that includes the Terminator class. For simplicity, I have set a post build event to copy the Unloader.dll to the output folder or WPFApp. In actual application scenario, this will have to be a file copy operation that HostApp needs to trigger. I have also modified the paths set in HostApp to relative path so that the code will run from where you copy it locally.

This brings me to end of this post. Comments welcome!

Comments

I am loading a wpf application using current application domain. However I have an error loading resources such as images. Any idea?

Boon, I will need more data than this to be able to help. What is it that you are exactly trying to do? What kind of errors are you getting? Is there any sample code that you can share?

Hi
I have query, is it possible to embed one wpf application in another applicant?

Sunil, that would be pretty same as trying to embed one process in another. You can use this multiple appdomain concept and give a feel for embedded application.

Hi,
Excellent article.I want to know that can i have the wpf usercontrol library loaded directly in appdomain through which i can create an instance. I am getting serialization issue because of that.Please help.

Thanks,

Anuj

@Anuj, I will need a bit more clarity to answer this. As such loading and working with a use control is not an issue

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter