C#: convertir le pointeur Générique En tableau

je veux convertir un byte* en un byte[] , mais je veux aussi avoir une fonction réutilisable pour faire ceci:

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

malheureusement je reçois une erreur de compilateur parce que T pourrait être un" .net managed type "et nous ne pouvons pas avoir de pointeurs vers ceux . Encore plus frustrant, c'est qu'il n'y a pas de contrainte de type qui peut restreindre T "types non gérés". Y a-t-il une fonction .net intégrée pour faire cela? Des idées?

2
demandé sur wj32 2009-06-12 12:44:34

4 réponses

La méthode qui pourrait correspondre à ce que vous essayez de faire est Maréchal.Copiez , mais il ne prend pas les paramètres appropriés pour faire une méthode générique.

bien qu'il ne soit pas possible d'écrire une méthode générique avec des contraintes génériques qui pourraient décrire ce qui est possible, tous les types ne peuvent pas être autorisés à être copiés en utilisant une manière" dangereuse". Il y a quelques exceptions; les classes en sont une.

Voici un échantillon code:

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

La méthode peut être générique, mais il ne peut pas prendre un pointeur de type générique. Ce n'est pas un problème puisque la covariance de pointeurs aide, mais cela a l'effet malheureux d'empêcher une résolution implicite du type d'argument Générique. Vous devez alors spécifier explicitement MakeArray.

j'ai ajouté un cas spécial pour les structures, où il est préférable d'avoir des types qui spécifient un disposition de la structure . Ce peut-être pas un problème dans votre cas, mais si les données du pointeur proviennent du code natif C ou c++, spécifier un type de layout est important (le CLR pourrait choisir de réorganiser les champs pour avoir un meilleur alignement de la mémoire).

mais si le pointeur provient exclusivement de données générées par le code géré, alors vous pouvez supprimer le contrôle.

aussi, si la performance est un problème, il y a de meilleurs algorithmes pour copier les données que de le faire octet par octet. (Voir la d'innombrables implémentations de memcpy pour référence)

5
répondu Jerome Laban 2009-10-03 00:49:01

Semble que la question devient: Comment spécifier un Type générique d'un type simple.

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

donne l'erreur:

Ne peut pas prendre l'adresse de, obtenir la taille de, ou déclarer un pointeur sur un type géré ('T')

1
répondu Henk Holterman 2009-06-12 09:18:54

et ça?

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

nous ne pouvons pas utiliser sizeof(T) ici, mais l'appelant peut faire quelque chose comme

byte[] b = MakeArray<byte>(pBytes, lenBytes, sizeof(byte));
1
répondu twon33 2009-10-01 14:11:56

Je n'ai aucune idée si ce qui suit fonctionnerait, mais il pourrait (au moins il compile :):

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 clé est d'utiliser Marshal.PtrToStructure pour convertir au type correct.

0
répondu samjudson 2009-06-12 08:59:58