承接前兩篇,本文解決 IOCP 完成端口在 managed/unmanaged 邊界傳遞物件參考的核心難題:利用 GCHandle 取得物件指標,配合 PostQueuedCompletionStatus / GetQueuedCompletionStatus 實現型別安全的資料傳遞。
[DllImport("Kernel32")]
private static extern bool PostQueuedCompletionStatus(
UInt32 completionPort,
int numberOfBytesTransferred,
IntPtr completionKey,
IntPtr overlapped);
[DllImport("Kernel32")]
private static extern bool GetQueuedCompletionStatus(
UInt32 completionPort,
ref int numberOfBytes,
ref IntPtr completionKey,
ref IntPtr overlapped,
UInt32 milliseconds);
int key = 0;
object data = new object(); // 任意 managed 物件
GCHandle gcData = GCHandle.Alloc(data); // 釘選,防止 GC 移動
GCHandle gcKey = GCHandle.Alloc(key);
// 發送至完成端口
PostQueuedCompletionStatus(portHandle, 4, (IntPtr)gcKey, (IntPtr)gcData);
uint bytes;
IntPtr recvKey, recvOverlapped;
GetQueuedCompletionStatus(port, out bytes, out recvKey, out recvOverlapped, INFINITE);
GCHandle gch = GCHandle.FromIntPtr(recvOverlapped);
object originalData = gch.Target; // 取回原始物件
gch.Free(); // 釋放 GCHandle,讓 GC 回收
| 類型 | 行為 | 適用場景 |
|---|---|---|
Normal | 防止 GC 回收,允許移動物件 | Managed↔Unmanaged 雙向傳遞,不影響 GC 效能 |
Pinned | 防止回收且固定記憶體位址 | 傳遞 byte[] 緩衝區給 Unmanaged 代碼填充 |
Weak/WeakTrackResurrection | 弱參考,不阻止 GC | 快取、觀察者模式,非本文重點 |
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
[StructLayout(LayoutKind.Sequential)]
class PerIoData { public string Data; }
public class IOCPApiTest
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateIoCompletionPort(IntPtr FileHandle, IntPtr ExistingCompletionPort, IntPtr CompletionKey, uint NumberOfConcurrentThreads);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetQueuedCompletionStatus(SafeFileHandle CompletionPort, out uint lpNumberOfBytesTransferred, out IntPtr lpCompletionKey, out IntPtr lpOverlapped, uint dwMilliseconds);
[DllImport("Kernel32", CharSet = CharSet.Auto)]
private static extern bool PostQueuedCompletionStatus(SafeFileHandle CompletionPort, uint dwNumberOfBytesTransferred, IntPtr dwCompletionKey, IntPtr lpOverlapped);
public static unsafe void TestIOCPApi()
{
var port = CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, IntPtr.Zero, 1);
if (port.IsInvalid)
Console.WriteLine($"建立失敗:{Marshal.GetLastWin32Error()}");
var worker = new Thread(ThreadProc);
worker.Start(port);
// 發送第一筆資料
var data1 = new PerIoData { Data = "Hi, I'm worker." };
var h1 = GCHandle.Alloc(data1);
PostQueuedCompletionStatus(port, (uint)sizeof(IntPtr), IntPtr.Zero, (IntPtr)h1);
// 發送結束訊號
var data2 = new PerIoData { Data = "EXIT" };
var h2 = GCHandle.Alloc(data2);
PostQueuedCompletionStatus(port, 4, IntPtr.Zero, (IntPtr)h2);
Console.WriteLine("主執行緒結束");
Console.ReadKey();
}
static void ThreadProc(object portObj)
{
var port = (SafeFileHandle)portObj;
while (true)
{
uint bytes; IntPtr key, overlapped;
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] 等待資料...");
GetQueuedCompletionStatus(port, out bytes, out key, out overlapped, 0xFFFFFFFF);
if (bytes == 0) continue;
var gch = GCHandle.FromIntPtr(overlapped);
var data = (PerIoData)gch.Target;
Console.WriteLine($"收到:{data.Data}");
gch.Free();
if (data.Data == "EXIT")
{
Console.WriteLine("收到結束指令,退出。");
port.Dispose();
break;
}
}
}
}
GCHandle 是跨越 Managed/Unmanaged 邊界傳遞物件的關鍵。正確選擇 GCHandleType、及時呼叫 Free(),才能避免記憶體洩漏與存取違規。本範例展示最小可行的 IOCP 物件傳遞模型,可擴充為高效能非同步伺服器核心。