6

我想做以下事情:

  [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)。

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

4

7 回答 7

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 属性中进行打包/解包。

于 2009-08-27T14:12:53.160 回答
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); } }
}

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

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

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

于 2009-08-25T13:09:43.723 回答
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;
}

当我在模拟器上对此进行测试时,它工作正常。

显然,除非您愿意允许重新排列,否则您无能为力。

这个答案这篇旧文章强烈表明你必须至少将你的结构对齐到它们大小的倍数上(我尝试在偏移量 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; }
    }
}
于 2009-08-27T15:11:42.660 回答
1

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

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

这些方面的东西:

[StructLayout(LayoutKind.Explicit)]
struct Foo
{
    [FieldOffset(0)]
    byte a;
    [FieldOffset(1)]
    uint b;
}
于 2009-07-14T21:50:17.220 回答
1

我认为应该接受斯蒂芬马丁的回答,让它接受一个 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
    }
}
于 2010-01-27T01:07:43.330 回答
0

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

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

于 2009-07-14T22:01:02.520 回答
0

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

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

于 2009-08-26T21:44:47.163 回答