Come ottenere IntPtr da byte [] in C #


127

Voglio passare un byte[]metodo a prende un IntPtrparametro in C #, è possibile e come?


Potresti fornire maggiori dettagli? Perché vorresti farlo?
Grzenio,

2
Ne hai bisogno se usi l'API DirectShow per esempio ... per ottenere dati da VideoRenderer, devi usare questo ... e il GCHandlemetodo funziona come un incantesimo ... anche il fixedmetodo. : P :))
Cipi

Ne hai bisogno per tutto ciò che sta trasferendo terrabyte di dati e vuoi evitare la copia aggiuntiva. Usa la tua immaginazione.
Brain2000,

Risposte:


93

Non sei sicuro di ottenere un IntPtr in un array, ma puoi copiare i dati per utilizzarli con codice non gestito usando Mashal.Copy:

IntPtr unmanagedPointer = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
// Call unmanaged code
Marshal.FreeHGlobal(unmanagedPointer);

In alternativa, è possibile dichiarare una struttura con una proprietà e quindi utilizzare Marshal.PtrToStructure, ma ciò richiederebbe comunque l'allocazione della memoria non gestita.

Modifica: Inoltre, come sottolineato da Tyalis, puoi anche usare fisso se il codice non sicuro è un'opzione per te


Giusto per chiarire, Marshal.Copycon quel sovraccarico occorre un indice iniziale. La chiamata dovrebbe essereMarshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
mkenyon,

meglio ottenere IntPtr senza creare nuova memoria, come la risposta di @ user65157.
Lin

208

Un altro modo,

GCHandle pinnedArray = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
// Do your stuff...
pinnedArray.Free();

1
@Cipi Per uno degli altri post di Eric Lipperts, questo dovrebbe avere la parola chiave Fixed invece di usare il GC
goodguys_activate

molte grazie. Funziona bene ed è abbastanza facile. Non sono così abile in GCHandles e Marshal. Qualcuno può dirmi quali vantaggi hanno il Maresciallo e cosa GCHandle e dove usare quale? Grazie
salvadanaio

1
Qualcuno potrebbe fornire il riferimento al post di Eric Lippert?
Cameron,

3
@piggy: penso che lo svantaggio di Marshal sia che devi fare una copia dei tuoi dati (che può richiedere molto tempo e sprecare memoria di cui potresti avere bisogno)
Riki,

6
@ makerofthings7 Non credo che Lippert stia dicendo di usare 'fixed' invece del GC [per bloccare gli oggetti], credo che stia dicendo di non usare il GC per bloccare un oggetto indefinitamente, insieme a dire di non usare fixed per fare il lo stesso, sia.
Cameron,

129

Questo dovrebbe funzionare ma deve essere utilizzato in un contesto non sicuro:

byte[] buffer = new byte[255];
fixed (byte* p = buffer)
{
    IntPtr ptr = (IntPtr)p;
    // do you stuff here
}

attenzione, devi usare il puntatore nel blocco fisso! Il gc può spostare l'oggetto una volta che non sei più nel blocco fisso.


16
Mi piace questa risposta perché non comporta l'allocazione di memoria aggiuntiva solo per accedere ai dati
Xcalibur

3
Questa è la risposta migliore se si utilizzano byte di grandi dimensioni []. Una copia ha un overhead eccessivo
goodguys_activate il

19

È possibile utilizzare Marshal.UnsafeAddrOfPinnedArrayElement(array, 0)per ottenere un puntatore di memoria all'array.


1
Questo è - penso - l'unico modo per ottenere un IntPtr per un elemento array che non è il 0 (senza usare un codice non sicuro).
Guido Domenici,

5
Per fare questo array affidabile codice dovrebbe essere appuntato prima msdn.microsoft.com/en-us/library/3k4y07x3.aspx
sergtk

13

Ecco una svolta alla risposta di @ user65157 (+1 per quello, a proposito):

Ho creato un wrapper IDisposable per l'oggetto appuntato:

class AutoPinner : IDisposable
{
   GCHandle _pinnedArray;
   public AutoPinner(Object obj)
   {
      _pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned);
   }
   public static implicit operator IntPtr(AutoPinner ap)
   {
      return ap._pinnedArray.AddrOfPinnedObject(); 
   }
   public void Dispose()
   {
      _pinnedArray.Free();
   }
}

quindi usalo così:

using (AutoPinner ap = new AutoPinner(MyManagedObject))
{
   UnmanagedIntPtr = ap;  // Use the operator to retrieve the IntPtr
   //do your stuff
}

Ho trovato che questo è un bel modo di non dimenticare di chiamare Free () :)


4
Potresti voler esaminare come derivare il tuo AutoPinner da SafeHandle poiché quella classe prende in considerazione gli "aspetti positivi" della concorrenza e della sicurezza, oltre a incoraggiare / utilizzare il modello IDisposable raccomandato.
kkahl,

0

Marshal.Copy funziona ma è piuttosto lento. Più veloce è copiare i byte in un ciclo for. Ancora più veloce è lanciare l'array di byte in un array ulong, copiare quanto più ultrale si adatta all'array di byte, quindi copiare i possibili 7 byte rimanenti (la traccia che non è allineata di 8 byte). Il più veloce è bloccare l'array di byte in un'istruzione fissa come proposto sopra nella risposta di Tyalis.


-1
IntPtr GetIntPtr(Byte[] byteBuf)
{
    IntPtr ptr = Marshal.AllocHGlobal(byteBuf.Length);
    for (int i = 0; i < byteBuf.Length; i++)
    {
       Marshal.WriteByte(ptr, i, byteBuf[i]);
    }
    return ptr;
}

Questo è un duplicato più inefficiente della risposta accettata. Perché dovresti copiare byte per byte anziché tutti contemporaneamente?
BDL il

Ho trovato l'errore nella risposta accettata nel mio codice. Ho chiamato la funzione DLL C ++ in c #. parametro char * usato in c ++, quindi ho usato il metodo accettato. [DllImport ("MyDll.dll", EntryPoint = "functionName", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] ma strano, quando si utilizza il metodo accettato, non è possibile ottenere IntPtr corretto, ho riscontrato che il buffer era rotto , alcuni buffer sono stati visualizzati come Unicode. quindi ho usato questo metodo
nexdev il

-6

In alcuni casi è possibile utilizzare un tipo Int32 (o Int64) nel caso di IntPtr. Se puoi, un'altra classe utile è BitConverter. Per quello che vuoi puoi usare BitConverter.ToInt32 per esempio.


14
Non utilizzare mai Int32 o Int64 al posto di un puntatore . Se devi trasferire il tuo codice su una piattaforma diversa (32-bit-> 64-bit), avrai tutti i tipi di mal di testa.
xxbbcc,

5
No, non esiste un caso valido in cui è possibile utilizzare correttamente e in modo sicuro Int32un puntatore. Questa è stata una cattiva pratica fatta anni fa e ha portato a tutti i tipi di problemi di porting. Anche un Int64non è sicuro perché ci sono già architetture a 128 bit e la dimensione del puntatore aumenterà. I puntatori dovrebbero essere sempre e solo rappresentati come puntatori.
xxbbcc,

1
L'ho usato nei progetti .NET CF senza problemi, ovviamente avresti problemi se provassi a portarlo su altri sistemi, ma c'è del codice là fuori che non dovrebbe mai essere portato.
Alejandro Mezcua,

È vero che non è pianificato il porting di alcuni codici, ma le cose possono cambiare abbastanza rapidamente. Anche se il tuo utilizzo su CF era giustificato (non lo so) è comunque un cattivo consiglio a una domanda generica. L'unico scenario valido per l'utilizzo int/ longper i puntatori è quando la lingua utilizzata non ne ha alcun concetto (ad esempio VB6). C # supporta i puntatori e ha IntPtr- non c'è mai bisogno di usare intmai al posto di un puntatore. Rimuoverò il mio -1 se aggiungi avvisi chiari e una spiegazione di potenziali problemi alla tua risposta.
xxbbcc,

3
Beh, si sta confrontando l'utilizzo di codice di smistamento molto specifica a questa domanda di carattere generale su utilizzando i puntatori in C #. In C # normale non esiste uno scenario in cui ciò sia valido. So che è una vecchia domanda ma l'ho votata perché l'ho trovata cercando il modo di allocare memoria per un IntPtr - anche altri lo vedranno. Vedo i tuoi consigli come molto pericolosi perché le persone seguiranno pensando di essere riusciti a risolvere facilmente un problema quando tutto ciò che hanno sono problemi futuri.
xxbbcc,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.