ฉันต้องการส่งผ่าน a byte[]ไปยังเมธอดใช้IntPtrพารามิเตอร์ใน C # เป็นไปได้หรือไม่และอย่างไร
GCHandleวิธีการทำงานเช่นเสน่ห์ ... นอกจากนี้ยังมีfixedวิธีการ : P :))
ฉันต้องการส่งผ่าน a byte[]ไปยังเมธอดใช้IntPtrพารามิเตอร์ใน C # เป็นไปได้หรือไม่และอย่างไร
GCHandleวิธีการทำงานเช่นเสน่ห์ ... นอกจากนี้ยังมีfixedวิธีการ : P :))
คำตอบ:
ไม่แน่ใจเกี่ยวกับการรับ 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);
อีกวิธีหนึ่ง
GCHandle pinnedArray = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
// Do your stuff...
pinnedArray.Free();
สิ่งนี้ควรใช้งานได้ แต่ต้องใช้ภายในบริบทที่ไม่ปลอดภัย:
byte[] buffer = new byte[255];
fixed (byte* p = buffer)
{
IntPtr ptr = (IntPtr)p;
// do you stuff here
}
ระวังคุณต้องใช้ตัวชี้ในบล็อกคงที่! gc สามารถย้ายวัตถุได้เมื่อคุณไม่ได้อยู่ในบล็อกคงที่อีกต่อไป
คุณสามารถใช้Marshal.UnsafeAddrOfPinnedArrayElement(array, 0)เพื่อรับตัวชี้หน่วยความจำไปยังอาร์เรย์
นี่คือคำตอบของ @ 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
}
ฉันพบว่านี่เป็นวิธีที่ดีในการไม่ลืมโทรฟรี () :)
Marshal.Copy ใช้งานได้ แต่ค่อนข้างช้า เร็วกว่าคือการคัดลอกไบต์ใน for loop เร็วกว่านั้นคือการโยนอาร์เรย์ไบต์ไปยังอาร์เรย์ ulong คัดลอก ulong ให้มากที่สุดเท่าที่จะพอดีกับอาร์เรย์ไบต์จากนั้นคัดลอก 7 ไบต์ที่เหลือที่เป็นไปได้ (เส้นทางที่ไม่ได้จัดแนว 8 ไบต์) เร็วที่สุดคือการตรึงอาร์เรย์ไบต์ในคำสั่งคงที่ตามที่เสนอไว้ข้างต้นในคำตอบของ Tyalis
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;
}
ในบางกรณีคุณสามารถใช้ประเภท Int32 (หรือ Int64) ในกรณีของ IntPtr ถ้าทำได้คลาสอื่นที่มีประโยชน์คือ BitConverter สำหรับสิ่งที่คุณต้องการคุณสามารถใช้ BitConverter ToInt32 เช่น
Int32เป็นตัวชี้ได้อย่างถูกต้องและปลอดภัย นี่เป็นแนวทางปฏิบัติที่ไม่ดีเมื่อหลายปีก่อนและนำไปสู่ปัญหาการย้ายข้อมูลทุกประเภท แม้Int64จะไม่ปลอดภัยเพราะมีสถาปัตยกรรม 128 บิตอยู่แล้วและขนาดตัวชี้จะเพิ่มขึ้น พอยน์เตอร์ควรใช้แทนพอยน์เตอร์เท่านั้น
int/ longสำหรับพอยน์เตอร์คือเมื่อภาษาที่ใช้ไม่มีแนวคิดเกี่ยวกับสิ่งเหล่านี้ (ตัวอย่างเช่น VB6) C # รองรับพอยน์เตอร์และมีIntPtr- ไม่จำเป็นต้องใช้intแทนพอยน์เตอร์เลย ฉันจะลบ -1 ของฉันหากคุณเพิ่มคำเตือนที่ชัดเจนและคำอธิบายเกี่ยวกับปัญหาที่อาจเกิดขึ้นในคำตอบของคุณ