您的位置:首页 > 理论基础 > 数据结构算法

《Windows图形编程》读书笔记——第3章 GDI/DirectDraw内部数据结构

2013-01-22 18:06 218 查看
      本章将会提示GDI句柄的每个细节和这些GDI句柄背后重要的数据结构,你也许对GDI数据结构的细节不感兴趣,但是理解GDI/DirectDraw的内部设计会使你成为知识渊博的程序员。

 

      对于GDI来说,常见的对象包括设备上下文、逻辑画笔、逻辑画刷、逻辑调色板、设备相关位图。因此所有的设备上下文对象都是设备上下文类的实例,所有逻辑调色板是逻辑调色板类的实例。Win32API中的对象可以认为是使用没有数据成员的抽象基类实现的,对象的数据表示对用户程序是完全隐藏的。Win32API提供的信息隐藏大大改善了程序的可移植性。

      #指针与句柄

      在面向对象语言中创建对象,需要分配一块保存对象成员变量的内存。如果它的类有虚函数,就要再分配一个额外的指针,并将它赋值成指向该类所有虚函数实现例程表的指针。在C++这样的语言中,指向对象的指针很重要。它被传递给该类的所有非静态成员函数,这样才能访问该对象的数据成员,调用正确的虚函数。这样的指针被称为C++中的this指针。

      对于Win32API,尽管为每个对象分配了数据块,但微软不想向用户应用程序返回指针。因为指针给出了对象存储的确切位置,指针一般允许对对象的内部表示进行读写操作,而这些内部表示也许正是操作系统想隐藏的。指针还使越过进程地址空间共识对象变得困难。

      为了对程序员进一步隐藏信息,Win32对象创建例程一般会返回对象句柄,而不是返回指针。句柄被定义为唯一地标识对象的值,或者是对象的间接引用。

      对象指针和句柄之间的映射可由函数Encode和Decode实现可视化。

      #基于表格的映射

      对象指针和句柄之间的映射最普通的机制是基于表格的映射。   在Win32API中,内核对象是用进程表实现的。内核对象包括互斥量、信号量、事件、注册键、端口、文件、符号链接、对象目录、内存映射文件、线程、进程、Windows工作站、桌面和计时器等。为了容纳大量内核对象,每个进程都有自己的内核对象表。  

     

      #解码GDI对象句柄

      创建了GDI对象,就会得到该对象的句柄。句柄的类型有可能是HPEN、HBRUSH、HFONT或HDC中的一种,这依赖于你创建的GDI对象类型。但是最普通的GDI对象类型是HGDIOBJ,HGDIOBJ被定义成空指针。HPEN的实际编译类型定义随编译时间宏STRICT的不同而不同。在STRICT版本中,编译对于GDI对象句柄的不正确混合将给出警告,或者对于非GDI句柄,如HWND、HMENU等的不正确混合也给出警告,把HPEN传递到接受HGDIOBJ的函数是可以的,但是如果不进行类型黑鬼就把HGDIOBJ传递给接受HBRUSH的函数是不可以的。

      不同GDI句柄的这些清晰的定义模仿了GDI对象不同类的类层次结构,但没有用真正的类层次结构。GDI对象只支持一个对象一个句柄,因此不能复制句柄为同一个对象创建另外一个句柄。GDI对象的句柄一般对进程是私有的。即只有创建GDI对象的进程才能使用它,将句西侧传到另外一个进程是无效的。

      GDI对象一般有多个创建函数和一个接受HGDIOBJ的析构函数——DeleteObject。资源加载函数如LoadBitmap或LoadImage可以创建必要的GDI对象。

      接下来作者利用程序Handles来说明HGDIOBJ的内部原理,其中对于对象表的映射利用到一个非文档公开函数GdiQueryTable函数,但是很遗憾,在Win7下Note: GdiQueryTable (undocumented GDI32.DLL Win32 API) and PEB->GetSharedGdiHandleTable don't work on Win7.(http://www.chromium.org/developers/how-tos/leak-gdi-object-in-windows),无法使用GdiQueryTable函数。(但很奇怪,在Win8上恢复了此函数的使用。无语。

      通过作者书上的例子(这个例子也需要修改才能在win7上正确运行),无论GetStockObject调用的顺序是如何,它返回的句柄看起来总是常数。可能的解释是系统初始化堆对象就被创建并被所有进程重新使用。另外我们注意到GDI句柄虽然在windef.h文件中被定义成指针,实际上不可能是指针。书是讨论了进程GDI和系统GDI最多创建的个数,我觉得现阶段不需要关注。

      HGDIOBJ的低4位16进制数内容为索引值,第3、4位16进制数内容是GDI对象类型的编码和堆对象标记。32位HGDIOBJ句柄值基本结构如下:

      8位未知+1位堆栈对象标记+7位对象类型+4位未使用+12位索引。

      书中列举了常规情况下GDI句柄类型的对应值(3,4位16进制数):

      gdi_objtypeb_dc = 0x01;

      gdi_objtypeb_region = 0x04;

      gdi_objtypeb_bitmap = 0x05;

      gdi_objtypeb_palette = 0x08;

      gdi_objtypeb_font = 0x0a;

      gdi_objtypeb_brush = 0x10;

      gdi_objtypeb_enhmetafile = 0x21;

      gdi_objtypeb_pen = 0x30;

      gdi_objtypeb_extpen = 0x50;

 

      #定位GDI句柄表

      这部分是比较有Hack意思的部分,分析起来可以学习到很多东西,如果对内存处理不熟练,快点补充学习《windows核心编程》内存相关章节吧。

      但是由于没法正确调用GdiQueryTable返回句柄表的地址,我无法验证我搜索到的那个地址是否真的是实际上的句柄表地址,可以使用其他版本的windows开发环境尝试一下。而再由于没法确定这个地址,对于这个地址上面作者猜测的数据结构我目前也无法验证。列举作者猜测的每个表项结构:

typedef struct

{

 void          *  pKernel;

 unsigned short  _nProcess; // NT/200 switch order for _nProcess, _nCount

 unsigned short  _nCount;

 unsigned short   nUpper;

 unsigned short   nType;

 void          *  pUser;

} GDITableCell;

 

      #GDI对象的用户模式数据结构

      用户模式画刷数据:纯色画刷优化;

      用户模式区域数据:正方形区域优化;

      用户模式字体数据:宽度表;

      用户模式设备上下文数据:存储设置信息;

 

      #存取内核模式地址空间

      内核模式驱动程序是特殊的DLL,它遵守一套规则。如不能在内核模式驱动程序中调用Win32API,因为Win32API的入口点在用户模式地址空间中。内核模式驱动程序被加载到内核地址空间。

      WindowsNT/2000内核I/O设备驱动程序一般只有入口点DriverEntry,当驱动程序加载后,DriverEntry就被调用。

      NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, IN PUNICODE_STRING RegistryPath)

      DriverEntry例程和_DllMainStartCRTStartup扮演同样的角色——用户模式DLL入口点,但是跟用户模式DLL不一样,内核模式驱动程序一般不导出函数。。。

      要深刻理解内核模式地址空间,需要把本章的Periscope例子编译执行并理解。然而这需要不少的前置知识,包括部分汇编,Win32API,StartService,打开设备文件,完成端口等。很幸运,我都在前面的读书笔记中学习过,虽然有的不是很深刻,但现在第二次看到,加强了印象,再回去复习一下,巩固知识。我目前已经在win7下调试还有点问题。

      总的来说,这一章很枯燥,但很基础,对后面的深入理解有重要的启示作用。我打算多花点时间在这枯燥的一章,以弄明白一些来龙去脉。

     

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: