如何将带有 Union 的 C++ 结构转换为 C#?

Posted

技术标签:

【中文标题】如何将带有 Union 的 C++ 结构转换为 C#?【英文标题】:How to convert a C++ Struct with Union into C#? 【发布时间】:2011-04-12 03:40:42 【问题描述】:

伙计们,在调用 DLL 中的函数后,我在检索结构成员值时遇到了困难。我尝试将 C++ 代码转换为 C#,但我不确定它是否正确。请帮助我理解我在这里的错误(如果有的话)以及如何更正。

我的问题是从 DLL 调用 ReceiveMessage 函数后,我无法正确检索 INNER STRUCTS (Union) 的值。例如 m_objMsg.MsgData.StartReq.MsgID 始终为 0。 但是当我尝试使用 C++ .exe 程序时,MsgID 的值是正确的。 (非 0)

C++ 代码:

extern int ReceiveMessage(SESSION, int, Msg*);  

typedef struct  
  
  char SubsId[15];  
  int Level;  
  char Options[12];  
 ConxReq;  

typedef struct

  char MsgId[25];
 StartReq;


typedef struct  
  
  long Length;  
  short Type;  
  union  
    
    ConxReq oConxReq;  
    StartReq oStartReq;  
   Data;  
  Msg;  


/////////////////////////////////////////////////////
Msg oMsg;
int rc=ReceiveMessage(Session, 0, &oMsg);

switch(rc)

  case 0:
     switch(oMsg.Type)
     
       case 0: // ConxReq
         …
         break;

      case 1: // StartReq
         …
         break;
   …  

这是我将其转换为 c# 的尝试:

[DllImport("MyDLL.dll",
  CallingConvention = CallingConvention.Cdecl,
  CharSet = CharSet.Ansi)]
  protected static extern Int32 ReceiveMessage(IntPtr session,
  Int32 nTimeOut,
  [MarshalAs(UnmanagedType.Struct)] ref Msg ptrMsg);


  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  public struct ConxReq
              
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]
     public string SubsId;

     public Int32 Level;

     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
     public string Options;
  

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]        
  public struct StartReq
              
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 25)]
     public string MsgId;
  


  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
  protected struct Msg
  
    public int Length;
    public Int16 Type;
    public Data MsgData;
  

  StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
  public struct Data
  
    [FieldOffset(0)]
    public ConxReq oConxReq;

    [FieldOffset(0)]
    public StartReq oStartReq;
  


  Msg m_objMsg = new Msg();
  m_objMsg.MsgData = new Data();
  m_objMsg.MsgData.oConxReq = new ConxReq();
  m_objMsg.MsgData.oStartReq = new StartReq();

  int rc = ReceiveMessage(m_Session, nTimeOut, ref m_objMsg);


  then the SWITCH Condition

如果我在 C++ 和 c# 的 UNION 中添加这个结构... 我收到一条错误消息,指出“...未正确对齐”或“...重叠...”

c++

ConxNack oConxNack;

typedef struct  


   int Reason;

 ConxNack;


[StructLayout(LayoutKind.Sequential)]        
public struct ConxNack
            
    public int nReason;


[FieldOffset(0)]
public ConxNack oConxNack;

非常感谢您的宝贵时间和帮助...

【问题讨论】:

【参考方案1】:

Akash 是对的,看看这里:http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/60150e7b-665a-49a2-8e2e-2097986142f3

另一种选择是创建两个结构并在知道它是哪种类型后使用适当的转换。

马里奥

【讨论】:

感谢您的回复和链接。根据链接,联盟内部有两个结构。他们所做的是在联合内部显式提取两个结构和布局的所有成员变量。我在转换中所做的更有可能是将 Union 内每个结构的起始 FieldOffSet 设置为 0。这不可能吗? 唯一的区别是我还是把所有的成员变量都放在了每个结构体里面。 创建两个结构并仅根据类型进行强制转换是什么意思?抱歉问... 感谢 Mario 提供的链接以及创建另一个结构的想法。请在下面查看我的答案。【参考方案2】:

在 C++ 中,我们知道 UNION 的所有成员共享相同的内存块,并且一次只能拥有一个对象的一个​​成员。 为了在 C# 中实现这一点,我们需要使用 LayoutKind to Explicit 并将每个成员的所有起点设置为 0。

在我之前的示例中,会显示一条错误消息,指出对象类型的偏移量未正确对齐或被非对象类型重叠。

答案是我们不能将 FieldOffSet 的所有成员都设置为 0,因为不允许将引用类型与值类型结合起来。 - 感谢Hans Passant的解释

我所做的是创建 UNION 成员结构的副本并将所有字符串成员变量的类型更改为字节。 我使用字节,因为这是一个值类型,所以我可以将此结构放入 FieldOffSet(0)。 请注意,我调整了下一个成员变量的 FieldOffSet,这样我仍然可以获得相同大小的字符串变量。 还有结构大小,因为我最后有字节成员。 感谢 Akash Kava 和 Mario The Spoon 给了我一个想法并为我提供了一个有用的链接。

在调用 DLL 中的函数并将这个 Struct Obj (ref m_objMsg) 作为参数传递之后,我需要提取值。 一种方法是拥有一个指向非托管内存中结构地址的指针,并将该指针转换为新的 带有相应成员变量的结构体(我原来的结构体)。

NEW STRUCTS (BYTES)  

////////////////////////////////////////////////////////////////  

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Size = 31)]  
public struct ConxReq  
            
   [FieldOffSet(0)]  
   public byteSubsId;  

   [FieldOffSet(15)]  
   public Int32 Level;  

   [FieldOffSet(19)]  
   public byte Options;  
  

[StructLayout(LayoutKind.Explicit, Size = 4)]        
public struct ConxNack  
            
   [FieldOffSet(0)]  
   public int nReason;  
  

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Size = 25)]          
public struct StartReq  
            
   [FieldOffSet(0)]    
   public byte MsgId;  
  

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
protected struct Msg  
  
  public int Length;  
  public Int16 Type;  
  public Data MsgData;  
  

StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]  
public struct Data  
  
  [FieldOffset(0)]  
  public ConxReq oConxReq;  

  [FieldOffset(0)]  
  public ConxNack oConxNack;  

  [FieldOffset(0)]  
  public StartReq oStartReq;  
  

////////////////////////////////////////////////////////////////  



MY ORIGINAL STRUCTS  

////////////////////////////////////////////////////////////////  

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
public struct MyConxReq  
            
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 15)]  
   public string SubsId;  

   public Int32 Level;  

   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]  
   public string Options;  
  

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]         
public struct MyStartReq  
            
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 25)]  
   public string MsgId;  
  

[StructLayout(LayoutKind.Sequential)]        
public struct MyConxNack  
            
   public int nReason;  
  

///////////////////////////////////////////////////////////////  


Since I have a Msg.Type, i know what kind of struct (type) I could cast the object.  
Like for example  

ReceiveMessage(m_Session, nTimeOut, ref oMsg);  


switch (oMsg.Type)  
  
  case 0: // ConxReq  
      IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(oMsg.MsgData.ConxReq); // use the new struct (bytes)  
      Marshal.StructureToPtr(oMsg.MsgData.ConxReq, ptr, false);  
      MyConxReq oMyConxReq = new MyConxReq;  
      oMyConxReq = (MyConxReq) Marshal.PtrToStructure(ptr, typeof(MyConxReq));  // convert it to the original struct  
      Marshal.FreeHGlobal(ptr);  

Then you can use now the oMyConxReq object to acccess the member variables directly.  

如果您有其他或更好的方法,请告诉我... 请告知我所做的是否正确或遗漏了什么。

非常感谢!!! :)

【讨论】:

【参考方案3】:

您必须使用 StructLayout(LayoutKind.Explicit) 和 FieldOffsets 来进行联合。

【讨论】:

感谢 Akash 的回复。我没有看到 LayoutKind.Absolute,我使用 .Explicit 并将所有 FieldOffSets 设置为 (0) 以使其成为 UNION。

以上是关于如何将带有 Union 的 C++ 结构转换为 C#?的主要内容,如果未能解决你的问题,请参考以下文章

调用旧版 C API 时,如何在现代 C++ 中正确地将指向结构的指针转换为指向其他结构的指针?

C Typedef Struct / Union 自动转换

如何将 System::^array 从 C# 函数转换为 C++ 中的等效数据类型

C++ 映射错误,带有结构和方法,无法将字符串转换为 _Tree_iterator<std::_Tree_val<std::_Tree_simple_types< 等等

如何正确地将这些转换为 c#,marshall,以便我可以将这些结构传递给 DLL (c++)?

如何将 C++ 结构转换为 C# 结构