Using the C# library on Linux and macOS with Mono

Using the C# library on Linux and macOS with Mono

We recently published two C# applications which use Yoctopuce modules: Yocto-Visualization and Yocto-Discovery. Thanks to Mono, we can run these two applications under Windows, macOS, and Linux (Intel and ARM). We needed a few trials until Mono accepted to use our library. So we thought that this would interest other users...




In this post, we are going to explain how to use our C# programming library with Mono. But this is not a tutorial on Mono. We assume that you know how to write a simple C# application and that you can make it work under Windows, Linux, and macOS with Mono. In the same way, if you have never used our modules, we recommend that you start with our tutorial series, in particular with the post "How to get started in C# with Yoctopuce modules".

Portability issues


In most cases, using a C# .NET application under Linux or macOS is not an issue because the code is interpreted by a virtual machine and not directly by the CPU. Under Windows, it's the .NET environment which implements this virtual machine, while under Linux and macOS, Mono takes care of it.

For example, you can run the following code on all the platforms:

static void Main(string[] args)
{
    Console.WriteLine("Hello world");
}



So why is it more complex with our C# library? Because the Yoctopuce library is divided into two layers: one high level layer implements Yoctopuce objects (YModules, YTemperature, and so on) and a low level layer manages communications with the hardware. The high level layer is written in C# and is thus portable. That is, the code generated by the compiler can run directly on any C# virtual machine (Windows or Mono).

The low level layer, called "yapi", works differently. It's a dynamic library written in C which directly interacts with the OS. Although we can compile this code for all the platforms that we are interested in, the resulting generated code is not portable. The 32-bit Windows version works only on Windows 32 bits, the Linux ARM version works only on Linux ARM, and so on. This is why you find several versions of this dynamic library in the /Source/dll sub-directory of our library.

When executing, the program using our library detects the OS and the CPU of the machine, and loads the proper version of the dynamic library.

Concretely, the portability "issue" of our library can be scaled down to copying the appropriate version the library in the correct directory so that the .NET virtual machine can find it.

Example


For this post, we implemented a very basic command line application. This application simply lists all the modules connected to the USB ports of the current machine.

The code is extremely simple. It initializes the API and it iterates on all the connected modules:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
  class Program
  {
    static void Main(string[] args)
    {
      YModule m;
      string errmsg = "";

      if (YAPI.RegisterHub("usb", ref errmsg) != YAPI.SUCCESS) {
          Console.WriteLine("RegisterHub error: " + errmsg);
          Environment.Exit(0);
      }

      Console.WriteLine("Device list");
      m = YModule.FirstModule();
      while (m != null) {
          Console.WriteLine(m.get_serialNumber() +
              " (" + m.get_productName() + ")");
          m = m.nextModule();
      }
      YAPI.FreeAPI();
    }
  }
}


To compile this code, you must add the yocto_api.cs file to the project implementing YAPI and YModules objects. Without doing any thing else, you can compile the project. But if you run the resulting executable file, the call to YAPI.RegisterHub returns an error: "Unable to load yapi.dll".

For the program to work properly, we must copy in the executable directory the version of the dynamic library corresponding to the platform on which the program is run.

Windows 32 bits


The Windows 32 bits version of the yapi dynamic library is the yapi.dll file located in the /Sources/dll directory of our library.

You must copy it in the directory of the executable file. You can do it either by hand, or by adding the dll to the project and telling Visual Studio to copy it when it is compiling thanks to the "Copy to Output Directory" property.

You must copy the dll in the same folder as the executable file
You must copy the dll in the same folder as the executable file



Windows 64 bits


The Windows 64 bits version of the yapi dynamic library is the yapi.dll file located in the /Sources/dll/amd64 directory of our library. For the C# virtual machine to find this library, you must copy it into an amd64 sub-directory.

Again, you can either copy the file by hand, or do it directly from Visual Studio. Note: for the dll for end up in an "amd64" sub-directory, you must create this directory beforehand in the "Solution Explorer" panel.

You must add the dll in an amd64 sub-directory
You must add the dll in an amd64 sub-directory



Then, you must add the dll to this sub-directory and the value "Copy always" for the "Copy to Output Directory" property.

Note that by adding the 32 bit dll and the 64 bit dll to your project, you can now generate an "Any CPU" application which can run on any Windows. When executing, the library automatically takes the appropriate version of the dll.

Installing Mono


To run our application under Linux or macOS, you must first install Mono. The procedure is relatively simple. Under macOS, you must download and run the Mono installer. Under Linux, most of the time you can install it directly with the following command:

sudo apt-get install mono-complete



If the package is not available in your distribution, use Mono's web site to learn how to install it.

Running application under Linux and macOS

Although you can compile the application under Linux or macOS, experience taught us that it's a lot simpler to always compile the application under Windows and to copy the executable directory onto the other OSes. To run the application, you can use the following command:

$ mono ConsoleApp1.exe




Copying the dynamic libraries


As you haven't yet copied the Linux or macOS versions of the dynamic library, the program returns an "Unable to load yapi.dll" error message.

yocto@linux-laptop:/tmp/bin/Release$ mono ConsoleApp1.exe RegisterHub error: Unable to load yapi.dll (yapi) yocto@linux-laptop:/tmp/bin/Release$



You must copy the four Linux versions and/or the two macOS versions of the yapi dynamic library:

  1. libyapi-i386.so for Linux Intel 32 bits
  2. libyapi-amd64.so for Linux Intel 64 bits
  3. libyapi-armhf.so for Linux ARM 32 bits
  4. libyapi-aarch64.so for Linux ARM 64 bits
  5. libyapi32.dylib for macOS 32 bits
  6. libyapi.dylib for macOS 64 bits


These files must be copied in the same directory as the executable file.

Update: Since version 1.10.34639 of the C# library, you don't need to modify the .config file and to add dllmap entries.

But in the opposite to Windows, there is an additional step: you must modify the consoleApp1.exe.config file and add a <dllmap> node for each version of the yapi library in the <configuration> section.

Here is the final file of our example:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <dllmap dll="yapi" os="linux" cpu="x86" target="libyapi-i386.so"/>
    <dllmap dll="yapi" os="linux" cpu="x86-64" target="libyapi-amd64.so"/>
    <dllmap dll="yapi" os="linux" cpu="arm" target="libyapi-armhf.so"/>
    <dllmap dll="yapi" os="osx" wordsize="32" target="libyapi32.dylib"/>
    <dllmap dll="yapi" os="osx" wordsize="64" target="libyapi.dylib"/>
</configuration>



As you can see, there is one item per platform. The os, cpu, and wordsize attributes are used to tell Mono which library to use for which platform.


Here you are, you now have a single application which works on a Windows machine (32 or 64 bits), under Linux 32 or 64 bits, on a Raspberry Pi, and under macOS.

In the end, if we summarize the list of required file, we get:

  • ConsoleApp1.exe : the C# executable
  • ConsoleApp1.exe.config : the configuration file with the dllmap inputs
  • yapi.dll : the Windows 32 bits low level layer
  • amd64/yapi.dll : the Windows 64 bits low level layer
  • libyapi-i386.so : the Linux 32 bits low level layer
  • libyapi-amd64.so : the Linux 64 bits low level layer
  • libyapi-armhf.so : the Linux under ARM low level layer
  • libyapi-aarch64.so : the Linux under ARM 64 bits low level layer
  • libyapi.dylib : the macOS (64 bits) low level layer
  • libyapi32.dylib : the macOS (32 bits) low level layer


The source code of the example


The source code of this example is available on GitHub: https://github.com/yoctopuce-examples/MultiPlatformCSharp.

Conclusion et comments


Although adding dllmap nodes in the configuration file doesn't bother the execution under Windows, we can't configure Visual Studio to automatically add them. As a matter of fact, when compiling, Visual Studio checks this file and generates an error if there is tag which is "not Microsoft certified". This means that you must manually patch the file after each compilation.

You may have noticed that we have a 32 bit version of the yapi library for macOS, although Apple has announced the end of 32 bit support in the future versions of macOS. The reason is that currently the support for Winform graphic applications doesn't work on the 64 bit version of Mono. So, the only solution to run a Winform graphic application under mac is to use the 32 bit version.

To conclude, despite some bugs and limitations here and there, Mono allows us to relatively easily create a graphic application which works on the three main OSes and on a Raspberry Pi. In any case, without Mono, we would probably not have had the resources to code three versions of our Yocto-Visualization and Yocto-Discovery applications.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.