C#中結構體與字節流互相轉換 [StructLayout(LayoutKind.Sequential)]


一、c#結構體

 
1、定義與C++對應的C#結構體

 
在c#中的結構體不能定義指針,不能定義字符數組,只能在里面定義字符數組的引用。 
C++的消息結構體如下: 
//消息格式 4+16+4+4= 28個字節 
struct cs_message

    u32_t        cmd_type; 
    char username[16]; 
    u32_t        dstID; 
    u32_t        srcID; 
};
 
C#定義的結構體如下:
 
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct my_message 

    public UInt32  cmd_type;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string username;    

    public UInt32  dstID;

    public UInt32  srcID;

    public my_message(string s)
    {
        cmd_type = 0;
        username = s;
        dstID = 0;
        srcID = 0;
    } 
}
 
在C++的頭文件定義中,使用了 #pragma pack 1 字節按1對齊,所以C#的結構體也必須要加上對應的特
性,LayoutKind.Sequential屬性讓結構體在導出到非托管內存時按出現的順序依次布局,而對於C++的
char數組類型,C#中可以直接使用string來對應,當然了,也要加上封送的特性和長度限制。

托管代碼指的是必須依靠.NET框架解釋運行的代碼,非托管代碼一般指的是傳統的不需要借助.NET框架解釋的代碼。在.NET出現之前,如VB,C++,DELPHI編寫的程序都是非托管代碼。
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
MarshalAs屬性指示如何在托管代碼和非托管代碼之間封送數據。
很多時候我們想直接在.NET中調用我們以前寫好的非托管程序或組件,這樣就會出現托管代碼與非托管代碼之間互相調用,數據交換的問題,而MarshalAs語法就是定義非托管數據類型與大小的。

 

2、結構體與byte[]的互相轉換
 
定義一個類,里面有2個方法去實現互轉:
 
public class Converter 

    public Byte[] StructToBytes(Object structure) 
    {

        Int32 size = Marshal.SizeOf(structure); 
        Console.WriteLine(size); 
        IntPtr buffer = Marshal.AllocHGlobal(size); 
        try 
        { 
            Marshal.StructureToPtr(structure, buffer, false); 
            Byte[] bytes = new Byte[size]; 
            Marshal.Copy(buffer, bytes, 0, size); 
            return bytes; 
        } 
        finally 
        { 
            Marshal.FreeHGlobal(buffer); 
        } 
    }

    public Object BytesToStruct(Byte[] bytes, Type strcutType) 
    { 
        Int32 size = Marshal.SizeOf(strcutType); 
        IntPtr buffer = Marshal.AllocHGlobal(size); 
        try 
        { 
            Marshal.Copy(bytes, 0, buffer, size); 
            return Marshal.PtrToStructure(buffer, strcutType); 
        } 
        finally 
        { 
            Marshal.FreeHGlobal(buffer); 
        } 
    } 
}
 
3、測試結果:
 
static void Main(string[] args) 

    //定義轉換類的一個對象並初始化 
    Converter Convert = new Converter();

    //定義消息結構體 
    my_message m;

    //初始化消息結構體 
    m = new my_message("yanlina"); 
    m.cmd_type = 1633837924; 
    m.srcID = 1633837924; 
    m.dstID = 1633837924;

    //使用轉換類的對象的StructToBytes方法把m結構體轉換成Byte 
    Byte[] message = Convert.StructToBytes(m); 
    //使用轉換類的對象的BytesToStruct方法把Byte轉換成m結構體 
    my_message n = (my_message)Convert.BytesToStruct(message, m.GetType()); 
    //輸出測試 
    Console.WriteLine(Encoding.ASCII.GetString(message)); 
    Console.WriteLine(n.username); 
}
 
結構體的size是28個字節和c++的結構體一樣,同時可以將結構體和字節數組互轉,方便UDP的發送和接收。

 

c#補充:

[StructLayout(LayoutKind.Sequential)]

結構體是由若干成員組成的.布局有兩種
1.Sequential,順序布局,比如
struct S1
{
int a;
int b;
}
那么默認情況下在內存里是先排a,再排b
也就是如果能取到a的地址,和b的地址,則相差一個int類型的長度,4字節
[StructLayout(LayoutKind.Sequential)]
struct S1
{
int a;
int b;
}
這樣和上一個是一樣的.因為默認的內存排列就是Sequential,也就是按成員的先后順序排列.
2.Explicit,精確布局
需要用FieldOffset()設置每個成員的位置
這樣就可以實現類似c的公用體的功能
[StructLayout(LayoutKind.Explicit)]
struct S1
{
[FieldOffset(0)]
int a;
[FieldOffset(0)]
int b;
}
這樣a和b在內存中地址相同


StructLayout特性支持三種附加字段:CharSet、Pack、Size。
     

·   CharSet定義在結構中的字符串成員在結構被傳給DLL時的排列方式。可以是Unicode、Ansi或Auto。     
  默認為Auto,在WIN   NT/2000/XP中表示字符串按照Unicode字符串進行排列,在WIN   95/98/Me中則表示按照ANSI字符串進行排列。     
·   Pack定義了結構的封裝大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示當前操作平台默認的壓縮大小。     
 

  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct LIST_OPEN
    {
        public int dwServerId;
        public int dwListId;
        public System.UInt16 wRecordSize;
        public System.UInt16 wDummy;
        public int dwFileSize;
        public int dwTotalRecs;
        public NS_PREFETCHLIST sPrefetch;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
        public string szSrcMach;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
        public string szSrcComp;
    }

此例中用到MashalAs特性,它用於描述字段、方法或參數的封送處理格式。用它作為參數前綴並指定目標需要的數據類型。
例如,以下代碼將兩個參數作為數據類型長指針封送給 Windows API 函數的字符串 (LPStr): 
[MarshalAs(UnmanagedType.LPStr)] 
String existingfile; 
[MarshalAs(UnmanagedType.LPStr)] 
String newfile; 
注意結構作為參數時候,一般前面要加上ref修飾符,否則會出現錯誤:對象的引用沒有指定對象的實例。
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )] 
public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM