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?