This document provides a walkthrough of producing an ‘interop assembly’ for the Netduino Plus 2, which facilitates bridging managed to native code implementation.
NetMF provides a mechansim whereby managed code can invoke implementation realised through native means (typically in C/C++, etc.), called an ‘interop assembly’. The use of this mechanism has been described elsewhere, including:
Porting SDK Documentation:
RCLPort.chm > The .NET Micro Framework Porting Kit > Porting Kit Overviews > Adding Features to Your Port > Adding Features to the .NET Micro Framework by Interoperating with Unmanaged Code
as well as others.
This document is intended not to repeat these, but rather to provide a brief step-by-step guide for implementing a simple interop assembly for the Netduino Plus 2 as quickly as possible, and to convey caveats particular to that activity. It is still worthwhile to consult other sources for details on more sophisticated interop capabilities.
Creating an interop assembly for NetMF consists of these tasks:
- Building Firmware
The Interop has a ‘native’ component, which must be built into the firmware image. It is not possible to ‘dynamically load’ (or otherwise later deploy) the managed component outside of the monolithic firmware image.
- Creating a ‘Class Library’ project
This project will be modified to also emit stubs of skeleton C code to be modified by adding your code.
- Integrating the Generate Stubs into the Firmware
The generated/modifed code will be placed in the firmware build hierarchy, and some project files will be modified to include these added files.
That is the basics of creating the interop assembly. Beyond that, you must:
- Compile the firmware and generate the flash image
- Flash it to your board
- Create your project that uses your new assembly, adding a reference to it
- Run your project, invoking your interop’ed methods
1 — Building Firmware
Setup for this is a biggie. I have covered it elsewhere, e.g.
Building Netduino Plus 2 Firmware 184.108.40.206 with Yagarto GCC 4.6
but you can use whatever build system you like. Regardless of what route you choose, this is the first thing you need to get working. Interops, at least in the present (4.2) release of NetMF, are required to have their native side linked into the firmware image.
2 — Creating the Interop Class Library Project
[this is from the perspective of VS2010 Ultimate, but you can figure out the variation for whatever you’re using, I’m sure]
a — First, we will create a C# class library project like any other:
From the File, New > New Project… choose
New Project, Other Languages, Visual C#, Micro Framework, Class Library
Choose a name, I’m going to call it DemoPeekPoke.
b — Next, we will modify the project to produce our native stubs:
Get to the project’s properties; you can use View, Solution Explorer, and right click on the DemoPeekPoke, and select ‘Properties’
The last tab on the left will be ‘.NET Micro Framework’. Select it. The configuration pane presented will have a checkbox ‘Generate native stubs for internal methods’. Select it. Personally, I leave the defaults as they are.
The ‘root name for stub files’ is interesting if you want to change the filenames the tool emits (ostensibly to avoid some conflicts if your toolchain has troubles with same-named files in different directories).
‘Create stub files in this directory’ might be interesting in some case, but I conscientiously don’t change it. Each time your build the project, the files here will be overwritten. It should be preset to a directory ‘Stubs’ within your project directory.
c — Make your class interface
Obviously what you put here is specific to your application, but for this demo we are going to do this:
Rename the class file from ‘Class1.cs’ to ‘HardwareInterface.cs’. (You will be prompted to fix things up, and I say ‘yes’. This should also fixup the class name within the file to be ‘HardwareInterface’)
d — add a ‘using’ to ‘System.Runtime.CompilerServices’
This will allow you to decorate your methods as needed to cause stubs to be generated.
e — add your methods of interest
Again, this will be whatever your application demands, but in my case, I’m going to make two methods that will allow you to ‘Peek’ at an arbitrary memory address, and ‘Poke’ a value as well. Sounds basic, but actually it is pretty powerful since most of the hardware is memory mapped.
//note, these addresses are meant to be dword-aligned
public extern UInt32 PeekDWord(UInt32 addr);
public extern void PokeDWord(UInt32 addr, UInt32 word );
The decoration [MethodImpl(MethodImplOptions.InternalCall)] is required, and the method must by public extern. Must. This will cause the stubs to be emitted.
f — build the project
OK, now stuff is happening.
As per usual, a ‘bin’ directory now contains ‘Debug’ and ‘Release’ (depending on what configuration you built, if you have configurations (Express edition doesn’t)), and those in turn have ‘be’ and ‘le’, ostensibly for a ‘bigendian’ and ‘littleendian’ machine (but really they are both the same). This is our managed side stuff, and what your applications would ‘add reference’ to. We’ll also be copying that into our firmware build tree soon.
Also created are things in ‘Stubs’. You should have these 7 items
DemoPeekPoke.cpp — we won’t mess with it
DemoPeekPoke.featureproj — we WILL mess with it
DemoPeekPoke.h — we won’t mess with it
DemoPeekPoke_DemoPeekPoke_HardwareInterface.cpp — we WILL mess with it
DemoPeekPoke_DemoPeekPoke_HardwareInterface.h — we won’t mess with it
DemoPeekPoke_DemoPeekPoke_HardwareInterface_mshl.cpp — we won’t mess with it
dotNetMF.proj — we won’t mess with it
3 – Create Firmware Project
Your Porting Kit, along with all the Netduino software is located in some directory often referred to as $(SPOCLIENT). We need to make a place to put our code (both managed and C) for the build system. It’s not critical where, and for this example I will use:
a — A Place for your Stuff
Make two directories under that for your managed and native code
b — Put Stuff in its Place
from your compiled DemoPeekPoke project, copy all the stuff in
/bin/Debug (or /bin/Release if you prefer)
from your stubs directory, copy all the stuff into
so, ManagedCode should have a built assembly and a be and le directory containing processed assemblies. NativeCode should have some project and source files.
c — Fixup Interop Project Files
Fixup some paths in NativeCode\DemoPeekPoke.featureproj. There is a tag MMP_DAT_CreateDatabase which I comment out altogether. This will cause the managed assembly to be built into the firmware (don’t need it). If you want to fix it up proper, though, change the Include attribute to be
Fixup the path of RequiredProjects to refer to your location in the porting kit:
<RequiredProjects Include=”$(SPOCLIENT)\MyInterops\DemoPeekPoke\NativeCode\dotnetmf.proj” />
CAVEAT: It is handy that we are copying this stuff, because every time you build your DemoPeekPoke project (e.g. you add/remove/change some methods), all the stubs will be re-generated, and any work you might have added there will be overwritten. If you need to do this, carefully merge the changes into this directory.
CAVEAT2: the managed and native code must be in sync. A checksum is computed when the project was built and embedded in the code. If you do rebuild your project, be sure to copy over both the native code and the managed code into these working directories in the porting kit. You’ll gert runtime errors otherwise.
d — Fixup Netduino Firmware Project File
This is perhaps the trickiest bit. In:
You will need to add an Import tag, and an ItemGroup tag.
Hunt for the Import tag containing ‘Microsoft.SPOT.System.Interop.Settings’ (around line 61 on my system) and place your Import tag just before it:
<Import Project=”$(SPOCLIENT)\MyInterops\DemoPeekPoke\NativeCode\DemoPeekPoke.featureproj” />
Also, add your ItemGroup tag; I just put it at the end:
CAVEAT: I found the hard way that the Import tag really needs to come before the one for Microsoft.SPOT.System.Interop.Settings. If you don’t, the firmware will build but you’ll get runtime errors when you try to use your interop.
4 — Implement your Functionality
This is what it’s all about. In your NativeCode directory, the file:
has the stubs you must implement. This test is simple; for PeekDWord(), use
UINT32 retVal = *(UINT32*)param0;
and for PokeDWord(), use:
*(UINT32*)param0 = param1;
So, Peek will read a machine word from an arbitrary address, and Poke will write a machine word to an arbitrary address. You could do tons more, of course, but this is enough for the demo.
5 – Build and Flash
Do your firmware build as usual, make your flash image, and flash it to your board.
6 — Build Test App
Make a Netduino Plus 2 app for testing. Add a reference to your interop using the Add Reference… Browse, and find your DemoPeekPoke.dll in the bin/Debug directory of the DemoPeekPoke project you first created and built.
Now you can do some peeking and poking. For instance, address 0x1fff7a22 contains a constant indicating how much Flash is on the chip.
UInt16 nFlashSize = (UInt16)hwif.PeekDWord(0x1fff7a22);
Debug.Print(“flash size = ” + nFlashSize + “k”);
Also, addresses 0x1fff7a10-1b contain a unique serial number.
UInt32 nDevId0 = hwif.PeekDWord(0x1fff7a10);
UInt32 nDevId1 = hwif.PeekDWord(0x1fff7a14);
UInt32 nDevId2 = hwif.PeekDWord(0x1fff7a18);
Debug.Print(“device id = ” + nDevId2.ToString(“x8”) + nDevId1.ToString(“x8”) + nDevId0.ToString(“x8”));
The device has a hardware random number generator (for true random numbers, useful for crypto).
Here’s some sample output from my board:
flash size = 1024k
device id = 363430373131471000190036
rand = 0x4BE4D446
rand = 0xF089656A
rand = 0x13C1798F
rand = 0x313FF4CA
rand = 0x60B2F729
OK, I had hoped this would be a shorter doc, but I think I pared it down to the bare minimum. Here is a checklist style summary that will make sense after you’ve gone through the expository part once.
Checklist of to-do to make Interop:
- Make a Class Library Project, name and define your interop’s interface class
- Declare the methods
attribute, and be
- Fixup the project properties for .NetMF to generate native stubs
- build the project, generating the native code and the stubs
- Make a spot in the firmware heirarchy; copy the managed code and native (stubs) code into place.
- Fixup the .featureproj file
- Fixup netduino tinyCLR.proj to include your stuff
- Implement the stub methods
- Build the firmware
- If you modify the interface to add/remove/change the methods, carefully merge the changes in the regenerated files into your firmware copies. Bear in mind that the managed and native are coupled by way of a computed checksum; the managed assembly your app uses must be the one that was generated with the stubs that the firmware incorporates.