【问题标题】:C# pinvoke structs with union and arrays带有联合和数组的 C# pinvoke 结构
【发布时间】:2013-11-28 20:28:27
【问题描述】:

我无法定义正确的 C# 代码以使用 C++ 库,该库定义了一个具有联合和数组的复杂结构,我在执行 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)

【问题讨论】:

  • 为什么使用 4 的偏移量?错误是什么?怎么调用函数?
  • 原来是0,但是会导致无效aling异常。我这里没有错误,但它是关于访问程序没有读/写权限的内存。
  • 另外我没有显式调用这个结构的方法,它实际上是一个从 c++ 库中触发的事件,它向我发送这个结构(这里没有错误),但是在事件完成后,作为 c++代码再次运行,它中断了。
  • 所以不要费心定义联合。并使用 Marshal 类来挑选字段。仅将 0 更改为 4 不是答案。
  • 不接收 ref 的结构。接收一个 IntPtr。然后手动挑选你需要的东西。

标签: c# c++ struct pinvoke unions


【解决方案1】:

正如您所发现的,封送拆收器不喜欢您在翻译联合时尝试将数组与其他字段重叠。您通过将数组的偏移量更改为 4 而不是 0 来抑制问题。但这无济于事。

现在,由于 marshaler 不会为您处理联合,因此您必须手动完成。我会通过省略联合的非数组成员来处理这个问题。

[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);

这样做,您可以简单地传递一个字符串,从而避免您当前实现的内存泄漏。

【讨论】:

  • 您将 SizeConst 变量设置为 128,但这只会保存 uData union 中定义的数组,那么其他字段呢?它实际上不应该更大吗? lData 4 个字节(或 8 个字节,因为它很长?)| iData 4 个字节 ucArray 128 个字节 cString 4 个字节(如果在 64 位机器上工作,则为 8 个,因为它是一个指针) pvoid 4 个字节(同上)
  • 我已经更新了我的问题,用你的答案,但它仍然不起作用,我真的迷路了。
  • 不,工会覆盖成员。因此,您的工具生成的所有成员的 FieldOffset 为 0。我不太确定这里的“不起作用”是什么意思。你没有照我说的去做。您的代码有 144 而不是 128。
  • 所有的 getter 和 setter 都需要在数组的开头工作。你应该再读一遍到底什么是联合。
  • IntPtr 怎么样?我该如何管理它们?我的演员表是否正确(假设我只在 32 位机器上工作)
【解决方案2】:

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

  • 如果是这样,那么 iTime、iHandle 和 iBilAmount 在 C# 和 C 定义中都被定义为 int。
  • 如果我错了,那么 lData 在 C 中定义为 long 而在 C# 中定义为 int(如果这是问题所在,那么不要忘记修复出现在 lData 之后)。

【讨论】:

  • 不。在 Windows 上,C++ int 是 32 位
  • 这是一个工会!偏移量均为 0。无需固定。在 Windows 上的 C++ 中,int 和 long 是 4 个字节宽。
  • 对不起,你是对的。错过了128长的数组,没看出他用的是windows。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多