如何在 C# 代码中使用 C/C++ 传递给回调的本机结构

How to use C/C++ native structure passed to callback in C# code

对不起,如果我不清楚。

我正在使用一些摄像机 SDK,因此需要收到某些警报的提醒,例如某些物体挡住了摄像机视图等。 SDK 提供订阅提醒功能。 该函数如下:

H264_DVR_SetDVRMessCallBack(fMessCallBack cbAlarmcallback, unsigned long lUser);

cbAlarmCallback 正在休假:

bool __stdcall MessCallBack(long lLoginID, char *pBuf,
                            unsigned long dwBufLen, long dwUser)
{
    return DealwithAlarm(lLoginID,pBuf,dwBufLen);

}
//Note here char *pBuf, in c++ its declared as char* but it needs to be memcpy to SDK_AlarmInfo struct, this is declared in c++ as fallows: 
//alarm information
typedef struct SDK_ALARM_INFO
{
    int nChannel;
    int iEvent;  ///< refer to SDK_EventCodeTypes
    int iStatus; ///< 0: start 1: stop
    SDK_SYSTEM_TIME SysTime;
}SDK_AlarmInfo;

typedef struct SDK_SYSTEM_TIME{
    int  year;///< year  
    int  month;///< month,January = 1, February = 2, and so on.   
    int  day;///< day  
    int  wday;///< week, Sunday = 0, Monday = 1, and so on   
    int  hour;///< hour  
    int  minute;///< minute  
    int  second;///< second  
    int  isdst;///< DST(daylight saving time) flag, Yes = 1, No = 0
}SDK_SYSTEM_TIME;

DealwithAlarm 函数:

void CClientDemoDlg::DealwithAlarm(long lDevcID,  char* pBuf , DWORD dwLen)
{
        SDK_AlarmInfo alarmInfo;
        memcpy ( &alarmInfo, pBuf, dwLen );

    if ( (SDK_EVENT_CODE_NET_ALARM == alarmInfo.iEvent ))
    {
    // Do something
    } 
}

所有这些在 C++ 中都运行良好,char* pBuf 转换为 SDK_AlarmInfo 完全没有问题。

在 C# 中我正在做:

public delegate bool fMessCallBack(int lLoginID, byte[] pBuf, uint dwBufLen, IntPtr dwUser);

[DllImport("NetSdk.dll")]
        public static extern void H264_DVR_SetDVRMessCallBack(fMessCallBack cbAlarmcallback, IntPtr lUser);

public 结构 SDK_ALARM_INFO { int nChannel; 内部事件; //参考SDK_EventCodeType int i状态; // 0:开始 1:停止 SDK_SYSTEM_TIME系统时间; }

  public struct SDK_SYSTEM_TIME
{
    public int year;//   
    public int month;//January = 1, February = 2, and so on.   
    public int day;//   
    public int wday;//Sunday = 0, Monday = 1, and so on   
    public int hour;//   
    public int minute;//   
    public int second;//   
    public int isdst;//   
}

  public enum SDK_EVENT_CODE_TYPES
  {
        SDK_EVENT_CODE_INIT = 0,
        SDK_EVENT_CODE_LOCAL_ALARM = 1, //local alarm
        SDK_EVENT_CODE_NET_ALARM,       //network alarm
        SDK_EVENT_CODE_MANUAL_ALARM,    //manual alarm
        SDK_EVENT_CODE_VIDEO_MOTION,    //motion detect
        SDK_EVENT_CODE_VIDEO_LOSS,      //loss detect
        SDK_EVENT_CODE_VIDEO_BLIND,     //blind detect    
  }
  public int Init()
  {
      fMessCallBack msgcallback = new fMessCallBack(MessCallBack);
      H264_DVR_SetDVRMessCallBack(msgcallback, dwUser);


  }
    bool MessCallBack(int lLoginID, byte[] pBuf, uint dwBufLen, IntPtr dwUser)
    {
        SDK_ALARM_INFO alarmInfo;
        //here i should translate pBuf param into alarm Info

        return true;
    }

在 C# 代码中,SDK 正在调用 MessCallBack 函数,我的问题是我无法将 alarmInfo 读取为 SDK_ALAM_INFO 结构,因此我无法从该结构中读取值比如报警发生的时间或者报警类型。

非常感谢任何帮助。提前致谢

必须解决的问题是将本机结构编组为 C# 结构。第一步是创建 C/C++ 结构到 C# 结构的精确映射。

[StructLayout(LayoutKind.Sequential)]
public struct SDK_ALARM_INFO
{
    int nChannel;
    int iEvent;  //refer to SDK_EventCodeType
    int iStatus; // 0: start 1: stop
    SDK_SYSTEM_TIME SysTime;
}

[StructLayout(LayoutKind.Sequential)]
public struct SDK_SYSTEM_TIME
{
    public int year;
    public int month;
    public int day;
    public int wday;
    public int hour;
    public int minute;
    public int second;
    public int isdst;
}

在上面的映射中有两个问题:iEvent 的大小有时可能是未知的,因为在 C 和 C++ 中枚举将采用枚举值适合的最小整数类型。因此,如果 iEvent 的大小在结构内部声明为这种类型,则可能取决于 C/C++ SDK_EventCodeType 定义。如果转换为 C# 表示整个枚举值集,那么 SDK_EventCodeType 的大小将是 1 个字节。如果 struct 声明为:

typedef struct SDK_ALARM_INFO
{
    int nChannel;
    SDK_EventCodeType iEvent;  ///< refer to SDK_EventCodeTypes
    int iStatus; ///< 0: start 1: stop
    SDK_SYSTEM_TIME SysTime;
}SDK_AlarmInfo;

结构的大小和布局与以代码清单中显示的方式声明的结构不同。但是,这里可能不存在这种情况。我正在写这个 bcs 我已经在硬件随附的 SDK 中遇到过此类错误。为了避免这个问题,我会仔细检查 iEventiStatus 声明以及在 SDK 其他地方的使用。

SDK_SYSTEM_TIME 类型的声明也需要创建一个托管对应物。可以调整第一个代码清单中的结构声明,使其在互操作期间更易于使用。修改后的声明演示了 fixed Array 的用法和 C/C++ 的创建,例如 union 借助 [StructLayout(LayoutKind.Explicit)][FieldOffset(12)] 属性:

[StructLayout(LayoutKind.Explicit)]
public unsafe struct SDK_ALARM_INFO
{
    [FieldOffset(0)]
    int nChannel;
    [FieldOffset(4)]
    int iEvent;  //refer to SDK_EventCodeType
    [FieldOffset(8)]
    int iStatus; // 0: start 1: stop
    [FieldOffset(12)]
    SDK_SYSTEM_TIME SysTime;
    [FieldOffset(12)]
    fixed int Time[8];
}

[StructLayout(LayoutKind.Sequential)]
public struct SDK_SYSTEM_TIME
{
    public int year;
    public int month;
    public int day;
    public int wday;
    public int hour;
    public int minute;
    public int second;
    public int isdst;
}

结构的最后一个字段是 fixed int Time[8]SDK_SYSTEM_TIME 的并集。不必创建联合来编组本机数据 - 它可以与 SDK_SYSTEM_TIMEfixed int Time[8].

一起使用

最终从传递给回调的本机指针到我们的 struct SDK_ALARM_INFO 的转换可以通过调用来实现:

var result = Marshal.PtrToStructure<SDK_ALARM_INFO>((IntPtr)pBuff);

在这种情况下,编组最困难的部分是创建托管结构,该结构将准确反映本机结构的内存布局。

一开始的一个有用技术可以是在托管端使用 Marshal.SizeOf<T>() 方法和 [StructLayout(LayoutKind.Explicit)] 属性,在本机端使用 sizeof(T) offsetof (type,member) 来检查是否正确陈述。