C #: convertire puntatore generico di serie
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?
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.