简体   繁体   English

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

[英]Is there an alternative for StructLayout “Pack” attribute in Compact Framework?

I would like to do the following: 我想做以下事情:

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

Is there an alternative since Pack is not supported in the compact framework? 是否有替代方案,因为紧凑框架不支持Pack?

Update: Explicitly setting up the structure and giving FieldOffset for each does not work either as it does not affect how the struct is packed 更新:显式设置结构并为每个提供FieldOffset都不起作用,因为它不会影响结构的打包方式

Update2: If you try the following, the CF program wont even run because of how the structure is packed: 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;
}

I know it seems hard to believe, but if you try it you will see. 我知道这似乎很难相信,但如果你尝试它,你会看到。 Add it to a CF project and try to run it and you will get a TypeLoadException. 将它添加到CF项目并尝试运行它,您将获得TypeLoadException。 Changing the offsets to 0,4,8,10 respectively and it will work (but the size ends up being 12). 将偏移分别更改为0,4,8,10并且它将起作用(但是大小最终为12)。

I was hoping maybe someone had a solution using reflection maybe to marshal the size of each of the field types individually (something involving recursion to handle structs within structs or arrays of types). 我希望也许某人有一个使用反射的解决方案可能单独编组每个字段类型的大小(涉及递归以处理结构或类型数组中的结构)。

This probably isn't the type of answer you're looking for, but I'll post it anyway for the hell of it: 这可能不是您正在寻找的答案类型,但无论如何我会发布它的地狱:

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

Basically it does the packing/unpacking itself in the APIStruct property. 基本上它在APIStruct属性中进行打包/解包。

The simplest method of dealing with this type of problem is along the same lines as you might for a bit field, simply pack your data into a private member (or members if it is large) of the appropriate data type and then present public properties that unpack the data for you. 处理此类问题的最简单方法与您对位字段的处理方式相同,只需将数据打包到适当数据类型的私有成员(或成员很大)中,然后显示公共属性即可为您解压缩数据。 The unpacking operations are extremely fast and will have little impact on performance. 拆包操作非常快,对性能影响不大。 For your particular type the following is probably what you want: 对于您的特定类型,以下可能是您想要的:

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

For some structures even this method is not workable due to the unfortunate way a structure has been defined. 对于某些结构,由于结构定义的不幸方式,即使这种方法也不可行。 In those cases you will generally have to use a byte array as a blob of data from which the elements can be unpacked. 在这些情况下,您通常必须使用字节数组作为数据blob,可以从中解压缩元素。

EDIT: To expand on what I mean about structs that can't be handled using this simple method. 编辑:扩展我对使用这种简单方法无法处理的结构的意思。 When you can't do simple packing/unpacking like this you need to manually marshal the irregular struct . 当你不能像这样做简单的打包/解包时,你需要手动编组不规则的结构。 This can be done using manual methods at the point you call the pInvoked API or by using a custom marshaler. 这可以在您调用pInvoked API时使用手动方法或使用自定义封送程序来完成。 The following is an example of a custom marhsaler that can be easily adapted to on the spot manual marshaling. 以下是一个定制的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
}

In the case of arrays of these structs you can't use custom marshaling unless the array size is fixed but it is relatively easy to manually marshal the array data as a whole using the same techniques. 对于这些结构的数组,除非数组大小是固定的,否则不能使用自定义编组,但使用相同的技术手动编组数组数据相对容易。

Do you absolutely require that specific layout or is it acceptable to simply make the size 8? 您是否绝对需要特定的布局,或者只需将尺寸设为8即可接受?

I ask this because the lay out as follows 我问这个因为布局如下

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

Has non word aligned fields which may be what is causing your problem. 具有非单词对齐字段可能是导致问题的原因。

If you can 'rearrange' things then this might work for you: 如果您可以“重新安排”事情,那么这可能适合您:

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

When I test with this on the emulator it works fine. 当我在模拟器上测试它时它工作正常。

Obviously unless you are willing to allow the rearrangement there's nothing you can do. 显然,除非你愿意允许重新安排,否则你无能为力。

This answer and this old article would strongly indicate that you must at a minimum align your structs on multiples of their size (I tried with an int aligned on offset 2 and this also triggered the error) 这个答案这篇旧文章强烈表明你必须至少在你的大小的倍数上对齐你的结构(我尝试使用在偏移2上对齐的int,这也触发了错误)

Given your need to interoperate with externally defined data then the following is likely your easiest solution: 鉴于您需要与外部定义的数据进行互操作,以下可能是您最简单的解决方案:

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

You need to post a more relevant example. 您需要发布一个更相关的示例。 Setting packing on that struct would have no effect anyway. 无论如何,在该结构上设置打包都没有任何效果。

My bet is that you need to use LaoutKind.Explicit and then give the offsets for each member. 我敢打赌,你需要使用LaoutKind.Explicit,然后为每个成员提供偏移量。 It's way better than messing with the packing anyway, because it's way more obvious to someone looking at the code that the original developer explicitly meant for things to be unaligned. 无论如何,这比打包装更好,因为对于那些查看原始开发人员明确表示未对齐的代码的人来说,这种方式更为明显。

Something along these lines: 这些方面的东西:

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

I think one should take Stephen Martin's answer, make it accept a T, and use reflection to generically implement the MarshalManagedToNative and MarshalNativeToManaged methods. 我认为应该采用斯蒂芬马丁的答案,让它接受一个T,并使用反射来一般地实现MarshalManagedToNative和MarshalNativeToManaged方法。 Then, you'll have a custom packed struct marshaler that will work for any type of struct. 然后,您将拥有一个自定义打包的struct marshaler,它适用于任何类型的struct。

Here's the code: 这是代码:

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

LayoutKind.Explicit would be your best bet for defining a specific memory layout. LayoutKind.Explicit是定义特定内存布局的最佳选择。 However, do not use LayoutKind.Explicit for structures that contain pointer-sized values such as true pointers, operating system handles or IntPtr s; 但是, 不要将LayoutKind.Explicit用于包含指针大小值的结构,例如true指针,操作系统句柄或IntPtr ; this is just asking for mysterious trouble at runtime on random platforms. 这只是在随机平台上运行时要求神秘的麻烦。

In particular, LayoutKind.Explicit is a poor substitute for anonymous unions . 特别是, LayoutKind.Explicit是匿名联合的不良替代品 If your target structure contains an anonymous union, convert it to a named union; 如果目标结构包含匿名联合,则将其转换为命名联合; you can safely represent a named union as a struct with LayoutKind.Explicit where all offsets are 0 . 您可以使用LayoutKind.Explicit安全地将命名联合表示为结构,其中所有偏移都为0

LayoutKind.Explicit and FieldOffsetAttribute will allow you to do anything you could do with the Pack property. LayoutKind.Explicit和FieldOffsetAttribute将允许您对Pack属性执行任何操作。 These explicit layout attributes allow you to specify the exact byte position of each field in the struct (relative to the beginning of the struct's range of memory). 这些显式布局属性允许您指定结构中每个字段的确切字节位置(相对于结构的内存范围的开头)。 The Pack property is used by the runtime to help determine the exact position of each field when using a sequential layout. 运行时使用Pack属性来帮助确定使用顺序布局时每个字段的确切位置。 The pack property has no other effect, so using explicit layout allows you to emulate the exact same behavior, albeit a bit more verbosely. pack属性没有其他效果,因此使用显式布局允许您模拟完全相同的行为,尽管更加冗长。 If you don't think this solves your problem, perhaps you could post a bit more information about what you're trying to do or why you think you need to use the Pack property. 如果你不认为这可以解决你的问题,也许你可以发布一些关于你想要做什么或者你认为你需要使用Pack属性的信息。

Edit: I just noticed the additional comment about trying to get the entire structure's size to 8 bytes. 编辑:我刚刚注意到关于尝试将整个结构的大小设置为8个字节的附加注释。 Have you tried using the StructLayoutAttribute.Size property? 您是否尝试过使用StructLayoutAttribute.Size属性? Unlike Pack, it is available in the Compact Framework. 与Pack不同,它在Compact Framework中可用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM