0

I am writing C++/CLI a wrapper for some C++ libraries (static .lib, no source code available) controlling Digital I/O pins on an industrial computer.

My objective is to handle the events of the DIO pins on an existing C# application using .NET CLR.

The only viable option I could think of is to use a delegate in C++/CLI to fire events when the pin state changes (informed by the existing lib), and then handle these events in the C# part. I have tried the basic functionality using simpler mock objects here: Firing events in C++ and handling them in C#

My problem is that the function used by the libraries to register the callback when a pin changes expects a (unmanaged) pointer to function, and I don't know how to glue all this out. The signature of this function (from the header file that accompany the libraries) is as follows:

typedef void (__stdcall* COS_INT_CALLBACK)(COS_INT_CALLBACK_ARG* arg);
BOOL RegisterCallbackDICOS(COS_INT_CALLBACK callback);   

I tried calling an EventHandler delegate from unmanaged code, but of course this can't work:

#include "stdafx.h"
#include <WDT_DIO.H>
#include <string.h>
#include <stdio.h>

using namespace System;
using namespace System::Runtime::InteropServices;

public ref class PinStateChangedEventArgs : public EventArgs
{
public:
    property int Direction;
    property DateTime TimeReached;
};

public ref class CppDIOHandler
{
private:

public:
    CppDIOHandler() {

};
    void StartDIO() {
        //Step 1, initialize DIO library by invoking InitDIO()
        if (!InitDIO())
        {
            Console::WriteLine("InitDIO --> FAILED");
            return;
        }
        Console::WriteLine("InitDIO --> PASSED");

        //Step 2, setup Change-of-State Interrupt mask and level/edge mode
        COS_INT_SETUP setup;
        memset(&setup, 0, sizeof(setup));
        setup.portMask = 0x0f; // 00001111b, enable ch.0~3
        setup.edgeMode = 0x00; // generate interrupt on level change
        setup.edgeType = 0x00; // rising/falling edge, only effective when edgeMode = 1

        if (!SetupDICOS(&setup, sizeof(setup)))
        {
            Console::WriteLine("SetupDICOS --> FAILED");
            return;
        }
        Console::WriteLine("SetupDICOS --> PASSED");

        //Step 3, register the callback function
        if (!RegisterCallbackDICOS(callback_function))
        {
            Console::WriteLine("RegisterCallbackDICOS --> FAILED");
            return;
        }
        Console::WriteLine("RegisterCallbackDICOS --> PASSED");
        //Step 4, start the DI Change-of-State Interrupt
        if (!StartDICOS())
        {
            Console::WriteLine("StartDICOS --> FAILED");
            return;
        }
        Console::WriteLine("StartDICOS --> PASSED");
    }

    void TriggerEvent(int direction)
    {
            PinStateChangedEventArgs^ args = gcnew PinStateChangedEventArgs();
            args->Direction = direction;
            args->TimeReached = DateTime::Now;
            OnPinStateChanged(args);
    }

    event EventHandler<PinStateChangedEventArgs^>^ PinStateChanged;

protected:
    virtual void OnPinStateChanged(PinStateChangedEventArgs^ e)
    {
        PinStateChanged(this, e);
    }
};

#pragma unmanaged 
//Step 0, define a Change-of-State Interrupt callback function
void __stdcall callback_function(COS_INT_CALLBACK_ARG* arg)
{
    printf("data=0x%02x, flag=0x%02x, seq=%02d\n",
        arg->portData, arg->intrFlag, arg->intrSeq);
    //Here should go some code to trigger an event with the managed delegate
    //The following can't be used on unmanaged code
    CppDIOHandler^ dh = gcnew CppDIOHandler();
    dh->TriggerEvent(1);
}
#pragma managed  

void main()
{
    return;
}

Is there any proper way to do this? Either a bypass for the previous code to work or any way of using my "PinStateChanged" delegate as callback function is valid to me.

Igb
  • 227
  • 2
  • 10
  • 1
    https://stackoverflow.com/q/2972452/17034 – Hans Passant Jun 05 '18 at 08:52
  • @hans-passant for the moment, I did not manage to make It work. I am trying to stick to the EventHandler delegate to make it easier to implement and mantain. Should this approach work aswell o is not possible to avoid using a custom delegate like in the response you linked? – Igb Jun 05 '18 at 10:13
  • 1
    You are mixing things up. You use the EventHandler to raise the managed event, no problem there. What is missing is the delegate declaration that matches the unmanaged function pointer, simply delegate void CallbackDelegate(COS_INT_CALLBACK_ARG*). GetFunctionPointerForDelegate() to get the function pointer you pass to the native code. Your callback function can now be a *managed* instance method of your CppDIOHandler class. Note that you have to keep the delegate object alive as long as callbacks can be made, store it in a static variable. – Hans Passant Jun 05 '18 at 10:34

1 Answers1

0

Using the information and the link provided by Hans Passant I got it working. The code ended as follows (might need some cleanup):

#include "stdafx.h"
#include <WDT_DIO.H>
#include <string.h>
#include <stdio.h>

using namespace System;
using namespace System::Runtime::InteropServices;

public ref class PinStateChangedEventArgs : public EventArgs
{
public:
    property int Direction;
    property DateTime TimeReached;
};

public ref class CppDIOHandler
{
public:
    CppDIOHandler() {}

    delegate void CallbackDelegate(COS_INT_CALLBACK_ARG*);

    void __stdcall callback_function(COS_INT_CALLBACK_ARG* arg)
    {
        printf("data=0x%02x, flag=0x%02x, seq=%02d\n",
            arg->portData, arg->intrFlag, arg->intrSeq);
        TriggerEvent(1);
    }


    static void StartDIO() {
        //Step 1, initialize DIO library by invoking InitDIO()
        if (!InitDIO())
        {
            Console::WriteLine("InitDIO --> FAILED");
            return;
        }
        Console::WriteLine("InitDIO --> PASSED");

        //Step 2, setup Change-of-State Interrupt mask and level/edge mode
        COS_INT_SETUP setup;
        memset(&setup, 0, sizeof(setup));
        setup.portMask = 0x0f; // 00001111b, enable ch.0~3
        setup.edgeMode = 0x00; // generate interrupt on level change
        setup.edgeType = 0x00; // rising/falling edge, only effective when edgeMode = 1

        if (!SetupDICOS(&setup, sizeof(setup)))
        {
            Console::WriteLine("SetupDICOS --> FAILED");
            return;
        }
        Console::WriteLine("SetupDICOS --> PASSED");

        CppDIOHandler^ cdh = gcnew CppDIOHandler();
        CallbackDelegate^ callback = gcnew CallbackDelegate(cdh, &callback_function);
        IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(callback);
        COS_INT_CALLBACK functionPointer = static_cast<COS_INT_CALLBACK>(stubPointer.ToPointer());
        GC::KeepAlive(callback);

        //Step 3, register the callback function
        if (!RegisterCallbackDICOS(functionPointer))
        {
            Console::WriteLine("RegisterCallbackDICOS --> FAILED");
            return;
        }
        Console::WriteLine("RegisterCallbackDICOS --> PASSED");
        //Step 4, start the DI Change-of-State Interrupt
        if (!StartDICOS())
        {
            Console::WriteLine("StartDICOS --> FAILED");
            return;
        }
        Console::WriteLine("StartDICOS --> PASSED");
    }

    void TriggerEvent(int direction)
    {
            PinStateChangedEventArgs^ args = gcnew PinStateChangedEventArgs();
            args->Direction = direction;
            args->TimeReached = DateTime::Now;
            OnPinStateChanged(args);
    }

    event EventHandler<PinStateChangedEventArgs^>^ PinStateChanged;

    virtual void OnPinStateChanged(PinStateChangedEventArgs^ e)
    {
        PinStateChanged(this, e);
    }
};

void main()
{
    return;
}
Igb
  • 227
  • 2
  • 10
  • @hans-passant If I may abuse of your knowledge, I still have a doubt I couldn't figure out: When creating the callback delegate, I have to pass a CppDIOHandler class as parameter, like in your response to the other question. Why is this needed? Is there any problem to use simply "this" if i make that method non-static? – Igb Jun 06 '18 at 07:25