在早期的 Windows 程式開發中,GDI (Graphics Device Interface,圖形裝置介面) 是在視窗表單上繪製圖形、文字與影像的核心 API。然而,GDI 功能有限,尤其在影像處理方面表現不足。GDI+ 作為 GDI 的增強版本,不僅繼承並優化了原有功能,更在二維向量圖形、文字渲染、影像處理、區域運算、路徑構建以及圖形資料矩陣等領域,提供了一系列完整的類別庫。其中 Graphics 類別是 GDI+ 的核心,絕大多數繪圖操作皆透過它完成。
相較於 GDI,GDI+ 新增了漸層筆刷、樣條曲線、持久化路徑物件、矩陣變換、Alpha 混色、色彩校正、反鋸齒以及中繼資料支援等特性。但 GDI+ 也有缺憾:不支援 GDI 的光柵操作 (ROP),如 AND、OR、XOR 等,且缺乏硬體加速。XOR 光柵操作是實現「橡皮筋」動態定位技術的關鍵,且 GDI+ 在影像處理速度上未必優於 GDI。因此,在托管 C++ 環境下結合 GDI+ 與 GDI 的優點,成為解決上述問題的有效途徑。
在 Visual C++ .NET 2003 中,開發者可使用 MFC 或托管 C++ (Managed Extensions for C++) 進行圖形開發。MFC 將 Windows API 封裝為面向物件的類別庫,可直接使用 CDC 類別或 GDI API。托管 C++ 則專為 .NET Framework 設計,保留標準 C++ 功能,並支援自動記憶體管理與跨語言互操作。
由於托管與非托管環境的差異,GDI API 無法像在 MFC 中那樣直接被托管 C++ 呼叫。但 GDI+ 的 Graphics 類別提供了 GetHdc 與 ReleaseHdc 方法,可獲取或釋放與 Graphics 物件關聯的裝置內容句柄 (HDC),成為混合編程的橋樑。
在托管 C++ 中呼叫 GDI API (gdi32.dll) 需透過平台呼叫服務,命名空間為 System::Runtime::InteropServices。步驟包括:標識 DLL 函數、建立托管原型、進行函數呼叫。使用 DllImport 屬性標識 DLL 函數,常用欄位有 EntryPoint、CharSet、ExactSpelling、CallingConvention 等。
using namespace System::Runtime::InteropServices;
typedef void* HDC;
[DllImport("gdi32", EntryPoint="LineTo")]
extern "C" bool LineTo(HDC hDC, int nXEnd, int nYEnd);
為避免命名衝突,建議將 DLL 函數置於自訂命名空間或類別中:
namespace GDI32API {
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HDC;
[DllImport("gdi32", EntryPoint="LineTo")]
extern "C" bool LineTo(HDC hDC, int nXEnd, int nYEnd);
}
// 呼叫範例
Graphics* g = this->panel1->CreateGraphics();
IntPtr hdc = g->GetHdc();
GDI32API::LineTo((GDI32API::HDC)hdc, 100, 200);
g->ReleaseHdc(hdc);
GDI API 使用的資料類型 (定義於 wingdi.h) 與 .NET 內建值類型存在差異。對於結構、陣列、字串等複雜類型,需使用 StructLayout、MarshalAs 等屬性進行封送。
StructLayout(LayoutKind::Sequential) 強制成員按順序佈局;或 LayoutKind::Explicit 搭配 FieldOffset 指定位移。MarshalAs(UnmanagedType::LPWStr) 對應 LPWSTR;結構中的固定長度字串用 UnmanagedType::ByValTStr 加 SizeConst。UnmanagedType::LPArray 加 SizeConst;結構中的固定陣列用 UnmanagedType::ByValArray。以下範例示範在滑鼠移動事件中,利用 GDI 的 XOR 模式實現橡皮筋效果:
namespace GDI32 {
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HDC;
typedef void* HPEN;
[StructLayout(LayoutKind::Sequential)]
public __value struct POINT { public: long x; long y; };
[DllImport("gdi32", EntryPoint="SetROP2")] extern "C" int SetROP2(HDC, int);
[DllImport("gdi32", EntryPoint="CreatePen")] extern "C" HPEN CreatePen(int, int, unsigned long);
[DllImport("gdi32", EntryPoint="SelectObject")] extern "C" void* SelectObject(HDC, void*);
[DllImport("gdi32", EntryPoint="LineTo")] extern "C" bool LineTo(HDC, int, int);
[DllImport("gdi32", EntryPoint="MoveToEx")] extern "C" bool MoveTo(HDC, int, int, POINT*);
}
private: System::Void On_MouseMove(Object* sender, MouseEventArgs* e) {
Graphics* g = this->panel1->CreateGraphics();
IntPtr hdc = g->GetHdc();
GDI32::HPEN hPen = GDI32::CreatePen(0, 0, 0xA0A0A0);
GDI32::SelectObject((GDI32::HDC)hdc, hPen);
GDI32::SetROP2((GDI32::HDC)hdc, 7); // R2_XORPEN
GDI32::MoveTo((GDI32::HDC)hdc, pt.X, pt.Y, NULL);
GDI32::LineTo((GDI32::HDC)hdc, ptPrev.X, ptPrev.Y);
ptPrev = Point(e->X, e->Y);
GDI32::MoveTo((GDI32::HDC)hdc, pt.X, pt.Y, NULL);
GDI32::LineTo((GDI32::HDC)hdc, ptPrev.X, ptPrev.Y);
g->ReleaseHdc(hdc);
}

圖 1 GDI+ 與 GDI 混合編程實例
直接 P/Invoke 程式碼繁瑣,可改以 MFC DLL 封裝 GDI API,再由托管程式碼呼叫該 DLL。例如建立 MFCGDIDLL 導出函數:
extern "C" __declspec(dllexport)
void DrawGDIXorSolidLine(HDC hDC, DWORD color, int nWidth,
int x1, int y1, int x2, int y2) {
HPEN pen = ::CreatePen(0, nWidth, color);
HPEN oldPen = (HPEN)::SelectObject(hDC, pen);
int nOldDrawMode = ::SetROP2(hDC, R2_XORPEN);
::MoveToEx(hDC, x1, y1, NULL);
::LineTo(hDC, x2, y2);
::SelectObject(hDC, oldPen);
::SetROP2(hDC, nOldDrawMode);
}
托管端僅需簡單宣告:
namespace MFCGDI {
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HDC;
[DllImport("mfcgdidll", EntryPoint="DrawGDIXorSolidLine")]
extern "C" void DrawGDIXorSolidLine(HDC, unsigned long, int, int, int, int, int);
}
滑鼠事件處理大幅簡化:
MFCGDI::DrawGDIXorSolidLine((MFCGDI::HDC)hdc, 0xA0A0A0, 1,
pt.X, pt.Y, ptPrev.X, ptPrev.Y);
ptPrev = Point(e->X, e->Y);
MFCGDI::DrawGDIXorSolidLine((MFCGDI::HDC)hdc, 0xA0A0A0, 1,
pt.X, pt.Y, ptPrev.X, ptPrev.Y);
透過平台呼叫實現 GDI+ 與 GDI 的混合編程,不僅彌補了 GDI+ 缺乏光柵操作與硬體加速的不足,更可藉由 MFC DLL 擴展圖形開發能力。托管 C++ 作為 Visual C++ 開發 .NET 應用的主力語言,配合此技術,能讓開發者在 .NET 框架下充分發揮 C++ 的效能與靈活性。