{S0}目录{A}{A7}{A8}
我开发这个项目的想法后,我开始研究MFC的GDI相关的类。我看到了很多代码,我从来没有使用过的功能。从那里,我想执行一个实验,在那里我可以确认如果使用的GDI类的精简版,它会转化为更好的绘画表现。正如你将看到的结论,我没有得到的结果,我希望,我犹豫了一下,写我写的代码一篇文章。我得出的结论是,如果一个老乡程序员是一种面向对象的GDI封装在一个非MFC项目,此代码可能是有用的的他。在这篇文章中,我将首先描述我不喜欢MFC代码中,突出"新类的设置要点",目前的演示程序,最后呈现的实验结果。{A9}
CDC:m_hAttribDC这些功能之一。 m_hAttribDC == m_hDC和m_hAttribDC存在的大部分时间只需要添加多余的开销各地疾病预防控制中心代码。下面是一个例子:CPoint CDC::MoveTo(int x, int y)
{
ASSERT(m_hDC != NULL);
CPoint point;
if (m_hDC != m_hAttribDC)
VERIFY(::MoveToEx(m_hDC, x, y, &point));
if (m_hAttribDC != NULL)
VERIFY(::MoveToEx(m_hAttribDC, x, y, &point));
return point;
}
此外,相关m_hAttribDC类CMetafileDC是有缺陷的。它迫使它的用户显式地设置m_hAttribDC,并CMetafileDC禁止设置m_hAttribDC到m_hDC,但是这是错误的!它应该工作,因为第一CreateEnhanced()的参数是:pDCRef - 标识为增强型图元文件的参考设备。
是NULL时,参考设备将被显示。为什么MFC忽略pDCRef,并设置m_hAttribDC为NULL的原因可能是因为CMetaFileDC还支持旧的Windows 1.0元文件格式,这些图元文件有没有参考设备的概念。在CMetaFileDC覆盖从疾病预防控制中心的职能大多是虚拟的,并在几乎所有情况下,就好像只有执行的有缺陷的规则,一个图元文件属性DC应该不等于DC输出。因此,虚拟函数调用的开销是适用于所有的疾病预防控制中心没有对象。
最后,在MFC中GDI类的开销最后一个来源是处理地图。窗口对象句柄映射是必不可少的,但我仍然要看到与GDI对象句柄映射是至关重要的情况。调用句柄映射附加功能(),分离(),和FromHandle()被称为。你可以认为,如果你不调用这些函数,那么你不使用它们,对吗?那么,这是错误的。每一个对象是一个CDC对象选定的时间,选择对象()返回该对象的指针功能来自CGdiObject::FromHandle()。为了获得这种开销源的幅度的想法,考虑为伪代码CGdiObject::FromHandle():尝试找到永久的对象映射请求处理。如果没有找到,试图找出在临时对象映射请求处理。如果没有找到,创建一个临时对象。
所有这些临时对象将被删除了MFC框架下一次进入闲置的功能。{A10}
OLIGDI包括以下类:ODrawObjOBitmap打开OBrushOFontORgn伊斯兰会议组织ODisplayInfoODCOClientDCOWindowDCOPaintDCOMemDCOFlickerFreeDCOMetaFileDC
OLIGDI并不算得上是一个完整的解决方案,只有很小比例的GDI函数的数百已经实施。这本来只是尝试我的实验要验证的概念落实为每个函数的包装过于繁琐。然而,框架布局,并添加功能并在必要时,它应该很容易。
这个新的类集的主要设计要求,是保持相同的API兼容的目的,为MFC。按照这一要求,很容易修改OLIGDI对象使用MFC对象的代码。所有这一切都需要的是改变对象类型变量的声明语句,并重新编译。第二个要求是从MFC中删除所有不必要的功能。这包括:m_hAttribDC虚函数处理地图
此外,OLIGDI介绍两个新的特点,借鉴作者简介Paul DiLascia写的书{A11}。第一个改进是,使用MFC,如果你想重用对象来存储不同的GDI句柄,你必须首先明确调用DeleteObject()。这是很容易出错,如果你忘了,使这个调用,它会创建一个GDI资源泄漏。 OLIGDI,这是每创造功能明确:{C}
下一个借来的特点是DC对象要记住默认的对象,当他们更换一个选择对象()调用,并重新选择到DC在对象销毁他们的能力。此功能消除了跟踪从用户的默认对象的负担。下面是此功能的相关代码:class OIC
{
public:
/*
* Each type of drawing object has an ID, used as offset to store
* handle in a table.
*/
enum WHICHOBJ { SELPEN=0, SELFONT, SELBRUSH, SELBITMAP,
NDRAWOBJ };
protected:
HDC m_hDC; // Windows handle to DC
BOOL m_del;
HANDLE m_origObj[NDRAWOBJ]; // original drawing objects
int m_anySelected; // whether any new objects are selected
// Other stuff omitted
};
/*
* OIC::select function
*
* Protected method to select a display object
* Destroys old selected object if required.
* "which" specifies whether object is a pen, brush, etc.
* "del" specifies whether to delete this object.
*/
HGDIOBJ OIC::select(WHICHOBJ which, HGDIOBJ h)
{
HGDIOBJ old;
WINASSERTD(h);
old = ::SelectObject(m_hDC, h);
WINASSERTD(old && old != HGDI_ERROR);
if( m_origObj[which] == NULL )
{
m_origObj[which] = old;
m_anySelected++;
WINASSERTD( m_anySelected <= NDRAWOBJ );
}
else if( m_origObj[which] == h )
{
m_origObj[which] = NULL;
m_anySelected--;
WINASSERTD( m_anySelected >= 0 );
}
return old;
}
OIC:: OIC()
{
if (m_hDC)
{
restoreSelection();
if( m_del )
{
LASTERRORDISPLAYD(::DeleteDC(m_hDC));
}
}
}
/*
* OIC::restoreSelection function
*
* Restore selected display objects (pens, brushes, etc.).
*/
void OIC::restoreSelection(void)
{
for (int i = 0; m_anySelected && i < NDRAWOBJ; i++)
{
restoreSelection((WHICHOBJ)i);
}
}
inline void OIC::restoreSelection(WHICHOBJ which)
{
if( m_origObj[which] )
{
WINASSERTD(m_hDC != NULL);
::SelectObject(m_hDC, m_origObj[which]);
m_origObj[which] = NULL; // don't restore twice!
span>
m_anySelected--;
WINASSERTD( m_anySelected >= 0 );
}
}
有一种情况,你必须持谨慎态度。如果直流对象和所选的GDI对象位于堆栈中,我相信的析构函数调用顺序将变量已宣布的逆转:
restoreSelection()void foo(void)
{
OPen p(PS_SOLID,1,RGB(255,0,0));
OClientDC dc(hwnd);
dc.SelectObject(&p);
// Ok, OClientDC destructor will be called first
}
void foo2(void)
{
OClientDC dc(hwnd);
OPen p(PS_SOLID,1,RGB(255,0,0));
dc.SelectObject(&p);
// Boom, the pen will be destructed before being unselected
span>
}
为了避免这种类型的问题,可以在函数的末尾显式调用。
一个警告,这是不是一个好主意,如果你正在规划之间的绘画程序和打印代码共享代码使用OLIGDI。 OLIGDI之上,除非你写自己的打印预览代码,MFC提供了一个特殊的类称为CPreviewDC,大大改变了打印预览CDC行为,如果你想在这一领域中使用MFC,你将无法重用的代码写OLIGDI。话虽这么说,它可能仍然是,如果你有绘画的表现问题,建议使用OLIGDI,如果你认为窗口将画更将打印文档的时间往往比。
从本质上讲,演示程序需要做的是使用OLIGDI或MFC,和时间的运作绘制了一堆东西,并显示两个paint方法之间的差异。我的出发点的演示程序是由Charles Petzold写他的书{A13}可爱的三叶草方案。他的三叶草计划绘制的线条和复杂的裁剪区域的三叶草。从演示程序"菜单中,您可以选择三种显示方式:OLIGDI,MFC和备用。前两个可以使用,使用户可以尝试观察主观调整窗口大小的两个绘画方法之间的差异。第三个选项,备用定时器选项,定期强制窗口重绘的帮助下,允许演示程序来计算两者之间的绘画模式的差异。与这个小助手类的帮助下进行的时机是:class cHighResolutionTimer
{
public:
cHighResolutionTimer();
void start();
double stop();
private:
LARGE_INTEGER frequency, startTime;
};
cHighResolutionTimer::cHighResolutionTimer()
{
startTime.QuadPart = 0;
LASTERRORDISPLAYD(QueryPerformanceFrequency(&frequency));
}
void cHighResolutionTimer::start()
{
LASTERRORDISPLAYD(QueryPerformanceCounter(&startTime));
}
double cHighResolutionTimer::stop()
{
LARGE_INTEGER stopTime;
LASTERRORDISPLAYD(QueryPerformanceCounter(&stopTime));
return
(double)(stopTime.QuadPart - startTime.QuadPart)/frequency.QuadPart;
}
编程演示程序中最具挑战性的的部分已输出的定时测量有意义的数字。东西,我注意到在发展过程中出现的是,测量在相同的绘图方法多次时机大的变化的结果。这可能是由于多种因素,如软件不一致(任务切换)和硬件不一致(GDI设备驱动程序不必等待在显卡的刷新周期为一个特定的时刻来执行写入)。由于时序变化的速度差异的顺序相同,我有很大的困难,以突出这种差异。与许多不同的方法尝试后,我设计了以下方案:采取为每个方法的NUMSAMP测量。排序的测量。废NUMSAMP / 3的最低和NUMSAMP / 3的最高测量。返回剩余的测量的平均值。#define NUMSAMP 12
class CTimingStat
{
public:
CTimingStat()
{ reset(); }
void reset(void) { m_nSamples = 0; }
void set(double s) { m_samplArr[m_nSamples++] = s; }
const UINT getnSamples(void) const { return m_nSamples; }
double getAverage(void);
private:
double m_samplArr[NUMSAMP];
UINT m_nSamples;
static int __cdecl compare(const void *elem1,
const void *elem2);
};
double CTimingStat::getAverage(void)
{
int a;
double xa = 0.0;
qsort(m_samplArr,NUMSAMP,sizeof(double),
CTimingStat::compare);
for( a = NUMSAMP/3; a < (2*NUMSAMP/3); a++ )
{
xa += m_samplArr[a];
}
xa /= NUMSAMP/3.0;
return xa;
}
int CTimingStat::compare(const void *elem1,
const void *elem2)
{
return (int)(*(double *)elem1 - *(double *)elem2);
}
要完成演示程序的描述,有一个有趣的错误,溜走了我的注意。当使用双缓冲以消除闪烁的内存DC,绘画是几乎所有的除外时,只有一小部分窗口需要重新绘制的时间。你可以改变窗口的大小和重绘被表现得完美无缺,但如果打开关于对话框,拖它周围的客户区,重新粉刷了所有搞砸了。问题来自裁剪区域的计算方法为整个客户区和内存DC窗口原点设置为无效矩形的左上角。调整窗口的大小时,整个客户区是无效的,一切适合,但只有一小部分客户区无效时,内存DC的窗口原点(0,0)和裁剪区域需要被转移到考虑这种差异。要看到自己的问题,只是注释掉OffsetClipRgn()调用,并从菜单中选择双缓冲选项。
{A14} dc.SelectClipRgn((HRGN)RgnClip.GetSafeHandle());
/*
* Since Clip region is in device point, it is important to offset
* it because the double buffering DC window origin is set at the top
* corner of the invalidated rect.
*/
dc.OffsetClipRgn(p.x,p.y);
的结果是非常令人失望。在我的机器上,我得到了一个害羞的改善,从1%到3%不等。看来,结果在很大程度上取决于硬件上运行演示程序,正如我在不同的机器上测试它与除少数例外,在那里我亲眼目睹了10%-15%的改善,改善一般在5%以下。如果没有测量,所不同的是没有视觉感知。从这个实验可以得出的结论是,尽管MFC的开销,它里面的GDI函数本身所花费的时间相比是微不足道的。
这就是它!我希望您喜欢这篇文章,如果你没有发现它很有用,请花几秒钟的排名。你可以这样做的权利在文章底部的。此外,如果你得到惊人的成绩,与你的机器上的演示程序,或如果您发现此代码的应用程序,我想从你喜欢听!{A15}作者简介Paul DiLascia {A16}艾迪生韦斯利,1992年查尔斯Petzold,{A17}微软出版社,1999杰夫Prosise,{A18}微软出版社,1999乔治牧羊犬,苏格兰温格,{A19}艾迪生韦斯利,1996年{A20}2007年6月19日下载更新:问题已修正。2006年1月9日原来的文章。| lano1106