How to create and use CustomProvider?

Hi, everyone.

I want to create CustomProvider in TinyCLR for Wio LTE. (See TinyCLR_Api_Type.Custom in TinyCLR.h)
But not found details.

Could you tell me how to create and use CustomProvider?

Thanks.

http://docs.ghielectronics.com/tinyclr/porting/native_apis.html has a few details.

With TinyCLR_Api_Type::Custom, it is expected that you’ll create a unique type with the high bit set, so OR your type value with Custom.

As for creating and registering a custom API, first fill out an instance of TinyCLR_Api_Info. Count and Implementation should be set as described in the docs:

What Implementation points to is up to you. If you are using one of the predefined API types, it should point to one of the structs in TinyCLR.h. If you are making a new custom API, create your own struct and set it to an instance of your struct. When you retrieve the API for use later, you’ll need to cast it back as appropriate.

Once you’ve create the API info, using an instance of TinyCLR_Api_Provider you get from the soft reset handler or a method interop parameter, call Add on it passing in the api provider and the address of the API info. Later you can use the various Find* functions to access it.

Once you’ve done that, to use it in managed code, you’ll need to create an interop that looks up and interacts with your API. The interop doc has more information.

Thank you for informaton!

I understood implementation in unmanaged code.
But I can’t implement managed code not yet.

Can I see the sample code? (i.e. GpioPin class)

There isn’t a formal definition of providers in managed code. All managed code has is native interops which allow it to call into native code. Native code on the other hand has a number of methods available to interact with managed data and other registered native APIs.

There is no direct link between native APIs (registered using TinyCLR_Api_Provider) and managed code. Instead, you’ll create your managed API as needed with a few native interops. In these native interops, likely a constructor or other initialization method, you’ll lookup the API you want from the ApiProvider (found on the interop method parameter along with the managed stack object) and then likely stored it in a field of the managed object declared as a System.IntPtr. That’s the pattern we follow in our providers, though it isn’t required. For example, the below code shows part of how we implemented ADC in our library. We look up the provider in managed code but it could have also been done in native code. The interop provider is not cached, but it potentially could be.

using System;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace GHIElectronics.TinyCLR.Devices.Adc.Provider {
    public interface IAdcControllerProvider {
        int ReadValue(int channelNumber);
    }

    public interface IAdcProvider {
        IAdcControllerProvider[] GetControllers();
    }

    public class AdcProvider : IAdcProvider {
        private readonly static Hashtable providers = new Hashtable();
        private readonly IAdcControllerProvider[] controllers;

        public string Name { get; }

        public IAdcControllerProvider[] GetControllers() => this.controllers;

        private AdcProvider(string name) {
            var api = Api.Find(name, ApiType.AdcProvider);

            this.Name = name;
            this.controllers = new IAdcControllerProvider[api.Count];

            for (var i = 0U; i < this.controllers.Length; i++)
                this.controllers[i] = new DefaultAdcControllerProvider(api.Implementation[i]);
        }

        public static IAdcProvider FromId(string id) {
            if (AdcProvider.providers.Contains(id))
                return (IAdcProvider)AdcProvider.providers[id];

            var res = new AdcProvider(id);

            AdcProvider.providers[id] = res;

            return res;
        }
    }

    internal class DefaultAdcControllerProvider : IAdcControllerProvider {
#pragma warning disable CS0169
        private readonly IntPtr nativeProvider;
#pragma warning restore CS0169

        internal DefaultAdcControllerProvider(IntPtr nativeProvider) {
            this.nativeProvider = nativeProvider;

            this.AcquireNative();
        }

        ~DefaultAdcControllerProvider() => this.ReleaseNative();

        [MethodImpl(MethodImplOptions.InternalCall)]
        public extern int ReadValue(int channelNumber);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private extern void AcquireNative();

        [MethodImpl(MethodImplOptions.InternalCall)]
        private extern void ReleaseNative();
    }
}
#include "GHIElectronics_TinyCLR_Devices.h"

TinyCLR_Result Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::AcquireNative___VOID(const TinyCLR_Interop_MethodData md) {
    auto interop = (const TinyCLR_Interop_Provider*)md.ApiProvider.FindDefault(&md.ApiProvider, TinyCLR_Api_Type::InteropProvider);

    TinyCLR_Interop_ManagedValue self, fld;
    interop->GetThisObject(interop, md.Stack, self);
    interop->GetField(interop, self, Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::FIELD___nativeProvider___I, fld);

    auto provider = (const TinyCLR_Adc_Provider*)fld.Data.Numeric->I;

    return provider->Acquire(provider);
}

TinyCLR_Result Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::ReleaseNative___VOID(const TinyCLR_Interop_MethodData md) {
    auto interop = (const TinyCLR_Interop_Provider*)md.ApiProvider.FindDefault(&md.ApiProvider, TinyCLR_Api_Type::InteropProvider);

    TinyCLR_Interop_ManagedValue self, fld;
    interop->GetThisObject(interop, md.Stack, self);
    interop->GetField(interop, self, Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::FIELD___nativeProvider___I, fld);

    auto provider = (const TinyCLR_Adc_Provider*)fld.Data.Numeric->I;

    return provider->Release(provider);
}

TinyCLR_Result Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::ReadValue___I4__I4(const TinyCLR_Interop_MethodData md) {
    auto interop = (const TinyCLR_Interop_Provider*)md.ApiProvider.FindDefault(&md.ApiProvider, TinyCLR_Api_Type::InteropProvider);

    TinyCLR_Interop_ManagedValue self, fld, arg, ret;
    interop->GetThisObject(interop, md.Stack, self);
    interop->GetField(interop, self, Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider::FIELD___nativeProvider___I, fld);
    interop->GetArgument(interop, md.Stack, 1, arg);
    interop->GetReturn(interop, md.Stack, ret);

    auto provider = (const TinyCLR_Adc_Provider*)fld.Data.Numeric->I;

    int32_t value;

    auto res = provider->ReadValue(provider, arg.Data.Numeric->S4, value);

    if (res == TinyCLR_Result::Success)
        ret.Data.Numeric->I4 = value;

    return res;
}

I did it that firmware included original code and can get native address in managed code.

So, your example has few interop functions. (Interop_GHIElectronics_TinyCLR_Devices_GHIElectronics_TinyCLR_Devices_Adc_Provider_DefaultAdcControllerProvider)
Do I store interop code in RLI region?
(I don’ want to use RLI.)

Try to include into main (firmware) without RLI

Interops can be loaded at runtime, hence Runtime Loadable Interops. We set aside a small region in memory that you can use to load them since we don’t currently support dynamic relocation.

But as @valon_hoti_gmail_com said, they can also be loaded statically from main. Taking a look at main.cpp, you get an instance of the API provider passed to the OnSoftReset handler. You can use this to get an InteropProvider, then call Add on it. When you use this method you don’t need to worry about where it’s place in memory, the linker will handle it for you.

@valon_hoti_gmail_com @John_Brochue
Thank you for your info.

My added code for firmware.(WioLTE_ApiInfo and WIoLTE_Controller)

And callable C# code.
https://github.com/matsujirushi/TinyCLR-WioLTE/blob/master/Projects/LedBlink2/Program.cs#L7

It can run. But I have problem.
Sometime Interop_LedBlink2 and methods changed. i.e. Add resource file to LedBlink2 project.
I need rebuild firmware. :sweat:
https://github.com/matsujirushi/TinyCLR-Ports/blob/dev-wiolte/Devices/WioLTE/WioLTE_Controller.cpp#L154

Cloud you tell me how to implement AdcControllerProviderProvider? Is it use TinyCLR_Interop_Assembly value?

Yes, many things in managed code can change the interop structure. Your best bet is to separate the interop parts into a class library that rarely needs to change then just change your main app that has no interops itself. I’m not sure what you mean by AdcControllerProviderProvider though. Did you mean IAdcProvider in managed or AdcProvider in native?

Sorry, It’s typo.

Oh, nice advice!:grinning:
I completely understand.
Thank you.

Happy to help. Do you still have questions about implementing AdcProvider?

I don’t need it.
I will implement and publish. :grin:

Done. :blush:

Native Code
Managed Library
Application

Thanks many help.

2 Likes

Congrulations FIRST Customised Firmware “with additional api directly compiled” without separated external compile

2 Likes

Thank you. :blush: