2009-07-14 7 views
6

Vorrei fare quanto segue:Esiste un'alternativa per l'attributo "Pacchetto" StructLayout in Compact Framework?

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

Esiste un'alternativa dal pacchetto non è supportata nel quadro compatto?

Aggiornamento: in modo esplicito impostare la struttura e dando FieldOffset per ogni non funziona né in quanto non influisce sul modo struct è confezionato

Update2: Se si tenta di seguito, il programma CF abituato anche correre a causa di come la struttura è ricco:

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

so che sembra difficile da credere, ma se lo provate vedrete. Aggiungilo a un progetto CF e prova ad eseguirlo e otterrai una TypeLoadException. Cambiando gli offset rispettivamente a 0,4, 8,10 e funzionerà (ma la dimensione termina con 12).

Speravo che qualcuno avesse una soluzione che utilizzava la riflessione, forse per eseguire il marshalling della dimensione di ciascun tipo di campo individualmente (qualcosa che implicava la ricorsione per gestire le strutture all'interno di strutture o matrici di tipi).

+0

commentato "Layout esplicita non risolve il problema sia" - qual è il vero problema si vuole risolvere? Ciò che attualmente non funziona in realtà. –

+0

Provare a impacchettare la struttura in modo che la dimensione risulti essere 8 byte. Senza l'attributo pack la dimensione viene eseguita il marshalling più grande del previsto (a causa delle dimensioni di impacchettamento predefinite) – SwDevMan81

+0

La soluzione potrebbe riguardare il marshalling dei singoli campi della struttura per determinare la dimensione (inoltre, si noti che potrei disporre anche di array all'interno della struttura); che introduce più problemi come visto con il mio altro post qui: http://stackoverflow.com/questions/1268898/how-do-i-getcustomattributes – SwDevMan81

risposta

6

Questo probabilmente non è il tipo di risposta che stai cercando, ma vi posterò comunque per il gusto di farlo:

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

Fondamentalmente si fa l'imballaggio/disimballaggio sé nel APIStruct proprietà.

+0

Sì, questo è un approccio che potrebbe funzionare, speravo di evitare manualmente di indicizzare ogni uno, ma forse questa è la soluzione migliore. – SwDevMan81

+0

Penso che potrebbe essere modificato per iterare attraverso le proprietà e usare SizeOf o qualsiasi altra cosa per comprimerlo in un byte [], quindi non dovresti indicizzare manualmente tutto. Questo è stato solo un breve esempio. Un altro vantaggio è che potrebbe funzionare in CF o nel framework completo senza modifiche (ma penso che forse sono l'unica persona che scrive app in esecuzione senza la ricompilazione). – MusiGenesis

+0

Sto provando: P Volevo assolutamente qualcosa che potesse funzionare su entrambi i framework. – SwDevMan81

0

È necessario pubblicare un esempio più pertinente. Impostare l'imballaggio su quella struttura non avrebbe comunque alcun effetto.

La mia scommessa è che è necessario utilizzare LaoutKind.Explicit e quindi assegnare gli offset per ogni membro. È molto meglio che scherzare con l'imballaggio, perché è molto più ovvio per qualcuno che guarda il codice che lo sviluppatore originale intendeva dire esplicitamente che le cose non fossero allineate.

Qualcosa in questo senso:

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

Layout esplicito non risolve il problema o – SwDevMan81

0

LayoutKind.Explicit sarebbe la soluzione migliore per definire un layout di memoria specifica. Tuttavia, non utilizzare LayoutKind.Explicit per le strutture che contengono valori di dimensioni del puntatore come puntatori veri, handle del sistema operativo o IntPtr s; questo è solo chiedere guai misteriosi in runtime su piattaforme casuali.

In particolare, LayoutKind.Explicit è un povero sostituto per le unioni anonime. Se la tua struttura di destinazione contiene un'unione anonima, convertila in un'unione nominata; è possibile rappresentare in modo sicuro un'unione denominata come una struct con LayoutKind.Explicit dove tutti gli offset sono 0.

+0

Il layout esplicito con i puntatori è in realtà una pratica abbastanza comune nel CF dove il Marshaller fa schifo e non può farlo per te. – ctacke

+0

D'accordo, il marshaller CF è debole. Di solito uso la seconda tecnica: LayoutKind.Sequential, ove possibile, e converto le unioni anonime in named. –

+0

Layout esplicito non risolve il problema – SwDevMan81

4

Il metodo più semplice per gestire questo tipo di problema è sulla stessa falsariga di un campo di bit, è sufficiente comprimere i dati in un membro privato (o membri se è di grandi dimensioni) del tipo di dati appropriato e quindi presenta proprietà pubbliche che decomprimono i dati per te. Le operazioni di spacchettamento sono estremamente veloci e avranno un impatto minimo sulle prestazioni. Per il tipo particolare quanto segue è probabilmente quello che si vuole:

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

Per alcune strutture anche questo metodo non è praticabile a causa del modo sfortunato di una struttura è stata definita. In questi casi, in genere devi utilizzare un array di byte come un blob di dati da cui è possibile decomprimere gli elementi.

MODIFICA: per approfondire cosa intendo per le strutture che non possono essere gestite utilizzando questo semplice metodo.Quando non è possibile eseguire semplici operazioni di imballaggio/disimballaggio, è necessario effettuare il marshalling manuale della struttura irregolare. Questo può essere fatto usando metodi manuali nel momento in cui chiami l'API pInvoked o usando un gestore di marshalling personalizzato. Quello che segue è un esempio di un marsaler personalizzato che può essere facilmente adattato al marshalling manuale sul posto.

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 
} 

Nel caso di array dei struct non è possibile utilizzare il marshalling meno che la dimensione della matrice è fissa ma è relativamente facile schierare manualmente i dati di matrice nel suo complesso utilizzando le stesse tecniche.

0

LayoutKind.Explicit e FieldOffsetAttribute ti permetteranno di fare qualsiasi cosa tu possa fare con la proprietà Pack. Questi attributi di layout espliciti consentono di specificare la posizione esatta in byte di ogni campo nella struttura (relativamente all'inizio della gamma di memoria della struttura). La proprietà Pack viene utilizzata dal runtime per determinare la posizione esatta di ciascun campo quando si utilizza un layout sequenziale. La proprietà pack non ha altri effetti, quindi l'utilizzo del layout esplicito consente di emulare lo stesso identico comportamento, anche se in modo un po 'più verboso. Se non pensi che questo risolva il tuo problema, forse potresti pubblicare un po 'più di informazioni su cosa stai cercando di fare o perché pensi di aver bisogno di usare la proprietà Pack.

Modifica: ho appena notato il commento aggiuntivo sul tentativo di ottenere l'intera dimensione della struttura su 8 byte. Hai provato a utilizzare la proprietà StructLayoutAttribute.Size? A differenza di Pack, è disponibile in Compact Framework.

+0

Vedere il mio aggiornamento2. La proprietà size non aiuta (penso che in realtà valuterà un TypeLoadException anche quando la dimensione è impostata a una dimensione della struct effettiva usando esplicita) – SwDevMan81

2

Avete assolutamente richiedere tale layout specifico o è accettabile semplicemente la dimensione 8?

Lo chiedo perché il lay out come segue

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

Ha utenti non di parole campi allineati, che possono essere che cosa sta causando il problema.

Se è possibile le cose 'Riordino' allora questo potrebbe funzionare per voi:

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

Quando ho prova con questo sull'emulatore funziona benissimo.

Ovviamente, a meno che non siate disposti a consentire il riarrangiamento, non c'è niente che possiate fare.

This answer e this old article sarebbe fortemente indicare che è necessario come minimo allineare le struct su multipli di loro dimensione (ho provato con un int allineato sulla compensazione 2 e questo anche innescato l'errore)

Dato il vostro bisogno di interoperare con i dati definiti esternamente allora il seguente è probabile che la soluzione più semplice:

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

Grazie per il post Shuggy e provarlo. Mi rendo conto che è un problema di allineamento, ma ho bisogno di mantenere la struttura così com'è (formato di messaggio specifico). – SwDevMan81

1

credo che si dovrebbe prendere la risposta di Stephen Martin, ne fanno accettare un T, e l'uso di riflessione genericamente implementare i metodi MarshalManagedToNative e MarshalNativeToManaged. Quindi, avrai un struct marshaler personalizzato che funzionerà con qualsiasi tipo di struttura.

Ecco il codice:

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