返回首页


{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,并CM​​etafileDC禁止设置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!

        m_anySelected--;

        WINASSERTD( m_anySelected >= 0 );

    }

}

有一种情况,你必须持谨慎态度。如果直流对象和所选的GDI对象位于堆栈中,我相信的析构函数调用顺序将变量已宣布的逆转:
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

}
restoreSelection()
为了避免这种类型的问题,可以在函数的末尾显式调用。
一个警告,这是不是一个好主意,如果你正在规划之间的绘画程序和打印代码共享代码使用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()调用,并从菜单中选择双缓冲选项。
    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);
{A14}
的结果是非常令人失望。在我的机器上,我得到了一个害羞的改善,从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

回答

评论会员:游客 时间:2011/12/06
您好,祝贺这个有趣的应用NBSP图形编程通常是一个艰巨的任务对于初学者,这种文章有很大的帮助,使其更清晰{BR!}好了,我在LT的编译错误;codegt;CGradient:InsertSortlt;/codegt。变"J"没有定义'"的说法。我认为这是典型的错误时已经取得了最后的变化,这是如此简单,它不值得重新编译......imgsrc=http://www.orcode.com/upimg/2011_12_06_01_31_22_1.gif再次祝贺卡洛斯!|lano1106
喜卡洛斯,

你是错误的右。这是奇怪的,我的编译器没有报告错误。什么是您正在使用的编译器? VC 2003.net不报告的问题,

只需移动循环j变量声明应该解决的问题。顺便说一句,我不是这个模块的作者,你应该将它的报告以及在:

{A21}

我什至不知道,我的代码是调用这个函数,但我会更新我的zip文件,因此,本文分布式文件错误。
的反馈!问候,


奥利维尔朗格卢瓦
{A22}
评论会员:达米尔Valiulin 时间:2011/12/06
尼斯的工作,但你的方法在我看来,就像一个在黑暗中拍摄。这看起来像一个分析程序会为您节省大量的时间和心怀不满的情况下

您可以建立您的演示,然后检查真正的瓶颈,然后决定是否是你可以改善,或者它只是API的限制。


评论会员:lano1106 时间:2011/12/06
你是正确的。这是我应该做的。也许是之前看到的结果,我相信,我会得到一个加速的问题是多少,我并没有理会概况第一。这是我的错。不过,我觉得这个实验没有一个很好的文章!

问候,

奥利维尔朗格卢瓦 {A23}
评论会员:lano1106 时间:2011/12/06
!杰克的恭维
我去看看你的网站,我有它的质量是非常深刻的印象,所以从你的恭维奉承。

我还去阅读前一段时间你写的文章,所以我作为灵感的路径,并在或多或少的3年我去看看你的时候我就发现自己的利基市场,像你有一个好的网站{ S1}

问候,

评论会员:Kochise 时间:2011/12/06
首先,不错的工作第二,请不要把它坏了,我不曳你,我不想成好像有凝固汽油弹战争的转折点,出于同样的原因这个线程(发布外部链接):

http://www.codeproject.com/string/TheStrings.asp

我只是指出一个非常令人印象深刻的的工作,从马克西姆Shemanarev:

http://www.antigrain.com/

反正支持多种平台,你做了另一块好的代码!

Kochise

我们相信在代码
评论会员:!Artchi 时间:2011/12/06
为什么不使用GDI的伟大?
评论会员:lano1106 时间:2011/12/06
什么的GDI增加了吗?
评论会员:吉姆Crafton 时间:2011/12/06
我怀疑,如果他的关注与表现,然后GDI将不利于有任。虽然GDI增加了一个很好的面向对象的C层,并增加了支持反锯齿绘图,我的印象是,它一直是基本的绘图比普通的旧的GDI慢。

"厄尔尼诺暗黑ESTA连接MIS pantalones! "沼泽泥潭!

实际Mentats使用只有100%纯,unfooled SAPHO果汁(TM)!
周围
SELECT * FROM用户WHERE线索> 0
返回0行{A24}
评论会员:汉斯迪特里希 时间:2011/12/06
GDI的性能始终是我的兴趣,所以我很欣赏这篇文章。

一些评论:
1。发布模式或调试模式中提到的百分比?两者之间的区别是什么?

2。目前尚不清楚多少时间被用来更新状态栏。您可能需要的时序输出到一个单独的日志文件,以确保这是不影响测量。

3。演示添加第三种选择,这也将是有趣的,"原始的Win32 GDI",看看这个比较其他两个。

4。这是习惯为CP的文章,包括在下载的zip发行模式EXE,让人们尝试您的演示,而无需重建。当我试图重建项目,我得到了无数的错误 - 无法找到文件,预处理指令等,你可能有你的系统设置编译这个项目,但你需要别人的系统上测试,确保将正确编译。

最美好的祝愿,
汉斯


评论会员:lano1106 时间:2011/12/06
您好汉斯,

感谢您的好话!

汉斯迪特里希说:1。发布模式或调试模式中提到的百分比?两者之间的区别是什么?

我提到的百分比是Release模式。 OLIGDI和MFC在调试模式下,内联函数内联扩展,所有的ASSERT宏扩展。它使绘画更慢。

汉斯迪特里希说:2。目前尚不清楚多少时间被用来更新状态栏。您可能需要的时序输出到一个单独的日志文件,以确保这是不影响测量。

我看到你的意思,但我不认为状态栏更新代码后执行的定时器已关闭,因为它的计算是影响测量。

汉斯迪特里希说:3。演示添加第三种选择,这也将是有趣的,"原始的Win32 GDI",看看这个比较其他两个。

我同意。这将是有趣。如果您尝试了,请一定要报告的结果。
我的预测是,它不会改变结果。
汉斯迪特里希说:4。这是习惯为CP的文章,包括在下载的zip发行模式EXE,让人们尝试您的演示,而无需重建。当我试图重建项目,我得到了无数的错误 - 无法找到文件,预处理指令等,你可能有你的系统设置编译这个项目,但你需要别人的系统上测试,确保将正确编译。

你是第一个报告,我的编译问题。在zip文件中包含的工作区是一个VC的6档。收到您的评论后,我试图解压缩包,并重新编译用VC NET2003和你说我的问题。我会解决这些问题,并在接下来的文章中更新包括一个EXE。在此期间,我发现了一些。如果immediatly选择释放模式,项目编译差不多instantenously。剩下的唯一问题是debug.h"iostream.h"在新的MS编译器不支持了。为了解决这个问题,只是改变"iostream.h""的iostream"

问候,
- 0:17星期五一月十三日,2006年
修改
评论会员:KarstenK 时间:2011/12/06
如果时机是记录我创建了一个大的日志的字符串,并写入在程序结束时,他的问题。 {五}

向您致以诚挚的问候德国