代码之家  ›  专栏  ›  技术社区  ›  SwDevMan81

在紧凑框架中,是否有structlayout“pack”属性的替代方法?

  •  6
  • SwDevMan81  · 技术社区  · 15 年前

    我想做以下工作:

      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      public struct SomeStruct
      {
         public byte  SomeByte;
         public int   SomeInt;
         public short SomeShort;
         public byte  SomeByte2;
      }
    

    是否有其他选择,因为压缩框架中不支持pack?

    更新:显式设置结构并为每个结构指定FieldOffset也不起作用,因为它不会影响结构的打包方式。

    更新2:如果您尝试以下操作,由于结构的打包方式,CF程序甚至无法运行:

    [StructLayout(LayoutKind.Explicit, Size=8)]
    public struct SomeStruct
    {
       [FieldOffset(0)]
       public byte SomeByte;
       [FieldOffset(1)]
       public int SomeInt;
       [FieldOffset(5)]
       public short SomeShort;
       [FieldOffset(7)]
       public byte SomeByte2;
    }
    

    我知道这似乎很难相信,但如果你试试看,你会发现的。将它添加到一个CF项目并尝试运行它,您将得到一个typeloadException。将偏移量分别更改为0、4、8、10,这将有效(但最终大小为12)。

    我希望可能有人有了一个使用反射的解决方案,也许可以单独封送每个字段类型的大小(涉及递归以处理结构或类型数组中的结构)。

    7 回复  |  直到 15 年前
        1
  •  6
  •   MusiGenesis    15 年前

    这可能不是你要找的答案类型,但我无论如何都会把它贴出来。

    public struct SomeStruct
    {
        public byte SomeByte;
        public int SomeInt;
        public short SomeShort;
        public byte SomeByte2;
    
    
        public byte[] APIStruct
        {
            get
            {
                byte[] output = new byte[8];
                output[0] = this.SomeByte;
                Array.Copy(BitConverter.GetBytes(this.SomeInt), 0,
                    output, 1, 4);
                Array.Copy(BitConverter.GetBytes(this.SomeShort), 0,
                    output, 5, 2);
                output[7] = this.SomeByte2;
                return output;
            }
            set
            {
                byte[] input = value;
                this.SomeByte = input[0];
                this.SomeInt = BitConverter.ToInt32(input, 1);
                this.SomeShort = BitConverter.ToInt16(input, 5);
                this.SomeByte2 = input[7];
            }
        }
    }
    

    基本上,它在apinstruct属性中进行打包/解包。

        2
  •  4
  •   Stephen Martin    15 年前

    处理这类问题的最简单方法是沿着与位字段相同的行,只需将数据打包到适当数据类型的私有成员(或成员,如果很大的话),然后呈现为您解包数据的公共属性。解包操作非常快,对性能影响很小。对于您的特定类型,以下可能是您想要的:

    public struct SomeStruct
    {
        private long data;
    
        public byte SomeByte { get { return (byte)(data & 0x0FF); } }
        public int SomeInt { get { return (int)((data & 0xFFFFFFFF00) << 8); } }
        public short SomeShort { get { return (short)((data & 0xFFFF0000000000) << 40); } }
        public byte SomeByte2 { get { return (byte)((data & unchecked((long)0xFF00000000000000)) << 56); } }
    }
    

    对于某些结构,即使这种方法也不可行,因为结构的定义方式很不幸。在这些情况下,通常必须使用字节数组作为数据块,从中可以解包元素。

    编辑:扩展我对这个简单方法无法处理的结构的意思。当您不能像这样简单地进行打包/解包时,您需要手动封送不规则结构。这可以在调用pinvoked API时使用手动方法或使用自定义封送拆收器来完成。下面是一个自定义marhsaler的示例,它可以很容易地适应现场手动编组。

    using System.Runtime.InteropServices;
    using System.Threading;
    
    public class Sample
    {
        [DllImport("sample.dll")]
        public static extern void TestDataMethod([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TestDataMarshaler))] TestDataStruct pData);
    }
    
    public class TestDataStruct
    {
        public byte data1;
        public int data2;
        public byte[] data3 = new byte[7];
        public long data4;
        public byte data5;
    }
    
    public class TestDataMarshaler : ICustomMarshaler
    {
        //thread static since this could be called on 
        //multiple threads at the same time.
        [ThreadStatic()]
        private static TestDataStruct m_MarshaledInstance;
    
        private static ICustomMarshaler m_Instance = new TestDataMarshaler();
    
        public static ICustomFormatter GetInstance(string cookie)
        {
            return m_Instance;
        }
    
        #region ICustomMarshaler Members
    
        public void CleanUpManagedData(object ManagedObj)
        {
            //nothing to do.
        }
    
        public void CleanUpNativeData(IntPtr pNativeData)
        {
            Marshal.FreeHGlobal(pNativeData);
        }
    
        public int GetNativeDataSize()
        {
            return 21;
        }
    
        public IntPtr MarshalManagedToNative(object ManagedObj)
        {
            m_MarshaledInstance = (TestDataStruct)ManagedObj;
            IntPtr nativeData = Marshal.AllocHGlobal(GetNativeDataSize());
    
            if (m_MarshaledInstance != null)
            {
                unsafe //unsafe is simpler but can easily be done without unsafe if necessary
                {
                    byte* pData = (byte*)nativeData;
                    *pData = m_MarshaledInstance.data1;
                    *(int*)(pData + 1) = m_MarshaledInstance.data2;
                    Marshal.Copy(m_MarshaledInstance.data3, 0, (IntPtr)(pData + 5), 7);
                    *(long*)(pData + 12) = m_MarshaledInstance.data4;
                    *(pData + 20) = m_MarshaledInstance.data5;
                }
            }
            return nativeData;
        }
    
        public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            TestDataStruct data = m_MarshaledInstance;
            m_MarshaledInstance = null; //clear out TLS for next call.
    
            if (data == null) data = new TestDataStruct(); //if no in object then return a new one
    
            unsafe //unsafe is simpler but can easily be done without unsafe if necessary
            {
                byte* pData = (byte*)pNativeData;
                data.data1 = *pData;
                data.data2 = *(int*)(pData + 1);
                Marshal.Copy((IntPtr)(pData + 5), data.data3, 0, 7);
                data.data4 = *(long*)(pData + 12);
                data.data5 = *(pData + 20);
            }
            return data;
        }
    
        #endregion
    }
    

    对于这些结构的数组,除非数组大小是固定的,否则不能使用自定义封送处理,但是使用相同的技术将数组数据作为一个整体手动封送比较容易。

        3
  •  2
  •   Community Lee Campbell    7 年前

    你确定吗 要求 具体的布局,还是只做8号的可以接受?

    我这样问是因为布局如下

    [StructLayout(LayoutKind.Explicit, Size=8)]
    public struct SomeStruct
    {
       [FieldOffset(0)]
       public byte SomeByte;
       [FieldOffset(1)]
       public int SomeInt;
       [FieldOffset(5)]
       public short SomeShort;
       [FieldOffset(7)]
       public byte SomeByte2;
    }
    

    具有不对齐的字段,这可能是导致问题的原因。

    如果你能“重新安排”事情,那么这可能对你有用:

    [StructLayout(LayoutKind.Explicit, Size=8)]
    public struct SomeStruct
    {
       [FieldOffset(0)]
       public byte SomeByte;
       [FieldOffset(1)]
       public byte SomeByte2;
       [FieldOffset(2)]
       public short SomeShort;
       [FieldOffset(4)]
       public int SomeInt;
    }
    

    当我在模拟器上用它进行测试时,它工作得很好。

    显然,除非你愿意重新安排,否则你无能为力。

    This answer this old article 它强烈地表明,您必须至少在其大小的倍数上对齐结构(我尝试在偏移量2上对齐int,这也触发了错误)

    考虑到您需要与外部定义的数据进行互操作,下面可能是您最简单的解决方案:

    [StructLayout(LayoutKind.Explicit, Size=8)]
    public struct SomeStruct
    { 
       [FieldOffset(0)] private byte b0;
       [FieldOffset(1)] private byte b1;
       [FieldOffset(2)] private byte b2;
       [FieldOffset(3)] private byte b3;
       [FieldOffset(4)] private byte b4;
       [FieldOffset(5)] private byte b5;
       [FieldOffset(6)] private byte b6;
       [FieldOffset(7)] private byte b7;
    
       // not thread safe - alter accordingly if that is a requirement
       private readonly static byte[] scratch = new byte[4];       
    
       public byte SomeByte 
       { 
           get { return b0; }
           set { b0 = value; }
       }
    
       public int SomeInt
       {
           get 
           { 
               // get the right endianess for your system this is just an example!
               scratch[0] = b1;
               scratch[1] = b2;
               scratch[2] = b3;
               scratch[3] = b4;
               return BitConverter.ToInt32(scratch, 0);
           }
       }
    
       public short SomeShort
       {
            get 
            { 
                // get the right endianess for your system this is just an example!
                scratch[0] = b5;
                scratch[1] = b6;
                return BitConverter.ToInt16(scratch, 0);
            }
        }
    
        public byte SomeByte2 
        { 
            get { return b7; }
            set { b7 = value; }
        }
    }
    
        4
  •  1
  •   ctacke    15 年前

    您需要发布一个更相关的示例。在该结构上设置packing无论如何都不会有任何效果。

    我打赌您需要使用laoutkind.explicit,然后为每个成员提供偏移量。不管怎样,这比处理打包要好得多,因为对于查看原始开发人员明确表示要使事物不对齐的代码的人来说,这更明显。

    沿着这些线的东西:

    [StructLayout(LayoutKind.Explicit)]
    struct Foo
    {
        [FieldOffset(0)]
        byte a;
        [FieldOffset(1)]
        uint b;
    }
    
        5
  •  1
  •   Jonathan Leonard    11 年前

    我认为应该采用StephenMartin的答案,使其接受T,并使用反射来一般地实现MarshalManagedTonative和MarshalNativeToManaged方法。然后,您将拥有一个自定义的打包结构封送拆收器,该封送拆收器可用于任何类型的结构。

    代码如下:

    using System;
    using System.Threading;
    using System.Reflection;
    using System.Runtime.InteropServices;
    
    namespace System.Runtime.InteropServices
    {
        public class PinnedObject : IDisposable
        {
            private GCHandle gcHandle = new GCHandle();
            public PinnedObject(object o)
            {
                gcHandle = GCHandle.Alloc(o, GCHandleType.Pinned);
            }
    
            public unsafe static implicit operator byte*(PinnedObject po)
            {
                return (byte*)po.gcHandle.AddrOfPinnedObject();
            }
    
            #region IDisposable Members
            public void Dispose()
            {
                if (gcHandle.IsAllocated)
                {
                    gcHandle.Free();
                }
            }
            #endregion
        }
    
        public class PackedStructMarshaler<T> : ICustomMarshaler where T : struct
        {
            private static ICustomMarshaler m_instance = new PackedStructMarshaler<T>();
    
            public static ICustomMarshaler GetInstance()
            {
                return m_instance;
            }
    
            private void ForEachField(Action<FieldInfo> action)
            {
                foreach (var fi in typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic))
                {
                    // System.Diagnostics.Debug.Assert(fi.IsValueType);
                    action(fi);
                }
            }
    
            private unsafe void MemCpy(byte* dst, byte* src, int numBytes)
            {
                for (int i = 0; i < numBytes; i++)
                {
                    dst[i] = src[i];
                }
            }
    
            #region ICustomMarshaler Members
            public void CleanUpManagedData(object ManagedObj)
            {
            }
    
            public void CleanUpNativeData(IntPtr pNativeData)
            {
                Marshal.FreeHGlobal(pNativeData);
            }
    
            public int GetNativeDataSize()
            {
                unsafe
                {
                    int ret = 0;
                    ForEachField(
                        (FieldInfo fi) =>
                        {
                            Type ft = fi.FieldType;
                            ret += Marshal.SizeOf(ft);
                        });
                    return ret;
                }
            }
    
            private object m_marshaledObj = null;
    
            public unsafe IntPtr MarshalManagedToNative(object obj)
            {
                IntPtr nativeData = (IntPtr)0;
    
                if (obj != null)
                {
                    if (m_marshaledObj != null)
                        throw new ApplicationException("This instance has already marshaled a managed type");
    
                    m_marshaledObj = obj;
    
                    nativeData = Marshal.AllocHGlobal(GetNativeDataSize());
                    byte* pData = (byte*)nativeData;
                    int offset = 0;
    
                    ForEachField(
                        (FieldInfo fi) =>
                        {
                            int size = Marshal.SizeOf(fi.FieldType);
                            using (PinnedObject po = new PinnedObject(fi.GetValue(obj)))
                            {
                                MemCpy(pData + offset, po, size);
                            }
                            offset += size;
                        });
                }
    
                return nativeData;
            }
    
            public object MarshalNativeToManaged(IntPtr pNativeData)
            {
                if (m_marshaledObj != null)
                    m_marshaledObj = null;
    
                unsafe
                {
                    byte* pData = (byte*)pNativeData;
                    int offset = 0;
    
                    object res = new T();
    
                    ForEachField(
                        (FieldInfo fi) =>
                        {
                            int size = Marshal.SizeOf(fi.FieldType);
                            fi.SetValue(res, (object)(*((byte*)(pData + offset))));
                            offset += size;
                        });
    
                    return res;
                }
            }
    
            #endregion
        }
    }
    
        6
  •  0
  •   Jeffrey Hantin    15 年前

    LayoutKind.Explicit 将是您定义特定内存布局的最佳选择。然而, 不要使用 布局类型.显式 对于包含指针大小值的结构 如真指针、操作系统句柄或 IntPtr 这只是在随机平台上的运行时要求神秘的麻烦。

    特别地, 布局类型.显式 是匿名工会的糟糕替代品 .如果目标结构包含匿名联合,请将其转换为命名联合;可以安全地将命名联合表示为具有 布局类型.显式 哪里 全部的 偏移量是 0 .

        7
  •  0
  •   Nimrand    15 年前

    layoutKind.Explicit和FieldOffsetAttribute将允许您对pack属性执行任何操作。这些显式布局属性允许您指定结构中每个字段的确切字节位置(相对于结构内存范围的开始)。运行时使用pack属性来帮助确定使用顺序布局时每个字段的确切位置。pack属性没有其他效果,因此使用显式布局允许您模拟完全相同的行为,尽管更详细一些。如果你认为这不能解决你的问题,也许你可以发布更多关于你想做什么或者为什么你认为你需要使用pack属性的信息。

    编辑:我刚刚注意到关于尝试将整个结构的大小调整为8字节的附加注释。是否尝试使用structlayoutattribute.size属性?与pack不同,它在紧凑框架中可用。

    推荐文章