Welcome to the Adeneo-Embedded blog. This is the centralized home for our engineers to post content of interest for our customers and the embedded community at large.

We specialize in BSP's, solutions and training for Windows Embedded CE, the Microsoft .NET Micro Framework, and Windows Embedded Standard. For more information, please visit our corporate website at http://www.adeneo-embedded.com/.


Wednesday, January 21, 2009

Using Interop in the .NET Micro Framework

The .NET Micro Framework is a sleek and robust way of bringing high-level functionality, quickly and easily, to low-specification platforms. A developer can enable a standard suite of features on a device using the Micro Framework Porting Kit. But what do you do when the functionality you want isn't supported in the Porting Kit?

One of the coolest new features of Micro Framework version 3.0 is Interop - the ability to extend the Micro Framework's functionality, adding features with C# interfaces in a fairly straightforward way. As the .NET MF is still young, this is extremely useful for filling in the blanks in the Micro Framework specification. At Adeneo we've already used interop to implement real-time timekeeping, power management, audio playback, and other features not natively supported by the MF.

In the following piece, I'll walk through the process of adding an interop feature to an existing Micro Framework port, step by step - although Microsoft recommends a method using their SolutionWizard tool, we've found this to be pretty flimsy. I'll use the example of a GPIO-driven LED feature, which is fairly simple from the driver perspective, but should be suitable for demonstrating the vast possibilities that interop gives us. Some of what I describe below is by convention, and not strictly necessary, so if you're wondering about a particular step - feel free to experiment.




Step 0: The Starting Point

As is hopefully obvious, this walkthrough assumes you've already got a piece of MF-compatible hardware, as well as Visual Studio 2008 SP1 and the Micro Framework SDK 3.0 (the SDK is freely available from Microsoft) for creating Micro Framework projects.

The walkthrough additionally assumes that you have access to the .NET Micro Framework Porting Kit, version 3.0, as well as the ability to build ports within the kit. (I will not describe the build process, as the kit's included documentation does a sufficient job of this.) It also assumes, for the sake of example, an LED driver which already exists in C++ code, and can be compiled/linked into your port. I'll assume that this driver has the following example functions:
    // enable the selected pin for LED control
BOOL LED_Initialize(GPIO_PIN PinLED);

// return the pin to general availability
BOOL LED_Uninitialize(GPIO_PIN PinLED);

// turn the LED on (when passed TRUE) or off (when passed FALSE)
void LED_SetState(GPIO_PIN PinLED, BOOL TurnOn);

// return TRUE if the LED is on, or FALSE if the LED is off
BOOL LED_GetState(GPIO_PIN PinLED);

For brevity I will not go into detail on how to implement a GPIO-driven LED driver, the mechanics of which should be pretty straightforward.




Step 1: Create Your C# Interface (Library)

The process of implementing interop starts at the top: designing the C# class you'll use as the high-level interface. Open Visual Studio, and create a new project of type Micro Framework -> Class Library. For organizational purposes, I suggest that you create this project in your PortingKit\Solutions\[Your Solution]\ManagedCode\ folder, although you can put it wherever you want.

Within this library, name and describe the C# functions you'll use to access your driver. Note well that a later step compiles a checksum of this library into your project, and so you should try to finalize this interface first, else you'll have to update more files later.

Here's an example for C# control of an LED, starting from a Library project named LEDInterop:
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware; // for Cpu.Pin type
using System.Runtime.CompilerServices;

namespace LEDInterop
{
public class LED
{
public LED(Cpu.Pin PinLED)
{
_pin = (UInt32)PinLED;
LED_Enable(_pin);
}

~LED()
{
LED_Disable(_pin);
}

public bool IsOn
{
get
{
if (LED_Get(_pin) == 1)
return true;
else
return false;
}
set
{
if (value)
LED_Set(_pin, 1);
else
LED_Set(_pin, 0);
}
}

//--//

// this is the LED object's associated pin
private UInt32 _pin;

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void LED_Enable(UInt32 PinLED);

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void LED_Disable(UInt32 PinLED);

[MethodImpl(MethodImplOptions.InternalCall)]
private extern Byte LED_Get(UInt32 PinLED);

[MethodImpl(MethodImplOptions.InternalCall)]
private extern void LED_Set(UInt32 PinLED, Byte State);
}
}

There are a few things to note about this C# code sample:

• This example uses the Cpu.Pin type, which requires the Microsoft.SPOT.Hardware reference. Right-click on References in the Solution Explorer and select Add Reference..., then click the .NET tab and choose Microsoft.SPOT.Hardware - without this, the LEDInterop project will not build!

• "Interop" functions - that is, functions which are defined in C++, but accessible in C# - are marked with this attribute:
    [MethodImpl(MethodImplOptions.InternalCall)]

These properties are inherited from System.Runtime.CompilerServices and, together with extern, indicate that the function is implemented in C++.

• The above sample has some forms of artificial abstraction (bool to Byte, Cpu.Pin to UInt32) that may seem unintuitive. Interop functions can only accept parameters of, and return values in, a limited number of types. Per Microsoft's documentation, here is a table of the allowed types and how they link up, e.g. a Byte in the CLR (C#) is the same as a UINT8 in the Porting Kit (C++):

C# TypeC++ TypeC++ Array Type
System.ByteUINT8, BYTECLR_RT_TypedArray_UINT8
System.UInt16UINT16CLR_RT_TypedArray_UINT16
System.UInt32UINT32CLR_RT_TypedArray_UINT32
System.UInt64UINT64CLR_RT_TypedArray_UINT64
System.SByteINT8CLR_RT_TypedArray_INT8
System.Int16INT16CLR_RT_TypedArray_INT16
System.Int32INT32CLR_RT_TypedArray_INT32
System.Int64INT64CLR_RT_TypedArray_INT64
System.SinglefloatCLR_RT_TypedArray_float
System.DoubledoubleCLR_RT_TypedArray_double
System.StringLPCSTRNot Supported


The C++ array types can be accessed with subscripts (Array[i]), but otherwise do not behave quite like normal C arrays. See PortingKit\CLR\Include\TinyCLR_Interop.h for more information on these custom types.

• You can build this project in Debug or in Release mode; either is fine. The rest of this example will assume you've remained in Debug mode.




Step 2: Insert Stubbed Functionality Into Your Port

Once your C# interface is well designed, you're ready to move back down to C++. Open your project's Properties (by right-clicking on the project, or double-clicking Properties in the Solution Explorer panel) and click the .NET Micro Framework tab. Below your deployment options - which don't matter at all for your Library - is a checkbox labeled Generate native stubs for internal methods. Check this box, and Build your project; this will construct stubbed-out C++ files for your interop interface, in the directory named below the checkbox - by default, in a Stubs sub-folder within your project folder. It will also, of course, generate the binary form of your C# class for a reference assembly.

First, copy the compiled binary files (your project's bin folder) into your Porting Kit Solution. I suggest putting them in the base project folder, e.g. PortingKit\Solutions\[Your Solution]\ManagedCode\LEDInterop\. These files are required for building your port, and for Micro Framework applications to use your new functionality.

Open the stubbed driver folder, and you should see several generated C++, H, and Project files. Following the example above,

dotNetMF.proj — project file for the generated code
LEDInterop.cpp — properties of your class
LEDInterop.featureproj — project file for the C# feature
LEDInterop.h — methods of your class
LEDInterop_LEDInterop_LED.cpp — implementation of the C#/C++ link
LEDInterop_LEDInterop_LED.h — definitions for this implementation
LEDInterop_LEDInterop_LED_mshl.cpp — CLR wrappers for your interop functions

Take this folder and copy its contents into your Solution as well - I'd suggest PortingKit\Solutions\[Your Solution]\DeviceCode\LED\. You'll need to edit two files to add the driver to your port.

LEDInterop.featureproj - You'll need to change two paths in this file to accomodate your port. The tag that refers to an MMP_DAT_CreateDatabase path should point to the .pe file from your Library project:
    <MMP_DAT_CreateDatabase Include="$(SPOCLIENT)\Solutions\[Your Solution]\ManagedCode\LEDInterop\bin\Debug\LEDInterop.pe" />

You also want to point the RequiredProjects path to your stubbed files:
    <RequiredProjects Include="$(SPOCLIENT)\Solutions\[Your Solution]\DeviceCode\LED\dotNetMF.proj" />


TinyCLR.proj - You also need to add this library and feature to your CLR project, in PortingKit\Solutions\[Your Solution]\TinyCLR\TinyCLR.proj. Open up this file, and near the top should be a list of .featureproj Import references: add yours in here.
    <Import Project="$(SPOCLIENT)\Solutions\[Your Solution]\DeviceCode\LED\LEDInterop.featureproj" />

Note that this Import must be above the Import for $(SPOCLIENT)\tools\targets\Microsoft.SPOT.System.Interop.Settings - otherwise, your project will not build!

You must also add an inclusion statement for the project that builds your new interop library, and the library itself. (The name of your library is tagged as AssemblyName in the generated dotNetMF.proj file.) Somewhere in the list of included libraries for TinyCLR, add the project and driver library:
    <ItemGroup>
<RequiredProjects Include="$(SPOCLIENT)\Solutions\[Your Solution]\DeviceCode\LED\dotNetMF.proj" />
<DriverLibs Include="LEDInterop.$(LIB_EXT)" />
</ItemGroup>


That's it! Now if you rebuild and flash your port, it should include your new interop feature. You can verify this by checking the output log at CLR startup - an assembly reference of your feature's name (in our case, LEDInterop) should appear in the list of included assemblies.




Step 3: Implement Your Feature

Now that the pieces are in place, it's time to link them together. One of the C++ files you generated previously (for the example, LEDInterop_LEDInterop_LED.cpp) serves as a bridge between your C# interface and your C++ codebase. This file contains skeleton functionality for the C# functions you defined as InternalCall, and which you will now fill in.

Following the example, here's how you might fill in this file:
#include "LEDInterop.h"
#include "LEDInterop_LEDInterop_LED.h"

using namespace LEDInterop;

void LED::LED_Enable( CLR_RT_HeapBlock* pMngObj, UINT32 param0, HRESULT &hr )
{
// param0 == PinLED
if(LED_Initialize((GPIO_PIN)param0) == FALSE)
hr = CLR_E_INVALID_ARGUMENT;
}

void LED::LED_Disable( CLR_RT_HeapBlock* pMngObj, UINT32 param0, HRESULT &hr )
{
// param0 == PinLED
if(LED_Uninitialize((GPIO_PIN)param0) == FALSE)
hr = CLR_E_INVALID_ARGUMENT;
}

UINT8 LED::LED_Get( CLR_RT_HeapBlock* pMngObj, UINT32 param0, HRESULT &hr )
{
// param0 == PinLED
if(LED_GetState((GPIO_PIN)param0) == TRUE)
return 1;
else
return 0;
}

void LED::LED_Set( CLR_RT_HeapBlock* pMngObj, UINT32 param0, UINT8 param1, HRESULT &hr )
{
// param0 == PinLED
// param1 == TurnOn
if(param1 == 1)
LED_SetState((GPIO_PIN)param0, TRUE);
else
LED_SetState((GPIO_PIN)param0, FALSE);
}

The trickiest thing about this file is the function parameters. Note that the C# parameters are renamed when they come down to this level, in the form param0, param1, and so on. For this reason it's wise to put some documentation in this file explaining what each of the parameters means.

With this new implementation, build your port, and flash it again. Now everything is assembled.




Step 4: Use It!

To use your interop feature in a C# application, you'll need to include the library reference. Open a new or existing Micro Framework application project. Right-click on References in the Solution Explorer and select Add Reference..., then click the Browse tab and browse for the output of your interop library - for the example, PortingKit\Solutions\[Your Solution]\ManagedCode\LEDInterop\bin\Debug\LEDInterop.dll - and add this Reference to your project. Now, you have access to the interop class you defined in Step 1.

Here's an example Micro Framework console application to control two LEDs:
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
// NOTE: you'll also need a reference to your
// hardware assembly, to use its pins!
using LEDInterop;

namespace MFConsoleApplication
{
public class Program
{
public static void Main()
{
LED led1 = new LED(Pins.GPIO_PORT_A_00);
LED led2 = new LED(Pins.GPIO_PORT_B_01);

led1.IsOn = true;
if(led2.IsOn)
Debug.Print("led2 is on!");
else
Debug.Print("led2 is off!");
}
}
}




Controlling hardware through intuitive C# interfaces is what the .NET Micro Framework is all about, and with a little practice, it's fairly easy to use interop features to push your platform beyond the standard Micro Framework: CODEC control, PWM, and arbitrary GPIO peripherals are only a few examples. Interop opens the door to a whole world of new Micro Framework-driven applications.

- TS




NOTES

• Properties of your C# class - such as _pin in our LEDInterop example - will be made accessible to your C++ code in the stub-generation process. If you create a property of a type that isn't compatible with C++ (per the table in Step 1), your C++ project will not build; but if you aren't using this property in C++, you can comment out the declaration for it in the .h file.

• As remarked in Step 2, the order in which you Import .featureproj references matters. Make sure you place them correctly!

• Changing the layout of your interop library class will require that you update all of your generated C++/H files, as they contain, among other things, a checksum of the class's signature. Be sure you update all of the generated files (and the binary output!) every time you change your interop library.

• The .pe binary file will only contain the aforementioned checksum value if the Generate native stubs for internal methods box is checked. If you build your class library without this checked, your port will be unable to accept deployed applications!

1 comment:

Dominik said...

at the end of Step 3, about "implement your feature", you say "... with this implementation, build your port and flash it again...."

what is the "port" ?