Есть ли альтернатива для атрибута "Pack" StructLayout в Compact Framework?

Я хотел бы сделать следующее:

  [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 ответов

Решение

Вероятно, это не тот тип ответа, который вы ищете, но я все равно выложу его:

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.

Простейший метод решения проблемы такого типа - это те же строки, что и для битового поля, просто упакуйте свои данные в закрытый член (или члены, если он большой) соответствующего типа данных, а затем представьте открытые свойства, которые распакуйте данные для вас. Операции распаковки выполняются очень быстро и мало влияют на производительность. Для вашего конкретного типа, вероятно, вам нужно следующее:

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

Для некоторых структур даже этот метод не работает из-за неудачного определения структуры. В этих случаях вам, как правило, придется использовать байтовый массив как блок данных, из которого элементы могут быть распакованы.

РЕДАКТИРОВАТЬ: расширить, что я имею в виду структуры, которые не могут быть обработаны с помощью этого простого метода. Когда вы не можете выполнить простую упаковку / распаковку таким образом, вам нужно вручную распаковать неправильную структуру. Это можно сделать с помощью ручных методов в точке вызова API pInvoked или с помощью пользовательского маршалера. Ниже приведен пример пользовательского маршалера, который может быть легко адаптирован для ручного маршалинга на месте.

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
}

В случае массивов этих структур вы не можете использовать пользовательское маршалинг, если размер массива не фиксирован, но относительно легко вручную маршировать данные массива в целом, используя те же методы.

Вам абсолютно необходим конкретный макет или приемлемо просто сделать размер 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;
}

Когда я тестирую с этим на эмуляторе, он работает нормально.

Очевидно, что если вы не хотите разрешить перестановку, вы ничего не можете сделать.

Этот ответ и эта старая статья будут строго указывать, что вы должны как минимум выровнять свои структуры по кратным их размерам (я попытался с int выровнять по смещению 2, и это также вызвало ошибку)

Учитывая вашу потребность во взаимодействии с внешне определенными данными, вероятно, следующее простое решение:

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

Я думаю, что нужно взять ответ Стивена Мартина, заставить его принять 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
    }
}

Вам нужно опубликовать более актуальный пример. Установка упаковки на эту структуру не будет иметь никакого эффекта в любом случае.

Я держу пари, что вам нужно использовать LaoutKind.Explicit, а затем дать смещения для каждого участника. В любом случае, это намного лучше, чем возиться с упаковкой, потому что для кого-то, кто смотрит на код, становится более очевидным, что первоначальный разработчик явно хотел, чтобы вещи были выровнены.

Что-то вроде этого:

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

LayoutKind.Explicit будет вашим лучшим выбором для определения конкретной схемы памяти. Однако не используйте LayoutKind.Explicit для структур, которые содержат значения размера указателя, такие как истинные указатели, дескрипторы операционной системы или IntPtr s; это просто таинственная проблема во время выполнения на случайных платформах.

Особенно, LayoutKind.Explicit плохая замена анонимных союзов. Если ваша целевая структура содержит анонимный союз, преобразуйте его в именованный союз; Вы можете безопасно представлять именованный союз как структуру с LayoutKind.Explicit где все смещения 0,

LayoutKind.Explicit и FieldOffsetAttribute позволят вам сделать все, что вы могли бы сделать со свойством Pack. Эти явные атрибуты макета позволяют вам указать точную байтовую позицию каждого поля в структуре (относительно начала диапазона памяти структуры). Свойство Pack используется средой выполнения, чтобы помочь определить точную позицию каждого поля при использовании последовательного макета. Свойство pack не имеет другого эффекта, поэтому использование явного макета позволяет эмулировать точно такое же поведение, хотя и более многословно. Если вы не думаете, что это решит вашу проблему, возможно, вы могли бы опубликовать немного больше информации о том, что вы пытаетесь сделать, или почему вы думаете, что вам нужно использовать свойство Pack.

Изменить: я только что заметил дополнительный комментарий о попытке получить размер всей структуры до 8 байт. Вы пытались использовать свойство StructLayoutAttribute.Size? В отличие от Pack, он доступен в Compact Framework.

Другие вопросы по тегам