-->

How to convert a C++ Struct with Union into C#?

2020-07-18 08:14发布

问题:

Guys I am having difficulties on retrieving struct member values after calling a function in the DLL. I tried to convert the C++ codes into C# but I’m not sure if it is correct or not. Please help me understand my mistakes here (if there is) and how to correct.

My problem here is I can’t correctly retrieved the values of the INNER STRUCTS (Union) after I called the ReceiveMessage function from the DLL. Like for example m_objMsg.MsgData.StartReq.MsgID is always 0. But when I try to use the C++ .exe program, the MsgID has a correct value. (not 0)

C++ Code:

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

And here is my attempt to convert this into 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

And If I add this struct inside the UNION for c++ and c#... I've got an error stating the "... incorrectly align" or "...overlapped..."

c++

ConxNack oConxNack;

typedef struct  
{

   int Reason;

} ConxNack;


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

[FieldOffset(0)]
public ConxNack oConxNack;

Thank you so much in advance for your time and help...

回答1:

Akash is right, have a look here: http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/60150e7b-665a-49a2-8e2e-2097986142f3

Another option is create two structs and use an appropriate cast once you know which type it is.

hth

Mario



回答2:

In C++, we know that all members of UNION shared the same memory chunk and can only have one member of an object at a time. In order to implement this in C#, we need to use the LayoutKind to Explicit and set all the starting point of each member to 0.

In my previous example, An error message is displayed stating that the offset of an object type is incorrectly aligned or overlapped by a non-object type.

Answer is we cannot set all the members to FieldOffSet to 0 since it is not allowed to combine the reference type with the value type. - Thanks to the explanation of Hans Passant

What I did is to create a copy of the UNION Member Structs and change the type of all the String Member Variables to bytes. I used bytes since this is a value type so I can put this struct into FieldOffSet(0). Take note, i adjust the FieldOffSet of the next member variable so i can still get the same size of my string variable. And also for the struct size since i have byte member at the last. Thanks to Akash Kava and Mario The Spoon for giving me an idea and providing me a useful link.

After calling the function in the DLL and passed this Struct Obj (ref m_objMsg) as a paramter, I need to extract the values. One way is to have a pointer that points to the address of the struct in the unmanaged memory and convert this pointer a new Struct with the corresponding member variables (my original structs).

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.  

Please let me know if you have other or better way to do this... Kindly advise if what I did is correct or if I missed something.

Thank you so much!!! :)



回答3:

You have to use StructLayout(LayoutKind.Explicit) and FieldOffsets to make union.