Click here to Skip to main content
15,892,005 members
Articles / Desktop Programming / Windows Forms

WPF: Hosting WinForms Control from Another AppDomain

Rate me:
Please Sign up or sign in to vote.
4.88/5 (8 votes)
18 Oct 2012CPOL4 min read 26.5K   620   11   5
How to host a WinForm control from another appdomain in a WPF application

The Problem

How do you host a Windows Forms control in a WPF application? You use WindowsFormsHost class. But what if the Windows Forms control is in another app domain? It is still possible to host it, but it's not trivial. I could find bits and pieces of the solution on the Itnernet, but not a complete sample, so I am publishing one.

Download sample (40K)
Browse CrossDomainHost.cs (1.5K)

Why Different App Domains?

I needed to embed a Windows Forms application of medium complexity in a WPF application. Embedding a Form in another window is asking for trouble, so I converted most of the legacy application to a WinForms UserControl and tried to host it in my WPF application.

I wanted to run the legacy code in its own AppDomain for a number of reasons:

  1. Legacy code can use its own configuration file.
  2. It can be loaded and unloaded at will.
  3. There is a lower chance that its assemblies will conflict with my assemblie.s
  4. It's easier to separate its exceptions from my exceptions.
  5. And so on, and so forth...

Naive Approach Fails

Windows Forms controls are derived from MarshalByRefObject, so they can be accessed across app domains. I was able to create my winforms control via Activator.CreateInstanceAndUnwrap(), but when I tried to attach it as a child to my WPF WindowsFormsHost like this:

C#
myWinFormsHost.Child = remoteWinFormControl;

I received the following exception:

RemotingException: Remoting cannot find field 'parent' on type 'System.Windows.Forms.Control'

Naive approach fails

What Went Wrong?

When MarshalByRefObjects are accessed across app domain boundaries, only public interfaces are marshaled. Any attempt to access private fields of the remote object will fail. Assigning to WindowsFormsHost.Child appears to require access to private field parent of class System.Windows.Forms.Control, and this means that the WPF host control must be in the same app domain as the hosted windows forms control, i.e. in the secondary app domain.

The trouble is, WPF controls are not derived from MarshalByRefObject, so they cannot be directly accessed from another app domain. I searched the Internet for an answer to this problem, and I found that one can use so called "AddIn API" to access WPF controls in other app domains indirectly, via so called "contract interfaces".

The Battle Plan

Here's the sequence of operations that will lead us to victory:

  1. Create a Windows Forms Control in another app domain.
  2. Create a WindowsFormsHost object in the same app domain.
  3. Convert WindowsFormsHost to a marshallable interface INativeHandleContract using FrameworkElementAdaptersViewToContract() method from the System.Addin.Contract assembly.
  4. Install interface pointer in a uniquely named app domain data slot. There may be more sofisticated ways to make data available across app domain boundaries, but this one works just as well.
  5. Read the interface pointer from the main app domain.
  6. Convert it back to WPF control (FrameworkElement to be exact) and embed in our WPF UI.
  7. Profit!

This approach is much more complicated than the original, but it works. Here's the diagram:

Working approach

Show Me The Code

The hosting code is exactly 42 lines long (hm...) and looks as follows:

C#
using System;
using System.AddIn.Contract;
using System.AddIn.Pipeline;
using System.IO;
using System.Windows;
using System.Windows.Forms.Integration;
 
namespace HostingLib
{
    public static class CrossDomainHost
    {
        const string SlotName = "Slot.01F1C93D-90F9-4D8A-86E4-44BE215E6CAE";
 
        public static FrameworkElement CreateHost()
        {
            var location = typeof(CrossDomainHost).Assembly.Location;
            var thisDir = Path.GetDirectoryName(location);
            var pluginsDir = Path.Combine(thisDir, "Plugins");
 
            var setup = new AppDomainSetup
            {
                ApplicationBase = thisDir,
                PrivateBinPath = pluginsDir,
                ConfigurationFile = Path.Combine(pluginsDir, "WinFormApp.exe.config"),
                AppDomainInitializer = InitDomain
            };
 
            var domain = AppDomain.CreateDomain("PluginsDomain", null, setup);
            var contract = (INativeHandleContract)domain.GetData(SlotName);
            var control = FrameworkElementAdapters.ContractToViewAdapter(contract);
            return control;
        }
 
        private static void InitDomain(string[] args)
        {
            var winFormControl = (System.Windows.Forms.Control)AppDomain.CurrentDomain.CreateInstanceAndUnwrap("WinFormApp", "WinFormApp.MainControl");
            var host = new WindowsFormsHost() { Child = winFormControl };
            var contract = FrameworkElementAdapters.ViewToContractAdapter(host);
            AppDomain.CurrentDomain.SetData(SlotName, contract);
        }
    }
}

This code reference 10 (ten) external assemblies, all of them required. That's one assembly per about 4 lines of code, which I think is quite impressive.

How Hosting Code Works

It closely follows the battle plan outlined above. After some setup steps, it creates an app domain (line 28) where the win forms code will be executed.. One of the arguments of this call is an AppDomainSetup instance. Among other things, it contains a pointer to a domain initializer method, in our case InitDomain(). This method is executed in the context of the newly created app domain.

InitDomain() method proceeds to create windows forms control (line 36), then wraps it in a WindowsFormsHost (line 37), then converts it to a marshallable INativeHandleContract (line 38). On line 39 the contract refrence is put in a named data slot, which is no more than a glorified global variable.

Once the domain is initialized, the flow of execution continues on line 29. It retrieves the (marshalled) contract pointer from the slot, and converts it to a WPF control (line 30) which exists in the context of the main app domain. This control is then returned to the caller (line 31).

Conclusion

Hosting a Windows Forms control from another appdomain is a little bit troublesome, but is certainly possible, as my sample demonstrates. When I face an annoying problem like that, it is really nice to find a "grab-and-go" code, that gets things done. So, whenever I can, I return the favor.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Thomson Reuters
United States United States
Ivan is a hands-on software architect/technical lead working for Thomson Reuters in the New York City area. At present I am mostly building complex multi-threaded WPF application for the financial sector, but I am also interested in cloud computing, web development, mobile development, etc.

Please visit my web site: www.ikriv.com.

Comments and Discussions

 
QuestionCould you help me see this question? Pin
Matthew.tian25-Jun-14 23:55
Matthew.tian25-Jun-14 23:55 
AnswerRe: Could you help me see this question? Pin
Ivan Krivyakov28-Jun-14 9:50
Ivan Krivyakov28-Jun-14 9:50 
GeneralRe: Could you help me see this question? Pin
Matthew.tian15-Jul-14 23:27
Matthew.tian15-Jul-14 23:27 
Thank you Very much .
I make an analysis with windbg. I think you could be intrested in it Smile | :)
obviously the memory exceeds lay in the code below ,and the communication resource cost between diffrent appdomain. these things do not collected by GC ,and will not disappear with the client appdomain unloaded.



var contract = (INativeHandleContract)domain.GetData(SlotName);
var control = FrameworkElementAdapters.ContractToViewAdapter(contract);

after load appdomain /Atthch winform congtrol between different appdomains/Unload appdomain several times

Termination on corruption : DISABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
003f0000 08000002 8192 7076 8192 337 164 4 0 0 LFH
00670000 08001002 64 20 64 3 3 1 0 0
005d0000 08041002 256 4 256 2 1 1 0 0
00780000 08001002 1088 188 1088 15 8 2 0 0 LFH
00580000 08041002 256 172 256 6 4 1 0 0 LFH
006f0000 08001002 256 12 256 3 3 1 0 0
00530000 08041002 256 4 256 2 1 1 0 0
076e0000 08001002 256 156 256 4 5 1 0 0 LFH
07f60000 08001002 64 12 64 3 2 1 0 0
09b10000 08001002 3136 2236 3136 535 22 3 0 0 LFH
External fragmentation 23 % (22 free blocks)
0b0e0000 08001002 256 192 256 143 2 1 0 0
0c210000 08001002 1088 1032 1088 48 16 2 0 0 LFH
0afe0000 08001002 3136 2084 3136 326 80 3 0 0 LFH
External fragmentation 15 % (80LFH
00670000 08001002 64 20 64 3 3 1 0 0
005d0000 08041002 256 4 256 2 1 1 0 0
00780000 08001002 1088 188 1088 15 8 2 0 0 LFH
00580000 08041002 256 172 256 6 4 1 0 0 LFH
006f0000 08001002 256 12 256 3 3 1 0 0
00530000 08041002 256 4 256 2 1 1 0 0
076e0000 08001002 256 156 256 4 5 1 0 0 LFH
07f60000 08001002 64 12 64 3 2 1 0 0
09b10000 08001002 3136 2236 3136 535 22 3 0 0 LFH
External fragmentation 23 % (22 free blocks)
0b0e0000 08001002 256 192 256 143 2 1 0 0
0c210000 08001002 1088 1032 1088 48 16 2 0 0 LFH
0afe0000 08001002 3136 2084 3136 326 80 3 0 0 LFH
External fragmentation 15 % (80 free blocks)

===============================================================================

after load appdomain /Atthch winform congtrol between different appdomains/Unload appdomain 1000 times

LFH Key : 0x239a37d7
Termination on corruption : DISABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
003f0000 08000002 113536 104344 113536 426 573 11 0 0 LFH
00670000 08001002 1088 188 1088 31 5 2 0 0 LFH
005d0000 08041002 256 4 256 2 1 1 0 0
00780000 08001002 1088 264 1088 7 11 2 0 0 LFH
00580000 08041002 1280 536 1280 14 5 2 0 0 LFH
006f0000 08001002 256 12 256 3 3 1 0 0
00530000 08041002 256 4 256 2 1 1 0 0
076e0000 08001002 256 156 256 4 5 1 0 0 LFH
07f60000 08001002 64 12 64 3 2 1 0 0
09b10000 08001002 3136 2300 3136 409 21 3 0 0 LFH
External fragmentation 17 % (21 free blocks)
0b0e0000 08001002 256 192 256 143 2 1 0 0
0c210000 08001002 7232 4308 7232 24 21 4 0 0 LFH
0afe0000 08001002 7232 3276 7232 14 003f0000 08000002 113536 104344 113536 426 573 11 0 0 LFH
00670000 08001002 1088 188 1088 31 5 2 0 0 LFH
005d0000 08041002 256 4 256 2 1 1 0 0
00780000 08001002 1088 264 1088 7 11 2 0 0 LFH
00580000 08041002 1280 536 1280 14 5 2 0 0 LFH
006f0000 08001002 256 12 256 3 3 1 0 0
00530000 08041002 256 4 256 2 1 1 0 0
076e0000 08001002 256 156 256 4 5 1 0 0 LFH
07f60000 08001002 64 12 64 3 2 1 0 0
09b10000 08001002 3136 2300 3136 409 21 3 0 0 LFH
External fragmentation 17 % (21 free blocks)
0b0e0000 08001002 256 192 256 143 2 1 0 0
0c210000 08001002 7232 4308 7232 24 21 4 0 0 LFH
0afe0000 08001002 7232 3276 7232 14 86 4 0 0 LFH
-----------------------------------------------------------------------------
86 4 0 0 LFH
-----------------------------------------------------------------------------

Mainly Growth is in the heep 003f0000



group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
3000 7d2 - 1776000 (25.21)
5000 3e9 - 138d000 (21.01)
4010 3e9 - fa7e90 (16.82)
2008 7d2 - fa7e90 (16.82)
1010 7d0 - 7d7d00 (8.43)
558 3ee - 14ffd0 (1.41)
4d4 3e9 - 12e0f4 (1.27)
300 3eb - bc100 (0.79)
5a900 2 - b5200 (0.76)
140 7dc - 9d300 (0.66)
200 3f2 - 7e400 (0.53)
20 3b5a - 76b40 (0.50)
1c8 3ec - 6fc60 (0.47)
40 1b88 - 6e200 (0.46)
78 be9 - 59538 (0.37)
70 bbf - 52390 (0.35)
130 3ed - 4a970 (0.31)
48 1037 - 48f78 (0.31)
104 3f6 - 405d8 (0.27)
100 3fc - 3fc00 (0.27)

Then i analysed these large ojects

0:013> !heap -p -a 1abc4280

address 1abc4280 found in
_HEAP @ 3f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
1abc4268 0641 0000 [00] 1abc4280 03000 - (busy)
77cddfa2 ntdll!RtlAllocateHeap+0x00000274
62822fee wpfgfx_v0400!WPF::ProcessHeapImpl::Alloc+0x0000001a
628396b7 wpfgfx_v0400!WPF::ProcessHeapImpl::Realloc+0x00000014
628396e2 wpfgfx_v0400!ReallocHeap+0x00000017
6283973e wpfgfx_v0400!HANDLE_TABLE::Resize+0x0000005e
6283bf01 wpfgfx_v0400!HANDLE_TABLE::GetNewEntry+0x00000019
62834c3b wpfgfx_v0400!CMilMasterHandleTable::CreateOrAddRefOnChannel+0x00000034
62834cc4 wpfgfx_v0400!CMilChannel::CreateOrAddRefOnChannel+0x00000025
62834cf9 wpfgfx_v0400!MilResource_CreateOrAddRefOnChannel+0x00000038



0:013> !heap -p -a 1aba21d8
address 1aba21d8 found in

_HEAP @ 3f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
1aba21c0 0a03 0000 [00] 1aba21d8 05000 - (busy)
77cddfa2 ntdll!RtlAllocateHeap+0x00000274
62822fee wpfgfx_v0400!WPF::ProcessHeapImpl::Alloc+0x0000001a
628396b7 wpfgfx_v0400!WPF::ProcessHeapImpl::Realloc+0x00000014
628396e2 wpfgfx_v0400!ReallocHeap+0x00000017
6283973e wpfgfx_v0400!HANDLE_TABLE::Resize+0x0000005e
6283bf01 wpfgfx_v0400!HANDLE_TABLE::GetNewEntry+0x00000019
6283d8a3 wpfgfx_v0400!CMilClientChannelTable::GetNewChannelEntry+0x00000029
6283e156 wpfgfx_v0400!CMilConnection::CreateChannel+0x00000035
6283e1ce wpfgfx_v0400!MilConnection_CreateChannel+0x0000004f


0:013> !heap -p -a 1ac022c0
address 1ac022c0 found in
address 1ac022c0 found in
_HEAP @ 3f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
1ac022a8 0805 0000 [00] 1ac022c0 04010 - (busy)
77cddfa2 ntdll!RtlAllocateHeap+0x00000274
62822fee wpfgfx_v0400!WPF::ProcessHeapImpl::Alloc+0x0000001a
628396b7 wpfgfx_v0400!WPF::ProcessHeapImpl::Realloc+0x00000014
628396e2 wpfgfx_v0400!ReallocHeap+0x00000017
6283973e wpfgfx_v0400!HANDLE_TABLE::Resize+0x0000005e
628397c2 wpfgfx_v0400!HANDLE_TABLE::ResizeToFit+0x0000004f
628397e6 wpfgfx_v0400!HANDLE_TABLE::AssignEntry+0x00000025
6283d96a wpfgfx_v0400!CMilServerChannelTable::AssignChannelEntry+0x0000000e
6283e244 wpfgfx_v0400!CConnectionContext::AssignChannelInTable+0x00000023
6283e32c wpfgfx_v0400!CConnectionContext::OpenChannel+0x000000ab
6283e0cb wpfgfx_v0400!CMilConnection::CreateChannelHelper+0x00000024
6283e175 wpfgfx_v0400!CMilConnection::CreateChannel+0x00000057
6283e1ce _HEAP @ 3f0000


address 1ac10d38 found in
_HEAP @ 3f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
1ac10d20 0441 0000 [00] 1ac10d38 02008 - (busy)
77cddfa2 ntdll!RtlAllocateHeap+0x00000274
62822fee wpfgfx_v0400!WPF::ProcessHeapImpl::Alloc+0x0000001a
628396b7 wpfgfx_v0400!WPF::ProcessHeapImpl::Realloc+0x00000014
628396e2 wpfgfx_v0400!ReallocHeap+0x00000017
6283973e wpfgfx_v0400!HANDLE_TABLE::Resize+0x0000005e
628397c2 wpfgfx_v0400!HANDLE_TABLE::ResizeToFit+0x0000004f
628397e6 wpfgfx_v0400!HANDLE_TABLE::AssignEntry+0x00000025
62834d66 wpfgfx_v0400!CMilSlaveHandleTable::AllocateEntryAtHandle+0x00000014
62834dac wpfgfx_v0400!CMilSlaveHandleTable::CreateEmptyResource+0x0000002a
62834e49 wpfgfx_v0400!CComposition::Channel_CreateResource+0x00000025
62834e77 wpfgfx_v0400!CComposition::ProcessCommandBatch+0x000001cf
62822bc9 wpfgfx_v0400!CComposition::ProcessPartitionCommand+0x0000004f


0:013> !heap -p -a 1ab21c30
address 1ab21c30 found in
_HEAP @ 3f0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
1ab21c18 0221 0000 [00] 1ab21c30 01010 - (busy)
77cddfa2 ntdll!RtlAllocateHeap+0x00000274
62822fee wpfgfx_v0400!WPF::ProcessHeapImpl::Alloc+0x0000001a
62830b40 wpfgfx_v0400!WarpPlatform::AllocateMemory+0x0000000c
62830bef wpfgfx_v0400!CMilDataStreamWriter::Initialize+0x00000038
62830c32 wpfgfx_v0400!CMilCommandBatch::Create+0x00000042
628308ed wpfgfx_v0400!CMilChannel::BeginCommand+0x00000056
62830ad0 wpfgfx_v0400!CMilChannel::SendCommand+0x00000039
62830b23 wpfgfx_v0400!MilResource_SendCommand+0x00000041

modified 16-Jul-14 5:35am.

QuestionWindowsFormsHost focus issues Pin
RakotVT25-Sep-13 4:59
RakotVT25-Sep-13 4:59 
AnswerRe: WindowsFormsHost focus issues Pin
Ivan Krivyakov30-Sep-13 7:25
Ivan Krivyakov30-Sep-13 7:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.