Interop sample

Hello,

first try with TinyCLR 1.0.0 Interop and guess what ? :roll_eyes:

Interop01_Interop01_MyNativeClass.cpp: In static member function 'static TinyCLR_Result Interop_Interop01_Interop01_MyNativeClass::MyNativeFunc___I4__I4(TinyCLR_Interop_MethodData)':
Interop01_Interop01_MyNativeClass.cpp:10:31: error: invalid initialization of reference of type 'const TinyCLR_Interop_StackFrame&' from expression of type 'int'
     ip->GetArgument(ip, 0, arg);
                               ^
make: *** [Interop01_Interop01_MyNativeClass.obj] Error 1

I have used the sample provided on this page and only changed the name of the class from “InteropTest” to “Interop01”.

Here is the code :

#include “Interop01.h”

TinyCLR_Result Interop_Interop01_Interop01_MyNativeClass::MyNativeFunc___I4__I4(const TinyCLR_Interop_MethodData md)
{
auto ip = md.InteropManager;

    TinyCLR_Interop_ClrValue arg;
    TinyCLR_Interop_ClrValue ret;

    ip->GetArgument(ip, 0, arg);			// Compilation error here
    ip->GetReturn(ip, md.Stack, ret);

    ret.Data.Numeric->I4 = arg.Data.Numeric->I4 * arg.Data.Numeric->I4;

    return TinyCLR_Result::Success;
}

TinyCLR_Result Interop_Interop01_Interop01_MyNativeClass::get_MyNativeProperty___I4(const TinyCLR_Interop_MethodData md)
{
	auto ip = md.InteropManager;

    const TinyCLR_Interop_ClrObject* self;
    TinyCLR_Interop_ClrValue field;
    TinyCLR_Interop_ClrValue ret;

    ip->GetThisObject(ip, md.Stack, self);
    ip->GetField(ip, self, Interop_Interop01_Interop01_MyNativeClass::FIELD___field___I4, field);
    ip->GetReturn(ip, md.Stack, ret);

    ret.Data.Numeric->I4 = field.Data.Numeric->I4;

    return TinyCLR_Result::Success;
}

What am I doing wrong ?

Documentation is not up to date.
I think it must be:

ip->GetArgument(ip, md.Stack, 0, val);

As Api has a lot of changes, and Interop Api too, documentation should be changed soon, I hope.

1 Like

Good point ! :slight_smile:
I had tried to change the order of parameters but not adding one :frowning:
Thank you !!!

Now everything is working fine ! At least with simple code as in the sample.

1 Like

Not enough hours in a day! Just happy that we are at preview 1, no more changes and we can finally finish the docs.

1 Like

Unfortunately, you are right, Gus :frowning:

But may I still ask for some help ? :flushed:

How do you return arrays and/or strings from Interop ?
I have tried the following code but it does not work, of course :

TinyCLR_Result Interop_Interop01_Interop01_MyNativeClass::TestArray___SZARRAY_U1__I4(const TinyCLR_Interop_MethodData md) {

auto ip = md.InteropManager;

TinyCLR_Interop_ClrValue arg;
TinyCLR_Interop_ClrValue ret;

uint8_t buff[12];
uint8_t * srcBuff = (uint8_t *)&buff[0];

ip->GetArgument(ip, md.Stack, 0, arg);
ip->GetReturn(ip, md.Stack, ret);

ret.Data.SzArray.Data = srcBuff;

ret.Data.SzArray.Length = 12;

return TinyCLR_Result::Success;

}

TinyCLR_Result Interop_Interop01_Interop01_MyNativeClass::TestString___STRING__I4(const TinyCLR_Interop_MethodData md) {

auto ip = md.InteropManager;

TinyCLR_Interop_ClrValue arg;
TinyCLR_Interop_ClrValue ret;

const char label = “String\0”;

ip->GetArgument(ip, md.Stack, 0, arg);
ip->GetReturn(ip, md.Stack, ret);

ret.Data.String.Data = (const char *)label;

ret.Data.String.Length = (size_t)6;

return TinyCLR_Result::Success;

}

As you can see, it does nothing. I just wanted to test return values.
The array method seems to return only a single byte (which is 0x00) and the string method returns null.

1 Like

You can’t create a local array and return it to managed. You are returning a printer that is only valid in the scope of your native method. You need to allocate memory on the head to do so.

An easier and better approach is to create the buffer you need on the managed side and then pass it to the interop.

1 Like

Here is my native code to flush the display super fast!

#include "AdafruitDisplayShield.h"

TinyCLR_Result Interop_AdafruitDisplayShield_GHIElectronics_TinyCLR_ST7735_ST7735::NativeFlushHelper___VOID__SZARRAY_U1(const TinyCLR_Interop_MethodData md) {

	
	auto ip = reinterpret_cast<const TinyCLR_Interop_Provider*>(md.ApiProvider.FindDefault(&md.ApiProvider, TinyCLR_Api_Type::InteropProvider));
	//auto ip = md.Interop;

	TinyCLR_Interop_ClrValue arg1;// , arg2;
	
	ip->GetArgument(ip, md.Stack, 1, arg1);
	uint8_t* data = reinterpret_cast<uint8_t*>(arg1.Data.SzArray.Data);
	uint8_t buffer2[2];

	auto spiProvider = (const TinyCLR_Spi_Provider*)md.ApiProvider.FindByIndex(&md.ApiProvider, "GHIElectronics.TinyCLR.NativeApis.STM32F4.SpiProvider",0,TinyCLR_Api_Type::SpiProvider);

    if (spiProvider == nullptr) 
		return TinyCLR_Result::ArgumentNull;

	for (int i = 0; i < 160 * 128; i++) {
		int blue = (data[i] & 3) << 3;
		int red = (data[i] & (7 << (2 + 3)));
		int green = (data[i] & (7 << 2)) >> 2;

		if (data[i] != 0) {
			buffer2[0] = (uint8_t)(red | green);
			buffer2[1] = (uint8_t)blue;
		}
		else {
			buffer2[0] = 0x00;
			buffer2[1] = 0x00;
		}

		size_t sz = 2;
		if(spiProvider->Write(spiProvider, 0, buffer2, sz) != TinyCLR_Result::Success)
			return TinyCLR_Result::InvalidOperation;

	}

	return TinyCLR_Result::Success;
}
2 Likes

This is what I have on managed side

[MethodImpl(MethodImplOptions.InternalCall)]
        private extern void NativeFlushHelper(byte[] data);
        public void NativeFlush(byte[] data) {
            SetClip(0, 0, Width, Height);
            WriteCommand(0x2C);
            controlPin.Write(GpioPinValue.High);
            NativeFlushHelper(data);
        }
2 Likes

Finally, I remember @John_Brochue saying that this release removed “this” from the argument stack and GetArgument needs 0 instead of 1. I didn’t get a chance to try it.

@Gus_Issa Thank you very much for the advice and for the code ! I will try that right now.

And your code is showing usage of SPI provider as well, nice bonus :clap:

1 Like

I know you want to make displays work super fast work quail :wink:

1 Like

Hello Gus,

Just to let you know, it does not seem to work if I locate RLP in flash.

The exact same code does work if I set the RLP region from 0x2002F000 to 0x20030000 but does not work if I set the region from 0x08100000 to 0x08200000.
Of course, this region is not used by deployment, which ends at 0x080FFFFF.

I am getting the following exception as soon as I call a method from the interop class :

The thread ‘’ (0x2) has exited with code 0 (0x0).
#### Exception System.NotSupportedException - CLR_E_NOT_SUPPORTED (1) ####
#### Message:
#### Interop01.Program::Main [IP: 0030] ####
Exception thrown: ‘System.NotSupportedException’ in Interop01.exe
An unhandled exception of type ‘System.NotSupportedException’ occurred in Interop01.exe

The interop scatter file looks like this :

MEMORY {
SDRAM (wx) : ORIGIN = 0x08100000, LENGTH = 0x100000
}

If I set the addresses back to the 0x2xxxxx range, then everything is working fine again.

I would say that this is not surprising to me but since you told me that RLP could be located in flash, I did that test.
For now, this is not a problem but maybe it will become an issue if/when RLP code size is growing and get beyond the 4KB actual size.
If I try to increase that size, I will have to decrease the heap size (that is located just before), which is not a good idea :wink:

What do you think ? Am I doing something wrong again ? I would gladly accept to see a “Yes” in your reply :smiley:

How did you load your program on flash? Flash has specific ways. You can’t simply use the marshalling class. Use ST told for example to load your native code.

I don’t really understand your question about “how do I load the program on flash”… It is Visual Studio since the native interop code is in a resource file.
I don’t do anything else.

Anyway, this is not important at the moment, as I am still learning and won’t fill the 4KB assigned to RLP. And btw, 4 kb ought to be enough for everyone :wink:

You need your interop to be loaded at a specific address in flash. How did you do so?

By the way, this is not RLP. It is a lot more powerful, maybe call it RLP 2.0 lol. Anyway, we simply call it interop.

For the working setup, here are the contents of different files.

Firmware scatter file :

ER_RLP_BEGIN 0x2002F000 :
{
* (SectionForRlpBegin)
}>IRAM

ER_RLP_END 0x20030000 - 0x08 : 
{
    * (SectionForRlpEnd)
}>IRAM

Interop scatter file :

MEMORY {
SDRAM (wx) : ORIGIN = 0x2002F000, LENGTH = 0x1000
}

Managed program :

        var interop = Resources.GetBytes(Resources.BinaryResources.Interop01);
        Marshal.Copy(interop, 0, new IntPtr(0x2002f000), interop.Length);
        Interop.Add(new IntPtr(0x2002f078));
        var cls = new MyNativeClass();
        
        byte[] toto = new byte[12];
        cls.GetMCUSerial(toto);

If I replace the 0x2002f000 value with 0x08100000 in those files, and of course after a successful build of the native code (the .map file is ginvg the “correct” addresses), then I get the exception mentionned above at the first call to a native method.

0x08100000 is outside of the deployment area, as shown in the Device.h file :

#define DEPLOYMENT_SECTORS { { 0x0A, 0x080C0000, 0x00020000 }, { 0x0B, 0x080E0000, 0x00020000 } }

The firmware scatter file is of course changed as well :

ER_RLP_BEGIN 0x08100000 :
{
* (SectionForRlpBegin)
}>LR_FLASH

ER_RLP_END 0x08200000 - 0x08 : 
{
    * (SectionForRlpEnd)
}>LR_FLASH

“Interop” is fine (and I will use it now) but we can see “RLP” and “RLI” in different files/locations :wink:

Your setup will not work. Stick to RAM for now and we will document flash on the future.

I remember @John_Brochue saying something about exposing the internal flash driver.

To Gus’s point, Marshal.Copy with a target address of internal flash will not work. Writing to flash has very specific procedures and cannot be done with a simple pointer write. You need to configure certain device registers and ensure the area is erased beforehand (which can only be done on a sector by sector basis). Of course, it can work from flash, it just isn’t as simple as with RAM. For reference, when using interops, if you get CLR_E_NOT_SUPPORTED when trying to invoke an interop, it is very likely the interop was not added correctly.

As for creating an array, while it’s easier on the managed side, you can see an example of it on the native side in the SPI driver: TinyCLR-Ports/GHIElectronics_TinyCLR_Devices_Spi_GHIElectronics_TinyCLR_Devices_Spi_Provider_SpiControllerApiWrapper.cpp at dev · ghi-electronics/TinyCLR-Ports · GitHub

For an even more comprehensive example take a look at storage: TinyCLR-Ports/GHIElectronics_TinyCLR_Devices_Storage_GHIElectronics_TinyCLR_Devices_Storage_Provider_StorageControllerApiWrapper.cpp at dev ¡ ghi-electronics/TinyCLR-Ports ¡ GitHub

I would recommend taking a look through the various devices interops in the ports repo. They were recently rewritten and all use the interop api, so you can see a bunch of different uses.

2 Likes

@John_Brochue : Thank you for all this information !
I knew it would not be simple to load code from internal flash. But since the answer was a quick yes when I asked, I said to myself that “it should work”. So this does not bother me much more than that.

Regarding examples of API usage, that’s exactly what I have done after my first posts and SPI was indeed the first one I looked at :wink:
And the first try was fine on my side (getting the MCU serial number). The second one with strings was unfortunately not as successful and required a reflash of the board :disappointed_relieved:

I now have to practice in order to acquire the good reflexes and I will use the latest interop sources to help.

Again, thank you very much to all of you !