In Part 2 we learned how to setup Visual Studio to compile ARM native code and how to generate and build the Interop managed/native hooks.
Now that you are able to call into native code, we want to start adding the native code. When you call a native function, you are likely passing down arguments from managed. These arguments are placed together into a “stack”, that holds all kind of info. For example, when you pass down an array, you will also have the array length available.
We now need to get out the argument that were passed to us. Looking the teh function name, you can get hints on the method prototype.
EncipherFast___STATIC___VOID__U4__SZARRAY_U4__SZARRAY_U4
Our EncipherFast method is static returning void and taking unsigned integer 4 bytes (U4) and then 2 arrays of unsigned int as well. Compare this to the C# code.
static public extern void EncipherFast(uint Rounds, uint[] Data, uint[] Key);
The system stack can now be used to fetch the arguments. First, get the InteropManager.
auto ip = md.InteropManager;
You can now geat each argument. The first one is unsigned int holding the rounds
// Arg 0 is the cipher round count
TinyCLR_Interop_ClrValue arg0;
ip->GetArgument(ip, md.Stack, 0, arg0);
uint32_t Rounds = arg0.Data.Numeric->U4
By the way, this is where the power of intellisense comes in handy.
The next arguments are the data and the key
// Arg 1 is the Data
TinyCLR_Interop_ClrValue arg1;
ip->GetArgument(ip, md.Stack, 1, arg1);
uint32_t* Data = reinterpret_cast<uint32_t*>(arg1.Data.SzArray.Data);
size_t DataLen = arg1.Data.SzArray.Length;
// Arg 2 is the Key
TinyCLR_Interop_ClrValue arg2;
ip->GetArgument(ip, md.Stack, 2, arg2);
uint32_t* Key = reinterpret_cast<uint32_t*>(arg2.Data.SzArray.Data);
size_t KeyLen = arg2.Data.SzArray.Length;
You are now ready to use the arguments at will! For example, you my want to check the length of the key and data before using the arrays. Remember you are now in C++ and there is no array boundary checks. I remember reading this in a book long time a go. It said something like “Programming in C is like going a super fact car without breaks”.
The return value from an interop is used for raising exceptions in the system. Like you can return an out of range exception.
TinyCLR_Result::ArgumentOutOfRange
But what about returning values? You can of course use the arrays to return value like we have in this example. This is standard C/C++/C# technique. However, this example does not return a value (void). If you are returning a value then you can insert the return value like this code.
TinyCLR_Interop_ClrValue ret;
ip->GetReturn(ip, md.Stack, ret);
ret.Data.Numeric->I4 = 123;
Just like arguments, the return type can be anything so make sure you match what you use with what the expected return. The example I gave above is I4 type so this is a signed integer of 4 bytes.
Where do you go from here? While this is everything you need to use Interops to build native code, TinyCLR gives you more functionality by providing access to other native drivers, the APIs. For example, a native OneWire driver will need to control GPIOs in near real-time. The GPIO native drivers, along with all other native APIs are exposed through TinyCLR.h. This is covered in the documentation.