Cách nhận IntPtr từ byte [] trong C #


127

Tôi muốn truyền a byte[]cho một phương thức lấy IntPtrThông số trong C #, điều đó có thể không và bằng cách nào?


Bạn có thể cung cấp thêm chi tiết? Tại sao bạn muốn làm điều đó?
Grzenio

2
Bạn cần điều này nếu bạn sử dụng API DirectShow chẳng hạn ... để lấy dữ liệu từ VideoRenderer, bạn phải sử dụng ... và GCHandlephương thức này hoạt động như một bùa mê ... cũng là fixedphương pháp. : P :))
Cipi

Bạn cần điều này cho bất cứ điều gì đang truyền terrabyte dữ liệu và bạn muốn tránh bản sao thêm. Sử dụng trí tưởng tượng của bạn.
Brain2000

Câu trả lời:


93

Không chắc chắn về việc nhận IntPtr vào một mảng, nhưng bạn có thể sao chép dữ liệu để sử dụng với mã không được quản lý bằng cách sử dụng Mashal.Copy:

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

Ngoài ra, bạn có thể khai báo một cấu trúc với một thuộc tính và sau đó sử dụng Marshal.PtrToStr struct, nhưng điều đó vẫn sẽ yêu cầu phân bổ bộ nhớ không được quản lý.

Chỉnh sửa: Ngoài ra, như Tyalis đã chỉ ra, bạn cũng có thể sử dụng cố định nếu mã không an toàn là một tùy chọn cho bạn


Chỉ cần làm rõ, Marshal.Copyvới sự quá tải đó cần một chỉ số bắt đầu. Cuộc gọi nên làMarshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
mkenyon

tốt hơn để có được IntPtr mà không cần tạo bộ nhớ mới, như câu trả lời của @ user65157.
Lin

208

Cách khác,

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

1
@Cipi Mỗi một trong những bài đăng khác của Eric Lipperts, bài viết này nên có từ khóa Cố định thay vì sử dụng GC
goodguys_activate

cảm ơn rất nhiều. Làm việc tốt và khá dễ dàng. Tôi không rành về GCHandles và Marshal. Ai đó có thể cho tôi biết điểm cộng nào có Nguyên soái và GCHandle không và sử dụng cái nào? Cảm ơn
heo con

1
Ai đó có thể vui lòng cung cấp tài liệu tham khảo cho bài viết Eric Lippert?
Cameron

3
@piggy: Tôi nghĩ nhược điểm của Nguyên soái là bạn phải tạo một bản sao dữ liệu của mình (việc này có thể mất nhiều thời gian và lãng phí bộ nhớ mà bạn có thể cần)
Riki

6
@ Makerofthings7 Tôi không tin Lippert đang nói sử dụng 'fixed' thay vì GC [để ghim các đối tượng], tôi tin rằng anh ta đang nói không sử dụng GC để ghim một đối tượng vô thời hạn, cùng với việc nói không sử dụng cố định để thực hiện giống nhau
Cameron

129

Điều này sẽ hoạt động nhưng phải được sử dụng trong bối cảnh không an toàn:

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

hãy cẩn thận, bạn phải sử dụng con trỏ trong khối cố định! Các gc có thể di chuyển đối tượng một khi bạn không còn trong khối cố định.


16
Tôi thích câu trả lời này vì nó không liên quan đến việc phân bổ thêm bộ nhớ chỉ để truy cập dữ liệu
Xcalibur

3
Đây là câu trả lời tốt nhất nếu sử dụng byte lớn []. Một bản sao có quá nhiều chi phí
goodguys_activate 17/12/12

19

Bạn có thể sử dụng Marshal.UnsafeAddrOfPinnedArrayElement(array, 0)để có được một con trỏ bộ nhớ đến mảng.


1
Đây là - tôi nghĩ - cách duy nhất để có được một IntPtr cho một phần tử mảng không phải là số 0 (không sử dụng mã không an toàn).
Guido Domenici

5
Để tạo mã này, mảng đáng tin cậy phải được ghim đầu tiên msdn.microsoft.com/en-us/l
Library / 3k4y07x3.aspx

13

Đây là một thay đổi trong câu trả lời của @ user65157 (+1 cho điều đó, BTW):

Tôi đã tạo một trình bao bọc IDis Dùng cho đối tượng được ghim:

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

sau đó sử dụng nó như vậy:

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

Tôi thấy đây là một cách hay để không quên gọi Free () :)


4
Bạn có thể muốn điều tra việc lấy AutoPinner của mình từ SafeHandle vì lớp đó có tính năng "gotchas" đồng thời và bảo mật, cũng như khuyến khích / sử dụng mẫu IDis Dùng được đề xuất.
kkahl

0

Marshal.Copy hoạt động nhưng khá chậm. Nhanh hơn là sao chép các byte trong một vòng lặp for. Thậm chí nhanh hơn là truyền mảng byte thành mảng ulong, sao chép càng nhiều ulong càng phù hợp với mảng byte, sau đó sao chép 7 byte còn lại có thể (đường dẫn không được căn chỉnh 8 byte). Nhanh nhất là ghim mảng byte trong một câu lệnh cố định như được đề xuất ở trên trong câu trả lời của 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;
}

Đây là một bản sao không hiệu quả hơn của câu trả lời được chấp nhận. Tại sao bạn lại sao chép từng byte thay vì tất cả cùng một lúc?
BDL

Tôi tìm thấy lỗi trong câu trả lời được chấp nhận trong mã của tôi. Tôi đã gọi hàm dll C ++ trong c #. tham số char * được sử dụng trong c ++, vì vậy tôi đã sử dụng phương thức được chấp nhận. [DLLImport ("MyDll.dll", EntryPoint = "functionName", CharSet = CharSet.Ansi, CallingCon Medi = CallingCon Medi.Cdecl)] nhưng lạ, khi sử dụng phương thức được chấp nhận, tôi đã nhận được một số bộ đệm bị hỏng , một số bộ đệm đã được hiển thị dưới dạng Unicode. vì vậy tôi đã sử dụng phương pháp này
nexdev

-6

Trong một số trường hợp, bạn có thể sử dụng loại Int32 (hoặc Int64) trong trường hợp IntPtr. Nếu bạn có thể, một lớp hữu ích khác là BitConverter. Đối với những gì bạn muốn, bạn có thể sử dụng BitConverter.ToInt32 chẳng hạn.


14
Bạn không bao giờ nên sử dụng Int32 hoặc Int64 thay cho con trỏ . Nếu bạn phải chuyển mã của mình sang một nền tảng khác (32-bit-> 64-bit), bạn sẽ bị đau đầu.
xxbbcc

5
Không, không có trường hợp hợp lệ nào trong đó bạn có thể sử dụng chính xác và an toàn Int32như một con trỏ. Đây là một thực tế tồi tệ được thực hiện nhiều năm trước và nó dẫn đến tất cả các loại vấn đề chuyển. Thậm chí Int64không an toàn vì đã có kiến ​​trúc 128 bit và kích thước con trỏ sẽ tăng lên. Con trỏ chỉ nên được đại diện như con trỏ.
xxbbcc

1
Tôi đã sử dụng nó trong các dự án .NET CF mà không gặp vấn đề gì, tất nhiên bạn sẽ gặp vấn đề nếu bạn cố gắng chuyển nó sang các hệ thống khác, nhưng có mã không có nghĩa là đã được chuyển.
Alejandro Mezcua

Đúng là một số mã không được lên kế hoạch để chuyển nhưng mọi thứ có thể thay đổi khá nhanh. Ngay cả khi việc sử dụng CF của bạn là hợp lý (tôi không biết), đó vẫn là lời khuyên tồi cho một câu hỏi chung chung. Kịch bản hợp lệ duy nhất để sử dụng int/ longcho con trỏ là khi ngôn ngữ được sử dụng không có khái niệm về chúng (ví dụ VB6). C # hỗ trợ con trỏ và có IntPtr- hoàn toàn không cần sử dụng intthay cho con trỏ. Tôi sẽ xóa -1 nếu bạn thêm cảnh báo rõ ràng và giải thích về các vấn đề tiềm ẩn cho câu trả lời của bạn.
xxbbcc

3
Chà, bạn đang so sánh việc sử dụng mã marshalling rất cụ thể với câu hỏi chung này về việc sử dụng con trỏ trong C #. Trong C # bình thường, không có kịch bản nào trong đó điều này sẽ hợp lệ. Tôi biết đó là một câu hỏi cũ nhưng tôi đã bỏ phiếu vì tôi đã tìm thấy nó bằng cách tìm cách phân bổ bộ nhớ cho một IntPtr - những người khác cũng sẽ thấy điều này. Tôi thấy lời khuyên của bạn là rất nguy hiểm bởi vì mọi người sẽ nghĩ rằng họ đã giải quyết vấn đề một cách dễ dàng khi tất cả những gì họ nhận được là rắc rối trong tương lai.
xxbbcc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.