问题
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.
回答1:
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;
}
来源:https://stackoverflow.com/questions/50695113/using-unmanaged-pointer-to-callback-function-on-managed-c