分类: C/C++
2008-04-23 21:54:08
C Q&A 专栏...
性能监视,托管扩展,和锁定工具栏
原著:Paul DiLascia
翻译:
原文出处:
原代码下载:
(248KB)
在 MSDN® Magazine 2004 六月 ,我描述了一个称为 ShowTime 的类,你可以用它来为你的应用程序执行一些简单的性能监视。ShowTime 用它的构造函数/析构函数来记录它存在的开始/结束时间,因此你可以在代码块中这样来实例化:
{你便会获得 TRACE 消息或日志文件,它会记录这样一条日志:“共耗时是:nnn 毫秒”,这个nnn 是从实例在栈上创建开始之时到析构函数调用之时的毫秒数。
ShowTime st(_T("Total time is:"));
// some lengthy operation
}
timeBeginPeriod(1);每次调用 timeBeginPeriod 后都必须以相同的粒度值配对调用 timeEndPeriod。在我的机器上,粒度的默认值为10毫秒(和 ::clock 一样),所以如果 你不想出什么差错,最好不要忘记调用timeBeginPeriod。timeXxx 函数是 Windows 多媒体支持的一部分,因此你必须 #include mmsystem.h 并链接 winmm.lib 库。这里有一个技巧,你可以用它来告诉连接器从源代码模块中包含某个特定的库——而不是向工程设置中添加库。它就是 使用 #pragma 声明,像下面这样:
// ... run performance testtimeEndPeriod(1);
// tell linker to use winmm.lib#pragma 声明有多种其有用的选项,你应该根据自己的情况选择使用;lib 选项填入一个库搜索记录到你的模块对象文件中。我写了一个新版本的 ShowTime,它使用 timeGetTime 和 pragma 来连接 winmm.lib。ShowTime 构造函数现在 有一个粒度参数,ShowTime 将该参数传递给 timeBeginPeriod (默认值=1)。(PerfTest 用 ShowTime 来测算分配 大批量字符串的时间。)除了这些简单的修订以外,ShowTime 与 2004 年六月刊中的一样棒,在此我就不赘述了。具体细节自己下载源代码看吧。
#pragma comment( lib, "winmm" )
#pragma push_macro("new")但我始终不明白为什么要这样做,因为我的其它模块不需要它们。此外,敲入 pragmas 相当不方便。有没有什么更好的方法使我不必总是要敲入 push_macro 和 pop_macro?
#undef new
// managed stuff here
#pragma pop_macro("new")
这个问题与 C 无关,它与 MFC 有关。C 的一个 比较晦涩难懂的特点是你可以重载 new 操作符,并且你甚至可以给它附加参数。通常,操作符 new 只接受拟分配对象的大小:
void* operator new(size_t nAlloc)但你也可以随心所欲附加参数来重载 new 操作符,只要在调用 new 时候提供这些参数即可。在各种应用程序向导(App Wizards)中,这 是 MFC 所做的事情。一个典型的 MFC 程序(.cpp)文件顶部都有下面这样的代码行,通常都由应用程序向导生成:
{
return malloc(nAlloc);
}
#ifdef _DEBUGMFC 将 new 重定义为 DEBUG_NEW。但 DEBUG_NEW 是什么? afx.h 道出了原委:
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// (simplified)在 debug 生成模式中,MFC 重载了操作符 new 以获取两个额外的参数,比如:
#ifdef _DEBUG
# define DEBUG_NEW new(THIS_FILE, __LINE__)
#else
# define DEBUG_NEW new
#endif
void* operator new(size_t nSize,重载的版本与普通的 new 同样都有表示对象大小的 size 参数,但还增加了两个参数:源文件名称和行数。因此,无论何时,只要你写:
LPCSTR lpszFileName, int nLine);
pfoo = new CFoo(..);预处理程序便会将它转变为:
pfoo = new (sizeof(CFoo), THIS_FILE, __LINE__) CFoo(...);__FILE__(用来初始化 THIS_FILE)和 __LINE__ 是专用的预处理符号,它保存当前被编译的模块文件名称和行数。 其主要用途是当你的应用程序泄漏时,MFC 能显示一个消息。如:
Shame on you! You didn''t free the CFoo object in foo.cpp, line 127!这对于调试来说,是个巨大的福音,但是当你使用托管C 时,它会导致混乱,因为公共语言运行时(CLR)用于托管对象的 new 操作符并不能理解 这些额外的参数(placement arguments)。但是 MFC 用 #define 重定义了 new,这导致预处理程序做了一个直接的词汇替换。当托管扩展看到了 额外的参数,它们便会歇斯底里。这就是为什么你必须用 #pragmas push_macro/pop_macro 来临时反定义 MFC 已经定义的东西, 然后再次重定义它以恢复到 MFC 中。
pfoo = mfcnew CFoo(...);如果你忘记使用 mfcnew,你便无法获得自动的内存泄漏报告。如果你认为这样是相当麻烦的(我就是这么觉得),那么你会很高兴知道这些问题将在 Visual C ® 2005中得到修正。这正相反:它有一个 gcnew 操作符,你必须用它来分配托管对象。这样就不可能再与普通操作符 new 相冲突了。 以我之见,这是一个更好的方案,因为强制程序员了解何时分配与本地堆对象相对的托管对象是有好处的。此外,它也使得将来在托管堆上分配本地对象成为可能,反之亦然,通过 自动创建相应的使用 GCHandle 或其它必需的代理类。
CManagedClass *pmc = new CManagedClass();
void* operator new(size_t size, void* p)这时你可以调用
{
return p;
}
new(p) CFoo ;如果你想要在 p 位置创建 CFoo 对象,而不是让 malloc 为你分配内存,就可以使用一个指针 p。
你是正确的,MFC 中没有任何可以让你锁定/解锁工具栏的代码,而且也没有
函数使你能在启用以后再禁用停靠功能。不过不要害怕,Windows 中总会有办法的!我写了一个小类,CLockBar,它可以让你锁定你的工具栏,还有一个测试程序,LBTest,
示范了这个类的使用。
CLockBar 的使用方法很容易。你只要在主框架类中实例化一个 CLockBar,一个 CLockBar 对应一个你想要锁定的工具栏,如下代码所示:
class CMainFrame : public CFrameWnd接着,你必须安装 CLockBar。最好是在主框架的 OnCreate 函数中:
{
…
protected:
CToolBar m_wndToolBar;
CLockBar m_lockToolbar; // toolbar lock
};
int CMainFrame::OnCreate(...)安装完这个锁之后,你便可以在任何时间用 TRUE 或 FALSE 调用 CLockBar::SetLocked 来锁定/解锁工具栏。LBTest有一个 View | Lock 工具栏命令,它转换锁定状态。Figure 3 展示了 LBTest 中主框架类的源文件,它是 LBTest 使用 CLockBar 的地方。
{
// create toolbar as normal
m_lockToolbar.Install(&m_wndToolBar);
return 0;
}
void CControlBar::OnLButtonDown(...)OnToolHitTest 是 MFC 用来确定是否显示以及什么时候显示工具提示的一个函数。对于工具栏来说,当鼠标在按钮上时,正好做正常处理。 如果鼠标不在某个按钮上,它便在用户可以拖拽工具栏的“死亡”地带,如工具栏的夹子上或边缘区域。因此你只要吃掉 OnToolHitTest 返回 -1 时的鼠标消息便可以防止拖拽。这正好是 CLockBar 所做的事情,如下面代码所示:
{
// only start dragging if clicked in "void" space
if (m_pDockBar != NULL && OnToolHitTest(pt, NULL) == -1) {
// start the drag
} else {
// ignore—pass to base class CWnd
CWnd::OnLButtonDown(...);
}
}
// in CLockBar::WindowProcCLockBar 派生于 CSubclassWnd,这是一个我在专栏里经常用来截获发送到其它窗口的窗口消息的类。当你安装这个锁,CLockBar 便会以老式 Windows 感应其自身窗口过程安装的方法那样子类化工具栏。然后 CLockBar 首先破解所有发送到工具栏的消息。CSubclassWnd 管理 着这个子类化机制, 并且 CLockBar::WindowProc 是 CSubclassWnd 改写过的,它处理指定的消息——在本例中就是 WM_LBUTTONDOWN 和 WM_LBUTTONDBLCLK。( 你是否知道 MFC 浮动工具栏使用的两个消息吗?双击工具栏可以在停靠/非停靠状态之间来回转换?)结果是当设置锁定时,CLockBar 便防止工具栏看到 导致 MFC 进入拖拽模式的鼠标消息。Figure 4 展示了所有 CLockBar 的代码,你应该注意到只有在工具栏已不再停靠状态时,SetLock(TRUE) 才调用 DockControlBar 来 停靠工具栏。祝编程快乐!
if ((msg==WM_LBUTTONDOWN ||
msg==WM_LBUTTONDBLCLK) && m_bLocked) {
CPoint pt(lp);
if (m_pBar->OnToolHitTest(pt, NULL) == -1)
return 0; // eat it
}