← home

Cross-platform WinForms

What it means to run Windows Forms code on macOS and Linux, what it costs, and how Majorsilence.Forms does it.

Windows Forms is Windows-only, and always has been. System.Windows.Forms is a managed wrapper over Win32 window classes and GDI+ — HWNDs, WM_PAINT, user32.dll. .NET itself runs everywhere, but that assembly ships only in the Windows Desktop runtime, so a WinForms app cannot be built or launched on macOS or Linux at all. That is the whole problem, and it is why “make our WinForms app cross-platform” has historically meant “rewrite it”.

Majorsilence.Forms takes the other route: reimplement the WinForms programming model on a cross-platform renderer. Same class names, same properties, same events, same designer-generated code — but nothing underneath is Win32.

using Majorsilence.Forms;   // instead of System.Windows.Forms

public class MainForm : Form
{
    public MainForm ()
    {
        var button = new Button { Text = "Click me", Location = new Point (12, 12) };
        button.Click += (s, e) => MessageBox.Show ("Hello from Linux.");
        Controls.Add (button);
    }
}

That file compiles and runs on Windows, macOS and Linux from a single net10.0 build. There is no -windows TFM, no Windows Desktop runtime, and no Wine.

How a WinForms compatibility layer actually works

Three approaches exist for getting WinForms code onto another platform, and they behave very differently:

Approach What it is Trade-off
Emulation (Wine, Mono’s old System.Windows.Forms) Reimplement Win32/GDI+ underneath the unmodified WinForms assembly Zero source changes, but you inherit an enormous Win32 surface, non-native behaviour, and a support story that ends the moment your app touches something unimplemented
Rewrite (WPF, .NET MAUI, Avalonia, the web) Re-express the UI in a different paradigm A genuinely modern result, at the cost of rebuilding every screen and retraining the team
API-compatible reimplementation (Majorsilence.Forms) Rebuild the WinForms API on a portable renderer Source-level compatibility with a mechanical namespace change; you give up Win32 escape hatches like Control.Handle and WndProc

Majorsilence.Forms is the third. Every control is drawn by the framework itself with SkiaSharp — the same GPU-accelerated 2D engine behind Chrome and Flutter — so a Button looks and behaves identically on all three desktops because it is literally the same paint code on all three.

The architecture in one diagram

Your app (Forms, controls, Designer files — the WinForms model you know) │ Majorsilence.Forms (controls + WinForms-compatible API, drawn with SkiaSharp) │ Swappable host backend ├─ Avalonia → Windows · macOS · Linux (default) · also Android · iOS · Browser ├─ Uno → desktop · iOS · Android · WebAssembly └─ Headless → offscreen rendering for tests / CI

The core Majorsilence.Forms assembly references no windowing toolkit — only SkiaSharp. A backend’s entire job is to create a native window, run a message loop, deliver input, and present a Skia surface. That seam is why the same application binary can target Avalonia on the desktop today and Uno or WebAssembly tomorrow. See Platform backends for the interfaces and how to add your own.

What you get on each platform

Platform Status Host
Windows Supported, out of the box Avalonia (default) or Uno
macOS (Intel and Apple Silicon) Supported, out of the box — see WinForms on macOS Avalonia (default) or Uno
Linux (X11) Supported, out of the box — see WinForms on Linux Avalonia (default) or Uno
WebAssembly / browser Working, young — try the live gallery Avalonia Browser or Uno Wasm
Android Early, work in progress Avalonia Android or Uno
iOS Early, unverified Avalonia iOS or Uno
Headless / CI Supported Headless backend, offscreen Skia

The Windows-only APIs a port has to replace

Making the controls portable is only half of it. A real WinForms application also leans on two other Windows-only stacks, and both have a cross-platform answer here:

What it costs

Being honest about the trade is more useful than a feature list:

Where to go next