Jak uzyskać IntPtr z bajtu [] w C #


127

Chcę przekazać byte[]do metody przyjmuje IntPtrparametr w języku C #, czy jest to możliwe i jak?


Czy mógłbyś podać więcej szczegółów? Dlaczego chcesz to zrobić?
Grzenio

2
Potrzebujesz tego, jeśli używasz DirectShow API na przykład ... aby pobrać dane z VideoRenderer, musisz użyć tego ... a GCHandlemetoda działa jak urok ... także fixedmetoda. : P :))
Cipi

Potrzebujesz tego do wszystkiego, co przenosi terabajty danych i chcesz uniknąć dodatkowej kopii. Użyj swojej wyobraźni.
Brain2000

Odpowiedzi:


93

Nie mam pewności co do pobrania IntPtr do tablicy, ale możesz skopiować dane do użycia z niezarządzanym kodem za pomocą Mashal.Copy:

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

Alternatywnie możesz zadeklarować strukturę z jedną właściwością, a następnie użyć Marshal.PtrToStructure, ale nadal wymagałoby to przydzielenia niezarządzanej pamięci.

Edycja: Ponadto, jak wskazał Tyalis, możesz również użyć opcji naprawionej, jeśli niebezpieczny kod jest dla ciebie opcją


Dla wyjaśnienia, Marshal.Copyprzy tym przeciążeniu potrzebny jest indeks początkowy. Wezwanie powinno byćMarshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
mkenyon

lepiej uzyskać IntPtr bez tworzenia nowej pamięci, na przykład odpowiedź @ user65157.
Lin

208

Inny sposób,

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

1
@Cipi Według jednego z innych postów Erica Lippertsa, powinno to mieć słowo kluczowe Fixed zamiast używać GC
goodguys_activate

wielkie dzięki. Pracuj dobrze i jest to całkiem łatwe. Nie mam takich umiejętności w GCHandles i Marshal. Czy ktoś może mi powiedzieć jakie plusy ma Marszałek a jakie GCHandle i gdzie z jakiego skorzystać? Dzięki
świnka

1
Czy ktoś mógłby podać odniesienie do postu Erica Lipperta?
Cameron

3
@piggy: Myślę, że wadą Marshal jest to, że musisz wykonać kopię swoich danych (co może zająć dużo czasu i marnować pamięć, której możesz potrzebować)
Riki

6
@ makerofthings7 Nie wierzę, że Lippert mówi, że używa „stałego” zamiast GC [do przypinania obiektów], myślę, że mówi, że nie używaj GC do przypinania obiektu w nieskończoność, a także mówiąc nie używaj ustalonego do robienia to samo.
Cameron,

129

To powinno działać, ale musi być używane w niebezpiecznym kontekście:

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

uważaj, musisz użyć wskaźnika w ustalonym bloku! Gc może przesuwać obiekt, gdy nie jesteś już w ustalonym bloku.


16
Podoba mi się ta odpowiedź, ponieważ nie wymaga przydzielania dodatkowej pamięci tylko po to, aby uzyskać dostęp do danych
Xcalibur,

3
To najlepsza odpowiedź, jeśli używane są duże bajty []. Kopia ma za dużo narzutów
goodguys_activate


13

Oto zwrot w odpowiedzi @ user65157 (przy okazji +1, BTW):

Utworzyłem opakowanie IDisposable dla przypiętego obiektu:

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

następnie użyj go w ten sposób:

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

Uważam, że to miły sposób, aby nie zapomnieć zadzwonić do Free () :)


4
Możesz chcieć zbadać wyprowadzanie twojego AutoPinner z SafeHandle, ponieważ ta klasa bierze pod uwagę współbieżność i „pułapki” bezpieczeństwa, a także zachęca / używa zalecanego wzorca IDisposable.
kkahl

0

Marshal.Copy działa, ale działa dość wolno. Szybsze jest skopiowanie bajtów w pętli for. Jeszcze szybsze jest rzutowanie tablicy bajtów na tablicę ulong, skopiowanie tyle ulong, ile mieści się w tablicy bajtów, a następnie skopiowanie możliwych pozostałych 7 bajtów (ślad, który nie jest wyrównany 8 bajtów). Najszybciej jest przypięcie tablicy bajtów w ustalonej instrukcji, jak zaproponowano powyżej w odpowiedzi Tyalisa.


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

Jest to mniej wydajna kopia zaakceptowanej odpowiedzi. Dlaczego miałbyś kopiować bajt po bajcie zamiast wszystkich naraz?
BDL

Znalazłem błąd w zaakceptowanej odpowiedzi w moim kodzie. Nazwałem funkcję dll C ++ w języku C #. parametr char * użyty w c ++, więc użyłem zaakceptowanej metody. [DllImport ("MyDll.dll", EntryPoint = "functionName", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] ale dziwne, gdy używam zaakceptowanej metody, nie można uzyskać poprawnego IntPtr, mam uszkodzony bufor , jakiś bufor był wyświetlany jako Unicode. więc użyłem tej metody
nexdev

-6

W niektórych przypadkach można użyć typu Int32 (lub Int64) w przypadku IntPtr. Jeśli możesz, inną przydatną klasą jest BitConverter. Do tego, co chcesz, możesz na przykład użyć BitConverter.ToInt32.


14
Nigdy, przenigdy nie należy używać Int32 lub Int64 zamiast wskaźnika . Jeśli musisz przenieść swój kod na inną platformę (32-bit-> 64-bit), będziesz mieć różnego rodzaju bóle głowy.
xxbbcc

5
Nie, nie ma ważnego przypadku, w którym można by poprawnie i bezpiecznie użyć znaku Int32jako wskaźnika. To była zła praktyka zrobiona lata temu i doprowadziła do różnego rodzaju problemów z portowaniem. Nawet Int64nie jest bezpieczny, ponieważ istnieją już architektury 128-bitowe, a rozmiar wskaźnika wzrośnie. Wskaźniki powinny być zawsze przedstawiane tylko jako wskaźniki.
xxbbcc

1
Używałem go w projektach .NET CF bez problemów, oczywiście miałbyś problemy, gdybyś spróbował przenieść go do innych systemów, ale istnieje kod, który nie jest przeznaczony do przeniesienia.
Alejandro Mezcua

To prawda, że ​​część kodu nie jest planowana do przeniesienia, ale sytuacja może się dość szybko zmienić. Nawet jeśli twoje użycie na CF było uzasadnione (nie wiem), nadal jest to zła rada na ogólne pytanie. Jedynym prawidłowym scenariuszem użycia int/ longdla wskaźników jest sytuacja, gdy używany język nie ma ich koncepcji (na przykład VB6). C # obsługuje wskaźniki i ma IntPtr- nie ma potrzeby używania intkiedykolwiek wskaźnika zamiast wskaźnika. Usunę moje -1, jeśli dodasz jasne ostrzeżenia i wyjaśnienie potencjalnych problemów do odpowiedzi.
xxbbcc

3
Cóż, porównując wykorzystanie bardzo specyficznym kodem rozrządowej na to ogólne pytanie o użyciu wskaźników w C #. W normalnym C # nie ma scenariusza, w którym byłoby to prawidłowe. Wiem, że to stare pytanie, ale przegłosowałem je, ponieważ natknąłem się na nie, szukając sposobu na przydzielenie pamięci dla IntPtr - inni też to zobaczą. Uważam, że twoja rada jest bardzo niebezpieczna, ponieważ ludzie pójdą z nią myśląc, że łatwo im się uda rozwiązać problem, podczas gdy jedyne, co mają, to przyszłe kłopoty.
xxbbcc
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.