David's Random Projects and Documents
Feel free to use and link to any docs you find here. Please give credit and link back to this page on any publicly avaliable copies.

Creating C# Libraries based on Intel's MRAA Lib

I've been meaning to write a set of libraries based on Intel's MRAA Library for a while now but my research has had a nasty habit of consuming all my time lately. So in order to help those who need low level I2C, SPI, UART, PWM, analog IO, or GPIO access but don't know where to start here are my tips on plugging the MRAA Library into C# code.

External imports

The most fundamental and arguably only piece of information you need to know is where to find and how to interface with external libraries. Here I will be discussing Intel's MRAA Lib found at (http://iotdk.intel.com/docs/master/mraa/) which provides API documentation. For C# integration you will be using the C API not C++.

Importing an external library function is fairly straight forward you need to first use System.Runtime.InteropServices

using System.Runtime.InteropServices;
...

Then import the function inside a class using the [DllImport(...)] tag and static extern function definition. For example to import the init function for the i2c interface we need to first import libmraa.so and then define the function prototype. I like to keep my extern's private to prevent quick hacks from killing things later on but static extern is the important bit here

class MyClass
{
  ...
  [DllImport("libmraa.so")]
  private static extern IntPtr mraa_i2c_init(int bus);
  ...
}

If you look at the API documentation you will find that the prototype in C# differs slightly from the API definition mraa_i2c_context mraa_i2c_init (int bus) this is because C# (without unsafe code) has no pointer type and does not know what an mraa_i2c_context is. Since we do not need to use this value other than to send it back in API argument lists we can safely tell C# to store this pointer as an integer or IntPtr. In general whenever an object or pointer is returned in an API an IntPtr should be used to store the value. Data can later be retrieved through marshaling if necessary but well get to that later. When passing pointers back to C APIs it is safe to use value type arrays. These will be sent as a pointer to a C array of the same type however length information must be passed separately.

Types

Many C APIs will use new types not available to you program such as enums, structures, and arrays information back and forth. These types will need to be redefined in your application. As a general rule enums (unless otherwise specified) can be treated as integers, Arrays returned from API calls (as pointers) are treaded as an IntPtr in C# to be marshaled separately, arrays (pointers) as arguments are simply the array type (..., byte[] arg2, ...), and structs must be redefined in C# taking care to map internal data types and positions exactly as defined in API.

Enums

Enumeration types defined by MRAA are typically integers which is the default underlying enumeration type of C# so strictly speaking explicit enum type declaration is not required. In my libraries you will notice that I like to explicitly state types even when not required in APIs as I think it makes the definitions easier to understand. This is just personal preference but also helps to illustrate the methods used to define enum types.

In order define enumerator types used in API calls both enum type and enumeration values may be explicitly defined. For example the I2C Mode enumeration found here(scroll to top of page for value definitions) can be defined as

enum I2CMode : int {
  STD = 0, FAST = 1, HIGH = 2
}

In this particular instance the type int and values could have been omitted as C# enums are implicitly integers and the enumeration values count up from 0 in the order they are defined.

Arrays as arguments

Passing arrays as arguments is as simple as passing a type[] array to the C API. These APIs typically also require an integer length argument as the conversion from C# array to C pointer looses length information. For example defining and using int mraa_i2c_read (mraa_i2c_context dev, uint8_t *data, int length) would be done as follows

[DllImport("libmraa.so")]
private static extern int mraa_i2c_read(IntPtr dev, byte[] data, int length);
...
void SomeFunc()
{
  ...
  byte[] buffer = new byte[4];
  mraa_i2c_read(device, buffer, buffer.length);
  ...

Arrays (pointers) as return values

Returned pointer are a little more tricky than their argument counter parts as there is no way for C# to restore length data without knowledge of the data being sent back. Luckily all returned pointers I have had to deal with in libmraa so far have either been used solely as arguments of the API (no data knowledge required) or are strings. Both of which are easy to handle. For the former an IntPtr type is sufficient to hold and pass back the pointer value to API functions. For strings or char* Marshal.PtrToStringAuto(IntPtr) is your friend. The import return type will still be treated as an IntPtr but a call to Marshal.PtrToStringAuto(retValue) will convert this into a C# string. For example using the char* mraa_get_platform_name() would go something like

[DllImport("libmraa.so")]
private static extern IntPtr mraa_get_platform_name();
...
string GetName()
{
  IntPtr strPtr = mraa_get_platform_name();
  return Marshal.PtrToStringAuto(strPtr);
}
...