yyzhen 发表于 2011-8-17 13:09:29

C/C++中的结构体转换到C#

在做项目移植的时候,经常会碰到数据类型的转换,而这一次碰到的是C/C++中的结构怎样转换到C#。 C/C++中的结构类型数据在C#下的转换
在做项目移植的时候,经常会碰到数据类型的转换,而我这一次碰到的是C/C++中的结构怎样转换到C#。例如我们在C/C++下的结构数据如下:
typedef struct
{
    char sLibName[ 256 ];
    char sPathToLibrary[ 256 ];
    INT32      iEntries;
    INT32      iUsed;
    UINT16    iSort;
    UINT16    iVersion;
    BOOLEAN    fContainsSubDirectories;
    INT32      iReserved;
} LIBHEADER;
我们想把它转成C#下的结构类型如下:
    public struct LIBHEADER
    {
      public char[] sLibName;
      public char[] sPathToLibrary;
      public Int32 iEntries;
      public Int32 iUsed;
      public UInt16 iSort;
      public UInt16 iVersion;
      public Boolean fContainsSubDirectories;
      public Int32 iReserved;
    }
看上去好像没问题了,呵呵呵,其实这样是不行的,我们得再给C#编译器一些信息,告诉它一些字符数组的大小。然后它们在C#下面长得样子就变成这样:
   
    public struct LIBHEADER
    {
      
      public char[] sLibName;
      
      public char[] sPathToLibrary;
      public Int32 iEntries;
      public Int32 iUsed;
      public UInt16 iSort;
      public UInt16 iVersion;
      public Boolean fContainsSubDirectories;
      public Int32 iReserved;
    }
然后写一个函数负责转换。
public StructType ConverBytesToStructure<StructType>(byte[] bytesBuffer)
      {
            // 检查长度。
            if (bytesBuffer.Length != Marshal.SizeOf(typeof(StructType)))
            {
                throw new ArgumentException("bytesBuffer参数和structObject参数字节长度不一致。");
            }

            IntPtr bufferHandler = Marshal.AllocHGlobal(bytesBuffer.Length);
            for (int index = 0; index < bytesBuffer.Length; index++)
            {
                Marshal.WriteByte(bufferHandler, index, bytesBuffer);
            }
            StructType structObject = (StructType)Marshal.PtrToStructure(bufferHandler, typeof(StructType));
            Marshal.FreeHGlobal(bufferHandler);
            return structObject;
      }
然后我们的函数用例是这样:
    FileStream file = File.OpenRead(@"D:\Jagged Alliance 2 Gold\INSTALL.LOG");
    byte[] buffer = new byte;
    file.Read(buffer, 0, buffer.Length);
    LIBHEADER testValue = CommonTools.ConverBytesToStructure<LIBHEADER>(buffer);
string libName = new string(testValue.sLibName);
string pathToLibrary= new string(testValue.sPathToLibrary);
OK,搞定。
如果想去掉后面两句的char数组的转换哪代码如下
C#中的结构代码
   
    public struct LIBHEADER
    {
      
      public string sLibName;
      
      public string sPathToLibrary;
      public Int32 iEntries;
      public Int32 iUsed;
      public UInt16 iSort;
      public UInt16 iVersion;
      public Boolean fContainsSubDirectories;
      public Int32 iReserved;
    }
其它代码不用作修改便可使用。

(1)定义结构体:
    //命名空间
    using System.Runtime.InteropServices;

    //注意这个属性不能少
   
    struct TestStruct
    {
      public int c;
      //字符串,SizeConst为字符串的最大长度
      
      public string str;
      //int数组,SizeConst表示数组的个数,在转换成
      //byte数组前必须先初始化数组,再使用,初始化
      //的数组长度必须和SizeConst一致,例test = new int;
      
      public int[] test;
    }

(2)结构体转byte数组:
      //// <summary>
      /// 结构体转byte数组
      /// </summary>
      /// <param name="structObj">要转换的结构体</param>
      /// <returns>转换后的byte数组</returns>
      public static byte[] StructToBytes(object structObj)
       {
            //得到结构体的大小
            int size = Marshal.SizeOf(structObj);
            //创建byte数组
            byte[] bytes = new byte;
            //分配结构体大小的内存空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将结构体拷到分配好的内存空间
            Marshal.StructureToPtr(structObj, structPtr, false);
            //从内存空间拷到byte数组
            Marshal.Copy(structPtr, bytes, 0, size);
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回byte数组
            return bytes;
      }
(3)byte数组转结构体:
      /// <summary>
      /// byte数组转结构体
      /// </summary>
      /// <param name="bytes">byte数组</param>
      /// <param name="type">结构体类型</param>
      /// <returns>转换后的结构体</returns>
      public static object BytesToStuct(byte[] bytes,Type type)
      {
            //得到结构体的大小
            int size = Marshal.SizeOf(type);
            //byte数组长度小于结构体的大小
            if (size > bytes.Length)
            {
                //返回空
                return null;
            }
            //分配结构体大小的内存空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将byte数组拷到分配好的内存空间
            Marshal.Copy(bytes,0,structPtr,size);
            //将内存空间转换为目标结构体
            object obj = Marshal.PtrToStructure(structPtr, type);
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回结构体
            return obj;
      }

以下为测试的一个例子:

namespace testcomm
{
    //注意这个属性不能少
[StructLayoutAttribute(
LayoutKind.Sequential,
CharSet=CharSet.Ansi,Pack=1)]
    struct TransData
    {
      public byte len;//长度
      public byte ctrl; //控制字
      //int数组,SizeConst表示数组的个数,在转换成 byte数组前必须先初始化数组,再使用,初始化的数组长度必须和SizeConst一致,例test = new int;
      public byte[] REAddr;//unsigned long int meterid;//表编号
      
      public byte[] DSTAddr;//unsigned long int columnid; //抄表列编号
      
      public byte[] cumulate;//unsigned long int cumulate; //累积量
      
      public byte[] left;//signed long int left;   //剩余气量
      public byte csValve1;   //控制状态
      public byte csValve2;
      public byte csLeftClose;
      public byte csLeftClue;
      public byte csFeeType;
      public byte csLeak;
      public byte csUnlaw;
      public byte csBattery;
      public byte ver;//软件版本
      public byte verify;   //和校验
      /*public TransData(int n)
      {
            REAddr = new byte;
            DSTAddr = new byte;
            cumulate = new byte;
            left = new byte;
            len = 0;
            ctrl = 0;
            csBattery = 0;
            csFeeType = 0;
            csLeak = 0;
            csLeftClose = 0;
            csLeftClue = 0;
            csValve1 = 0;
            csValve2 = 0;
            csUnlaw = 0;
            ver = 0;
            verify = 0;
      }*/
      
    }; //定义的结构体类型
    public partial class Form1 : Form
    {
      private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。   
      private long received_count = 0;//接收计数
      private long send_count = 0;//发送计数

      private bool Listening = false; //是否没有执行完invoke相关操作
      private bool Closing1 = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke

      public Form1()
      {
            InitializeComponent();
            //txSend.Text = "10120276";
            txSend.Text = "00000000";
         
      }
      public static byte[] StructToBytes(object structObj)
      {
            //得到结构体的大小
            int size = Marshal.SizeOf(structObj);
            //创建byte数组
            byte[] bytes = new byte;
            //分配结构体大小的内存空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将结构体拷到分配好的内存空间
            Marshal.StructureToPtr(structObj, structPtr, false);
            //从内存空间拷到byte数组
            Marshal.Copy(structPtr, bytes, 0, size);
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回byte数组
            return bytes;
      }
      public static object BytesToStuct(byte[] bytes, Type type)
      {
            //得到结构体的大小
            int size = Marshal.SizeOf(type);
            //byte数组长度小于结构体的大小
            if (size > bytes.Length)
            {
                //返回空
                return null;
            }
            //分配结构体大小的内存空间
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            //将byte数组拷到分配好的内存空间
            Marshal.Copy(bytes, 0, structPtr, size);
            //将内存空间转换为目标结构体
            object obj = Marshal.PtrToStructure(structPtr, type);
            //释放内存空间
            Marshal.FreeHGlobal(structPtr);
            //返回结构体
            return obj;
      }
      private void Form1_Load(object sender, EventArgs e)
      {
            comboBox1.Text ="com1";
            comboBox2.Text = "9600";
            serialPort1.BaudRate = 9600;
            serialPort1.PortName = "com1";
            serialPort1.DataBits = 8;
            serialPort1.StopBits = StopBits.One;
            serialPort1.NewLine="/r/n";
            serialPort1.Encoding = Encoding.GetEncoding("Gb2312");//必须的,否则中文显示乱码
      
      }

      private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
      {

          if (Closing1) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循
            try{
               
                Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。
                int n = serialPort1.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致   
               // byte[] buf = new byte;//声明一个临时数组存储当前来的串口数据   
                char[] buf= new char;//必须的,声明为char 型,而非byte型数组,否则中文显示乱码
               received_count += n;//增加接收计数   
               serialPort1.Read(buf, 0, n);//读取缓冲数据
               //builder.Clear();//清除字符串构造器的内容
            // builder.Remove(0,n);
               builder.Length = 0;//清除字符串构造器的内容
                this.Invoke((EventHandler)(delegate
                   {
                     
                      if (checkBoxHexView.Checked)//判断是否是显示为16禁止   
                     {
                         //依次的拼接出16进制字符串   
                        foreach (byte b in buf)
                           {
                               builder.Append(b.ToString("X2") + " "); //X代表十六进制,2代表2个为一组长度
                           }
                     }
                     else
                     {
                        
                         // builder.Append(System.Text.Encoding.Default.GetString(buf)); //直接按ASCII规则转换成字符串
                         //builder.Append(Encoding.Unicode.GetString(buf)); //
                        // builder.Append(Encoding.GetEncoding("gb2312").GetString(buf));
                           // string str = Convert.ToString(buf);
                           // builder.Append(buf);
                           builder.Append(buf);
                        
                      }
                         //追加的形式添加到文本框末端,并滚动到最后。   
                         textBox1.AppendText(builder.ToString());
                      //修改接收计数   
                        labelGetCount.Text = "Get:" + received_count.ToString()+" Bytes";
                }));
            }
         finally
         {
             Listening = false;//我用完了,ui可以关闭串口了。   
         }

         /* TransData trans1 = new TransData();
            int n = serialPort1.BytesToRead;//
            byte[] buf = new byte;//声明一个临时数组存储当前来的串口数据   
            received_count += n;//增加接收计数   
            serialPort1.Read(buf, 0, n);//读取缓冲数据
             this.Invoke((EventHandler)(delegate{})
         // labelGetCount.Text = "Get:" + received_count.ToString() + " Bytes";
            Type t = typeof(TransData);
            trans1 = (TransData)Form1.BytesToStuct(buf, t);
            string str,t1;
            str = "表编号:";
            t1 = "";
            byte[] dat = new byte;
            Array.Copy(dat, 0, buf, 2, 8);//原机编号
            foreach (byte b in dat) { t1 += b.ToString(); }
            str+=t1 ;
            textBox1.Text = str;*/

       }

      private void button3_Click(object sender, EventArgs e)
      {
            if (button3.Text == "打开串口")
            {
                try
                {
                  serialPort1.Open();
                  MessageBox.Show("打开串口成功");
                  if (serialPort1.IsOpen)
                  {
                        //serialPort1.Close();
                        button3.Text = "关闭串口";
                  }
                  else button3.Text = "打开串口";
                }
                catch
                {
                  MessageBox.Show("串口已经打开");
                }
            }
            else
            {
                // Closing1 = true;
               //while(Listening) Application.DoEvents();
                serialPort1.Close();
                button3.Text = "打开串口";
                MessageBox.Show("串口已经关闭");
            }
      }

      private void button4_Click(object sender, EventArgs e)
      {
            //this.Close();
         // this.Dispose();
            Application.Exit();
      }

      private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
      {
            if(comboBox1 .Text=="com1")
            {
                serialPort1.PortName = "com1";
               // MessageBox.Show("com1 is selected!");
            }
            else if (comboBox1.Text == "com2")
            {
                serialPort1.PortName = "com2";
                MessageBox.Show("com2 is selected!");
            }
            else if (comboBox1.Text == "com3")
            {
                serialPort1.PortName = "com3";
                MessageBox.Show("com3 is selected!");
            }
            else if (comboBox1.Text == "com4")
            {
                serialPort1.PortName = "com4";
                MessageBox.Show("com4 is selected!");
            }
            else if (comboBox1.Text == "com5")
            {
                serialPort1.PortName = "com5";
                MessageBox.Show("com5 is selected!");
            }
            else if (comboBox1.Text == "com6")
            {
                serialPort1.PortName = "com6";
                MessageBox.Show("com6 is selected!");
            }
      }

      private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
      {
            if (comboBox2.Text == "9600")
            {
                serialPort1.BaudRate= 9600;
                // MessageBox.Show("com1 is selected!");
            }
            else if (comboBox2.Text == "4800")
            {
                serialPort1.BaudRate = 4800;
                MessageBox.Show("4800 is selected!");
            }
            else if (comboBox2.Text == "5600")
            {
                serialPort1.BaudRate = 5600;
                MessageBox.Show("5600 is selected!");
            }
            else if (comboBox2.Text == "19200")
            {
                serialPort1.BaudRate = 19200;
                MessageBox.Show("19200 is selected!");
            }
            else if (comboBox2.Text == "115200")
            {
                serialPort1.BaudRate= 115200;
                MessageBox.Show("115200 is selected!");
            }
         
      }

      private void button2_Click(object sender, EventArgs e)
      {
            textBox1.Text = "";

      }

      private void btnsend_Click(object sender, EventArgs e)
      {
            
         int n = 0;   //定义一个变量,记录发送了几个字节   
            //16进制发送
         string hexOutput = "";
         if (checkBoxHexSend.Checked)
          {

            /*
               //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数   
                MatchCollection mc = Regex.Matches(txSend.Text, @"(?i){2}");
                List<byte> buf = new List<byte>();//填充到这个临时列表中   
            //依次添加到列表中   
               foreach (Match m in mc)
               {
                   buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));
               }
                //转换列表为数组后发送   
            serialPort1.Write(buf.ToArray(), 0, buf.Count);
               //记录发送的字节数   
                n = buf.Count;
                */
            char[] values = txSend.Text.ToCharArray();
               //txSend.Text.ToCharArray(
            foreach (char letter in values)
            {
                  int value = Convert.ToInt32(letter);
                  hexOutput += String.Format("{0:X}", value);
            }
            serialPort1.Write(hexOutput);
            //txSend.Text = hexOutput;
            n = txSend.Text.Length;
         }
         else//ascii编码直接发送   
            {
               
               serialPort1.Write(txSend.Text);
               n = txSend.Text.Length;
             }
         send_count += n;//累加发送字节数   
         labelSendCount.Text = "Send:" + send_count.ToString()+" Bytes";//更新界面

       }

      private void btnclnum_Click(object sender, EventArgs e)
      {
            //复位接受和发送的字节数计数器并更新界面。   
         send_count = received_count = 0;
         labelGetCount.Text = "Get:0";
          labelSendCount.Text = "Send:0";

      }

      private void btntest_Click(object sender, EventArgs e)
      {
            try
            {
                int n = 0;   //定义一个变量,记录发送了几个字节
                TransData trans = new TransData();
                trans.len = 47;
                trans.verify = 255;
               // trans.DSTAddr =1;//不注释掉为什么会出错?提示未将对象引用设置到对象的实例
                byte[] bufsend=new byte;
                bufsend = 47;
                bufsend = 0x18;
                bufsend=255;
                //txSend.Text = "10120276";
                byte[] dat = System.Text.Encoding.ASCII.GetBytes(txSend.Text); //获取文本框中字符串存储到byte数组
                //byte[] dat = BitConverter.GetBytes(int.Parse(textBox1.Text,);
             //   byte[] dat = BitConverter.GetBytes(Convert.ToInt32(textBox1.Text));
            //bufsend = dat;
               // bufsend = dat;
               // bufsend = dat;
               // bufsend = dat;
                Array.Copy(dat,0 ,bufsend ,2,8);//原机编号
                Array.Copy(dat, 0, bufsend, 11, 8);//目的机编号
               // textBox1.Text.CopyTo(0,bufsend ,2,8);
                Type t = typeof(TransData);
                trans = (TransData)Form1.BytesToStuct(bufsend, t);
                byte[] buf = Form1.StructToBytes(trans);
                string t1="";
            
                if (checkBoxHexView.Checked)
                {
                  //依次的拼接出16进制字符串   
                  foreach (byte b in buf)
                  {
                        t1 +=b.ToString("X2") + " "; //X代表十六进制,2代表2个为一组长度
                  }
                }
                else
                foreach (byte b in buf) { t1 += b.ToString("") + " "; }
               // textBox1.Text = t1;
            serialPort1.Write(buf,0,buf.Length);//发送单抄数据
            n = buf.Length;
            send_count += n;//累加发送字节数   
            labelSendCount.Text = "Send:" + send_count.ToString() + " Bytes";//更新界面   
            }
            catch (Exception ex)
            {
               MessageBox.Show ( ex.ToString());
            }
      }
   }
}

yajira 发表于 2011-8-19 17:44:35

      /// &lt;summary&gt;
      /// 结构体转Byte数组
      /// &lt;/summary&gt;
      /// &lt;param name="StructObj"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static Byte[] StructToByteA( object StructObj )
      {
            IntPtr structPtr;
            //得到结构体的大小
            int size = Marshal.SizeOf(StructObj);
            Byte[] bytes = new byte[size];
            //分配结构体大小的内存空间
            structPtr = Marshal.AllocHGlobal(size);
            //将结构体拷到分配好的内存空间
            Marshal.StructureToPtr(StructObj, structPtr, false);
            Marshal.Copy(structPtr, bytes, 0, size);
            Marshal.FreeHGlobal(structPtr);
            return bytes;
      }

      public static void ByteToStruct&lt;T&gt;( Byte[] bytes, out T ReObj ) where T : struct
      {            
            Int32 size = Marshal.SizeOf(typeof(T));
            Int32 CopyLenght = size;
            IntPtr structPtr = Marshal.AllocHGlobal(size);
            if (size &gt; bytes.Length)
            {
                /*return null;*/
                for (Int32 i = 0; i &lt; (size - bytes.Length); i++)
                {
                  Marshal.WriteByte(structPtr, (bytes.Length + i), 0);
                }
                CopyLenght = bytes.Length;
            }
            Marshal.Copy(bytes, 0, structPtr, CopyLenght);
            //将内存空间转换为目标结构体
            T Sobj = (T)Marshal.PtrToStructure(structPtr, typeof(T));
            ReObj = Sobj<font color="#000000">;
      }

我两个函数怎么样,加入了泛型的特性

yyzhen 发表于 2011-8-19 18:05:03

呵呵,不错啊。我就用的这个实现的一个上位机抄表小软件。
页: [1]
查看完整版本: C/C++中的结构体转换到C#