【问题标题】:Using unmanaged pointer to callback function on managed C++在托管 C++ 上使用指向回调函数的非托管指针
【发布时间】:2018-06-05 08:04:04
【问题描述】:

我正在为控制工业计算机上的数字 I/O 引脚的一些 C++ 库(静态 .lib,无源代码)编写 C++/CLI 包装器。

我的目标是使用 .NET CLR 处理现有 C# 应用程序上的 DIO 引脚事件。

我能想到的唯一可行的选择是使用 C++/CLI 中的委托在引脚状态更改时触发事件(由现有库通知),然后在 C# 部分中处理这些事件。我在这里使用更简单的模拟对象尝试了基本功能:Firing events in C++ and handling them in C#

我的问题是库用于在引脚更改时注册回调的函数需要一个(非托管)函数指针,我不知道如何将所有这些粘合出来。该函数的签名(来自库随附的头文件)如下:

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

我尝试从非托管代码调用 EventHandler 委托,但这当然行不通:

#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;
}

有没有合适的方法来做到这一点?绕过之前的代码工作或使用我的“PinStateChanged”委托作为回调函数的任何方式对我来说都是有效的。

【问题讨论】:

  • @hans-passant 目前,我没有设法让它工作。我试图坚持使用 EventHandler 委托,以使其更容易实现和维护。这种方法是否也可以使用 o 无法避免在您链接的响应中使用自定义委托?
  • 你把事情搞混了。您使用 EventHandler 引发托管事件,没有问题。缺少的是与非托管函数指针匹配的委托声明,只需委托 void CallbackDelegate(COS_INT_CALLBACK_ARG*)。 GetFunctionPointerForDelegate() 获取传递给本机代码的函数指针。您的回调函数现在可以是您的 CppDIOHandler 类的 托管 实例方法。请注意,只要可以进行回调,您就必须保持委托对象处于活动状态,并将其存储在静态变量中。

标签: .net c++-cli managed


【解决方案1】:

使用 Hans Passant 提供的信息和链接,我得到了它的工作。代码结束如下(可能需要一些清理):

#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;
}

【讨论】:

  • @hans-passant 如果我可能会滥用您的知识,我仍然有一个疑问我无法弄清楚:创建回调委托时,我必须传递一个 CppDIOHandler 类作为参数,例如你对另一个问题的回答。为什么需要这个?如果我将该方法设为非静态方法,那么简单地使用“this”有什么问题吗?
猜你喜欢
  • 2012-07-03
  • 1970-01-01
  • 2020-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多