如何将带有Union的c++结构体转换为c#

本文关键字:结构体 c++ 转换 Union | 更新日期: 2023-09-27 17:49:40

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

我这里的问题是我不能正确地检索INNER STRUCTS (Union)的值后,我从DLL调用ReceiveMessage函数。例如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;

提前感谢您的时间和帮助…

如何将带有Union的c++结构体转换为c#

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

另一种选择是创建两个结构体,并在确定其类型后使用适当的强制转换。

hth

马里奥

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

在我前面的例子中,显示一个错误消息,指出对象类型的偏移量被非对象类型不正确地对齐或重叠。

答案是我们不能将FieldOffSet的所有成员设置为0,因为不允许将引用类型与值类型结合起来。——感谢汉斯·帕桑的解释

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

在DLL中调用函数并传递此Struct Obj (ref m_objMsg)作为参数后,我需要提取值。一种方法是拥有一个指向非托管内存中结构的地址的指针,并将该指针转换为new(我的原始结构)。

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.  
如果你有其他或更好的方法,请告诉我……如果我做的是正确的,或者如果我遗漏了什么,请告诉我。

非常感谢!!:)

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