Як отримати IntPtr з байту [] в C #


127

Я хочу перейти byte[]до методу, який займає IntPtrпараметр в C #, це можливо і як?


Чи можете ви надати більше деталей? Чому б ти хотів це зробити?
Grzenio

2
Це вам потрібно, якщо ви використовуєте DirectShow API, наприклад ... для отримання даних від VideoRenderer, ви повинні використовувати це ... і GCHandleметод працює як шарм ... також fixedметод. : P :))
Cipi

Це вам потрібно для всього, що передає терабайти даних, і ви хочете уникнути зайвої копії. Використовуйте свою фантазію.
Brain2000

Відповіді:


93

Не впевнені в отриманні IntPtr до масиву, але ви можете скопіювати дані для використання з некерованим кодом за допомогою програми Mashal.Copy:

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

Крім того, ви можете оголосити структуру з одним властивістю, а потім скористатися Marshal.PtrToStructure, але це все одно вимагатиме виділення некерованої пам'яті.

Редагувати: Також, як вказував Тяліс, ви також можете використовувати фіксовану, якщо небезпечний код є варіантом для вас


Просто для уточнення, Marshal.Copyдля цього перевантаження потрібен індекс старту. Дзвінок повинен бутиMarshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
mkenyon

краще отримати IntPtr, не створюючи нової пам'яті, як-от відповідь @ user65157.
Лін

208

Інший спосіб,

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

1
@Cipi На одній з інших публікацій Еріка Ліпперта, це повинно мати ключове слово "Фіксований" замість використання GC
goodguys_activate

дуже дякую. Працювати приємно і досить просто. Я не такий досвідчений в GCHandles і маршалі. Хтось може сказати мені, які плюси має маршал, а які - GCHandle і де використовувати який? Спасибі
скарбничка

1
Може хтось, будь ласка, надати посилання на пост Еріка Ліпперта?
Камерон

3
@piggy: Я думаю, що недоліком Маршала є те, що вам потрібно зробити копію своїх даних (що може зайняти довгий час і втратити пам'ять, яка може знадобитися)
Riki

6
@ makerofthings7 Я не вірю, що Ліпперт говорить використовувати "фіксований" замість GC [для закріплення об'єктів], я вважаю, що він говорить, що не використовуйте GC для закріплення об'єкта на невизначений час, а також не використовуйте фіксовану, щоб зробити те ж.
Камерон

129

Це має працювати, але його слід використовувати в небезпечному контексті:

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

будьте обережні, вам потрібно використовувати вказівник у фіксованому блоці! Gc може перемістити об'єкт, коли ви вже не перебуваєте у фіксованому блоці.


16
Мені подобається ця відповідь, тому що вона не передбачає виділення додаткової пам'яті лише для доступу до даних
Xcalibur

3
Це найкраща відповідь, якщо використовується великий байт []. У копії занадто багато накладних витрат
goodguys_activate

19

Ви можете використовувати Marshal.UnsafeAddrOfPinnedArrayElement(array, 0)для отримання вказівника пам'яті на масив.


1
Це - я думаю - єдиний спосіб отримати IntPtr для елемента масиву, який не є 0-м (без використання небезпечного коду).
Guido Domenici

5
Щоб зробити цей код надійним масивом, слід спочатку закріпити msdn.microsoft.com/en-us/library/3k4y07x3.aspx
sergtk

13

Ось поворот на відповідь @ user65157 (+1 для цього, BTW):

Я створив обгортку, що не використовується, для закріпленого об’єкта:

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

то використовуйте його так, таким чином:

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

Я вважав, що це хороший спосіб не забути зателефонувати безкоштовно () :)


4
Можливо, ви захочете дослідити походження свого AutoPinner від SafeHandle, оскільки цей клас бере до уваги параметри одночасності та безпеки, а також заохочує / використовує рекомендовану схему IDisposable.
kkahl

0

Маршал. Копія працює, але досить повільно. Швидше - скопіювати байти в цикл for. Ще швидше - це передати байтовий масив до масиву ulong, скопіювати стільки ж улонг, скільки вміститься в байтовий масив, а потім скопіювати можливі інші 7 байтів (слід, який не вирівнюється на 8 байт). Найшвидше - зафіксувати масив байтів у фіксованому операторі, як було запропоновано вище у відповіді Тяліса.


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

Це більш неефективний дублікат прийнятої відповіді. Чому б ви копіювали байт-байт замість усіх одразу?
BDL

Я знайшов помилку у прийнятій відповіді у своєму коді. Я назвав функцію dll C ++ у c #. Параметр char *, що використовується в c ++, тому я використовував прийнятий метод. [DllImport ("MyDll.dll", EntryPoint = "functionName", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)], але дивно, що при використанні прийнятого методу не вдається виправити IntPtr, я отримав деякий буфер , деякий буфер відображався як Unicode. тому я використав цей метод
nexdev

-6

У деяких випадках для IntPtr можна використовувати тип Int32 (або Int64). Якщо можете, ще один корисний клас - BitConverter. Для того, що ви хочете, ви можете використовувати BitConverter.ToInt32, наприклад.


14
Ви ніколи не повинні використовувати Int32 або Int64 замість вказівника . Якщо вам доведеться перенести свій код на іншу платформу (32-бітну -> 64-бітну), у вас виникнуть всілякі головні болі.
xxbbcc

5
Ні, не існує дійсного випадку, в якому можна правильно та безпечно використовувати Int32вказівник. Це було поганою практикою, зробленою роками тому, і це призводило до різного роду переносів проблем. Навіть не Int64є безпечним, оскільки вже є 128-бітна архітектура, і розмір вказівника збільшиться. Покажчики повинні бути представлені лише як покажчики.
xxbbcc

1
Я використовував його в проектах .NET CF без проблем, звичайно, у вас виникнуть проблеми, якщо ви спробуєте перенести його на інші системи, але там є код, який не повинен був ніколи переноситися.
Алехандро Мескуа

Це правда, що якийсь код не планується переносити, але все може змінитися досить швидко. Навіть якщо ваше використання CF не було виправданим (я не знаю), це все ще погана порада для загального питання. Єдиний вірний сценарій використання int/ longдля покажчиків - це коли мова, що використовується, не має їх поняття (наприклад, VB6). C # підтримує покажчики і має IntPtr- взагалі немає потреби використовувати intзамість вказівника. Я видаляю -1, якщо ви додасте чіткі застереження та пояснення можливих проблем до своєї відповіді.
xxbbcc

3
Ну, будуть порівнювати використання дуже конкретного коду сортувального цей загальний питання про використання покажчиків в C #. У звичайному C # немає сценарію, де це було б дійсним. Я знаю, що це давнє питання, але я проголосував за нього, тому що я натрапив на нього, шукаючи спосіб виділити пам'ять для IntPtr - і інші це побачать. Я вважаю вашу пораду дуже небезпечною, тому що люди пітимуть з нею, думаючи, що вони вирішили проблему легко, коли все, що у них є, - це майбутні проблеми.
xxbbcc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.