Compact Framework中是否有StructLayout“Pack”属性的替代方法?

时间:2009-07-14 19:54:02

标签: c# struct compact-framework structlayout

我想做以下事情:

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

是否有替代方案,因为紧凑框架不支持Pack?

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

Update2:如果您尝试以下操作,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 个答案:

答案 0 :(得分:6)

这可能不是您正在寻找的答案类型,但无论如何我会发布它的地狱:

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];
        }
    }
}

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

答案 1 :(得分:4)

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

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); } }
}

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

编辑:扩展我对使用这种简单方法无法处理的结构的意思。当你不能像这样做简单的打包/解包时,你需要手动编组不规则的结构。这可以在您调用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
}

对于这些结构的数组,你不能使用自定义编组,除非数组大小是固定的,但使用相同的技术手动编组数组数据相对容易。

答案 2 :(得分:2)

你绝对要求那个特定的布局还是可以接受简单的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 answerthis 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; }
    }
}

答案 3 :(得分:1)

您需要发布更相关的示例。无论如何,在该结构上设置打包都没有效果。

我敢打赌,您需要使用LaoutKind.Explicit,然后为每个成员提供偏移量。无论如何,这比打包装更好,因为对于那些查看原始开发人员明确表示未对齐的代码的人来说,这种方式更为明显。

这些方面的东西:

[StructLayout(LayoutKind.Explicit)]
struct Foo
{
    [FieldOffset(0)]
    byte a;
    [FieldOffset(1)]
    uint b;
}

答案 4 :(得分:1)

我认为应该采用Stephen Martin的答案,让它接受一个T,并使用反射来一般地实现MarshalManagedToNative和MarshalNativeToManaged方法。然后,您将拥有一个自定义打包的struct marshaler,它适用于任何类型的struct。

以下是代码:

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
    }
}

答案 5 :(得分:0)

LayoutKind.Explicit是定义特定内存布局的最佳选择。但是,不要将LayoutKind.Explicit用于包含指针大小值的结构,例如真指针,操作系统句柄或IntPtr;这只是在随机平台上运行时遇到的神秘问题。

特别是, LayoutKind.Explicit是匿名工会的不良替代品。如果目标结构包含匿名联合,则将其转换为命名联合;您可以安全地将命名联合表示为LayoutKind.Explicit的结构,其中所有偏移量为0

答案 6 :(得分:0)

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

编辑:我刚刚注意到关于尝试将整个结构的大小设置为8个字节的附加注释。您是否尝试过使用StructLayoutAttribute.Size属性?与Pack不同,它在Compact Framework中可用。