当前位置: 首页 > news >正文

兰州网站建设哪家专业南京科技网站设计有特点

兰州网站建设哪家专业,南京科技网站设计有特点,青岛大学网站建设,建筑材料网站建设第三部分调试技术 第8章基本调试技术 本章回答了很多Windows下调试程序的常见问题#xff0c;主要是基本的调试技术。所以它看起来可能有点像Windows调试常见问题解答#xff0c;其中很多是我在调试新闻组里见到的问题。 8.1普通调试技术 我应该采取什么步骤使得我调试代码…第三部分调试技术 第8章基本调试技术 本章回答了很多Windows下调试程序的常见问题主要是基本的调试技术。所以它看起来可能有点像Windows调试常见问题解答其中很多是我在调试新闻组里见到的问题。 8.1普通调试技术 我应该采取什么步骤使得我调试代码的能力最大 请釆取以下步骤 •重定位你的程序的可执行代码以防止虚拟地址空间冲突。关于虚拟地址空间冲突和重定位的讨论见第6章“在Windows中调试”。 •为程序的可执行代码创建MAP文件并存档。MAP文件的创建和使用见第6章。 •在程序的可执行文件中创建调试符(即使是在发行版中)并存档PDB文件。请注意不要使用链接选项Seperate types。PDB文件的创逮和使用见第7章“使用Visual C调试器调试”。 •将程序的工程、源代码和可执行文件存档。 •为用户创建一个提交错误报告的机制包括一个错误报告表单和指示。错误指示应该推荐用户安装系统调试符号将屏冪上的内容全部拷贝下来并提交Dr. Watson关于此错误的日志文件。错误报告表单和指令见第1章“调试的过程”。 我希望在发行版本中包含一些调试代码但不希望它影响程序的运行性能应当怎么做 你可以使用 API函数IsDebuggerPresent将发行版本中的调试代码关闭(注意Windows 95不支持这个函数)。例如下面这段代码仅当程序在调试器中运行才会影响性能 i[(IsDebuggerPresent()) PerformTimeConsumingDebugCheck(); 用户一般都不会启动调试器因此一个更好的方法是通过INI文件或注册表设置将发行版本中的调试代码关闭。代码如下所示 BOOL ReleaseDebug GetPrivateProfileInt(_T(MyApp), _T(Debug), FALSE, _T(MyApp.ini)); ... if(ReleaseDebug) PerformTimeConsumingDebugCheck (); 我无法对某个问题进行调试因为在调试器中有太多的数据无法将问题隔离出来。应该怎么办 你可以使用以下几种方法之一 •创建调试数据。创建特殊的调试数据消除所有与问题无关的冗余数据。 •从程序中临时过滤数据。如果创建调试数据不太现实你可以在程序中过滤掉所有无关的数据从而隔离问题。例如如果你的程序显示一大堆数据但只有当数据的X坐标为42时才会发生问题你可以采取如下措施将问题隔离出来 #ifdef DEBUG_FILTER_DATA //filter data only when DEBUG_FILTER_DATA is defined if(data[i] ! 42) continue; #endif •使用一个全局变量动态地激活调试代码。典型情况下调试代码或者全部工作或者全部不工作。因为它的状态是在编译时刻由预处理器决定的。但有的时候我们希望当程序的状态设置成“再现错误”时能动态地激活调试代码。例如你可以定义一个全局变量Debug通过代码INI文件、注册表设置或者调试器(可以使用Watch窗口修改值)控制它的取值。 #ifdef _DEBUG BOOL Debug FALSE; // define global debug variable #endif ... #ifdef _DEBUG // filter data using aglobal variable, change Debug value from Watch window if(Debug data[i].x ! 42) continue; #endif •使用键盘动态地激活调试代码。有的时候我们希望用键盘动态地控制调试代码例如某几个键的组合。例如你可以借助 API函数GetAsyncKeyState检查键盘的状态使程序仅当Control键按下的时候激活调试代码。 #ifdef _DEBUG // filter data only when the Contrl key is pressed if(GetAsyKeyState(VK_CONTRL) 0) data[i].x ! 42) continue; #endif 我觉得C预处理器产生的代码不是我所期望的如何进行调试 C预处理器是一个伟大的东西但是它的行为无论是从源代码还是从调试器的角度都完全不可见。例如假设你多次用到了重载的operator new函数预处理器会重定义new以帮助你动态地调试内存分配。但是如果你的程序不使用你定义的operator new版本甚至不对它进行编译这时候就很难使用你的源代码进行调试因为我们很难知道预处理器到底做了些什么。 你可以使用以下几种技术检查预处理器的输出在Project Setting对话框里选择整个工程然后单击C/C标签。在Project Options输入框中在设置的最后添加编译选项/P。现在重新build你觉得有问题的文件例如bogus.cpp你会发现bogus.i出现在工程文件夹里而不是在Debug或Relese文件夹里。然后你就可以用任何种文本编辑器察看这个文件。预处理器文件可能会很大因为文件的开始部分都是预处理器产生的垃圾所以你可以直接跳到文件的最后这才是你的程序代码所在的地方。然后你就可以搜索这个文件看有关的预处理器符号到底是怎么定义的。 看完之后记得把/P编译选项从工程设置中去掉。设罝了这个选项编译器会把预处理器输出的结果放在目标文件列表里所以必须把这个选项去掉才能编连你的工程。 我优化了程序的速度但似乎存在优化的错误而且无法绕过去~我该怎么办 你当然不希望将未经优化的代码犮布出去因为它太不“优”了。你可以尝试优化程序的代码大小这种方法安全得多而且同样有效因为小的代码一般也是快的代码。如果问题不存在了而且性能可以接受你就成功了。否则优化其中某些文件的性能。或者如果你知道优化时哪个函数出的问题可以按照你希望的方式对程序进行优化然后用#pragm optimize对这个出问题的函数进行细致的优化。如果你想了解更多关于优化的内容请阅读Matt Pietrek的著作“Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools”。 请记住一点虽然Visual C的优化工具可能存在问题但更有可能是在你的代码中存在问题然后被优化工具暴露出来了。但是如果你不能在代码中找出与优化有关的错误的根源使用上述技巧是最合适的。 忽然之间所有的东西都乱了套我该怎么办 重启Windows把工程的Debug和Re!ease文件夹都删掉从头编连程序。如果这样还不能解决问题请看第12章“非常规策略”。 8.2 Visual C调试器技术 怎么调试死循环 使用Debug菜单下的Break命令。Windows 2000下如果程序有输入请求可以使用F12键中断程序然后检査窗口的调用栈或者单步跟踪代码找到死循环的发生原因。 如何确定一个消息框是哪里产生的 如果消息框是在一个单线程程序中出现的使用Debug菜单下的Break命令或者在Windows 2000下使用F12键中断程序然后检查窗口的调用栈找出消息框产生的原因。 我的程序使用F12键完成一个重要的功能但是每次我按下这个键调试器就会中新程序怎么办 显然这是Windows 2000使用F12键进行调试中断的一个不利的影响。一种解决的方法是当定义了_DEBUG的时候在程序中将这个功能重新指定到其他键如AltF12、ShiftF12等。如果是Windows 2000(但不能是Win NT4.0)你还可以在注册表中修改调试中断键盘控制。例如要将调试中断改成使用ScrollLock键可以将HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug中UserDebuggerHotKey的值设置成145(ScrollLock键的虚拟键代码)。必须重启Windows修改才能生效。 程序的某个部分看起来速度很慢怎么才能确定这个问题 你可以指令你的程序进行profiling(如果确实需要的话不妨如此)但很多时候你只需要在调试器中运行程序就可以知道性能不好的原因所在。在性能不好的时候用几次Break命令并检查调用堆栈窗口就可以找到原因。如果性能问题只限制在小段代码内随机中断时碰到这段代码的概率就会很大。 你可以借助调试器进简单的profiling。 如果你希望进一步知道执行某一段特定的代码需要多长时间可以在Watch窗口中输入“CLK”这是一个调试伪寄存器用来显示时间(以微秒为单位)。或者你也可以在Watch窗口中输入“CLK/1000得到以毫秒为单位的时间这样可能看起来更方便些。还可以加上观察窗口的格式符号“,d”以保证输出是十进制格式的。如果想看到调试器中每一步消耗的时间而不是累加时间在观察窗口中紧跟着“CLKd”输入“CLKd”(注意是在之后不是在之前)如图8.1所示。 图8.1 在观察窗口中使用CLK伪寄存器得到代码的profiling 单步跟踪调试代码时不小心越过了想要调试的代码怎么办 在调试的时候不小心越过了感兴趣的代码是经常发生的事。这时你一般不需要从头开始执仃程序即使你关心的代码在整个程序运行过程中只执行一次。Visual C调试器的确没有类似Step Back的命令来撤销Step Into和Step Over命令但是它提供了Set Next Statement命令允许用户在源代码窗口中选择一条语句设定成下一条执行的语句。也可以在disassembly窗口中选择指令用Set Next Statement设置为下一条“语句”。我们以下面这段代码为例说明Set Next Statement命令的作用 int ReallyBadNews() { int value 0; POINT *pPoint 0; pPoint-x 4; // will crash here with an access violation pPoint-y 5; // will crash here with an access violation value pPoint-x * pPoint-y; return value; } int BadNew() { int value; { int value2 40, value3; value3 ReallyBadNew(); value value2 / value3; // will crash here with integer divide by zero } return value; } 如果你在单步跟踪BadNews函数时使用的是Step Over命令就会发现在运行ReallyBadNews函数时发生一个未处理的非法访问异常导致程序崩溃。显然产生这个错误是因为pPoint没有初始化指向一个合法的POINT结构。在ReallyBadNews中你可以使用Set Next Statement命令指定继续执行函数中的其他任意一个语句即使此时程序已经因为未处理的异常而崩溃了。一旦你回到ReallyBadNews你也可以使用Set Next Statement命令继续执行该函数的任意语句。在这两个函数中你都可以重新执行任何代码而不会导致崩溃只要代码本身没有严重的副作用导致程序不能运行。唯一不可以做的是使用Set Next Statement命令将控制点从一个函数移到另一个函数否则就会出现如图8.2的消息框。必须在消息框中选择“Cancel”因为继续执行会破坏堆栈。 图8.2 当试图使用Set Next Statement命令将程序执行的控制点从一个函数移到另一个函数时出现的消息框 你可能对此感觉奇怪为什么在发生了未处理的异常之后还能够继续运行程序这是因为在调试器中运行程序时Visual C自己处理了异常所以只要使用了Set Next Statement命令避开有问题的代码就可以继续执行。你可以在ReallyBadNews函数中设置为“return;下一条语句]”。典型地这意味着你可以在源代码发生未处理的异常后继续运行程序。但是如果在一个Windows API函数或库函数中发生了未处理的异常你就不太可能在同一函数中继续执行所以程序就很可能不得不终止了。 你可能会觉得奇怪为什么可以自由地跳入/跳出一个块(BadNews函数中花括号中间的部分)。要理解其中的原因就必须知道Set Next Statement命令到底做了些什么。很简单它做的事就像它的名字所说的通过修改指令指针寄存器(EIP)的值设置下一条将被执行的指令。它不修改堆栈不执行任何代码不创建或释放任何变量也不做其他任何事情。要注意的是局部的自动变量编译器在函数入口点就为该函数中定义的所有自动变量分配了空间而不是在相应的块的入口点才分配。所以虽然变量value2和value3看起来都只存在于块内部但分配给它们的堆栈空间在整个函数的生存期内都有效。块的入门点的唯一动作是自动变量的初始化。在本例中value2被初始化成40。 如果涉及到对象问题就更复杂了。如果变量value2和value3是对象进入块的时候会调用它们的构造函数离开的时候调用其析构函数。检查Disassembly窗口中的代码可以明显地看到这一点。显然构造函数和析构函数的不匹配不是一件好事情所以当使用Set Next Statement命令涉及到对象的创建和解构时要格外小心。 —旦你熟悉了Set Next Statement命令你会发现它真的很有用它可以使你的调试过程效率更高。如果你不经常使用Set Next Statement说明你用得还不太对。不过要时刻记住当你使用Set Next Statement的时候你实际上执行的是不同于实际程序的另一个程序这样的程序执行最好只看成是一次实验。这意味着即使程序崩溃、泄露资源或者发生了其他错误你也不必人担心因为真实的程序执行起来会是另外一个效果。另外调试版本基本上是对程序代码的直接翻译而发行版本一般不是。因此在发行版本下在Disassembly窗口中使用Set Next Statement命令通常会取得较好的结果。 使用Set Next Statement命令使你的调试过程效率更高。 我需要调试或测试某一段错误处理代码但是不能使错误发生怎么办 你可以使用Set Next Statement命令有时或者还需要在观察窗口中调整一些有关的变量的值来创造特定的错误条件从而调试你的错误处理代码。例如考虑如下的代码 BOOL CMyApp::InitInstance() { try { m_pMainWnd new CMyMainWnd; m_pMainWnd-ShowWindow(m_nCmdShow); m_pMainWnd-??? return TRUE; } catch(CMemoryException *e) { e-ReportError(); e-Delete(); return FALSE; } } 一般宋说operator new几乎不可能因为内存不足而失败这使得我们的错误处理代码很难测试或调试。虽然这个函数中处理内存不足错误的代码相当简单你可能需要在这个函数之外的地方调试这个错误的处理机制看程序是否能正确地处理FALSE的返回值。值得注意的是你不能仅仅靠使用Set Next Statement命令设置异常处理代码中的语句为下一条指令来测试这段异常处理代码(即使你跳过ReportError和Delete函数)因为不允许用户像跳入一个块那样跳入一个异常处理代码段。调试异常处理代码的最简单的方法是Step Into跟踪进入operator new的函数体本例中是MFC的版本AFXMEM.CPP void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine) { #ifdef _AFX_NO_DEBUG_CRT UNUSED_ALWAYS(nType); UNUSED_ALWAYS(lpszFileName); UNUSED_ALWAYS(nLine); return ::operator new(nSize); #else void* pResult; #ifdef _AFXDLL _PNH pfnNewHandler _pfnUninitialized; #endif for (;;) { pResult _malloc_dbg(nSize, nType, lpszFileName, nLine); if (pResult ! NULL) return pResult; #ifdef _AFXDLL if (pfnNewHandler _pfnUninitialized) { AFX_MODULE_THREAD_STATE* pState AfxGetModuleThreadState(); pfnNewHandler pState-m_pfnNewHandler; } if (pfnNewHandler NULL || (*pfnNewHandler)(nSize) 0) break; #else if (_afxNewHandler NULL || (*_afxNewHandler)(nSize) 0) break; #endif } return pResult; #endif } 现在你可以产生一个内存不足的异常了——一种方法是在_malloc_dbg之后设置pResult为0这样做会泄露内存但是在这个测试中不会引起大的问题或者你可以使用Set Next Statement命令跳过_malloc_dbg及其后的两个语句。 使用Set Next Statement命令并在观察窗口中改变相关变量的值调试不太可能执行到的代码。 这里介绍的原理适用于一般情况通过Set Next Statement命令和在观察窗口中改变变量的值这两种手段的结合你可以轻松地测试/调试一般情况下很难执行到的代码。 我怎么才能检査一个函数的返回值如果我没有将返回值赋给一个变量 你可以直接在调试器中检査函数的返回值所以你不必为观察返回值而专门在代码中加个临时变量并重新编译。如果返回值不大于32位它会被直接放在EAX寄存器中你可以在寄存器窗口中察看或者在观察窗口中键入“EAX”„如果返回值长为64位其低32位会放在EAX寄存器中高32位放在EDX寄存器中。如果返回值长度大于64位会在EAX寄存器中放入指向返回值的指针可以通过在观察窗口中进行类型转换(例如若某函数返回一个CRect则可以键入“(CRect*)EAX”显示结果)或在内存窗口Address栏中直接键入“EAX”查看返回值。 类似地如果一个Windows API函数调用失败你可以在调试器的观察窗口中直接键入ERR检查GetLastErro的值。这是一个调试器伪寄存器用来存放最近—次错误的代码。你可以使用格式符号“,hr”翻译这个错误代例如“ERRhr”。 程序产生了—个“The thread 0xFF0BC5CF has exited with code -1C0xFFFFFFFF”(线程0xFF0BC5CF退出代码为-1(0xFFFFFFFF))消息应该如何调试 线程退出代码为-1不是一个错误所以不必为之担心。Windows自身会创建退出代码为-1的线程例如当你的程中显示某一个Windows通用对话框时。 我的程序崩溃了而且没有跟踪怎么办 激活即时(JIT)调试能够帮助你在调试器中检查这个问题。要激活JIT调试在Tools菜单中选择Options命今在Options对话框中选择Debug标签选择Just-in-Time Debugging选项。 我想看到堆栈中的所有数琚而不仅仅是调用堆栈窗口中显示的那些函数调用情况怎么办 你可以使用存储器窗口査看堆栈的内容。显示存储器窗口然后或者在Address栏中键入“ESP”显示堆栈指针或者键入“EBP”显示堆栈的基址指针。然后在存储器窗口的上下文菜单中选择Long Hex Format显示选项这个选项可以使得堆栈的内容更易阅读。 8.3Windows调试技术 如何调试画图的代码 正如第1章中所说窗口的画图代码是调试过程中海森堡不确定原理的一个最好的例子——正是调试器的存在干扰了你所要调试的东西。特别地如果你正在画的窗口被正在激活的调试器覆盖这个画图窗口就会被使无效也就是说调试器的激活影响了窗口的画图方式——如果它可以被画的话。 这个问题的解决方法是阻止窗口的重叠。你可以适当地对窗口进行布局使程序和调试器窗口在屏幕上相邻显示。使用你的系统所能支持的最高的屏幕设置也许能对你有所帮助。如果没有足够的空间来避免所有的重叠你至少应该使正在被调试的窗口不被调试器覆盖。可能你还不得不调整一下Visual C的工具栏以使得调试能正常进行。如果你的程序只能在最大化的窗口下运行(游戏和屏幕保护程序就是两个很好的例子)你必须在调试版本中将程序设定成允许以Restored窗口(即不是最大化的)方式运行。类似地如果正在被调试的窗口具有WS_EX_TOPMOST属性这个属性在调试版本编连时必须去掉。 另外一种选择是使用远程调试即被调试的程序和调试器运行在网络中的不同计算机上。关于如何使用Visual C进行远程调试的步骤请见MSDN的《Visual C Programmers Guide》的“Debugging Remote Applications”部分。 如果你需要调试很多画图代码可能最好的选择是再装一块显卡和一台显示器。这样你可以在一个显示器上运行程序在另一台上运行调试器。这个解决方案可能要贵一些但更灵活而且耗费的精力最少。 最后注意GDI会将GDI函数调用累积起来放在一个批处理文件中然后一次处理一个批处理文件而不是一次执行一个调用。这一技术可以大大地提高画图效率但是它也会使得画图代码难以调试因为你不能看到每行代码的执行结果。你可以在调试版本中关掉批处理功能为此只要在线程初始化时加入如下代码即可 #ifdef _DEBUG GdiSetBatchLimit(1); // disable thread GDI batch processing #endif 画图的代码闪烁应该如何调试 不必要的闪烁是窗口画图最难调试的地方之一。当窗口的背景在不需要的时候被擦掉就会造成不必要的闪烁。这个问题很难调试因为Windows的基于消息的特性使我们很难确定到底是哪行代码发出了不必要的擦除背景的消息。 我总结了一些可能导致这个问题的源代码的情况这可能比使用调试工具要简单一点 •不适当的UpdateWindow调用。Windows给paint消息指定比较低的优先级以防止窗口不必要的重画。但是一个程序可以使用 API函数UpdateWindow迫使窗口立即被重绘。不适当的UpdateWindow调用会导致不必要的重画。 •调用InvalidateRect而不指定更新矩形。 API函数InvalidateRect允许用户指定更新矩形使得重画只限于需要重画的区域可以传递一个空指针给InvalidateRect函数来更新整个窗口但是这样做画图需要更长的时间结果是不必要的闪烁和低速的画图。 •调用InvalidateRect而将擦除背景参数不适当地设置为真。如果背景不需要重画你可以将InvalidateRect函数中檫除背景的参数设置为false。注意MFC将这个擦除背景的参数默认设置为true。 •不适当地使用CS_HREDRAW和CS_VREDRAW窗口风格。仅当客户区的大小改变需要重画整个窗口时才需要设置这两种窗口风格。如果窗口中的某些元素需要居中放置这是必要的但是大多数的窗口不需要居中排列任何东西所以没有必要使用这类风格。MFC默认使用的就是这两种风格所以如果你使用的是MFC最好在你自己的类的窗口构造函数中去掉这两个属性。 如何调试鼠标处理代码 鼠标处理的代码是调试过程中海森堡不确定原理的又一个典型例子。鼠标处理代码很难调试。因为被激活的调试器干扰了鼠标消息的队列。 调试WM_MOUSEMOVE消息尤其困难因为一旦你在一个鼠标移动消息的处理函数中设置了断点当鼠标一进入被调试窗口调试器就会立刻中断。而大部分情况下你希望在鼠标移动到窗口的特定位置或在特殊的环境下才发生中断。我发现本章前面部分介绍过的GetAsyncKeyState调试技术对于解决鼠标移动消息的调试特别管用。请看下面这段代码 CMyWnd::OnMouseMove(UINT nFlags, CPoint point) { #ifdef _DEBUG if(GetAsyncKeyState(VK_CONTROL) 0) { int bogus 0; // set brakpoint here to break when control key is down } #endif ... } 如果你在给bogus赋值的代码上设置了一个断点仅当你按下Control键时才会进入鼠标移动消息处理函数。 调试WM_LBUTTONDOWN和WM_LBUTTONUP消息也是一个问题因为在WM_LBUTTONDOWN消息处理函数中设置一个断点很可能会导致WM_LBUTTONUP消息被调试器吸收(eat)。绕开这个问题的一个办法是在调试器中一直保持鼠标按下的状态只使用键盘控制调试器。一旦程序重新获得了输入焦点你就可以释放鼠标按钮了。 如何调试与消息有关的问题 与消息有关的问题很难在调试器中调试因为调试器给你的信息很少。在不同的消息处理函数中设置断点你可以知道收到了哪条消息。以及消息的参数是什么在调试器中你看不到消息的来源也看小到消息是由 API函数SendMessage还是PostMessage发的因为消息传递机制禁止这些信息在调用堆栈中出现。也很难通过调试器看到接收到的消息及其顺序的整体画面。 调试消息的最好力案是使用Visual C提供的Spy工具。Spy允许程序员查看窗口、消息、进程和线程。要控制并调试消息首先选择Spy菜单中的Message命令这个命令将显示一个消息选项对话框。在Windows标签下你可以使用Windows FInder Tool选择你要监视的窗口然后在Messages标签下选择你想监视的消息这一步很重要因为Windows会传递成千上万个不同的消息给一个窗口你还可以通过Output标签选择希望的消息信息的显示格式注意你可以选择解码消息的参数和返回值这将使得结果更容易阅读。你可以选择将输出结果重定向到一个日志文件中这样你就可以以最小化窗口的模式运行Spy以防止它干扰你正在调试的程序从而避免海森堡不确定原理的问题。 图8.3显示了典型的Spy消息输出假定使用默认的输出选项第一栏显示的是Spy的输出行号第二栏显示接受消息的窗口的句柄。第三栏中的“S”表示消息是用SendMessage发出的“P”代表消息是由PostMessage发出的“R”是消息句柄的返回值。第四栏给出解码后的消息名其他的信息还有解码后的消息参数或返回值。 图8.3使用Spy调试与消息有关的问题 你可以使用Spy观察发送给你的程序的消息也可以看看发送到其他程序的消息观察两者行为上的差异。一般只需要关心消息的行为而消息的顺序仅供参考。一般情况下最好避免在你的代码中假定消息的顺序因为在不同的Windows版本中消息的顺序可能会有所不同。消息的顺序也可能被程序中的 API函数PeekMessage的调用影响这个函数可以以下同于消息到达时的顺序处理消息队列中的消息。但是如果在Windows文档中定义了某种消息顺序你就可以利用这类特定的消息顺序信息。例如Windows API文档中规定WM_NCDESTROY是一个窗口被销毁之前发出的最后一条消息MFC的设计中就利用了这一信息。 如何调试工具提示代码 调试工具提示(tooltip)代码的问题是一旦你把一切东西建立起来了Windows自己会管理工具提示的显示。如果工具提示显示得不正确或者根本就不显示你得不到任何有关的信息因为所有有关的代码都在Windows内部。调试工具提示的技巧是指定LPSTR_TEXTCALLBACK使用回调来获得工具提示的文本即使这个文本是静态的不需要回调。使用这个方法然后你可以在回调代码中设罝断点确认工具提示正以你所期望的工作方式工作。例如你可以把鼠标移动到预期的位置并查看回调函数是否被调用、其参数是否正确从而验证工具提示的矩形框是否在正确的位置。 这个原则适用于一般情况。回调允许程序员比较方便地调试因为它允许你进入Windows查看它在做什么。 我的可执行文件在载入的时候就崩溃了怎么调试 要调试这个问题你必须首先了解一个事实程序的启动代码不一定是程序中的第一行要运行的代码。因此如果你把断点设置在程序的第一行启动代码上(Windows API程序的WinMain函数或MFC程序的CWinApp::initlnstance)你的程序仍可能在到达这一行代码之前就崩溃了。 要调试这样的崩溃了解Windows可执行文件的载入过程可能会对你有所帮助。这里我们简要地看一下这个过程 •Windows为主线程创建默认的堆、栈和线程局部存储空间。 •Windows将可执行文件的主体和它的所有DLL载入虚拟存储空间如果有必要DLL会被重定位。 •Windows解释所有的函数和数据引入符号。 •对所有DLL_PROCESS_ATTACH符号中带有该文件的DLLWindow调用其DllMain函数。 •Windows调用C的运行时刻函数库中的WinMainCRTStartup启动代码。 •运行时刻函数库分解命令行并设罝环境变量。 •运行时刻函数库初始化自己。 •运疔时刻函数中为主可执行文件和所有DLL创建全局和静态变量。 •运行时刻函数中调用程序的WinMain函数。对于MFC程序WinMain立即调用AfxWinMain。该函数调用AfxWinlnit初始化MFC自身然后调用CWinApp::InitInstance初始化应用程。 在这些步骤中构造全局和静态对象是最容易导致崩溃的一步因为在对象的构造函数中可能存在错误。DllMain函数中的代码也可能产生问题尤其是当这些函数的调用发生在程序被完全载入之前的时候。你可以通过在调试器中检查调用堆栈窗口来定位这样的问题。寻找可执行文件并将它们载入到虚拟存储空间这一过程中发生的所有问题Windows都会自动报错。关于Windows 2000的程序载入过程的详细信息参考Matt Pietrek的《Under the Hood》。 我的可执行文件在退出的时候崩溃了应该怎么调试 程序退出的过程是启动过程的一个镜像因此程序退出的代码也不一定是程序中要运行的最后一行代码。所以如果你在程序退出代码的最后一行上设置断点(Windows API函数的WinMain函数或MFC的CWinApp::ExitInstance函数)程序仍然可能在这些代码执行之后崩溃。在退出的过程中最容易导致崩溃的是在析构全局和静态对象时或者调用带有DLL_PROCESS_DETACH符号的DllMain时。你可以通过在调试器中检查调用堆栈窗口来定位这样的问题。 我的一个调试函数在返回的对候崩溃了应该怎么调试 如果一个函数在堆栈中的返回地址被破坏了该函数就会在返回的时候崩溃。以下是最容易导致返回地址破坏的原因 •写自动变量越界。如果试图写—个自动变量数组时越界可能破坏返回地址。因为栈在内存中是向下增长的试图写自动变量时越界就会破坏最后一个压入栈中的数据例如函数的返回地址。这个问题在调试版本和发行版本中都有可能发生。 •函数原型不匹配。如果函数的参数不匹配或者调用函数与被调用函数之间的调用规则不匹配都会造成对堆栈的破坏而且很可能破坏函数的返回地址。这个问题在调试版本中不会出现因为调试版本使用堆栈基址指针寄存器访问函数的返回地址但在优化使用FPO的发行版本中可能发生。 这两种堆栈破坏的问题将在第9章“内存调试”中进一步讨论。 8.4 MFC调试技术 MFC程序中有什么常见的错误需要注意 以下是最常见的特定于MFC的错误: •使用错误的函数原型处理用户定义消息。使用错误的函数原型处理用户定义消息可能是MFC程序中最常见的错误了。在MFC中会发生这个问题是因为ON_MESSAGE和相关的宏转换了输入函数因此即使存在不匹配编译器也不会提醒你。一般来说调试这类错误的最简单的方法就是使用编译选项/GZ编译你的调试版本。不幸的是目前的MFC在调试版本中不使用这一选项。注意正确的函数原型是这样的 afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); 这个问题在第9章中还将详细讨沦。 •保存指向临时MFC对象的指针。MFC试图给用户一个假象即Wimtows是一个C的面向对象的操作系统。因此它把Windows API函数返回的窗口句柄都封装成一个MFC对象。每个MFC线程都包含对象的两个映射图一个是永久对象列表这个列表中的对象一直存在直到程序显式地将之销毁另一个是临时对象列表一旦程序的消息循环有空闲MFC就会把这种对象销毁。任一列表都允许MFC将Windows的句柄转换成C的对象指针。 例如如果你调用CWnd::GetParent获得一个MFC窗口对象的父窗口有可能此时父窗口已经不是一个永久的MFC对象了这时候MFC就要创建一个临时对象。GetParent函数的实现如下 _AFXWIN_INLINE CWnd* CWnd::GetParent() const { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); } CWnd::FromHandle函数会先在永久对象列表中寻找需要的句柄然后在临时对象队列中寻找。如果在这两个地方都没有找到就会自己创建一个Cwn对象加到临时对象列表中然后返回一个指向该对象的指针。这个机制工作相当正常除非你试图保存这个CWnd对象。当处理下一个消息时一个临时对象很可能已经变得非法了这样就会产生错误而旦很难发现。 如果你要保存一个指向你没有显式创建的对象的指针你必须非常小心。任何一个从调用了FromHandle的函数返回的对象都不能跨消息地保存这样的函数包括CWnd的函数GetDlgItem、GetFocus、GetWindow、GetActiveWindow、FindWindow、GetOwner、GetFont、GetMemu、GetDC。为了将一个此类函数返回的对象转化成永久对象你必须首先分配一个MFC对象然后使用Attach函数将该对象和窗口句柄联系起来如下列代码所示 CWnd* GetPermanentParent(CWnd* pWnd) { ASSERT_VALIDE(pWnd); HANDLE hParent ::GetParent(pWnd-GetSafeHwnd()); CWnd* pParent new CWnd; pParent-Attach(hParent); return pParent; } 任何一个从调用了Fromhand]e的函数返回的对象都不能跨消息地保存。 当传递对象时使用了消息或跨了线程一定要传递底层的窗口句柄而不是MFC对象。如果想更多的信息清参考《MSDN Technical Nate TN003》的“Mapping of Window Handles to Object”。 •忘了在消息映射中加入消息处理函数。为了防止对象的vtable过于庞大MFC使用消息映射而不是虚函数来处理大多数的窗口消息。刚入门的MFC程序员习惯于使用MFC类向导模板来建立消息处理代码。而有经验的程序员经常手工输入代码。如果你定义了一个消息处理函数但是忘记在消息映射中增加相应的条目那么这个消息处理函数不会被调用而且编译器和链接器不会报错。如果你觉得你定义的消息处理代码很奇怪地从来没有被执行请检查你的消息映射。 •不正确地创建或销毁CFrameWnd和CV[ew的派生对象。CFrameWnd和CView对象都被设计成自清理。这一点在PostNcDestroy的实现中可以看到 void CFrameWnd::PostNcDestroy() { // default for frame windows is to allocate them on the heap // the default post-cleanup is to delete this // never explicitly call delete on a CFrameWnd, use // DestroyWindow instead delete this; } 这些自清理对象在接受到WM_NCDESTROY消息——这是窗口接到的最后一个消息——时就会删除自已。这个设计暗示了两个信息一是这些对象必须在堆上创建因为你永远不会希望删除一个栈中的对象第二点是要删除这样的窗口对象必须调用 API函数DestroyWindow因为显式地调用delete会造成两次删除同一对象。如果需要更多的信息请参考《MSDN Technical Note TN017》的(Destroying Window Objects)。 MFC好像没有产生跟踪我该怎么办 MFC TRACE宏的输出可以被关闭或打开这是通过Afx.ini文件中Diagnostic区的TraceEnabled设置实现的。MFC的跟踪输出默认设置为打开。不过你最好不要直接编辑这个文件来修改这个值标准的方法是运行Visual C的Tracer工具确定其中的EnableTracing选项被选中。 8.5 推荐阅读 DiLascia, Paul. Meandering Through the Maze of MFC Message and Command Routing, Microsoft Systems Journal, July 1995. 详细地阐释了MFC的消息路由。如果你要调试MFC程序中的消息问题这是一本经典之作。 ...
http://www.w-s-a.com/news/961258/

相关文章:

  • 成都捕鱼网站建设wordpress自定义文章类别
  • wordpress网站怎么加速湖北网站建设企业
  • 迁安做网站中的cms开发南平网站建设公司
  • 肥西县住房和城乡建设局网站代驾系统定制开发
  • 网站建设明细报价表 服务器qq是哪家公司的产品
  • html链接网站模板wordpress怎么调用简码
  • 网站域名怎么查简述网站推广的五要素
  • 咸宁网站设计公司app安装下载
  • 丝网外贸做哪些网站最优的赣州网站建设
  • 如何做网站不被查网站开发工程师岗位说明书
  • 做网站需要vps吗网站建设后怎样发信息
  • 网站建立风格二手交易网站开发可参考文献
  • 成都微信网站开发优化大师优化项目有哪些
  • 哪个网站做自考题目免费郑州网站建设公司qq
  • 地方性的网站有前途顺的网络做网站好不好
  • 学校申请建设网站的原因不要网站域名
  • 推荐响应式网站建设子域名查询工具
  • 如何建设学校的微网站广告推广是什么
  • 设计类专业哪个就业前景好网站建设seoppt
  • 济南建站公司网站网站友链查询源码
  • 校园失物招领网站建设涪陵网站建设公司
  • 怎么做盗号网站手机网站建设需要租用什么科目
  • 成品网站是什么意思沈阳seo推广
  • 购物网站后台流程图昆明官网seo技术
  • 创建自己网站全网零售管理系统
  • 江苏省建设厅网站建筑电工证wordpress收费插件大全
  • 北京中国建设银行招聘信息网站宁德蕉城住房和城乡建设部网站
  • 泉州做网站优化哪家好wordpress站点预览
  • 创建门户网站一页网站首页图如何做
  • 服装手机商城网站建设sns社交网站有哪些