Bundling Mono With A .NET Executable Using mkbundle On Windows

In a previous post I talked about building cross platform GUI applications using Eto.Forms. One thing that came up was that to run the applications on Linux, you needed to have Mono installed and to run mono with the executable as an input argument. That’s probably fine if you are building a web application and it’s going to be on limited servers, but if you are distributing a desktop application you may not want to have to guide a user into downloading and using mono themselves.

Luckily, there is actually a way of bundling mono with your application that removes the need for a user to have to download, install, and then run mono. This means you can distribute your application to a Linux machine without any worry about prerequisites.Now I know this isn’t really .NET Core, but one of the most important things about Core is the cross platform-ness. And since Core doesn’t have desktop application support, I may aswell write a little bit on how to make that happen using Mono.

Setup and Install

The craziest thing about looking up how to use Mkbundle on Windows is that there is very little information about it in the past 5 years. Everything I found was from 2013 or earlier, and it was very outdated. It often involved downloading Cygwin and fiddling with paths and environment variables until things just sort of fell into place. To be fair, there comes a time in every Windows developers life when they resign themselves to the fact they will have to install Cygwin for whatever flaky piece of software they need to use, but today is not that day!

Head over to the Mono website and download the latest version for your PC : https://www.mono-project.com/download/stable/

I personally went with the 32 bit, just because everything I read out there was using that version and I didn’t want to run into an issue that “needed” the 32 bit version after all.


This part is important so I’ve put it in bold and put it between horizontal rules just to make sure you read this part! 

After install comes an extremely important step. This might sound like a little side bar but please, if this is your first time trying mkbundle on Windows, you will thank me later. If you try and run MKBundle right away, you will get an error that likely looks like this :

ERROR: The SDK location does not contain a C:\Program Files (x86)\Mono/bin/mono runtime

I smacked my head against the wall for an age with this. Eventually I actually tracked down the source code for mkbundle on Github. And found this line here : https://github.com/mono/mono/blob/master/mcs/tools/mkbundle/mkbundle.cs#L536

runtime = Path.Combine (sdk_path, "bin", "mono");
if (!File.Exists (runtime))
    Error ($"The SDK location does not contain a {path}/bin/mono runtime");

What it’s trying to do is check that you have a *file* called “C:\Program Files (x86)\mono\bin\mono”. Now I bold that part about it being a file, because it’s not looking for a directory. On non Windows systems, files don’t need to have extensions (like .exe), but on Windows they typically do. So what we actually need to do is make sure that this “test” passes. And it’s simple. Go to your mono/bin folder. In there, you should find a mono.exe. Make a *copy* of this file, and remove the extension so it is simply called “mono”. And it should sit side by side with your existing exe.

Now this should satisfy the file check and mono should run fine. I have logged a ticket on Github with Mono around this issue here : https://github.com/mono/mono/issues/7731 . So if you have the same problem or you’re coming from Google after smacking your head against the desk repeatedly with this issue, jump on and add your 2 cents!


Fetching The Correct Mono Runtime

OK with that out of the way, after installing everything you should now have a “Mono Command Prompt” available to you on your machine. Just type “Mono” in your start menu and it should pop up!

This works essentially like a regular command prompt, with the Mono commands already built in.

Now the next step is a little tricky. It’s not like you can run mkbundle and suddenly you have an executable for every OS in existence. Instead you need to fetch the runtime for the particular OS you want your application to run on, and bundle it for that particular runtime. If we package an exe with mkbundle right now then the only people that can run that application are people with the same OS (In our case Windows). This is not as dumb as you might first think. If we did do this, it would mean we could distribute an application that could run on Windows without Mono (Obviously), but more importantly without .NET Framework. There is definitely times where this could come in handy, but for now, we want to build for Linux, so let’s do that.

There is supposed to be commands to fetch and download various runtimes to your machine right from the command prompt. Ofcourse, Mono being a flaky POS at times (Sorry, it’s getting irritating working through these issues), the command doesn’t work at all. Instead if we run the command that “should” fetch available runtimes we get :

System.Net.WebException: Error: TrustFailure (The authentication or decryption has failed.)

And if instead we try the command that supposedly will download the runtime we want (For example we know the runtime signature so just slam that in), we will get :

Failure to download the specified runtime from https://download.mono-project.com/runtimes/raw/

So, we have to do everything manually.

First go to this URL : https://download.mono-project.com/runtimes/raw/. You need download the runtime for the particular OS you want to compile to. Once downloaded, you need to extract this to a particular directory in your documents folder. If I downloaded mono 5.10.0 for Ubuntu, then my directory that I extract to should be : C:\Users\myuser\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64/ .

Once this has been done, we should then be able to run a command within the mono command prompt : mkbundle –local-targets . The output of this should be all “targets” we have available to us.

C:\Program Files (x86)\Mono>mkbundle --local-targets
Available targets locally:
        default - Current System Mono
        mono-5.10.0-ubuntu-16.04-x64

mkbundle Command

Navigate to your applications directory that you want to “bundle”. In my case I’ve created a simple “HelloWorldConsole” application that does nothing but print out “Hello Mono World”. Inside this directory I run the following command mkbundle HelloWorldConsole.exe –simple -o HelloWorldBundleUbuntu –cross mono-5.10.0-ubuntu-16.04-x64  Where HelloWorldConsole.exe is my built console app, the -o flag is what I want the output filename to be, and the –cross flag tells us which runtime we want to compile for.

mkbundle HelloWorldConsole.exe --simple -o HelloWorldBundleUbuntu --cross mono-5.10.0-ubuntu-16.04-x64
From: C:\Users\wadeg\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64
Using runtime: C:\Users\wadeg\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64\bin\mono
     Assembly: C:\Projects\HelloWorldConsole\HelloWorldConsole\HelloWorldConsole\bin\Debug\HelloWorldConsole.exe
       Config: C:\Projects\HelloWorldConsole\HelloWorldConsole\HelloWorldConsole\bin\Debug\HelloWorldConsole.exe.config
     Assembly: C:\Users\wadeg\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64\lib\mono\4.5\mscorlib.dll
     Assembly: C:\Users\wadeg\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64\lib\mono\4.5\I18N.West.dll
     Assembly: C:\Users\wadeg\Documents\.mono\targets\mono-5.10.0-ubuntu-16.04-x64\lib\mono\4.5\I18N.dll
Generated HelloWorldBundleUbuntu

Looks good to me! But let’s test it. Just for comparison sake, I also run the following command : mkbundle HelloWorldConsole.exe –simple -o HelloWorldBundleDefault . This will give us a bundled application but it will be using our default mono runtime (Which in this case is Windows). This will be important later for showing the differences.

Just quickly, I also want to show the size difference between our original application, and our bundled app.

So in terms of bundling, using Windows mono we add about 4mb. For the Ubuntu bundle, we added 8mb. Your guess is as good as mine when it comes to why the huge size difference, but the main thing is that adding 4 – 8mb actually isn’t that bad when you consider a user now doesn’t have to worry about download mono themselves. That’s actually a relatively small bundle when you think about other assets that may be going along with the app like sound, sprites, images etc.

Let’s go ahead and copy these two bundles to our Ubuntu machine for testing.

First we will take a look at our default bundle. Now this shouldn’t work at all because it’s been built for Windows. We even have a command that we can use to check the file type. So let’s try that.

$ file HelloWorldBundleDefault
HelloWorldBundleDefault: PE32 executable (console) Intel 80386, for MS Windows

So straight away it’s telling us this is for Windows. And when we run it…

$ ./HelloWorldBundleDefault
run-detectors: unable to find an interpreter for ./HelloWorldBundleDefault

Yep, so it ain’t happening. Let’s instead try our Ubuntu bundle. First the file command to see if it recognizes that it’s a different sort of application :

$ file HelloWorldBundleUbuntu 
HelloWorldBundleUbuntu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=eed5097fca4fbf56883313da9cbc2e

So even though under the hood it’s actually the exact same C# executable, it’s been compiled correctly for Ubuntu. And when we run it :

$ ./HelloWorldBundleUbuntu
Hello Mono World

Yes! We did it! It only took me a day of headaches to work out every little issue with mkbundle to get a HelloWorld bundle working on Ubuntu! But we did it!

Final Notes

One final thing I want to say is Mono/mkbundle is pretty crap to work with on Windows. I know there is definitely going to be someone out there that hates me for saying that, but it truly is. Some of these issues that I ran into (Notably the fact it tries to check for an extension-less mono exe when running mkbundle) I can see stackoverflow questions from over a year ago having the exact same problem. That means that these problems are nothing new.

Even the documentation was rife with issues on Windows. Simply put, they didn’t work. In some ways I can understand that Mono is built for non Windows machines in mind, but it is shockingly bad for a product that Microsoft has now taken under it’s wing.

10 thoughts on “Bundling Mono With A .NET Executable Using mkbundle On Windows”

  1. Hello, thanks for this great tutorial! I’m having trouble building with mkbundle, and I wanted to know if you knew the cause of these issues I’m having.

    Firstly, trying mkbundle with my .exe on Windows generates it, but it does not let me run it.
    “mkbundle –simple Game.exe -o Game” produces the file, but when I try to run it with Mono then I get the following error message:
    “Cannot open assembly ‘Game’: File does not contain a valid CIL image.”

    I’m not sure if I’m supposed to be able to run this on Windows or not. Onto the next problem: I have the mono-5.10.1-ubuntu-16.04-x64 runtime installed, but attempting to build it like so: “mkbundle –simple Game.exe -o Game –cross mono-5.10.1-ubuntu-16.04-x64” causes the following error:
    “Unable to load assembly `Mono.WebBrowser’ referenced by `C:\Users\user\Documents\.mono\targets\mono-5.10.1-ubuntu-16.04-x64\lib\mono\4.5\System.Windows.Forms.dll'”

    I don’t use a WebBrowser anywhere in my application, so I’m unsure what the deal is. It looks like this issue is prevalent here on GitHub: https://github.com/mono/mono/issues/7454

    I followed your tutorial and mostly everything worked except the building part. I’m not sure if this is in your scope, but if you can help I’d greatly appreciate it!

    Reply
    • Hmm, I haven’t come across either images. I would first try and create a “HelloWorld” console application, and try bundling that. Atleast then you can get MKBundle up and running without worrying it’s something in particular with your application.

      Reply
  2. Hi. Nice tutorial. But after completing all its steps I get application error when trying to run created “*.exe” on machine without Mono and DotNet. Error says that my app needs a “mono-2.0-sgen.dll” to run. I created bundle on windows7x64 and trying to run it on clear windows7x64.

    Reply
    • If you’re trying to do this on Linux, I unfortunately can’t help too much as this guide is specific to Windows (And I only have a Windows box)

      Reply
      • I work with Windows but i Downloaded the OS for that i want to compile so mono-6.6.0-ubuntu-18.04-x64 , but you wrote that i need to extract it in your tutorial, but which programs can extract that file format? In your tutorial you downloaded: mono-5.10.0-ubuntu-16.04-x64 and extracted it but how did you extract the file???? Because if i only put the file in the File “mono-6.6.0-ubuntu-18.04-x64” in C:\Users\Gaaammmler\Documents\.mono\targets\mono-5.16.0-ubuntu-18.04-x64 it don’t show up with the command mkbundle –local-targets

        Reply
      • Sorry now I follow!

        Even the file is not .zip, it is an archive. If you have something like 7Zip installed, you can just right click and open with a zip application. Otherwise select Open With and then select your local archive tool.

        Reply
    • 2 years later, I wanted to know this as well. Running a Mac here and would like to be able to build two bundles: MacOS (done) and Windows. But the runtimes URL above does not seem to contain a lib for Windows/PE32 exes. Which really sucks and seems to indicate you need a Windows box just to create that bundle.

      Reply

Leave a Comment