WPF: Implementing Exception Isolation Client via Multi-Process

WPF: Implementing Exception Isolation Client via Multi-Process

When a WPF client needs to implement a plugin system, it can generally be based on containers or processes. If exception isolation for external plugins is required, then the only way is to load plugins in child processes, so that even if a plugin throws an exception, it will not affect the main process.

Last updated 9/22/2021 10:10 PM
鹅群中的鸭霸
4 min read
Category
WPF
Tags
.NET WPF Plugin System Modular Win32

When a WPF client needs to implement a plugin system, it can generally be based on containers or processes. If exception isolation for external plugins is required, only a child process can be used to load the plugin. This way, even if the plugin throws an exception, the main process will not be affected. WPF elements cannot be transmitted across processes, but window handles (HWND) can. Therefore, WPF elements can be wrapped into HWNDs and transmitted to the client via inter-process communication, thereby achieving plugin loading.

1. Using HwndSource to Embed WPF into a Win32 Window

HwndSource creates a Win32 window that can embed WPF content. Use HwndSource.RootVisual to add a WPF element.

private static IntPtr ViewToHwnd(FrameworkElement element)
{
    var p = new HwndSourceParameters()
    {
        ParentWindow = new IntPtr(-3), // message only
        WindowStyle = 1073741824
    };
    var hwndSource = new HwndSource(p)
    {
        RootVisual = element,
        SizeToContent = SizeToContent.Manual,
    };
    hwndSource.CompositionTarget.BackgroundColor = Colors.White;
    return hwndSource.Handle;
}

2. Using HwndHost to Convert a Win32 Window into a WPF Element

Win32 windows cannot be directly embedded into WPF pages. Therefore, .NET provides the HwndHost class to perform the conversion. HwndHost is an abstract class. By implementing the BuildWindowCore method, you can convert a Win32 window into a WPF element.

class ViewHost : HwndHost
{
    private readonly IntPtr _handle;

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetParent(HandleRef hWnd, HandleRef hWndParent);

    public ViewHost(IntPtr handle) => _handle = handle;

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        SetParent(new HandleRef(null, _handle), hwndParent);
        return new HandleRef(this, _handle);
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
    }
}

3. Defining the Plugin's Entry Method

There are multiple ways to return the plugin's UI. Here, I define that each plugin's DLL must have a PluginStartup class, and PluginStartup.CreateView() returns the plugin's UI.

namespace Plugin1
{
    public class PluginStartup
    {
        public FrameworkElement CreateView() => new UserControl1();
    }
}

4. Starting the Plugin Process and Using Anonymous Pipes for Inter-Process Communication

There are multiple ways to achieve inter-process communication. For full functionality, you can use gRPC; for simplicity, pipes are sufficient.

  • The client loads the plugin by specifying the plugin DLL path. When loading the plugin, a child process is started, and the Win32 window handle encapsulating the plugin is transmitted via pipe communication.
private FrameworkElement LoadPlugin(string pluginDll)
{
    using (var pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
    {
        var startInfo = new ProcessStartInfo()
        {
            FileName = "PluginProcess.exe",
            UseShellExecute = false,
            CreateNoWindow = true,
            Arguments = $"{pluginDll} {pipeServer.GetClientHandleAsString()}"
        };

        var process = new Process { StartInfo = startInfo };
        process.Start();
        _pluginProcessList.Add(process);
        pipeServer.DisposeLocalCopyOfClientHandle();
        using (var reader = new StreamReader(pipeServer))
        {
            var handle = new IntPtr(int.Parse(reader.ReadLine()));
            return new ViewHost(handle);
        }
    }
}
  • The console application loads the plugin DLL, converts the plugin UI into a Win32 window, and transmits the handle through the pipe.
[STAThread]
[LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args)
{
    if (args.Length != 2) return;
    var dllPath = args[0];
    var serverHandle = args[1];
    var dll = Assembly.LoadFile(dllPath);
    var startupType = dll.GetType($"{dll.GetName().Name}.PluginStartup");
    var startup = Activator.CreateInstance(startupType);
    var view = (FrameworkElement)startupType.GetMethod("CreateView").Invoke(startup, null);

    using (var pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, serverHandle))
    {
        using (var writer = new StreamWriter(pipeClient))
        {
            writer.AutoFlush = true;
            var handle = ViewToHwnd(view);
            writer.WriteLine(handle.ToInt32());
        }
    }
    Dispatcher.Run();
}

5. Effect

References and Notes

Keep Exploring

Related Reading

More Articles
Same category / Same tag 9/13/2025

Migration Series from WPF to Avalonia: Why I Must Migrate My WPF Application to Avalonia

In the past few years, our host computer software has mainly been developed using WPF and WinForm . These technologies work well on the Windows platform and have accompanied us from small-scale trial production to the current stage of large-scale delivery. However, with business development and changes in customer requirements, the single Windows technology stack has gradually become a hurdle we must overcome.

Continue Reading
Same category / Same tag 1/26/2025

Implementing Internationalization in WPF Using Custom XML Files

This article details the method of implementing internationalization in WPF applications using custom XML files, including installing the necessary NuGet packages, dynamically retrieving the language list, dynamically switching languages, using translated strings in code and XAML interfaces, and provides a source code link to help developers easily achieve internationalization in WPF applications.

Continue Reading