Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Building and Running Embedded Linux .NET Applications from First Principles

5.00/5 (6 votes)
23 Feb 2016CPOL11 min read 65.3K  
.NET applications on Yocto/OpenEmbedded Linux

Overview

This walk-through has the aim of taking you from a clean system through to including Mono in a build image using the meta-mono layer, then building and packaging an example .NET project for inclusion in that image.

You may already have Yocto installed and just be looking to work with Mono for the first time, in which case you can jump forward to the section you find most relevant, such as Build an example project on the host for testing (optional) or Adding the meta-mono layer to the Yocto build system.

The following assumptions are made. You are:

Obtain the Required Packages for your Host System to Support Yocto

First, we will install the required host packages for Ubuntu as detailed in the quickstart, i.e.:

$ sudo apt-get install gawk wget git-core diffstat unzip texinfo
gcc-multilib build-essential chrpath socat libsdl1.2-dev xterm nano

Full details of system requirements and installation can be found in the Yocto Quickstart.

Install Mono

Also, install Mono on your host system as we'll use it to build and run some examples for test later.

$ sudo apt-get install mono-complete
$ mono --version

With Ubuntu 14.04.4 LTS, this will install Mono version 3.2.8 which is now quite old.

If you wish to install a newer build of Mono to your host system, you can follow these instructions.

$ sudo add-apt-repository ppa:directhex/monoxide
$ sudo apt-get update
$ sudo apt-get install mono-complete
$ mono --version

Currently, this will also install Mono 3.2.8 but may in future provide newer builds.

If you wish to use the absolute latest Mono, then there are instructions you can follow to build a release tarball here and from git here. Be aware that this may not be straightforward and that there can be issues, such as with missing files, if you follow this process.

Additionally, I have written a detailed walk-through on how to build Mono (3.6.1 tarball / git) on Windows, which can be found here.

Download and Extract the Yocto 2.0 Release

At the time of writing, the current release of Yocto (2.0) can be found here.

$ cd ~
$ mkdir yocto
$ wget http://downloads.yoctoproject.org/releases/yocto/yocto-2.0/poky-jethro-14.0.0.tar.bz2
$ tar xjvf poky-jethro-14.0.0.tar.bz2

This will get you the Yocto 2.0 base meta-data and the bitbake tool. You can also add in extra layers, usually of the form "meta-foo" to provide machine support and additional functionality.

Configure the Build Environment to Build an Emulator Image

$ cd ~/yocto/poky-jethro-14.0.0
$ source oe-init-build-env build_qemux86

This will create a build tree in build_qemux86 although you could use a different name if you so wish with no adverse effects.

It is entirely possible to have many build trees in parallel in different folders and to switch between them using oe-init-build-env.

oe-init-build-env will create a default configuration file in conf/local/conf which will build an emulator image suitable for execution with qemu.

Build a Baseline Image

After configuring the environment, you will be left in the build_qemux86 folder.

You should then build a baseline image, which will take some time (numbers of hours).

$ bitbake core-image-minimal

Build an Example Project on the Host for Testing (Optional)

Building with autotools

The most straightforward way to compile non-.NET projects for different targets within Yocto is to make use of autotools.

Projects which support autotools provide a set of template files which are then used by the autotools to generate Makefiles and associated configuration files which are appropriate to build for the target environment.

Similarly, it is possible to compile Mono/.NET projects using autotools.

A very basic example 'Hello World' style project called mono-helloworld has been committed to GitHub here.

If you take a look at the two source files, helloworld.cs and helloworldform.cs, you can see that the first outputs a 'Hello World' message to the console, and the second creates a Windows Form titled 'Hello World'.

Discussion of <code><code><code>autotools template configuration for Mono is outside the scope of this guide, but the mono-helloworld project is based on the mono-skel example which can be found in the Autotools section of the Mono Application Deployment guidelines.

The project itself builds two .NET executables, helloworld and helloworldform respectively, the first of which is a console application and the second of which is a simple Windows Forms application.

To build the project on the host independently of Yocto, first clone the example repository:

$ mkdir ~/host
$ cd ~/host
$ git clone https://github.com/DynamicDevices/mono-helloworld

Then run the autotools, configure the build, and make the project:

$ cd mono-helloworld
$ ./autogen.sh
$ ./configure --enable-winformdemo
$ make

Following a successful compilation, you will have a number of new files in the root of the build folder.

There are two new .NET executables, src/helloworld.exe and src/helloworldform.exe.

You can run the first with:

$ mono src/helloworld.exe

It will output:

HelloWorld 

You can run the second with:

$ mono src/helloworldform.exe

Depending on your host environment (e.g. using SSH), you may need to explicitly set the DISPLAY variable for this to work, with:

$ export DISPLAY=:0
$ mono src/helloworldform.exe

This will give you a basic Windows Forms window title:

Image 1

So you have now shown that you can successfully fetch configure and build the project on the host.

Next, we will look at how Yocto automates this process of fetching, configuring and building, then also installs and packages the output files.

Building with xbuild

Many individuals develop with Visual Studio, Mono Develop, Xamarin Studio or other similar integrated development environments (IDEs).

Mono provides xbuild which is the Mono implementation of Microsoft's msbuild, discussed here.

In essence, this enables a developer to create a solution of projects within their IDE of choice, then use xbuild to build within the Mono environment.

A useful workflow to follow may be to develop locally with an IDE of choice, commit to a git repository upon release, then use a Yocto recipe to build and package that release into an existing image, or for provision to a package feed for update to existing targets in the field.

The mono-helloworld project discussed Building with autotools|above]] also provides a solution and project files to support build with xbuild, or indeed with an IDE such as Visual Studio.

If you have already built the examples using autotools, move the folder and start again.

$ cd ~/host
$ rm -Rf mono-helloworld

Check out the mono-helloworld project again.

$ git clone https://github.com/DynamicDevices/mono-helloworld

Run xbuild. (As you might guess from the name of the .sln file, you could clone this example project to a Windows host and open it up with Visual Studio, and in fact that is how it was created.)

<code> </code> $ xbuild /p:Configuration=Debug mono-helloworld_vs2010.sln 

This results in a number of new files, including two new Mono/.NET executables in bin/Debug helloworld.exe and helloworldform.exe.

You can run the first with:

$ mono bin/Debug/helloworld.exe

It will output:

HelloWorld 

You can run the second with:

$ mono bin/Debug/helloworldform.exe

Depending on your host environment (e.g. using SSH), you may need to explicitly set the DISPLAY variable for this to work, with:

$ export DISPLAY=:0
$ mono bin/Debug/helloworldform.exe

This will give you a basic Windows Forms window title.

Image 2

So you have now shown that you can successfully fetch configure and build the project on the host.

Next, we will look at how Yocto automates this process of fetching, configuring and building, then also installs and packages the output files.

Adding the meta-mono Layer to the Yocto Build System

A preferred method for adding recipes to the build environment, and the method shown with this guide, is to place them within a new layer.

Layers isolate particular sets of build meta-data based on machine, functionality or similar, and help to keep the environment clean.

The meta-mono layer contains Mono specific recipes to support execution of .NET applications on target boards. The layer can be found here.

To use a new layer such as this, you first clone the layer from its git repository and then add the layer to your bitbake configuration by editing conf/bblayers.conf.

$ cd ~/yocto/poky-jethro-14.0.0
$ git clone git://git.yoctoproject.org/meta-mono

You will also need the jethro branch of the meta-openembedded layer as this provides a giflib recipe which is needed by libgdiplus.

$ cd ~/yocto/poky-jethro-14.0.0
$ git clone git://git.openembedded.org/meta-openembedded -b jethro
$ cd ~/yocto/poky-jethro-14.0.0/build_qemux86
$ nano conf/bblayers.conf

Your bblayers.conf should look similar to this:

# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "6"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  /home/user/yocto/poky-jethro-14.0.0/meta \
  /home/user/yocto/poky-jethro-14.0.0/meta-yocto \
  /home/user/yocto/poky-jethro-14.0.0/meta-yocto-bsp \
  "
BBLAYERS_NON_REMOVABLE ?= " \
  /home/user/yocto/poky-jethro-14.0.0/meta \
  /home/user/yocto/poky-jethro-14.0.0/meta-yocto \
  "

Make the new layer visible to bitbake by adding a line to BBLAYERS.

BBLAYERS ?= " \
  /home/user/yocto/poky-jethro-14.0.0/meta \
  /home/user/yocto/poky-jethro-14.0.0/meta-yocto \
  /home/user/yocto/poky-jethro-14.0.0/meta-yocto-bsp \
  /home/user/yocto/poky-jethro-14.0.0/meta-openembedded/meta-oe \
  /home/user/yocto/poky-jethro-14.0.0/meta-mono \
  "

Now bitbake can see the recipes in the new layer.

You will also see when bitbake runs and shows the Build Configuration that the repository branch and hash of your layer is shown which is useful to know, particularly when comparing notes with others as to why a build fails, e.g.:

Build Configuration:
BB_VERSION        = "1.28.0"
BUILD_SYS         = "x86_64-linux"
NATIVELSBSTRING   = "Ubuntu-14.04"
TARGET_SYS        = "i586-poky-linux"
MACHINE           = "qemux86"
DISTRO            = "poky"
DISTRO_VERSION    = "2.0"
TUNE_FEATURES     = "m32 i586"
TARGET_FPU        = ""
meta              
meta-yocto        
meta-yocto-bsp    = "<unknown>:<unknown>"
meta-mono         = "master:836115be90cac01cfbb677b24d6ad6d802a795e7"
meta-oe           = "jethro:dc5634968b270dde250690609f0015f881db81f2"

Build an Image including Mono/.NET Support

The meta-mono layer includes a recipe to build an image core-image-mono based on the Yocto standard image core-image-sato.

To build this image:

$ bitbake core-image-mono

This may take a while, even if you have already built core-image-minimal as additional GUI support packages need to be built.

The core-image-mono recipe can be found here and pulls in an include file from here.

You can see in the include file that extra packages are added to the standard core-image-sato image.

IMAGE_INSTALL += "mono mono-helloworld"

This is how you would add Mono support to your image within a recipe, or within a .bbappend file. In fact, it should only be necessary to add the mono package as it is not necessary to have the examples unless you wish to for testing purposes.

The mono-helloworld recipe included here shows how to build the example project using autotools. For details, see the recipe itself here, and more importantly, the include file it pulls in here.

You could choose to replace mono-helloworld with mono-helloworld-xbuild which as the name suggests shows how to build the example project with xbuild.

Testing the .NET Executables on an Emulated Target

Having built core-image-mono, you can then run it up under qemu.

To run up the image, simply use:

$ runqemu qemux86

This will boot the emulator, load up the image, you'll see a kernel loading and then a basic user interface.

If you find that your keymap is incorrect, you might wish to set this explicitly, for example:

$ runqemu qemux86 qemuparams='-k en-gb'

or:

$ runqemu qemux86 qemuparams='-k en-us'

Open up a terminal window using the appropriate icon, log into the emulator as 'root', no password and run the examples.

You can run the console executable with:

$ mono helloworld.exe

Or alternatively, the recipe installs a script to wrap use of Mono, so you can use the form.

$ helloworld

This will output:

HelloWorld  

Image 3

You can run the Windows Forms executable with:

$ mono /usr/lib/helloworldform.exe

or:

$ helloworldform

Depending on your host environment (e.g. using SSH), you may need to explicitly set the DISPLAY variable for this to work, with:

$ export DISPLAY=:0
$ mono /usr/lib/helloworld/helloworldform.exe

This will show a test Windows Forms form titled 'Hello World':

Image 4

You can run the GTK# executable with:

$ mono /usr/lib/helloworldgtk.exe

or:

$ helloworldgtk

Depending on your host environment (e.g. using SSH), you may need to explicitly set the DISPLAY variable for this to work, with:

$ export DISPLAY=:0
$ mono /usr/lib/helloworld/helloworldgtk.exe

This will show a test Windows Forms form titled 'Hello World'.

Breakdown of an autotools recipe:

This is the content of the mono-helloworld_1.2.bb recipe.

require mono-helloworld.inc

SRC_URI[md5sum] = "ae22f282d36ae5cb82ae5a2e9bcbb8b5"
SRC_URI[sha256sum] = "365360d674bd63ab7ca1762e64e3d5d6c6d4841edf6e59f67ff8b40fafeb1137"

It can be seen that we provide a couple of check-sums which relate to the release tarball that will be downloaded.

Similarly, the included mono-helloworld.inc file which can be found here.

SUMMARY = "Mono Hello World"
DESCRIPTION = "Test applications for Mono console and windows forms"
AUTHOR = "Alex J Lennon <ajlennon@dynamicdevices.co.uk>"
HOMEPAGE = "http://www.dynamicdevices.co.uk"
SECTION = "mono/applications"
PRIORITY = "optional"
LICENSE = "GPLv3"
LIC_FILES_CHKSUM = "file://LICENSE;md5=783b7e40cdfb4a1344d15b1f7081af66"

SRC_URI = "https://github.com/DynamicDevices/mono-helloworld/releases/download/v${PV}/$

inherit autotools-brokensep
inherit mono

DEPENDS_${PN} = "mono"
RDEPENDS_${PN} = "mono"

EDEPENDS_X11 = "libgdiplus"
ERDEPENDS_X11 = "libgdiplus"
EDEPENDS_GTK = "gtk-sharp"
ERDEPENDS_GTK = "gtk-sharp"
EDEPENDS_WAYLAND = "wayland"
ERDEPENDS_WAYLAND = "wayland"

PACKAGECONFIG[x11] = "--enable-winformdemo=yes,,${EDEPENDS_X11},${ERDEPENDS_X11}"
PACKAGECONFIG[wayland] = ",,${EDEPENDS_WAYLAND},${ERDEPENDS_WAYLAND}"
PACKAGECONFIG[gtk] = "--enable-gtkdemo=yes,,${EDEPENDS_GTK},${ERDEPENDS_GTK}"

# Default configuration, distros might want to override
PACKAGECONFIG ??= "${@base_contains('DISTRO_FEATURES', 'x11', 'x11 gtk', '', d)} \
                   ${@base_contains('DISTRO_FEATURES', 'wayland', 'wayland gtk', '', d$
"

FILES_${PN} = "${libdir}/helloworld/helloworld.exe \
                ${bindir}/helloworld \
                ${libdir}/helloworld/helloworldform.exe \
                ${bindir}/helloworldform \
                ${libdir}/helloworld/helloworldgtk.exe \
                ${bindir}/helloworldgtk \
"  

For more details on check-sums, licenses and so forth, see Adding 3rd Party Components to Yocto/OpenEmbedded Linux and the Yocto Recipe & Style Patch Guide.

We have a dependency on the mono package, and again, we inherit the autotools class to make use of the bitbake autotools functionality.

Lastly, we override FILES_${PN} which controls the installed files which are added to the main output package. ${libdir}, ${bindir} are standard GNU variable naming conventions for installation paths. For details, see here and here.

In this case, we have made sure that the helloworld executable goes to /usr/lib/helloworld/helloworld.exe as does the helloworldform.exe.

It might seem quite strange to be installing the executable assemblies to the /usr/lib location, but this is in line with Mono application deployment recommendations.

We then install wrapper scripts to /usr/bin which can be called directly to run the respective examples. These scripts take the form:

Bash
#!/bin/sh
exec @MONO@ @prefix@/lib/helloworld/@APP@.exe $MONO_EXTRA_ARGS "$@"

Breakdown of an xbuild Recipe

The xbuild recipe is similar to the autotools recipe above, except that we override a couple of methods to ensure xbuild runs as we wish it to.

This recipe can be found here.

First, we include the definitions in the include file, and set the version specific check-sums for the archive to be retrieved.

require mono-helloworld.inc

SRC_URI[md5sum] = "ae22f282d36ae5cb82ae5a2e9bcbb8b5"
SRC_URI[sha256sum] = "365360d674bd63ab7ca1762e64e3d5d6c6d4841edf6e59f67ff8b40fafeb1137"

Then, we set our source directory which must be correct for bitbake to find extracted files from the retrieved archive.

REALPN = "mono-helloworld"
S = "${WORKDIR}/${REALPN}-${PV}"

Now we override the compilation method to call xbuild to build a particular .NET configuration against the a .SLN file in the archive.

CONFIGURATION = "Debug"
do_compile() {
        xbuild /p:Configuration=${CONFIGURATION} ${REALPN}_vs2010.sln
}

Next, we modify the installation method to make sure that the correct output files are installed and the executable scripts are modified to run the output assemblies.

 do_install() {
        install -d "${D}${bindir}"
        install -d "${D}${libdir}/helloworld/.debug"
        install -m 0755 ${S}/bin/${CONFIGURATION}/*.mdb ${D}${libdir}/helloworld/.debug
        install -m 0755 ${S}/bin/${CONFIGURATION}/*.exe ${D}${libdir}/helloworld

        install -m 0755 ${S}/script.in ${D}${bindir}/helloworld
        sed -i "s|@MONO@|mono|g" ${D}${bindir}/helloworld
        sed -i "s|@prefix@|/usr|g" ${D}${bindir}/helloworld
        sed -i "s|@APP@|helloworld|g" ${D}${bindir}/helloworld
        install -m 0755 ${S}/script.in ${D}${bindir}/helloworldform
        sed -i "s|@MONO@|mono|g" ${D}${bindir}/helloworldform
        sed -i "s|@prefix@|/usr|g" ${D}${bindir}/helloworldform
        sed -i "s|@APP@|helloworld|g" ${D}${bindir}/helloworldform
}

Lastly, we make sure that the .MDB debug files which are output are packaged correctly in the -dbg package. The other assemblies in ${libdir} and ${bindir} will be packaged correctly in the main output package by default.

FILES_${PN}-dbg += "${libdir}/helloworld/.debug/*"

For more details on check-sums, licenses and so forth, see Adding 3rd Party Components to Yocto/OpenEmbedded Linux and the Yocto Recipe & Style Patch Guide.

Feedback

This is a living document. Please feel free to send comments, questions, corrections to Alex Lennon.

History

  • 23/05/14 v1.0 - Initial release
  • 22/02/16 v2.0 - Updated for Yocto Jethro

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)