For more than 3 years now our company has developed and sold a desktop application for cross-browser testing called BrowseEmAll. As we started out we developed the application using the .Net Framework which means the application was only capable of running on the Microsoft Windows platform. As more and more web development is done on other operating systems like Linux and macOS the most common feature request was a version that could run on these other platforms as well. So we set out to see if we could come up with a viable strategy to make cross-platform happen without having to give up our existing codebase (because this is not a good idea in general).

Our goals in detail were:

Reuse as much of the current code as possible

Let the application look and feel native on the different platforms

Keep platform specific code to a minimum

Use only a single UI Framework so we don’t end up with platform specific UI code

Technology choice

Luckily our application is already designed using the Model-View-Controller pattern so we could just rewrite the View part to make the application cross platform using .Net on windows and Mono on Linux / macOS. The old UI was based on Winforms and was contained lot’s of calls into native Windows APIs so using Monos Winforms implementation was not an option. By sheer luck, I stumbled onto the cross-platform .Net / Mono UI Toolkit Eto.Forms which gives you the ability to reuse UI code across different platforms and it uses native controls as much as possible.

After a few small test applications, we choose Eto.Forms and started recreating the UI. Because of the existing code, one person was able to do the transition in about 1 month of effort while the rest of the team was able to continue to add new features as we went along.

Implementation Gotchas

A few things that you need to keep in mind if you want to modify your .Net application for cross-platform compatibility.

Mono is not a drop in replacement

In theory, Mono aims to be a drop-in replacement for the .Net Framework and it works reasonable well for this. But still a few things can fall on your feet in unexpected ways:

You need to make sure you handle paths correctly in your application. Always use Path.Combine and don’t hard-code platform specific path separators anywhere.

Other things to look out why porting to Mono

To build an Application bundle for macOS you need a mac and XCode.

Mono is only available as a 32bit build for macOS (you can build a 64bit version yourself).

You need platform specific Installers

For the installation of the application, you will need separate installers. We used InnoSetup for Windows, a simple dmg image for macOS and currently a deb/rpm package for Linux distributions.

Platform specific code cannot be fully avoided

We did not manage to avoid platform specific code fully and it turns out detecting the platform you are running on is not trivial at all. We used this which seems to work quite good:

[DllImport("libc")] static extern int uname(IntPtr buf); public static Platform RunningPlatform() { switch (Environment.OSVersion.Platform) { case PlatformID.Unix: { IntPtr buf = IntPtr.Zero; try { buf = Marshal.AllocHGlobal(8192); // This is a hacktastic way of getting sysname from uname () if (uname(buf) == 0) { string os = Marshal.PtrToStringAnsi(buf); if (os == "Darwin") return Platform.Mac; } } catch { } finally { if (buf != IntPtr.Zero) Marshal.FreeHGlobal(buf); } return Platform.Linux; } case PlatformID.MacOSX: return Platform.Mac; default: return Platform.Windows; } }

Code Template

To make things easier for anybody just starting out with this we have created a simple template as a baseline for a cross-platform .Net / Mono application using Eto.Forms. You can find the code in this repository.

Results

All in all, we are quite happy with the new version of our software. The experience if mostly the same across all targeted platforms and we have quite a few happy users already running BrowsEmAll as an on-premise solution for cross-browser testing. Feel free to share your own wisdom in the comments!

Image by Kecko