2009-06-12 7 views
2

Je veux convertir un byte* à un byte[], mais je veux aussi avoir une fonction réutilisable pour ce faire:C#: convertir pointeur générique tableau

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 compilation parce que T peut être un "type managé .NET" et nous ne pouvons pas avoir de pointeurs vers ceux. Encore plus frustrant est qu'il n'y a pas de contrainte de type générique qui peut restreindre T à des "types non gérés". Existe-t-il une fonction .NET intégrée pour cela? Des idées?

Répondre

5

La méthode qui pourrait correspondre à ce que vous essayez de faire est Marshal.Copy, mais il n'a pas prenez 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 copiés en utilisant une méthode "dangereuse". Il y a quelques exceptions; les classes sont l'un d'entre eux.

Voici un exemple de 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 d'un type générique. Ce n'est pas un problème puisque la covariance des pointeurs aide, mais cela a malheureusement pour effet d'empêcher une résolution implicite du type d'argument générique. Vous devez ensuite 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 struct layout. Cela peut ne pas être un problème dans votre cas, mais si les données du pointeur proviennent du code C ou C++ natif, il est important de spécifier un type de disposition (le CLR peut choisir de réorganiser les champs pour mieux aligner la mémoire).

Mais si le pointeur provient exclusivement de données générées par du code managé, vous pouvez supprimer la vérification. En outre, si la performance est un problème, il existe de meilleurs algorithmes pour copier les données que de le faire octet par octet. (Voir les innombrables implémentations de memcpy pour référence)

0

Je ne sais pas que ce soit si les conditions suivantes 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 le type correct.

+0

Cela ne fonctionne pas parce que T peut ne pas être une struct-byte, int, long, etc – wj32

+0

Regarde près mais je ne vois pas le pointeur incrémenter - il sera toujours utilise le premier octet.Aussi, y a-t-il une raison pour void *? –

+3

'struct' signifie type de valeur, donc int, byte etc sont valides. Et vous ne pouvez pas avoir un pointeur vers un type de valeur none, donc struct est le meilleur que vous pouvez faire. – samjudson

1

Apparaît que la question devient: Comment spécifier un type générique pour être un type simple.

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

donne l'erreur:
ne peut pas prendre l'adresse, obtenir la taille, ou déclarer un pointeur vers un type managé (« T »)

1

Comment cela?

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), mais l'appelant peut faire quelque chose comme

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

vous pouvez utiliser Marshal.SizeOf (typeof (T)); pour avoir la taille. – SoLaR