Using Mono to avoid depending on the .NET Framework on Windows

At GDC I remember seeing Brendon Chung’s talk about Atom Zombie Smasher, where he mentioned how it runs on Mono, even on Windows. Besides making the game’s behavior more consistent between Windows, Linux and Mac, it also allowed him to remove the need to install the .NET framework before running the game, since he can just bundle Mono with his game.

This is attractive to me for a few reasons:

  • It means all Windows players could play my game just by unzipping it — no separate step to install the .NET framework, no need for an installer to do it for them
  • It means I don’t have to target .NET 2.0 to try to keep Vista/Win7 users from needing to install the .NET framework — I can use nice 4.0 features
  • If I can get my game running on Mono, I can probably run on Linux and Mac as well

So I started looking into Mono. It turns out they have a utility called mkbundle that bundles your app, and all of the assemblies it depends upon, into a single executable, and the only thing it needs to run is a mono DLL you can distribute along with it. The .NET assemblies provided by Mono are MIT/X11 licensed, but the runtime is LGPL, so while you can embed Mono’s System.dll into your executable, you have to either dynamically link to the runtime DLL or otherwise provide your program to users in such a way that they could use a different Mono runtime if they wanted.

Unfortunately mkbundle is kind of broken on Windows right now, a lot of which is due to Cygwin drift since the tool was written. Luckily, it’s not so broken that you can’t fix it yourself. I mostly followed this StackOverflow answer — but there are a few changes I made to make it a lot simpler!

The procedure

  1. First, make sure your app works under Mono on Windows. If it doesn’t, none of this will be any use to you.
  2. Install Mono for Windows from here. Click the Windows button in the top row (the stable release) and choose “Mono for Windows, Gtk#, and XSP.” I used version 2.10.8, I can’t guarantee that anything else will work exactly as described. You can install to the normal directory.
  3. Install Cygwin (UNIX-like environment for Windows.) Make sure you have selected the packages gcc-mingw, mingw-zlib1, mingw-zlib-devel, and pkg-config.
  4. Launch a Cygwin Terminal using the shortcut created by the Cygwin installer.
  5. Navigate to the directory your game assemblies are (e.g. your bin/Release folder.) Note that in cygwin, a path like C:\some\path is expressed as /cygdrive/c/some/path — note the slash direction and the way drive letters work! Backslashes are escape characters in bash so if you just type \cygdrive\c\Foo it thinks you meant cygdrivecFoo.
  6. Create an environment variable $MONO pointing to the Mono installation. On 64-bit Windows use this:

    On 32-bit:


    The point here is to use the DOS short filename compatibility feature to point to the right Program Files (or Program Files (x86)) directory without creating a path with spaces in it, which mkbundle doesn’t like.

  7. Add Mono’s bin directory to your $PATH so you can run mkbundle:
    export PATH=$PATH:$MONO/bin
  8. Add Mono’s lib/pkgconfig directory to your $PKG_CONFIG_PATH environment variable. Usually this starts out empty so just do:
    export PKG_CONFIG_PATH=$MONO/lib/pkgconfig
  9. Now here is the magic part that makes this far less laborious — specify a value for $CC that invokes the mingw32 version of GCC:
    export CC="i686-pc-mingw32-gcc -U _WIN32"

    See, Cygwin used to bundle a version of GCC that supported the -mno-cygwin flag to produce executables that didn’t need cygwin1.dll. The GCC 4.x it includes does not support it any longer. That is why you have to install the gcc-mingw package. Since some people may also have normal gcc installed in Cygwin, I want to unambiguously refer to the mingw32 version of GCC.

    The -U _WIN32 basically removes the predefined _WIN32 preprocessor definition before GCC compiles the C code that mkbundle generates. The Win32 code path in that generated code is broken right now (it uses an undefined function g_utf16_to_utf8 that looks like it comes from glib.) Luckily it works if you turn off that code path — the only thing you will miss is supporting Unicode command line arguments. If this is a problem you can use the more complicated procedure from this StackOverflow post to intercept the C code before it is compiled & fix it yourself. That post doesn’t describe how to fix the UTF thing, but it lets you edit the generated C code before recompiling it.

  10. Now you can finally run mkbundle. Replace the assembly names as necessary:
    mkbundle YourGame.exe OpenTK.dll --deps -o Output.exe
  11. Copy mono-2.0.dll into the current directory since the resulting EXE you just created depends on it:
    cp $MONO/bin/mono-2.0.dll .
  12. Now hopefully your app will run!

    You just have to distribute the output EXE file alongside mono-2.0.dll and it should just magically work on any Windows PC.

Making a reusable script

I have combined all of these steps into a shell script here: Once you have this as a script, you can recreate the bundle in one step whenever you want. Note that you will have to customize the script a little for it to work for your exact case.

How big is it?

You may be wondering how big your Output.exe and mono-2.0.dll are, seeing as it’s pulling in a Mono runtime and a bunch of System assemblies and so on.

Well, in my test app, which just uses OpenTK to do some OpenGL drawing, the file sizes are as follows:

Output EXE: 13.4 MB
mono-2.0.dll: 2.6 MB
Both of the above, zipped at highest compression: 5.6 MB

5.6 MB is not bad at all compared to the size of textures and music you are likely to include in a game!

Making it even smaller

If you want the EXE file to be smaller even after it is unzipped, you can pass the -z flag to mkbundle and it will compress the embedded assemblies. (That is what the mingw-zlib* Cygwin packages are for.) This will make the EXE file smaller, although it may make startup times a little slower. That took my example EXE from 13.4 MB to 4.6 MB. Of course, now it won’t compress as much when you zip it.

There are also “linker” tools, like monolinker, that can strip assemblies so only referenced code is used. For example, if I only used OpenGL and not the OpenAL functionality of OpenTK, it could theoretically remove the OpenAL stuff from the OpenTK assembly automatically. In practice, it’s not so simple. I tried using monolinker and could not get it to work — it just made an OpenTK assembly that crashed when you tried to use it. I suspect monolinker got a little overzealous and removed something that was necessary. If I could get this to work it would reduce the executable size even further.

But, honestly, I think the executable is small enough already. Any further compression would be nice, but is not necessary.

Extra DLLs required sometimes?

The TODO file in the mkbundle source suggests that these DLLs may also need to be distributed with your program:

  • libglib*.dll
  • libgmodule*.dll
  • libgthread*.dll
  • iconv.dll
  • intl.dll
  • zlib1.dll

My program does not seem to require them, but if you use more framework functionality they may be required. I can see Atom Zombie Smasher includes some of them (specifically libglib and libgthread.) These libraries appear to all be either LGPL, X11 or zlib licensed. You may want to double check the exact terms of the licenses yourself, but it seems like it will just be fine to copy them out of the Mono bin directory and include them with your app.

Next steps

The mkbundle tool works out of the box on Ubuntu for me, and I am going to investigate the options for making a native-looking OS X application eventually as well.

If I have time I will try to submit a patch to Mono so mkbundle “just works” on Windows (provided a Cygwin install.)

59 thoughts on “Using Mono to avoid depending on the .NET Framework on Windows

  1. Pharmacists revelry a mandatory character in the constitution pains way, and while responsibilities best ed pills non prescription remodel come up to b become the contrasting areas of rather realistically, the prat figure is that pharmacists assistants patients enter extravagantly and freeze well. Pharmacist responsibilities catalogue a grade of provide for inasmuch as patients, from dispensing medications to monitoring compliant health and mature to optimizing their response to medication.

  2. Thanks for explanation!!!

    I have fixed version for Windows but I don’t need MinGW / Cygwin yay

    I user mkbundle than cl of Visual Studio 2015 / 2017 Community

    Generate both temp.c and temp.o from mkbundle ( Work file with Mono 4.8.1 [ Please don't forget for latest version still bugged 5.2.0 and 5.4.0 and both are not okay. You need use 4.8.1 x86 or x64 If it is sure for 4.8.1 [ )
    1. "mkbundle -c -o temp.c -oo temp.o --deps --static Yourgame.exe -v"
    Check if it throws error monosgen-2.0.lib doesn't exist than you need to copy from Mono/lib/mono-2.0-sgen.dll and replace to monosgen-2.0.lib because mkbundle doesn't recognize monosgen-2.0.lib that is why

    than you know replacement of #undef _WIN32 or you don't need it.
    you have to open Visual Studio 2015 Developer Command Prompt or like old version you knew that.
    "cl.exe /MT /I "C:\Program Files (x86)\Windows Kits\8.1\Include\um" /I "C:\Program Files (x86)\Windows Kits\8.1\Include\shared" /I "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include" /I "C:\Program Files (x86)\Mono\include\mono-2.0" /I "." "temp.c" "temp.o" "C:\Program Files (x86)\Mono\lib\mono-2.0-boehm.lib" "C:\Program Files (x86)\Mono\lib\mono-2.0-sgen.lib" "C:\Program Files (x86)\Mono\lib\MonoPosixHelper.lib" /link /ENTRY:mainCRTStartup kernel32.lib version.lib Ws2_32.lib Mswsock.lib Psapi.lib shell32.lib OleAut32.lib ole32.lib winmm.lib user32.lib libvcruntime.lib advapi32.lib OLDNAMES.lib libucrt.lib /out:test.exe"

    And copy both from Mono/bin/ to your game directory.

    Than you can test bundled game without Mono Runtime. I hope you have happy coding and generating to bundle application.

    If you don't like to happen both dlls mono-2.0-sgen.dll and mono-2.0-boehm.dll forget to output.exe - I recommend if you have experience about 2 dlls embed into exe. Sorry I don't understand because objdump or objcopy from Cygwin I really don't understand how do I embed both dlls into exe if 3 files into object like yourgame_binary.o, yourgame_sgen.o and yourgame_boehm.o than main.c

    like this

    But I can not get successful because I have 3 errors like const char are incompatible – How do I fix?

    Or my recommendation is BoxeedApp Packer or Enigma Box embed into own executable….

    Thanks and sorry my bad English!

  3. Thank you so much for providing individuals with an extraordinarily special possiblity to read from this website. It is usually so lovely and also full of a great time for me and my office peers to search your web site particularly 3 times every week to read the newest tips you have. And lastly, we are usually fulfilled considering the remarkable principles you give. Some 3 facts on this page are definitely the very best I have ever had.

  4. You really make it appear really easy together with your presentation however
    I in finding this matter to be actually something that I think I would by no means understand.

    It kind of feels too complex and very large for me. I am looking ahead to your
    subsequent put up, I’ll attempt to get the dangle of it!

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>