簡體   English   中英

C#中使用union的奇怪的解組行為

[英]Strange unmarshalling behavior with union in C#

我想將類似C的聯合導出為字節數組,如下所示:

[StructLayout(LayoutKind.Explicit)]
struct my_struct
{
    [FieldOffset(0)]
    public UInt32 my_uint;

    [FieldOffset(0)]
    public bool other_field;
}

public static void Main()
{
    var test = new my_struct { my_uint = 0xDEADBEEF };
    byte[] data = new byte[Marshal.SizeOf(test)];

    IntPtr buffer = Marshal.AllocHGlobal(data.Length);
    Marshal.StructureToPtr(test, buffer, false);
    Marshal.Copy(buffer, data, 0, data.Length);
    Marshal.FreeHGlobal(buffer);

    foreach (byte b in data)
    {
        Console.Write("{0:X2} ", b);
    }
    Console.WriteLine();
}

我們獲得的輸出( https://dotnetfiddle.net/gb1wRf )是01 00 00 00而不是預期的EF BE AD DE

現在,如果我們將other_field類型更改為byte (例如),我們會得到什么?

奇怪的是,我們首先獲得了我們想要的輸出, EF BE AD DEhttps://dotnetfiddle.net/DnXyMP

此外,如果我們交換原來的兩個字段,我們再次得到我們想要的相同輸出( https://dotnetfiddle.net/ziSQ5W

為什么會這樣? 為什么字段的順序很重要? 做同樣的事情是否有更好(可靠)的解決方案?

這是結構編組方式不可避免的副作用。 起點是結構值不是blittable,它的副作用包含bool 這在托管結構中占用1個字節,而在封送結構中占用4個字節(UnmanagedType.Bool)。

因此,結構值不能一下子被復制,編組人員需要轉換每個成員。 所以my_uint是第一個,產生4個字節。 other_field是下一個,也在完全相同的地址產生4個字節。 這會覆蓋my_uint生成的所有內容。

bool類型一般是奇怪的,它永遠不會產生blittable結構。 即使你應用[MarshalAs(UnmanagedType.U1)]也不行。 這本身對您的測試有一個有趣的影響,您現在將看到my_int產生的3個高位字節被保留。 但結果仍然是垃圾,因為成員仍然是逐個轉換的,現在在偏移0處產生值為0x01的單個字節。

您可以通過將其聲明為字節來輕松獲得所需內容,現在結構是blittable:

    [StructLayout(LayoutKind.Explicit)]
    struct my_struct {
        [FieldOffset(0)]
        public UInt32 my_uint;

        [FieldOffset(0)]
        private byte _other_field;

        public bool other_field {
            get { return _other_field != 0; }
            set { _other_field = (byte)(value ? 1 : 0); }
        }
    }

我承認我沒有權威的答案為什么Marshal.StructureToPtr()以這種方式運行, Marshal.StructureToPtr() ,它不僅僅是復制字節。 相反,它必須解釋struct本身,通過解釋該字段的常規規則將每個字段單獨編組到目的地。 由於bool被定義為只是兩個值中的一個,因此非零值被映射為true ,其將原始字節封送為0x00000001

請注意,如果您真的只想要來自struct值的原始字節,您可以自己進行復制,而不是通過Marshal類。 例如:

var test = new my_struct { my_uint = 0xDEADBEEF };
byte[] data = new byte[Marshal.SizeOf(test)];

unsafe
{
    byte* pb = (byte*)&test;

    for (int i = 0; i < data.Length; i++)
    {
        data[i] = pb[i];
    }
}

Console.WriteLine(string.Join(" ", data.Select(b => b.ToString("X2"))));

當然,要實現這一點,您需要為項目啟用unsafe代碼。 您可以為相關項目執行此操作,或者將上述內容構建到單獨的幫助程序集中,其中unsafe風險較低(即,您不介意將其用於其他代碼,和/或不關心程序集正在可驗證的等)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM