程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 托管C++中GDI+和GDI混合編程技術

托管C++中GDI+和GDI混合編程技術

編輯:關於C++

1.引言

早期的Windows程序中,可以使用GDI(Graphics Device Interface,圖形設備接口)在一個窗體中繪制圖形、文本和圖像,但它的功能比較有限,尤其是圖像處理方面。GDI+是GDI的一個新版本,它不僅在GDI基礎上添加許多新特性,而且對原有的GDI功能進行優化,並在為開發人員提供的二維矢量圖形、文本、圖像處理、區域、路徑以及圖形數據矩陣等方面構造了一系列相關的類。其中,圖形類Graphics是GDI+接口中的一個核心類,許多繪圖操作都可用它來完成。

與GDI相比,GDI+增加了漸變畫刷、樣條曲線、持久的路徑對象、矩陣和矩陣變換、Alpha混色、色彩修正、消除走樣以及元數據等新的特性。但是,GDI+卻並不支持GDI中的AND(與)、OR(或)以及XOR(異或)等光柵操作(ROP)以及硬件加速。其中,XOR光柵操作是實現圖元動態定位的橡皮條技術的最重要方法,其次GDI+中的圖像處理速度上並不比GDI更具優勢。為此,本文通過若干托管C++實例來探討在托管環境下GDI+和GDI的混合編程的方法和技巧。

2.托管C++和GDI

在Visual C++ .NET 2003中,程序員可以使用MFC和托管C++( Managed Extensions for C++,C++托管擴展)等編程方式進行圖形圖像程序開發。MFC是一套Microsoft基礎類庫,它是使用面向對象技術對Windows API進行封裝。因此在MFC中進行圖形圖像程序開發時既可以使用MFC類CDC來編程,也可直接使用GDI API中的函數和結構。

托管C++是在C++基礎上建立的,用來為Visual C++程序員開發.NET框架應用程序而設計。它除了保留標准C++的全部功能,還可通過.NET Framework(.NET框架)來創建對象,實現自動化內存管理以及與其他.NET語言的互操作性。由於托管環境與非托管環境的區別,因此GDI API並不能像MFC那樣直接在托管C++中進行調用。但在GDI+中的Graphics類[4]提供了與GDI交互的一些方法,如GetHdc和ReleaseHdc,分別用於獲取或釋放與Graphics對象相關聯的設備環境句柄。

由於GDI API不使用托管代碼,它使用的數據類型與托管C++中所用的數據類型不同,且它也不是COM對象,所以在托管C++使用GDI是通過平台調用(PInvoke)來實現的。

3.平台調用和數據封送

平台調用[5]是一種服務,它使托管代碼能夠調用DLL中實現的非托管函數,使用時需要指定Runtime::InteropServices命名空間。

3.1 調用GDI API函數的一般方法

在托管C++中調用GDI API(GDI32.DLL)一般是按標識 DLL中的函數、在托管代碼中創建原型和函數調用三個部分。其中,函數調用與一般托管C++中調用相同,這裡不作討論。

在托管C++中,DLL 函數的標識是通過DllImport屬性來操作的,它包括常用的EntryPoint、CharSet、ExactSpelling和CallingConvention等字段。EntryPoint字段用來指定要調用的DLL入口點的名稱。CharSet字段用來指定控制名稱損壞和封送字符串參數的方式。ExactSpelling字段用來指定是否在非托管DLL中搜索入口點指定的函數或方法名稱。CallingConvention字段用來指定入口點的調用約定,默認為WinAPI。

需要說明的是,DLL 函數的標識中不一定全部指定上述字段,通過設置一個或多個字段可以改變DllImport屬性的默認行為。例如:

using namespace System::Runtime::InteropServices;
typedef void* HDC;
[DllImport("gdi32", EntryPoint="LineTo")]
extern "C" bool LineTo(HDC hDC, int nXEnd, int nYEnd);

在托管代碼中訪問非托管DLL函數之前,首先需要用DllImport屬性操作來指定該函數的名稱以及將其導出的DLL的名稱。獲取以上信息後,就可以為該DLL中的非托管函數編寫托管方法的定義,即在托管代碼中創建函數或方法的原型。例如上面的緊跟DllImport屬性操作後的代碼是用來創建gdi32.dll中的LineTo函數原型,extern "C"是在托管C++中創建原型的標記,除了此標記名外,函數原型的聲明和C++函數的聲明是一樣的,函數名可以與EntryPoint字段指定的函數名相同,也可不同。為了避免聲明的函數名與托管程序中其他方法重名,通常將要調用的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);
}
  此時調用LineTo函數時須指定其所在的命名空間,例如:

Graphics *g = this->panel1->CreateGraphics();
// 創建與panel1控件相關聯的Graphics
IntPtr hdc = g->GetHdc();
GDI32API::LineTo( (GDI32API::HDC)hdc,100, 200 );
g->ReleaseHdc( hdc );

若將將要調用的DLL函數放在一個自定義類中,則該函數一般要定義成靜態類型。但由於extern "C"標記會在自定義的類中出現編譯錯誤,若不使用extern "C"標記,對於沒有內置結構或類的GDI API函數是可以的。例如:

public __gc class GDI32API
{
  public:
   typedef void* HDC;
   [DllImport("gdi32", EntryPoint="LineTo")]
   static bool LineTo(HDC hDC, int nXEnd, int nYEnd);
};

3.2 數據封送

由於在 GDI API(在wingdi.h中列出)函數中所使用的數據類型和托管C++( .NET Framework內置值類型)存在一些區別(如表1所示),雖然在托管C++中可以不通過平台調用中的數據封送來直接調用GDI API,但對於結構、數組和字符串數據類型來說,通過使用平台調用中的屬性和方法來封送數據可以更好地實現自己的數據定制。

表1 數據類型

wtypes.h C++ 托管C++ .NET類名 說明 GDI句?/TD> void * void * IntPtr, UIntPtr 32 位 BYTE unsigned char unsigned char Byte 8 位 SHORT short short Int16 16 位 WORD unsigned short unsigned short UInt16 16 位 INT int int Int32 32 位 UINT unsigned int unsigned int UInt32 32 位 LONG long long Int32 32 位 BOOL long bool Boolean 32 位 DWORD unsigned long unsigned long UInt32 32 位 ULONG unsigned long unsigned long UInt32 32 位 CHAR char char Char 用 ANSI 修飾 LPSTR char * String * [in], StringBuilder * [in, out] String [in], StringBuilder [in, out] 用 ANSI 修飾 LPCSTR const char * String * String 用 ANSI 修飾 LPWSTR wchar_t * String * [in], StringBuilder * [in, out] String [in], StringBuilder [in, out] 用 Unicode 修飾 LPCWSTR const wchar_t * String * String 用 Unicode 修飾 FLOAT float float Single 32 位 DOUBLE double double Double 64 位

(1) 封送結構

在托管C++中,當調用的GDI API函數有內置結構時,需要對其使用StructLayout屬性來進行封送。通過該屬性類的構造函數來指定被封送的結構的數據成員在非托管內存中的排列方式。當為LayoutKind::Explicit時,則每個成員必須使用FieldOffset屬性來指定該字段在類型中的位置。當為LayoutKind::Sequential時,則強制將成員按其出現的順序進行順序布局。例如:

namespace GDI32API
{
  using namespace System;
  using namespace System::Runtime::InteropServices;
  typedef void* HDC;
  [StructLayout(LayoutKind::Sequential)]
  public __value struct RECT
  {
   public:
    long left; // long或Int32
    long top;
    long right;
    long bottom;
  };
  [DllImport("gdi32", EntryPoint="GetClipBox")]
  extern "C" int GetClipBox(HDC hDC, RECT* rect);
}

(2) 封送字符串和數組等

在托管C++中,使用MarshalAs屬性類可以封送GDI API函數中的參數、內置結構的字段或返回值。MarshalAs屬性通常需要指定UnmanagedType枚舉來標識非托管數據的格式。

當需要封送String *字符串時,可指定UnmanagedType枚舉中的LPStr、LPWStr或LPTStr來封送,這些類型分別對應於GDI API中的LPSTR、LPWSTR或LPTSTR字符串類型。例如:

[DllImport("gdi32", EntryPoint="TextOut")]
extern "C" bool TextOut(HDC hDC, int x, int y,
[MarshalAs(UnmanagedType::LPWStr)] String *str, int nNum);

若封送結構中的字符串成員,則需指定UnmanagedType::ByValTStr類型,並指定SizeConst值來確定要導入的字符串中的字符數。

當需要封送數組時,需指定UnmanagedType::LPArray類型,並指定SizeConst值來確定要導入的數組大小,根據需要也可用ArraySubType字段指定數組元素的數據類型。例如,若有DLL中有這樣的非托管函數:

HRESULT New1(int ar[10]);
HRESULT New2(double ar[10][20]);
HRESULT New3(LPWSTR ar[10]);

則封送的托管代碼如下:

void New1([MarshalAs(UnmanagedType::LPArray, SizeConst=10)] int ar __gc[]);
void New2([MarshalAs(UnmanagedType::LPArray, SizeConst=200)] double ar __gc[]);
void New2([MarshalAs(UnmanagedType::LPArray,
ArraySubType=UnmanagedType::LPWStr, SizeConst=10)] String[] ar);

若封送結構中的數組成員,則需指定UnmanagedType::ByValArray類型,並指定SizeConst值來確定要導入的數組大小。

3.3 實例

這個實例是用來顯示實現繪制直線的橡皮條過程,如圖1所示的窗體。其中平台調用的GDI函數和結構如下面的代碼:

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或Int32
    long y;
  };
  [DllImport("gdi32", EntryPoint="SetROP2")]
  extern "C" int SetROP2(HDC hDC, int fnDrawMode); // 設置光柵操作模式
  [DllImport("gdi32", EntryPoint="CreatePen")]
  extern "C" HPEN CreatePen(int fnPenStyle, int nWidth, unsigned long crColor);
  // 創建畫筆
  [DllImport("gdi32", EntryPoint="SelectObject")]
  extern "C" void* SelectObject(HDC hDC, void* hGdiobj);
  // 選入GDI屬性對象
  [DllImport("gdi32", EntryPoint="LineTo")]
  extern "C" bool LineTo(HDC hDC, int nXEnd, int nYEnd);
  // 畫線
  [DllImport("gdi32", EntryPoint="MoveToEx")]
  extern "C" bool MoveTo(HDC hDC, int x, int y, POINT* pt);
  // 移動當前位置
}

在鼠標移動事件(MouseMove)處理方法中的主要代碼如下:

private: System::Void On_MouseMove(System::Object * sender, System::Windows::Forms::MouseEventArgs * e)
{
  ……
  Graphics *g = this->panel1->CreateGraphics();
  // 創建與panel1控件相關聯的Graphics
  IntPtr hdc = g->GetHdc();
  GDI32::HPEN hPen = GDI32::CreatePen( 0, 0, 0xA0A0A0 ); // 創建灰色畫筆
  GDI32::SelectObject( (GDI32::HDC)hdc, hPen ); // 選入畫筆
  GDI32::SetROP2( (GDI32::HDC)hdc, 7 ); // 7表示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混合編程實例

4.調用MFC DLL封裝的GDI

通過平台調用可以在托管C++中使用GDI API,但代碼有時比較繁瑣。事實上,還可以使用MFC DLL[6]來封裝GDI API,然後再通過平台調用,則顯得比較簡潔。例如,創建一個擴展MFC DLL應用程序MFCGDIDLL,在MFCGDIDLL.cpp文件的最後添加下列代碼:

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

然後將編譯後的mfcgdidll.dll復制到前面實例中的項目文件夾中,並添加下列平台調用的函數代碼:

namespace MFCGDI
{
  using namespace System;
  using namespace System::Runtime::InteropServices;
  typedef void* HDC;
  [DllImport("mfcgdidll", EntryPoint="DrawGDIXorSolidLine")]
  extern "C" void DrawGDIXorSolidLine( HDC hdc, unsigned long color,
  int nWidth, int x1, int y1, int x2, int y2);
}

最後修改前面實例中的鼠標移動事件(MouseMove)處理方法中的代碼:

private: System::Void On_MouseMove(System::Object * sender, System::Windows::Forms::MouseEventArgs * e)
{
  ……
  Graphics *g = this->panel1->CreateGraphics();
  // 創建與panel1控件相關聯的Graphics
  IntPtr hdc = g->GetHdc();
  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 );
  g->ReleaseHdc( hdc );
}

5.結語

Visual C++.NET中,雖然MFC和托管C++均可以使用.NET框架中的GDI+,但托管C++專為Visual C++程序員開發.NET框架應用程序而設計,它除了保留標准C++的全部功能,還可通過.NET Framework(.NET框架)來創建對象,實現自動化內存管理以及與其他.NET語言的互操作性。在托管C++中通過平台調用來實現GDI+和GDI的混合編程,不僅可以克服GDI+中的不足,更主要的是可以借助MFC DLL來拓展GDI+的圖形圖像的開發能力。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved