Check WSLg is active:
echo $DISPLAY
Should return something like :0. If empty, GUI apps won't display.
sudo apt update sudo apt install mono-complete
Verify:
mono --version mcs --version
Note:
gtk-sharp2is not available on Ubuntu 24.04 (noble). Usegtk-sharp3instead.
sudo add-apt-repository universe sudo apt update sudo apt install gtk-sharp3
Verify the package is registered:
pkg-config --list-all | grep gtk-sharp
sudo apt install glade
Launch it:
glade
Design your UI visually, then save as yourfile.glade (XML format).
Only needed if your code uses System.Drawing.Bitmap, Graphics, etc. (not required for plain GTK#/Glade apps):
sudo apt install libgdiplus
mcs myprogram.cs mono myprogram.exe
mcs -pkg:gtk-sharp-3.0 myapp.cs mono myapp.exe
mcs -pkg:gtk-sharp-3.0 -pkg:glade-sharp-3.0 myapp.cs mono myapp.exe
Make sure the .glade file referenced in code sits in the same folder (or update the path in new Builder("...")).
using System;
class Hello {
static void Main() {
Console.WriteLine("Hello from Mono in WSL!");
}
}
mcs hello.cs mono hello.exe
using System;
using Gtk;
class GtkCheck {
static void Main() {
Application.Init();
var win = new Window("Hello GTK#3");
win.SetDefaultSize(300, 200);
win.DeleteEvent += (o, args) => Application.Quit();
var button = new Button("Click me");
button.Clicked += (o, args) => Console.WriteLine("Clicked!");
win.Add(button);
win.ShowAll();
Application.Run();
}
}
mcs -pkg:gtk-sharp-3.0 gtkcheck.cs mono gtkcheck.exe
using System;
using Gtk;
class Program {
static void Main() {
Application.Init();
var builder = new Builder("myui.glade");
builder.Autoconnect(new Program());
var win = (Window)builder.GetObject("window1");
win.DeleteEvent += (o, args) => Application.Quit();
win.ShowAll();
Application.Run();
}
}
mcs -pkg:gtk-sharp-3.0 -pkg:glade-sharp-3.0 program.cs mono program.exe
By default, mcs file.cs names the output after the source file (file.exe). You can control this with -out: and change what kind of binary is produced with -target:.
mcs -pkg:gtk-sharp-3.0 Program.cs -out:MyCustomApp.exe
Run it the same way:
mono MyCustomApp.exe
-target options| Target | Produces | Notes |
|---|---|---|
exe (default) | Console executable | Shows a console window when run on Windows |
winexe | Windows GUI executable | No console window — correct choice for GTK#/Glade GUI apps distributed on Windows |
library | .dll class library | No Main entry point required to run directly; used by other programs |
module | .netmodule | Rarely used; for combining into a single assembly later |
Your example, building a windowed GUI executable with a custom name:
mcs -target:winexe -pkg:gtk-sharp-3.0 Program.cs -out:programwin.exe
This produces programwin.exe, which on Windows will run without popping up a console window alongside your GTK window.
If you want to package reusable code (helper classes, business logic, etc.) as a library instead of a standalone app:
mcs -target:library Program.cs -out:MyLibrary.dll
Main() method is required in a library target (though it's fine if one class in your project has one — it just won't be used as an entry point for the DLL itself).MyLibrary.dll, a Mono/.NET assembly that other C# programs can reference.Suppose MyLibrary.dll contains:
// MyLibrary.cs
namespace MyLib {
public class Greeter {
public string Greet(string name) => $"Hello, {name}!";
}
}
Compiled with:
mcs -target:library MyLibrary.cs -out:MyLibrary.dll
Then in a separate consumer program:
// ConsumerApp.cs
using System;
using MyLib;
class ConsumerApp {
static void Main() {
var greeter = new Greeter();
Console.WriteLine(greeter.Greet("World"));
}
}
Compile it while referencing the DLL with -r::
mcs ConsumerApp.cs -r:MyLibrary.dll -out:ConsumerApp.exe
Run it (make sure MyLibrary.dll is in the same folder as ConsumerApp.exe, or on Mono's library search path):
mono ConsumerApp.exe
-target:library with GTK# packagesIf your DLL itself uses GTK# types (e.g., a shared custom widget), include the package flag when building the library too:
mcs -target:library -pkg:gtk-sharp-3.0 MyGtkWidgets.cs -out:MyGtkWidgets.dll
And reference both the package and the DLL when compiling the consumer:
mcs -pkg:gtk-sharp-3.0 ConsumerApp.cs -r:MyGtkWidgets.dll -out:ConsumerApp.exe
# Console exe, custom name mcs Program.cs -out:myapp.exe # Windowed (no console) exe, custom name mcs -target:winexe -pkg:gtk-sharp-3.0 Program.cs -out:programwin.exe # Class library (DLL) mcs -target:library Library.cs -out:MyLibrary.dll # Program referencing that DLL mcs Consumer.cs -r:MyLibrary.dll -out:Consumer.exe
Compiling in WSL produces a .exe that targets the .NET/Mono runtime — it is not a native Windows binary, and it will not run on Windows by itself. Two things are needed:
1. Install a runtime on the Windows machine
Pick one:
.exe files built with mcs can run via the Mono runtime. After install, you can either double-click the .exe (Mono associates itself with .exe files) or run it explicitly:mono programwin.exe
2. Copy the required DLLs alongside the .exe
Your compiled .exe references managed DLLs at runtime (not just at compile time). Anything you referenced with -r: or -pkg: must physically travel with the .exe:
MyLibrary.dll or MyGtkWidgets.dll.glade file, if the app loads one via Builder("myui.glade") — this isn't a DLL but is just as required; it must sit next to the .exe at the same relative path your code expectsMinimum folder layout to copy over to Windows: programwin.exe MyLibrary.dll (any custom DLLs you referenced with -r:) myui.glade (if using Glade)
Common pitfall: a program that runs fine in WSL but fails on Windows with FileNotFoundException or Could not load file or assembly almost always means a referenced DLL (or the .glade file) wasn't copied over, or Mono for Windows isn't installed.
| Error | Cause | Fix |
|---|---|---|
File does not contain a valid CIL image | Ran mono on .cs source instead of compiled .exe | Compile first: mcs file.cs, then mono file.exe |
CS0246: type or namespace 'Gtk' could not be found | Missing package reference during compile | Add -pkg:gtk-sharp-3.0 to mcs command |
CS0103: name 'Console' does not exist | Missing using System; | Add using System; at top of file |
Unable to locate package gtk-sharp2 | Package dropped in Ubuntu 24.04 | Use gtk-sharp3 instead |
DllNotFoundException: libgdiplus | Code uses System.Drawing without the lib installed | sudo apt install libgdiplus |
Could not load file or assembly 'MyLibrary.dll' | DLL not in same folder / not found | Place DLL next to the .exe, or set MONO_PATH |
FileNotFoundException / Could not load file or assembly (on Windows) | Runtime not installed, or referenced DLL/.glade not copied alongside the .exe | Install Mono for Windows; copy all -r: DLLs and .glade files into the same folder as the .exe |
GTK# is a legacy, lightly-maintained binding. For new projects, consider:
sudo apt install dotnet-sdk-8.0 dotnet new install Avalonia.Templates dotnet new avalonia.app -o MyApp
Use GTK#/Mono/Glade for legacy projects or learning; use Avalonia + dotnet for anything new and long-term.
Personal note:
Compile note: to compile the cs file mcs NAME_OF_THE_FILE.cs for GTK appilcation mcs -pkg:gtk-sharp-3.0 NAME_OF_THE_FILE.cs custom output name / windowed exe mcs -target:winexe -pkg:gtk-sharp-3.0 NAME_OF_THE_FILE.cs -out:CUSTOM_NAME.exe export as DLL mcs -target:library NAME_OF_THE_FILE.cs -out:CUSTOM_NAME.dll use a DLL in another program mcs Consumer.cs -r:CUSTOM_NAME.dll -out:Consumer.exe
to run note: same for all cases mono NAME_OF_THE_FILE.exe
To test the application/setup across different Linux distributions, I used Ventoy to create a multiboot USB drive. Ventoy lets you copy multiple ISO files onto a single USB stick and choose which one to boot at startup, without needing to reformat or re-flash the drive for each OS.
.iso files directly onto the Ventoy partition (no extraction needed).