0

I am trying to trigger events from a C++ library into an app in C#. Previous question regarding this: Using unmanaged pointer to callback function on managed C++

Thanks to the help I received in that question comments, and this example on a previous question answered by Hans Passant: c++/cli pass (managed) delegate to unmanaged code I thought I got It working, but when tested on real hardware, the events do not trigger. From the debugger I can see the function callback_function is called when an event happens triggered by hardware I/O, the instructions there print the corresponding lines, then TriggerEvent(1) is called, but the event is not received in the C# code I did to handle it.

I suspect this has to do with how a new instance of CppDIOHandler is created just before "Step 3", instead of creating the delegate in the original. I tried to get a handle of current object (similar to "this" but with managed code, using GCHandle) to create the CallbackDelegate object, with no luck. I do not understand why I can't simply use callback = gcnew CallbackDelegate(&callback_function) instead of that line.

#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*);
    static CallbackDelegate^ callback;

    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();
        callback = gcnew CallbackDelegate(cdh,&callback_function);
        IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(callback);
        COS_INT_CALLBACK functionPointer = static_cast<COS_INT_CALLBACK>(stubPointer.ToPointer());
        //TO-DO: Make callback static and remove the GC instruction
        //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 StopDIO() {
        //Step 5, stop the DI Change-of-State Interrupt operation
        if (!StopDICOS())
        {
            Console::WriteLine("StopDICOS --> FAILED");
            return;
        }
        Console::WriteLine("StopDICOS --> 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);
    }
};

The C# code is trivial, so seems unlikely the problem is there, I just did:

CppDIOHandler cc = new CppDIOHandler();
CppDIOHandler.StartDIO();
cc.PinStateChanged += Cs_PinStateChanged;

But Cs_PinstateChanged callback is never executed. What Is the proper way to solve this? Is really necessary to use GCHandle to reference current object in managed code or this has nothing to do with my problem?

Igb
  • 227
  • 2
  • 10
  • 1
    Code is largely correct, you got into trouble by making StartDIO() *static*. Which forced you to use gcnew CppDIOHandler. Now there are two objects of this class, one created by the C# code and which has an event handler. And the other created by your C++/CLI code which does *not* have an event handler. So no event is raised. Just don't make it static, easy peasy, pass *this* to the delegate constructor. You should enforce a singleton, easy and correct to do by verifying that the callback variable is nullptr, throw an exception if it is not. – Hans Passant Jun 07 '18 at 11:32
  • @HansPassant saving my life again it seems :-). Still could not compile it when using CallbackDelegate(this,&callback_function) , errors C3364/C2276 (delegate destination must be a pointer to a member function). But... I don't understand, since it is a member function. Making callback_function static might give me another bunch of problems I guess (like not being able to call "TriggerEvent(n)") – Igb Jun 07 '18 at 12:03
  • 1
    Syntax is a wee bit clunky, using an instance function requires naming the class as well. So it is gcnew CallbackDelegate(this, &CppDIOHandler::callback_function). The language standard is highly readable and a [free download](https://www.ecma-international.org/publications/standards/Ecma-372.htm), you'll want to keep it handy. – Hans Passant Jun 07 '18 at 12:12
  • @HansPassant I assure you I tried some cumbersome syntaxes, and some of them were very near to the solution you provided, but I did not manage to get it right. Now It is working flawlessly. I will implement the singleton for safety and check the standard when I have the chance, thank you for all your help. – Igb Jun 07 '18 at 13:22

0 Answers0