Domanda

voglio convertire un byte* ad un byte[], ma voglio anche avere una funzione riutilizzabile per fare questo:

public unsafe static T[] Create<T>(T* ptr, int length)
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
        array[i] = ptr[i];

    return array;
}

Purtroppo ottengo un errore di compilazione perché T potrebbe essere un "NET tipo gestito" e non possiamo avere puntatori a quelli . Ancora più frustrante è che non v'è alcun vincolo di tipo generico che può limitare a "T tipi non gestiti". C'è una funzione NET built-in per fare questo? Tutte le idee?

È stato utile?

Soluzione

Il metodo che potrebbe corrispondere a ciò che si sta cercando di fare è Marshal.Copy , ma non prende i parametri appropriati per rendere un metodo generico.

Sebbene ci non è possibile scrivere un metodo generico con vincoli generici che potrebbero descrivono ciò che è possibile, non ogni tipo può essere consentito di essere copiati utilizzando un modo "non sicuro". Ci sono alcune eccezioni; classi sono uno di questi.

Ecco un esempio di codice:

    public unsafe static T[] Create<T>(void* source, int length)
    {
        var type = typeof(T);
        var sizeInBytes =  Marshal.SizeOf(typeof(T));

        T[] output = new T[length];

        if (type.IsPrimitive)
        {
            // Make sure the array won't be moved around by the GC 
            var handle = GCHandle.Alloc(output, GCHandleType.Pinned);

            var destination = (byte*)handle.AddrOfPinnedObject().ToPointer();
            var byteLength = length * sizeInBytes;

            // There are faster ways to do this, particularly by using wider types or by 
            // handling special lengths.
            for (int i = 0; i < byteLength; i++)
                destination[i] = ((byte*)source)[i];

            handle.Free();
        }
        else if (type.IsValueType)
        {
            if (!type.IsLayoutSequential && !type.IsExplicitLayout)
            {
                throw new InvalidOperationException(string.Format("{0} does not define a StructLayout attribute", type));
            }

            IntPtr sourcePtr = new IntPtr(source);

            for (int i = 0; i < length; i++)
            {
                IntPtr p = new IntPtr((byte*)source + i * sizeInBytes);

                output[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
            }
        }
        else 
        {
            throw new InvalidOperationException(string.Format("{0} is not supported", type));
        }

        return output;
    }

    unsafe static void Main(string[] args)
    {
        var arrayDouble = Enumerable.Range(1, 1024)
                                    .Select(i => (double)i)
                                    .ToArray();

        fixed (double* p = arrayDouble)
        {
            var array2 = Create<double>(p, arrayDouble.Length);

            Assert.AreEqual(arrayDouble, array2);
        }

        var arrayPoint = Enumerable.Range(1, 1024)
                                   .Select(i => new Point(i, i * 2 + 1))
                                   .ToArray();

        fixed (Point* p = arrayPoint)
        {
            var array2 = Create<Point>(p, arrayPoint.Length);

            Assert.AreEqual(arrayPoint, array2);
        }
    }

Il metodo può essere generico, ma non può prendere un puntatore di un tipo generico. Questo non è un problema in quanto puntatori covarianza sta aiutando, ma questo ha l'effetto perverso di impedire una risoluzione implicita del tipo di argomento generico. È quindi necessario specificare MakeArray in modo esplicito.

Ho aggiunto un caso speciale per le strutture, dove è meglio avere i tipi che specificano un layout di struct . Questo potrebbe non essere un problema nel tuo caso, ma se i dati puntatore proviene da C nativo o codice C ++, specificando un tipo di layout è importante (CLR potrebbe scegliere di riordinare i campi per avere un allineamento memoria migliore).

Ma se il puntatore è in arrivo esclusivamente dai dati generati dal codice gestito, allora si può rimuovere il controllo.

Inoltre, se la prestazione è un problema, ci sono gli algoritmi migliori per copiare i dati che farlo byte per byte. (Vedi le innumerevoli implementazioni di memcpy per riferimento)

Altri suggerimenti

Sembra che la domanda diventa: come specificare un tipo generico di essere un tipo semplice.

unsafe void Foo<T>() : where T : struct
{
   T* p;
}

dà l'errore:
Non può prendere l'indirizzo, ottenere la dimensione di, o dichiarare un puntatore a un tipo gestito ( 'T')

Che ne dici di questo?

static unsafe T[] MakeArray<T>(void* t, int length, int tSizeInBytes) where T:struct
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    {
        IntPtr p = new IntPtr((byte*)t + (i * tSizeInBytes));
        result[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
    }

    return result;
}

Non possiamo utilizzare sizeof (T) qui, ma il chiamante può fare qualcosa di simile

byte[] b = MakeArray<byte>(pBytes, lenBytes, sizeof(byte));

Non ho idea di sorta se la seguente avrebbe funzionato, ma potrebbe (almeno compila:):

public unsafe static T[] Create<T>(void* ptr, int length) where T : struct
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
    {
        array[i] = (T)Marshal.PtrToStructure(new IntPtr(ptr), typeof(T));
    }

    return array;
}

La chiave è quella di utilizzare Marshal.PtrToStructure per convertire al tipo corretto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top