托管C++中GDI+和GDI殽杂编程技能
副标题#e#
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);
#p#副标题#e#
在托管代码中会见非托管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 );
#p#分页标题#e#
若将将要挪用的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) 封送布局
#p#分页标题#e#
在托管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);
}
在鼠标移动事件(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+的图形图像的开拓本领。