C#pinvoke结构使用union和数组

时间:2013-11-28 20:28:27

标签: c# c++ struct pinvoke unions

我没有定义正确的C#代码来使用C ++库来定义带有union和数组的复杂结构,我在执行C ++代码时不断得到一些内存异常,并且我非常确定它是由于这个。

c ++结构如下,忽略枚举和其他结构定义(如果需要,我会发布它们,但它们非常宽容)

typedef struct
{
DG_CCTALK_APP_EVT_CODE  eEventCode;
DG_CCTALK_APP_EVT_TYPE  eEventType;
int         iTime;
int         iHandle;
unsigned char       uchAddress;
DG_CCTALK_BILL_INFO sBillInfo;
int         iBillAmount;
union
{
    long        lData;
    int     iData;
    unsigned char   ucArray[128];
    char        *cString;
    void        *pvoid;
} uData;
void            *_private;  // for use by cctalk app layer
} DG_CCTALK_APP_EVT, *PDG_CCTALK_APP_EVT;

C#代码是这样的:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Explicit)]
    public struct Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
    {
        //deberian llevar todos 0, pero el array genera problemas al ponerle 0, y cambiandolo explota en otro lado...

        /// int
        [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
        public int lData;

        /// int
        [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
        public int iData;

        /// unsigned char[128]
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 128, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
        [System.Runtime.InteropServices.FieldOffsetAttribute(4)]
        public byte[] ucArray;

        /// char*
        [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
        public System.IntPtr cString;

        /// void*
        [System.Runtime.InteropServices.FieldOffsetAttribute(0)]
        public System.IntPtr pvoid;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DG_CCTALK_APP_EVT
    {

        /// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
        public DG_CCTALK_APP_EVT_CODE eEventCode;

        /// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
        public DG_CCTALK_APP_EVT_TYPE eEventType;

        /// int
        public int iTime;

        /// int
        public int iHandle;

        /// unsigned char
        public byte uchAddress;

        /// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
        public DG_CCTALK_BILL_INFO sBillInfo;

        /// int
        public int iBillAmount;

        /// Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40
        public Anonymous_92d21a81_2a8b_42f4_af32_f7606aa9cf40 uData;

        /// void*
        public System.IntPtr _private;
    }

委托和功能导入

public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);

[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_enable_device", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_enable_device(ref DG_CCTALK_APP_EVT param0);

更新:asnwer之后的新代码,仍然无效: 错误是:“尝试读取或写入受保护的内存这通常表示其他内存已损坏”

奇怪的是,我可以在第一个事件调用中使用结构,我打印所有字段并且它们似乎是正确的(lData和iData具有相同的值,cData具有我发送的字符串)但是在事件之后结束程序死亡。好像我的C#代码正在破坏结构或其他东西......

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct DG_CCTALK_APP_EVT
    {

        /// DG_CCTALK_APP_EVT_CODE->_DG_CCTALK_APP_EVT_CODE
        public DG_CCTALK_APP_EVT_CODE eEventCode;

        /// DG_CCTALK_APP_EVT_TYPE->_DG_CCTALK_APP_EVT_TYPE
        public DG_CCTALK_APP_EVT_TYPE eEventType;

        /// int
        public int iTime;

        /// int
        public int iHandle;

        /// unsigned char
        public byte uchAddress;

        /// DG_CCTALK_BILL_INFO->_DG_CCTALK_BILL_INFO
        public DG_CCTALK_BILL_INFO sBillInfo;

        /// int            
        public int iBillAmount;            

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
        public byte[] uData;

        #region setters y getters para mapeo

        public int lData
        {
            set
            {
                Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
            }
            get
            {
                return BitConverter.ToInt32(uData, 0);
            }
        }

        public int iData
        {
            set
            {
                Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));
            }
            get
            {
                return BitConverter.ToInt32(uData, 0);
            }
        }

        public byte[] ucArray
        {
            set
            {
                Array.Copy(value, uData, 128);
            }
            get
            {
                return uData;

            }
        }

        public IntPtr cString
        {
            set
            {
                Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
            }
            get
            {
                return (IntPtr)BitConverter.ToInt32(uData, 0);
            }
        }

        public IntPtr pvoid
        {
            set
            {
                Array.Copy(BitConverter.GetBytes(value.ToInt32()), uData, 32);
            }
            get
            {
                return (IntPtr)BitConverter.ToInt32(uData, 0);
            }
        }


        #endregion

        /// void*
        public System.IntPtr _private;
    }

C ++声明:

typedef void (*DG_CCTALK_APP_EVT_HANDLER)(PDG_CCTALK_APP_EVT pEVT);

DGCCTALK_PREFIX DG_ERROR cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);
DGCCTALK_PREFIX DG_ERROR cctalk_app_add_link(char *szPortName);

注意爸爸DGCCTALK_PREFIX被定义为__declspec(dllexport)

C#代码:

public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);

[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint =     "cctalk_app_init", CallingConvention = CallingConvention.Cdecl)]
 public static extern int cctalk_app_init(DG_CCTALK_APP_EVT_HANDLER pfnHandler);

[System.Runtime.InteropServices.DllImportAttribute("cctalk.dll", EntryPoint = "cctalk_app_add_link", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_add_link(System.IntPtr szPortName);

调用它们的C#代码

var handler = new DG_CCTALK_APP_EVT_HANDLER(this._handler);
var resultado = cctalk_app_init(handler);
cctalk_app_add_link(Marshal.StringToHGlobalAnsi("port=" + port));

C#处理程序头

private void _handler(ref DG_CCTALK_APP_EVT pEVT)

2 个答案:

答案 0 :(得分:3)

正如您所发现的那样,封送者不喜欢您尝试将该数组与您的联合翻译中的其他字段重叠。你已经通过将数组的偏移量更改为4而不是0来抑制问题。但这没有帮助。

现在,由于编组人员不会为你处理联盟,你必须手动完成。我会通过省略union的非数组成员来处理这个问题。

[StructLayout(LayoutKind.Sequential)]
public struct DG_CCTALK_APP_EVT
{
    public DG_CCTALK_APP_EVT_CODE eEventCode;
    public DG_CCTALK_APP_EVT_TYPE eEventType;
    public int iTime;
    public int iHandle;
    public byte uchAddress;
    public DG_CCTALK_BILL_INFO sBillInfo;
    public int iBillAmount;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
    public byte[] uData;
    public IntPtr _private;
}

请注意,我已删除了您声明的大部分详细信息。我怀疑该类型是由工具自动生成的。但所有这些冗长都让人很难阅读。

这将为结构生成正确的布局,但是会让您做一些工作来访问联合中的其他字段。

那么,我们怎样才能阅读这些字段?好吧,数据包含在字节数组uData中,因此只需从那里读取值即可。例如,您可以将以下属性添加到DG_CCTALK_APP_EVT结构:

public int lData
{
    get { return BitConverter.ToInt32(uData, 0); }
    set { uData = BitConverter.GetBytes(value); }
}

public int iData
{
    get { return BitConverter.ToInt32(uData, 0); }
    set { uData = BitConverter.GetBytes(value); }
}

// etc.

请注意,setter将删除数组的前4个字节,因为它会覆盖uData。我不太了解本机代码的协议,以确保这是你想要的。如果它不是您想要的,那么您可以像这样编写setter:

Array.Copy(BitConverter.GetBytes(value), uData, sizeof(int));

您现在已经添加了互操作边界的C ++端。最明显的问题是您的C#委托似乎有错误的调用约定。它使用stdcall,但C ++代码需要cdecl。这肯定足以解释回调返回后的灾难性崩溃。您需要确保您的委托指定了调用约定。

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DG_CCTALK_APP_EVT_HANDLER(ref DG_CCTALK_APP_EVT pEVT);

cctalk_app_add_link的翻译也是错误的。它应该是:

[DllImport("cctalk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int cctalk_app_add_link(string szPortName);

完成这样的操作后,您只需传递一个字符串,从而避免当前实现的内存泄漏。

答案 1 :(得分:-1)

检查你的int和long字段。 我认为C int是16位而C#int是32:

  • 如果这是真的,那么iTime,iHandle和iBilAmount在C#和C定义中都被定义为int。
  • 如果我错了 ,则lData在C中定义为long,但在C#中定义为int(如果这是问题,则不要忘记修复偏移lData之后出现的字段。