Есть ли альтернатива для атрибута "Pack" StructLayout в Compact Framework? - PullRequest
6 голосов
/ 14 июля 2009

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

  [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 ]

6 голосов
/ 27 августа 2009

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

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.

4 голосов
/ 25 августа 2009

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

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
}

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

2 голосов
/ 27 августа 2009

Вы абсолютно требуете этой конкретной компоновки или допустимо просто сделать размер 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; }
    }
}
1 голос
/ 27 января 2010

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

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

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

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

[StructLayout(LayoutKind.Explicit)]
struct Foo
{
    [FieldOffset(0)]
    byte a;
    [FieldOffset(1)]
    uint b;
}
0 голосов
/ 27 августа 2009

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

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

0 голосов
/ 15 июля 2009

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

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

...