วิธีรับ IntPtr จาก byte [] ใน C #


127

ฉันต้องการส่งผ่าน a byte[]ไปยังเมธอดใช้IntPtrพารามิเตอร์ใน C # เป็นไปได้หรือไม่และอย่างไร


คุณช่วยให้รายละเอียดเพิ่มเติมได้หรือไม่? ทำไมถึงอยากทำ
Grzenio

2
คุณต้องนี้ถ้าคุณใช้ API DirectShow ตัวอย่าง ... ที่จะได้รับข้อมูลจาก VideoRenderer คุณต้องใช้นี้ ... และGCHandleวิธีการทำงานเช่นเสน่ห์ ... นอกจากนี้ยังมีfixedวิธีการ : P :))
Cipi

คุณต้องการสิ่งนี้สำหรับทุกสิ่งที่ถ่ายโอนข้อมูลหลายเทอราไบต์และคุณต้องการหลีกเลี่ยงการคัดลอกเพิ่มเติม ใช้จินตนาการของคุณ.
Brain2000

คำตอบ:


93

ไม่แน่ใจเกี่ยวกับการรับ IntPtr ไปยังอาร์เรย์ แต่คุณสามารถคัดลอกข้อมูลเพื่อใช้กับโค้ดที่ไม่มีการจัดการได้โดยใช้ Mashal คัดลอก:

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

หรือคุณสามารถประกาศโครงสร้างด้วยคุณสมบัติเดียวแล้วใช้ Marshal.PtrToStructure แต่ยังคงต้องมีการจัดสรรหน่วยความจำที่ไม่มีการจัดการ

แก้ไข:ตามที่ Tyalis ชี้ให้เห็นคุณสามารถใช้แก้ไขได้หากรหัสที่ไม่ปลอดภัยเป็นตัวเลือกสำหรับคุณ


เพื่อชี้แจงMarshal.Copyด้วยการโอเวอร์โหลดนั้นจำเป็นต้องมีดัชนีเริ่มต้น ควรโทรMarshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
mkenyon

ดีกว่าที่จะรับ IntPtr โดยไม่ต้องสร้างหน่วยความจำใหม่เช่นคำตอบของ @ user65157
Lin

208

อีกวิธีหนึ่ง

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

1
@Cipi ต่อหนึ่งในโพสต์อื่นของ Eric Lipperts ควรมีคีย์เวิร์ดคงที่แทนที่จะใช้ GC
goodguys_activate

ขอบคุณมาก. ทำงานได้ดีและค่อนข้างง่าย ฉันไม่ค่อยชำนาญใน GCHandles และ Marshal ใครช่วยบอกทีว่าจอมพลมีข้อดีอะไรบ้างและ GCHandle ใช้อะไรได้บ้าง ขอบคุณ
piggy

1
ใครช่วยกรุณาให้ข้อมูลอ้างอิงกับโพสต์ของ Eric Lippert ได้ไหม
Cameron

3
@piggy: ฉันคิดว่าข้อเสียของ Marshal คือคุณต้องทำสำเนาข้อมูลของคุณ (ซึ่งอาจใช้เวลานานและเสียหน่วยความจำที่คุณอาจต้องใช้)
Riki

6
@ makerofthings7 ฉันไม่เชื่อว่า Lippert บอกให้ใช้ 'คงที่' แทน GC [เพื่อตรึงวัตถุ] ฉันเชื่อว่าเขาบอกว่าอย่าใช้ GC เพื่อตรึงวัตถุไปเรื่อย ๆ พร้อมกับบอกว่าอย่าใช้การแก้ไขเพื่อทำ เหมือนกัน
Cameron

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):

ฉันสร้าง Wrapper IDisposable สำหรับวัตถุที่ตรึง:

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 เนื่องจากคลาสนั้นคำนึงถึง "gotchas" ที่เกิดขึ้นพร้อมกันและการรักษาความปลอดภัยรวมทั้งให้กำลังใจ / ใช้รูปแบบ IDisposable ที่แนะนำ
kkahl

0

Marshal.Copy ใช้งานได้ แต่ค่อนข้างช้า เร็วกว่าคือการคัดลอกไบต์ใน for loop เร็วกว่านั้นคือการโยนอาร์เรย์ไบต์ไปยังอาร์เรย์ ulong คัดลอก ulong ให้มากที่สุดเท่าที่จะพอดีกับอาร์เรย์ไบต์จากนั้นคัดลอก 7 ไบต์ที่เหลือที่เป็นไปได้ (เส้นทางที่ไม่ได้จัดแนว 8 ไบต์) เร็วที่สุดคือการตรึงอาร์เรย์ไบต์ในคำสั่งคงที่ตามที่เสนอไว้ข้างต้นในคำตอบของ 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;
}

นี่เป็นการทำซ้ำคำตอบที่ยอมรับแล้วไม่มีประสิทธิภาพมากกว่า ทำไมคุณถึงคัดลอกแบบไบต์ต่อไบต์แทนที่จะทำทั้งหมดในครั้งเดียว
BDL

ฉันพบข้อผิดพลาดในคำตอบที่ยอมรับในรหัสของฉัน ฉันเรียกฟังก์ชัน C ++ dll ใน c # พารามิเตอร์ char * ที่ใช้ใน c ++ ดังนั้นฉันจึงใช้วิธีการที่ยอมรับ [DllImport ("MyDll.dll", EntryPoint = "functionName", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] แต่แปลกเมื่อใช้วิธีการยอมรับไม่สามารถรับ IntPtr ที่ถูกต้องฉันได้บัฟเฟอร์บางส่วนเสีย บัฟเฟอร์บางส่วนแสดงเป็น Unicode เลยใช้วิธีนี้
nexdev

-6

ในบางกรณีคุณสามารถใช้ประเภท Int32 (หรือ Int64) ในกรณีของ IntPtr ถ้าทำได้คลาสอื่นที่มีประโยชน์คือ BitConverter สำหรับสิ่งที่คุณต้องการคุณสามารถใช้ BitConverter ToInt32 เช่น


14
คุณควรจะไม่เคยใช้ Int32 Int64 หรือในสถานที่ของตัวชี้ หากคุณต้องย้ายรหัสของคุณไปยังแพลตฟอร์มอื่น (32-bit-> 64-bit) คุณจะต้องปวดหัวทุกรูปแบบ
xxbbcc

5
ไม่ไม่มีกรณีใดที่คุณสามารถใช้Int32เป็นตัวชี้ได้อย่างถูกต้องและปลอดภัย นี่เป็นแนวทางปฏิบัติที่ไม่ดีเมื่อหลายปีก่อนและนำไปสู่ปัญหาการย้ายข้อมูลทุกประเภท แม้Int64จะไม่ปลอดภัยเพราะมีสถาปัตยกรรม 128 บิตอยู่แล้วและขนาดตัวชี้จะเพิ่มขึ้น พอยน์เตอร์ควรใช้แทนพอยน์เตอร์เท่านั้น
xxbbcc

1
ฉันใช้มันในโครงการ. NET CF โดยไม่มีปัญหาแน่นอนว่าคุณจะมีปัญหาหากคุณพยายามที่จะพอร์ตไปยังระบบอื่น แต่มีรหัสออกมาซึ่งไม่ได้หมายความว่าจะถูกย้าย
Alejandro Mezcua

เป็นความจริงที่ว่าโค้ดบางโค้ดไม่ได้ถูกวางแผนให้พอร์ต แต่สิ่งต่างๆสามารถเปลี่ยนแปลงได้อย่างรวดเร็ว แม้ว่าการใช้งาน CF ของคุณจะถูกต้อง (ฉันไม่รู้) แต่ก็ยังเป็นคำแนะนำที่ไม่ดีสำหรับคำถามทั่วไป สถานการณ์เดียวที่ถูกต้องสำหรับการใช้int/ longสำหรับพอยน์เตอร์คือเมื่อภาษาที่ใช้ไม่มีแนวคิดเกี่ยวกับสิ่งเหล่านี้ (ตัวอย่างเช่น VB6) C # รองรับพอยน์เตอร์และมีIntPtr- ไม่จำเป็นต้องใช้intแทนพอยน์เตอร์เลย ฉันจะลบ -1 ของฉันหากคุณเพิ่มคำเตือนที่ชัดเจนและคำอธิบายเกี่ยวกับปัญหาที่อาจเกิดขึ้นในคำตอบของคุณ
xxbbcc

3
คุณกำลังเปรียบเทียบการใช้รหัสมาร์แชลที่เฉพาะเจาะจงกับคำถามทั่วไปเกี่ยวกับการใช้พอยน์เตอร์ใน C # ใน C # ปกติไม่มีสถานการณ์ใดที่จะถูกต้อง ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันลงคะแนนเพราะฉันเจอคำถามนี้โดยมองหาวิธีจัดสรรหน่วยความจำสำหรับ IntPtr คนอื่น ๆ ก็จะเห็นสิ่งนี้เช่นกัน ฉันเห็นว่าคำแนะนำของคุณอันตรายมากเพราะผู้คนจะคิดว่าพวกเขาแก้ปัญหาได้ง่ายเมื่อสิ่งที่พวกเขาได้รับคือปัญหาในอนาคต
xxbbcc
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.