<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>MSDN摘译</title><link>http://blog.vckbase.com/iwaswzq/category/633.html</link><description>要成为合格的程序员，MSDN上面很多东西还是不得不看的。找几篇容易的摘译下来，不当之处欢迎指出。</description><managingEditor>九月鹰飞</managingEditor><dc:language>af</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>九月鹰飞</dc:creator><title>第一章、调试和错误处理 [4] 结构化异常处理</title><link>http://blog.vckbase.com/iwaswzq/articles/6925.html</link><pubDate>Mon, 20 Jun 2005 10:19:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/articles/6925.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6925.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/articles/6925.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6925.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6925.html</trackback:ping><description>&lt;P&gt;第一章、调试和错误处理 [4] 结构化异常处理&lt;/P&gt;
&lt;P&gt;=======================================================&lt;/P&gt;
&lt;P&gt;&amp;nbsp;异常是程序执行过程中出现的事件，通常表现为要执行正常控制流之外的代码。一般有两种异常：硬件异常和软件异常。硬件异常是CPU产生的。例如除0错误，非法内存地址访问等。软件异常是程序或者操作系统检测到错误以后主动抛出的。&lt;/P&gt;
&lt;P&gt;结构化异常处理就是处理软硬件异常的一种机制。因此，程序处理两种异常时，应该区别对待。通过结构化异常处理方式，可以完全控制异常的处理方式，从而为调试器提供支持，在所有编程语言和机器上都有用途。&lt;/P&gt;
&lt;P&gt;系统还支持终止处理，可以保证只要一个受保护代码段被执行，特定的终止代码段也被执行。无论保护代码段的执行流怎样离开该代码段。例如，终止处理函数可以用于保证清理任务被执行。&lt;/P&gt;
&lt;P&gt;1、关于结构化异常&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;结构化异常处理机制和中止处理机制是系统的一部分，保证系统实现冗余。程序员可以利用这个机制创建稳定可靠的程序。&lt;/P&gt;
&lt;P&gt;结构化异常处理主要通过编译器支持实现。例如Microsoft 32-bit C/C++ 优化编译器支持try关键字，声明受保护的代码段，except关键字给出一个异常处理函数。finally关键字用于终止处理函数。虽然下面的例子都来自Microsoft C/C++ 编译器，其它提供这些支持的编译器也可以实现。&lt;/P&gt;
&lt;P&gt;2、异常处理&lt;/P&gt;
&lt;P&gt;无论硬件还是软件、无论在内核模式还是用户模式都可能产生异常。例如程序在执行过程中，要访问一个虚拟内存地址，但是进程没有访问该地址的权限，则硬件会产生一个&amp;#8220;非法访问&amp;#8221;异常。无论什么异常，都可以通过结构化异常机制来处理。&lt;/P&gt;
&lt;P&gt;异常可以分为可继续执行和不可继续执行两种。如果异常发生后，硬件无法继续执行或者继续执行则无任何意义，这就是不可继续执行异常。不可继续异常不会终止程序，因此，程序可以捕获它，然后继续运行。但是，出现不可继续执行的异常后，如果强行继续，会导致栈错误或者其它更严重的问题。&lt;/P&gt;
&lt;P&gt;3、异常的分发&lt;/P&gt;
&lt;P&gt;当异常出现后，处理器中止执行，然后把控制权交给系统。系统首先保存当前线程的机器状态以及描述异常的信息。然后系统尝试查找能够处理该异常的函数。&lt;/P&gt;
&lt;P&gt;线程的机器状态（叫做context record）保存在一个 CONTEXT结构中，该信息用于成功处理异常以后，系统从异常出现点恢复线程的运行。异常的信息保存在一个EXCEPTION_RECORD结构中。由于 context结构中机器相关的数据和机器无关的数据是分开的，所以异常处理机制可以在平台之间移植。&lt;/P&gt;
&lt;P&gt;通过函数GetExceptionInformation 可以获取这两个结构信息。其中描述异常的信息包括：&lt;/P&gt;
&lt;P&gt;（1）一个异常代码。给出异常的类型。&lt;BR&gt;（2）一个标志，给出异常是否可以继续。对于不可继续异常如果想继续执行，会导致新的异常产生。&lt;BR&gt;（3）一个指针。指向另一个异常结构。因此如果嵌套异常出现，可以创建一个异常链表。&lt;BR&gt;（4）异常出现的地址。&lt;BR&gt;（5）一个32位参数数组，给出更多信息。&lt;/P&gt;
&lt;P&gt;当异常出现在用户模式时，系统用下面的方法查找处理函数：&lt;/P&gt;
&lt;P&gt;（1）如果进程正在被调试，系统通知调试器。&lt;BR&gt;（2）如果没有调试，或者相关的调试器没有处理该异常，系统尝试查找一个基于框架的处理函数。它首先搜索线程当前的栈，然后向后逐级搜索栈框架。&lt;BR&gt;（3）如果没有找到处理函数，但是该进程正在被调试，则系统第二次通知调试器&lt;BR&gt;（4）如果进程没有被调试，或者调试器仍然没有处理，则系统根据异常的类型提供缺省的处理函数。对于大多数异常，缺省的处理是调用ExitProcess &lt;/P&gt;
&lt;P&gt;当异常出现在内核模式，系统在内核栈中搜索，查找处理函数，如果没有找到，则系统关机，就好像调用ExitWindows函数一样。&lt;/P&gt;
&lt;P&gt;4、调试器的异常处理&lt;/P&gt;
&lt;P&gt;系统对用户模式下产生的异常的处理方式，用于支持复杂的调试器。如果出现异常的进程正在被调试，系统会产生一个调试事件。如果调试器正在用WaitForDebugEvent等待调试事件，则该函数立刻返回，并返回一个 DEBUG_EVENT 结构指针。该结构包含进程和线程的id,调试器可以用它访问线程的context信息，该结构还包含一个EXCEPTION_DEBUG_INFO 结构，里面给出描述异常的信息。&lt;/P&gt;
&lt;P&gt;当系统查找某进程的异常处理函数时，它分两次通知该进程的调试器。第一次通知的用途是，给调试器一个机会去处理断点异常或者单步运行异常。这叫做first-chance notification。用户可以在这个时候发送调试命令，在执行任何异常处理函数之前改变进程的运行环境。第二次通知发生在系统无法找到基于框架的异常处理函数时，叫做 last-chance notification。如果调试器在第二次通知后仍然不处理该异常，则系统中止被调试的进程。&lt;/P&gt;
&lt;P&gt;收到异常通知后，调试器可以处理该异常，改变线程状态。然后调用ContinueDebugEvent 将控制权交还系统。调用这个函数的时候，调试器可以告诉系统它是否已经处理了该异常，如果处理了，则系统可以恢复原来保存的机器状态，并继续执行线程。如果调试器没有处理，则系统继续寻找可用的处理函数。&lt;/P&gt;
&lt;P&gt;5、浮点异常&lt;/P&gt;
&lt;P&gt;通常，系统关闭所有浮点异常，因此浮点计算如果出问题，结果会得到NAN 或者INFINITY，而不是异常。如果要用结构化异常处理方法捕捉浮点计算异常，必须先调用 C 运行库函数_controlfp：&lt;/P&gt;
&lt;P&gt;// 取得缺省控制字&lt;/P&gt;
&lt;P&gt;int cw = _controlfp( 0, 0 );&lt;/P&gt;
&lt;P&gt;// 设置异常屏蔽位关闭，从而打开异常&lt;/P&gt;
&lt;P&gt;cw &amp;amp;=~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL&lt;/P&gt;
&lt;P&gt;// 设置新的控制字&lt;/P&gt;
&lt;P&gt;_controlfp( cw, MCW_EM );&lt;/P&gt;
&lt;P&gt;上面的代码打开所有可能出现的浮点计算异常。要捕捉特定的异常，只需打开相应的标志位。注意任何处理浮点错误的函数第一条语句必须是 _clearfp，就是清除浮点异常。&lt;/P&gt;
&lt;P&gt;6、基于框架的异常处理&lt;/P&gt;
&lt;P&gt;使用基于框架的异常处理机制，可以处理可能出现在特定代码序列中的异常。基于框架的异常处理代码包括下列内容：&lt;BR&gt;（1）一个受保护的代码体&lt;BR&gt;（2）一个过滤表达式&lt;BR&gt;（2）一个异常处理函数体&lt;/P&gt;
&lt;P&gt;基于框架的异常处理代码的定义和语言有关。例如，对于Microsoft C/C++优化编译器，是通过 try-except语句实现的。&lt;/P&gt;
&lt;P&gt;受保护的代码体可以是一段代码，也可以是多个嵌套的代码或者一个完整的函数。在 C/C++ 编译器下，受保护的代码体用try 加上括号{}组成。&lt;/P&gt;
&lt;P&gt;当保护体中的代码出现异常后，系统计算过滤表达式的值，然后根据计算结果做下面的动作：&lt;BR&gt;（1）系统停止查找异常处理函数，恢复机器状态，继续线程的执行。&lt;BR&gt;（2）系统继续查找异常处理函数。&lt;BR&gt;（3）系统把控制交给处理函数，线程继续在处理函数所在的栈框架上面运行。如果处理函数没有位于异常出现的栈框架上，系统会回卷栈(unwind)，直到回到异常处理函数所在的栈框架。需要说明的是，在执行异常处理函数之前，先执行中止处理，详细情况见后面的说明。&lt;/P&gt;
&lt;P&gt;过滤表达式可以是一个简单的表达式，也可以是一个函数调用。可以在这个表达式中调用GetExceptionCode / GetExceptionInformation 来获得异常的信息用于计算。但是，如果过滤表达式是一个函数，则在这个函数内部不能调用这两个函数，但是可以把它们的返回值作为参数传递给过滤函数。 &lt;/P&gt;
&lt;P&gt;GetExceptionCode 可以用在异常处理块的内部，但是GetExceptionInformation不行，因为它指向的信息位于栈上，当控制转向处理函数后会破坏其数据。如果程序需要在异常处理函数内部使用这些数据，可以先把这些信息复制到一个安全的存储位置，然后在异常处理函数内部使用。&lt;/P&gt;
&lt;P&gt;使用过滤函数的优点是可以直接在过滤函数内处理异常，然后返回一个值引导系统从出现异常的地方继续执行。如果用异常处理函数，则继续执行的位置是异常处理函数之后的代码，而不是出现异常的地方之后的代码。&lt;/P&gt;
&lt;P&gt;最简单的处理异常可以是提示一个错误，然后设置一个标志，打印一个警告或者错误信息或者其它简单动作。如果出现异常以后，程序可以继续执行的话，只需修改线程的上下文，改变机器的状态即可。在&amp;#8220;使用虚拟内存&amp;#8221;一文中给出了处理页面错误异常的过滤函数例子。&lt;/P&gt;
&lt;P&gt;在过滤表达式中可以使用 UnhandledExceptionFilter函数。如果进程正在被调试，它返回 EXCEPTION_CONTINUE_SEARCH，否则返回EXCEPTION_EXECUTE_HANDLER。&lt;/P&gt;
&lt;P&gt;7、中止处理&lt;/P&gt;
&lt;P&gt;中止处理函数用于确保无论控制如何离开特定的受保护代码段，指定代码段一定被执行。它的内容包括：&lt;BR&gt;（1）一个受保护代码段&lt;BR&gt;（2）当控制离开该受保护代码体后，应执行的一段中止代码。&lt;/P&gt;
&lt;P&gt;中止函数的声明语法和语言有关。在C/C++中，用的是 try-finally 语句。&lt;/P&gt;
&lt;P&gt;受保护的代码体可以是一段代码，或者嵌套代码，或者函数。只要受保护代码体被执行，中止代码就会执行。唯一的例外是线程突然中止的情况。（比如在受保护代码体内部调用了ExitThread或者 ExitProcess）。&lt;/P&gt;
&lt;P&gt;当执行控制流离开受保护代码段的时候就执行中止函数，无论是正常离开还是非正常离开。如果受保护代码段中最后一条语句被执行就认为是正常离开。其它由于发生异常，或者由return, goto, break, continue等语句导致的离开都认为是非正常离开。&lt;/P&gt;
&lt;P&gt;要判断程序是否正常离开受保护的代码体，可以在中止函数内部调用函数AbnormalTermination。&lt;/P&gt;
&lt;P&gt;8、处理函数语法&lt;/P&gt;
&lt;P&gt;在C/C++中，下面的关键字被编译器解释成结构化异常处理机制：&lt;BR&gt;------------------------------------&lt;BR&gt;关键字&amp;nbsp;&amp;nbsp;描述&lt;BR&gt;------------------------------------&lt;BR&gt;try &amp;nbsp;&amp;nbsp;开始一个受保护代码段。和except或者finally一起使用&lt;BR&gt;except&amp;nbsp;&amp;nbsp;开始一个异常处理代码段。与try对应。&lt;BR&gt;finally&amp;nbsp;&amp;nbsp;开始一个中止代码段。与try对应。&lt;BR&gt;__leave&amp;nbsp;&amp;nbsp;用于try-finally组合，使try中的代码立即终止，但属于&lt;BR&gt;&amp;nbsp;&amp;nbsp;正常离开&lt;BR&gt;----------------------------------------------&lt;/P&gt;
&lt;P&gt;编译器还把 GetExceptionCode, GetExceptionInformation,和AbnormalTermination解释为关键字。如果在异常处理语法之外使用会导致编译错误。 &lt;BR&gt;----------------------------------------------&lt;BR&gt;函数&amp;nbsp;&amp;nbsp;&amp;nbsp;解释&lt;BR&gt;GetExceptionCode &amp;nbsp;返回异常的类型。这个函数可以在&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;过滤表达式或异常处理函数中调用。&lt;BR&gt;GetExceptionInformation 返回一个指向EXCEPTION_POINTERS 结构&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;的指针。可以在过滤表达式中调用。&lt;BR&gt;AbnormalTermination &amp;nbsp;指出控制流是否正常离开try 代码段，&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;可以在finally中调用。&lt;BR&gt;----------------------------------------------&lt;/P&gt;
&lt;P&gt;9、异常处理函数语法&lt;/P&gt;
&lt;P&gt;try和 except关键字用于构造一个基于框架的异常处理函数。例如：&lt;/P&gt;
&lt;P&gt;try &lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 受保护代码段&lt;BR&gt;} &lt;BR&gt;except (过滤表达式) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 异常处理代码段&lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;注意 try 和 except代码段需要{}。不允许使用goto跳到这些代码段内部。对于 try-finally也一样。&lt;/P&gt;
&lt;P&gt;try中的代码受到异常处理函数的保护。一个函数可以有多个try-except 语句。这些处理语句可以嵌套。如果在某个try块中出现异常，系统获得控制权，开始查找合适的处理函数。&lt;/P&gt;
&lt;P&gt;异常处理函数仅仅可以接受当前线程中出现的异常。就是说，如果 try中包含一个 CreateProcess 或者 CreateThread 调用，则在新的进程或者线程中出现的异常不会发送到这个处理函数中。&lt;/P&gt;
&lt;P&gt;系统通过计算每个处理函数的过滤表达式，保证执行合适的处理函数。一个过滤表达式应该得到下面三个结果之一：&lt;BR&gt;----------------------------------------------------------&lt;BR&gt;值&amp;nbsp;&amp;nbsp;&amp;nbsp;含义&lt;BR&gt;----------------------------------------------------------&lt;BR&gt;EXCEPTION_EXECUTE_HANDLER 系统把控制权交给处理函数，然&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;后继续在处理函数所在的栈框架上运行&lt;BR&gt;EXCEPTION_CONTINUE_SEARCH 系统继续查找处理函数&lt;BR&gt;EXCEPTION_CONTINUE_EXECUTION 系统停止查找处理函数，然后返回&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;出现异常的点。如果该异常是不可继续&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;的，则会继续导致&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;EXCEPTION_NONCONTINUABLE_EXCEPTION&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 异常。 &lt;BR&gt;----------------------------------------------------------&lt;/P&gt;
&lt;P&gt;过滤表达式在try-except语句所在的函数的栈框架上面计算。即使异常出现在其它函数中。这意味着，表达式可以访问函数的局部变量。类似的，异常处理代码段也可以访问它所在的函数的局部变量。&lt;/P&gt;
&lt;P&gt;10、中止处理函数语法&lt;/P&gt;
&lt;P&gt;try和finally关键字用于构造中止处理函数。例如：&lt;/P&gt;
&lt;P&gt;try &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 受保护代码段&lt;BR&gt;&amp;nbsp;&lt;BR&gt;} &lt;BR&gt;finally &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 最后执行的代码段&lt;BR&gt;&amp;nbsp;&lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;不允许使用goto语句跳到括号内部。&lt;/P&gt;
&lt;P&gt;一个函数可以有多个try-finally语句，这些中止处理函数可以嵌套。&lt;/P&gt;
&lt;P&gt;注意如果在try块中调用 ExitProcess, ExitThread, 或者 abort函数 ，则finally块不会被执行到。&lt;/P&gt;
&lt;P&gt;finally 块可以访问函数的局部变量。finally块中代码的执行可以被如下情况中止：&lt;BR&gt;（1）执行到最后一条语句，然后继续执行外面的下一条语句。&lt;BR&gt;（2）使用了return, break, continue,或者 goto语句。 &lt;BR&gt;（3）使用了 longjmp 或者 jump 跳到另一个异常处理函数中。&lt;/P&gt;
&lt;P&gt;如果 try块中代码的执行导致的异常会引起一个基于框架的异常处理函数执行，则会先执行finally 块中的代码，然后执行异常处理函数。因此如果try中要通过长跳转到一个 C 运行库函数，则会先执行finally块中的代码，然后执行跳转。如果try中的代码由于return, break, continue,或者 goto而结束，则执行先转向finally块。&lt;/P&gt;
&lt;P&gt;在finally中可以用 AbnormalTermination 函数判断try中代码结束的类型 &amp;#8212; 即是因为正常到达括号结尾}，然后离开的try块，还是因为其它非正常中止。非正常中止会导致系统反向搜索栈框架，来判断是否需要调用某个中止处理函数。&lt;/P&gt;
&lt;P&gt;要避免try-finally中代码的非正常中止，执行应该尽量持续到末尾的括号。还可以使用 leave语句，立即离开try块，而不导致非正常中止。但是这个语句需要编译器支持。&lt;/P&gt;
&lt;P&gt;11、异常处理函数的使用例子&lt;/P&gt;
&lt;P&gt;下面给出strcpy函数的代码，其中处理了非法指针错误。用于解决非法访问异常，但对于其它异常不合适。因此过滤表达式中使用了 GetExceptionCode 检查异常的类型。从而对于其它异常允许系统继续寻找合适的处理函数。&lt;/P&gt;
&lt;P&gt;注意在 try-except表达式的try块中使用return和try-finally语句的try中使用return不同，后者会导致非正常中止异常。&lt;/P&gt;
&lt;P&gt;LPTSTR SafeStrcpy(LPTSTR lpszString1, LPTSTR lpszString2) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; try &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return strcpy(string1, string2); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; EXCEPTION_EXECUTE_HANDLER : &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; EXCEPTION_CONTINUE_SEARCH ) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return NULL; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;下面是一个嵌套try-finally 和 try-except的例子。RaiseException导致一个异常发生，系统先计算 FilterFunction函数，返回turn会导致异常处理函数被执行。但是在执行之前，先要执行finally中的代码。&lt;/P&gt;
&lt;P&gt;DWORD FilterFunction() &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("1 ");&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 最先输出 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return EXCEPTION_EXECUTE_HANDLER; &lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;VOID main(VOID) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; try &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; try &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RaiseException(1,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 异常代码 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 可继续异常&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0, NULL);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 没有参数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; finally &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("2 ")&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第二次输出&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; except ( FilterFunction() ) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("3\n");&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 最后输出&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;12、使用中止处理函数的例子&lt;/P&gt;
&lt;P&gt;下面给出如何使用try-finally语句，确保资源被释放。&lt;BR&gt;在这个例子中，一个线程使用EnterCriticalSection等待一个临界区对象。当线程执行完被临界去保护的代码段后，它必须调用LeaveCriticalSection。使用try-finally语句可以确保该函数被调用。&lt;/P&gt;
&lt;P&gt;LPTSTR lpBuffer = NULL; &lt;BR&gt;CRITICAL_SECTION csCriticalSection; &lt;BR&gt;&amp;nbsp;&lt;BR&gt;try &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 和其它线程保持同步&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; EnterCriticalSection(&amp;amp;CriticalSection); &lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 执行可能导致异常的任务&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; lpBuffer = (LPTSTR) LocalAlloc(LPTR, 10); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; strcpy(lpBuffer,"Hello");&amp;nbsp; // 可能非法访问&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; printf("%s\n",lpBuffer); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; LocalFree(lpBuffer); &lt;BR&gt;} &lt;BR&gt;// 即使出现异常也会调用LeaveCriticalSection&lt;BR&gt;finally &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; LeaveCriticalSection(&amp;amp;CriticalSection); &lt;BR&gt;} &lt;BR&gt;&amp;nbsp;&lt;BR&gt;=======================================================&lt;BR&gt;END IWASWZQ 2005/6/15&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6925.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>技术备忘录058 : MFC 模块状态的实现</title><link>http://blog.vckbase.com/iwaswzq/articles/6923.html</link><pubDate>Mon, 20 Jun 2005 10:07:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/articles/6923.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6923.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/articles/6923.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6923.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6923.html</trackback:ping><description>&lt;P&gt;MFC 模块状态的实现&lt;/P&gt;
&lt;P&gt;本技术备忘录介绍MFC &amp;#8220;模块状态&amp;#8221;结构的实现。充分理解模块状态这个概念对于在DLL中使用MFC的共享动态库是十分重要的。&lt;/P&gt;
&lt;P&gt;MFC的状态信息分为三种:全局模块状态数据、进程局部状态数据和线程局部状态数据。有时这些数据类型之间没有严格界限，例如MFC的句柄表既是全局模块状态数据也属于线程局部状态数据。&lt;/P&gt;
&lt;P&gt;进程局部状态数据和线程局部状态数据差不多。早先这些数据是全局的，但是为了更好的支持Win32和多线程，现在设计成进程或者线程相关的。模块状态数据既可以包含真正的全局状态数据，也可以指向进程或者线程相关的数据。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;一、什么是模块状态？&lt;/P&gt;
&lt;P&gt;模块状态实际上是指可执行模块运行所需的一个数据结构。首先要说明，这里的"模块"指的是一个MFC可执行程序，或者使用共享版本MFC动态库的DLL或者ActiveX控件。没有使用MFC的程序或者DLL等不在讨论范围之内。&lt;/P&gt;
&lt;P&gt;正如下图"单个模块的状态数据"所描述的，使用MFC的每个模块都有一套状态数据。这些数据包括包括:窗口进程句柄(用于加载资源)，指向当前程序的CWinApp和CWinThread对象的指针，OLE模块引用次数，以及很多关于Windows对象和其对应句柄的映射表等等。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 单个模块（程序）的状态数据&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +-------------MFC程序&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; \/&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------------------+&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; 线程对象&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp; m_pModuleState&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +---+&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------+&amp;nbsp;&amp;nbsp; |&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; \/&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------------------+&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; 状态数据&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------------------------------+&lt;/P&gt;
&lt;P&gt;(注意，因为采用的字符画图，如果图形显示有问题，请复制到记事本中看）&lt;/P&gt;
&lt;P&gt;一个模块的所有状态数据包含在一个结构中，这个结构在MFC中被打包成一个类 AFX_MODULE_STATE， 它派生自 CNoTrackObject。关于这个类后面会谈到。AFX_MODULE_STATE类的定义位于AfxStat_.H中。内容如下所示：&lt;/P&gt;
&lt;P&gt;// AFX_MODULE_STATE (模块的全局数据)&lt;BR&gt;class AFX_MODULE_STATE : public CNoTrackObject&lt;BR&gt;{&lt;BR&gt;public:&amp;nbsp;//构造函数&lt;BR&gt;#ifdef _AFXDLL&lt;BR&gt;&amp;nbsp;AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);&lt;BR&gt;&amp;nbsp;AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,&lt;BR&gt;&amp;nbsp;&amp;nbsp;BOOL bSystem);&lt;BR&gt;#else&lt;BR&gt;&amp;nbsp;AFX_MODULE_STATE(BOOL bDLL);&lt;BR&gt;#endif&lt;BR&gt;&amp;nbsp;~AFX_MODULE_STATE(); &amp;nbsp;&amp;nbsp;&amp;nbsp;//析构函数&lt;/P&gt;
&lt;P&gt;&amp;nbsp;CWinApp* m_pCurrentWinApp;&amp;nbsp;&amp;nbsp;//指向CWinApp对象的指针&lt;BR&gt;&amp;nbsp;HINSTANCE m_hCurrentInstanceHandle;&amp;nbsp;//当前进程句柄&lt;BR&gt;&amp;nbsp;HINSTANCE m_hCurrentResourceHandle;&amp;nbsp;//当前资源句柄&lt;BR&gt;&amp;nbsp;LPCTSTR m_lpszCurrentAppName;&amp;nbsp;&amp;nbsp;//当前程序的文件名&lt;BR&gt;&amp;nbsp;BYTE m_bDLL;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;//TRUE表示模块是 DLL，否则是EXE&lt;BR&gt;&amp;nbsp;BYTE m_bSystem; &amp;nbsp;&amp;nbsp;&amp;nbsp;//TRUE表示模块是系统模块。&lt;BR&gt;&amp;nbsp;BYTE m_bReserved[2]; &amp;nbsp;&amp;nbsp;&amp;nbsp;//字节对齐&lt;/P&gt;
&lt;P&gt;&amp;nbsp;DWORD m_fRegisteredClasses; &amp;nbsp;&amp;nbsp;//窗口类注册标记&lt;/P&gt;
&lt;P&gt;&amp;nbsp;。。。//很多其它运行态数据&lt;BR&gt;};&lt;/P&gt;
&lt;P&gt;二、为什么需要切换模块状态&lt;/P&gt;
&lt;P&gt;模块状态数据是十分重要的。因为很多MFC函数都要使用这些状态数据。如果一个MFC程序使用多模块，比如一个MFC程序需要调用多个DLL或者OLE控件的情况，则每个模块都拥有自己的一套MFC状态数据。&lt;/P&gt;
&lt;P&gt;MFC程序运行过程中，每个线程都包含一个指向&amp;#8220;当前&amp;#8221;或者&amp;#8220;有效&amp;#8221;模块状态的指针(自然，这个指针是MFC的线程局部状态数据的一部分)。当线程执行代码流跨越模块边界，转入一个特定的模块的时候，就要改变这个指针的值，如下图所示，m_pModuleState必须设置成指向有效的模块状态数据。这一点是非常重要的，否则将导致无法预知的程序错误。&lt;/P&gt;
&lt;P&gt;多模块下的状态数据&lt;/P&gt;
&lt;P&gt;&amp;nbsp;MFC程序 &lt;BR&gt;&amp;nbsp;&amp;nbsp; \&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; \&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;+--------------------------------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; DLL模块1&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; +----------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 转向模块1&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; 线程对象&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +-----------+--------&amp;gt;|&amp;nbsp; 状态数据&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; +----------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; | m_pModuleState +-----+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 转向模块2&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; DLL模块2&amp;nbsp;&amp;nbsp; |&amp;nbsp; &lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +-----------------+----+&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp; &lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; +----------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; +---&amp;gt;|&amp;nbsp; 状态数据&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;+--------------------------------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------+&lt;BR&gt;&amp;nbsp;|&amp;nbsp;&amp;nbsp; 状态数据&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;+--------------------------------------+ &lt;/P&gt;
&lt;P&gt;(注意，因为采用的字符画图，如果图形显示有问题，请复制到记事本中看）&lt;/P&gt;
&lt;P&gt;比如说，如果你在DLL中导出了一个函数，该函数要创建一个对话框，而这个对话框的模板资源位于DLL中。缺省情况下，MFC是使用主程序中的资源句柄来加载资源的，但现在这个对话框的资源位于DLL中，所以，必须设置m_pModuleState指向DLL模块的状态数据，否则，就会导致加载资源失败。&lt;/P&gt;
&lt;P&gt;因此，每个模块要负责在它的所有入口点进行状态数据的切换。所谓"入口点" 就是任何执行代码流可以进入模块的地方，包括: &lt;BR&gt;1、DLL中导出的函数；&lt;BR&gt;2、COM接口函数&lt;BR&gt;3、窗口过程&lt;/P&gt;
&lt;P&gt;首先谈dll中的导出函数。一般来说，如果从一个DLL中导出了一个函数，应该使用AFX_MANAGE_STATE 宏维护正确的全局状态。&lt;/P&gt;
&lt;P&gt;调用这个宏的时候，它设置pModuleState指向有效的模块状态数据，从而该函数后面的代码就可以通过该指针得到有效的状态数据。当函数执行完毕，即将返回时，该宏将自动恢复指针原来的值。&lt;/P&gt;
&lt;P&gt;这个自动切换是这样完成的，在栈空间上创建一个AFX_MODULE_STATE类的实例，并把当前的模块状态指针保存在一个成员变量里面，然后把pModuleState设置成有效的模块状态，在这个实例对象的析构函数中，对象恢复以前保存的指针。&lt;/P&gt;
&lt;P&gt;所以，对于上面所说的DLL导出函数，可以在该函数的开始加入如下预句：&lt;/P&gt;
&lt;P&gt;AFX_MANAGE_STATE(AfxGetStaticModuleState( ))&lt;/P&gt;
&lt;P&gt;这个代码将当前的模块状态设置成AfxGetStaticModuleState返回的值。离开当前作用域之后恢复原来的模块状态。&lt;/P&gt;
&lt;P&gt;但是，不是任何DLL中导出的函数都需要使用AFX_MANAGE_STATE。例如InitInstance函数，MFC在调用这个函数的时候是自动切换模块状态的。对于MFC常规动态库中的所有消息处理函数来说也不需要使用这个宏。因为常规DLL会链接一个特殊的主窗口过程，里面会自动切换模块状态。对于其它导出函数，如果没有用到模块状态中的数据，也可以不使用这个宏。&lt;/P&gt;
&lt;P&gt;对于COM接口的成员函数来说，一般使用METHOD_PROLOGUE宏来维护正确的模块状态数据。这个宏实际上也使用了AFX_MANAGE_STATE。详细信息可以参考技术备忘录38："MFC/OLE IUnknown的实现"。&lt;/P&gt;
&lt;P&gt;对于窗口过程，如果模块使用了MFC，则该模块会静态链接一个特殊的窗口过程实现函数，首先用AFX_MANAGE_STATE宏设置有效的模块状态，然后调用AfxWndProc，这个函数接着调用某窗口具体的WindowProc函数。具体可以参考WINCORE.CPP。&lt;/P&gt;
&lt;P&gt;三、模块状态是如何切换的&lt;/P&gt;
&lt;P&gt;一般来说，设置当前的模块状态数据可以通过函数AfxSetModuleState。但是大多数情况下，无需直接使用这个API函数，MFC知道应该如何正确设置模块状态数据，它会替你调用它，比如在WinMain函数、OLE入口、AfxWndProc中等等。这是通过静态链接一个特殊的WndProc和WinMain (或者DllMain)实现的。可以参考 DLLMODUL.CPP或者APPMODUL.CPP，找到这些实现代码。&lt;/P&gt;
&lt;P&gt;设置当前的模块状态，而又不把它设置回去的情况是十分少见的，一般来讲，在改变了模块状态后，都要进行恢复。可以通过AFX_MANAGE_STATE宏和AFX_MAINTAIN_STATE类来实现。我们看看这个宏的定义:&lt;/P&gt;
&lt;P&gt;#ifdef _AFXDLL&amp;nbsp;//定义了这个符号表示动态链接MFC&lt;BR&gt;struct AFX_MAINTAIN_STATE&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pModuleState);//参数是AFX_MODULE_STATE类对象指针&lt;BR&gt;&amp;nbsp;~AFX_MAINTAIN_STATE();&lt;/P&gt;
&lt;P&gt;protected:&lt;BR&gt;&amp;nbsp;AFX_MODULE_STATE* m_pPrevModuleState;&amp;nbsp;&amp;nbsp;//保存在这个私有变量中&lt;BR&gt;};&lt;/P&gt;
&lt;P&gt;class _AFX_THREAD_STATE;&amp;nbsp;//线程局部状态数据，这个类也是派生自CNoTrackObject&lt;BR&gt;struct AFX_MAINTAIN_STATE2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//多线程版本&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pModuleState);&lt;BR&gt;&amp;nbsp;~AFX_MAINTAIN_STATE2();&lt;/P&gt;
&lt;P&gt;protected:&lt;BR&gt;&amp;nbsp;AFX_MODULE_STATE* m_pPrevModuleState;&amp;nbsp;&amp;nbsp;//用来保存模块状态数据的指针&lt;BR&gt;&amp;nbsp;_AFX_THREAD_STATE* m_pThreadState;&amp;nbsp;&amp;nbsp;//指向线程局部状态数据的指针&lt;BR&gt;};&lt;BR&gt;#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE2 _ctlState(p);&amp;nbsp;//定义AFX_MANAGE_STATE宏&lt;BR&gt;#else&amp;nbsp; // _AFXDLL&lt;BR&gt;#define AFX_MANAGE_STATE(p)&amp;nbsp;//否则，这个宏没有意义。&lt;BR&gt;#endif //!_AFXDLL&lt;/P&gt;
&lt;P&gt;我们再来看看AFX_MAINTAIN_STATE2的构造函数，很简单的代码：&lt;/P&gt;
&lt;P&gt;AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;m_pThreadState = _afxThreadState;&amp;nbsp;&amp;nbsp;//首先保存线程局部状态数据指针&lt;BR&gt;&amp;nbsp;m_pPrevModuleState = m_pThreadState-&amp;gt;m_pModuleState; //保存全局模块状态数据指针&lt;BR&gt;&amp;nbsp;m_pThreadState-&amp;gt;m_pModuleState = pNewState;&amp;nbsp;//设置全局模块状态数据指针，指向pNewState。&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;由此可见，线程局部状态数据里面包含一个指向全局模块状态数据的指针。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;四、进程局部数据&lt;/P&gt;
&lt;P&gt;对于Win32 DLL，在每个关联它的进程中都有一份独立的数据拷贝。考虑如下代码:&lt;/P&gt;
&lt;P&gt;static CString strGlobal; // at file scope&lt;/P&gt;
&lt;P&gt;__declspec(dllexport) &lt;BR&gt;void SetGlobalString(LPCTSTR lpsz)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; strGlobal = lpsz;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;__declspec(dllexport)&lt;BR&gt;void GetGlobalString(LPCTSTR lpsz, int cb)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; lstrcpyn(lpsz, strGlobal, cb);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;如果上述代码位于一个DLL中，并且该DLL被两个进程A和B加载(或者同一个程序的两个实例)，那么将会发生什么事情呢？ A调用SetGlobalString("Hello from A")，结果，在进程A的上下文中为该CString对象分配内存空间，现在B 调用GetGlobalString(sz, sizeof(sz))。那么B是否可以访问到A 设置的数据呢？&lt;/P&gt;
&lt;P&gt;在WIN3.1中是可以的，因为Win32s没有提供象Win32那样的进程间的保护措施。显然这是有问题的，为了解决这个问题。MFC 3.x 是采用线程局部存储(TLS)技术解决这个问题，和Win32下保存线程局部数据的方法类似。但是每个MFC DLL都要在每个进程中使用两个TLS索引，如果加载过多DLL，会很快消耗完TLS索引(只有64个)。除此以外，还有其它问题。所以在MFC 4.x的版本中，采用了一套模板类，来包装这些进程相关的数据。例如下面的方法:&lt;/P&gt;
&lt;P&gt;struct CMyGlobalData : public CNoTrackObject&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; CString strGlobal;&lt;BR&gt;};&lt;BR&gt;CProcessLocal&amp;lt;CMyGlobalData&amp;gt; globalData;&lt;/P&gt;
&lt;P&gt;__declspec(dllexport) &lt;BR&gt;void SetGlobalString(LPCTSTR lpsz)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; globalData-&amp;gt;strGlobal = lpsz;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;__declspec(dllexport)&lt;BR&gt;void GetGlobalString(LPCTSTR lpsz, int cb)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; lstrcpyn(lpsz, globalData-&amp;gt;strGlobal, cb);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;MFC采用两个步骤实现该方法。首先，在Win32 Tls* API (包括TlsAlloc, TlsSetValue, TlsGetValue等)之上实现一个接口层，无论进程加载多少DLL，每个进程仅需使用两个TLS索引。其次，通过CProcessLocal模板访问数据，它重载了-&amp;gt;操作符。所有打包进CProcessLocal的对象必须派生自CNoTrackObject。而 CNoTrackObject提供一个底层的内存分配函数(LocalAlloc/LocalFree)以及一个虚析构函数，保证进程终止的时候，MFC可以自动销毁该进程局部数据。这些CNoTrackObject派生类对象可以有自己的析构函数，用于其它必要的清除操作。上面的例子里面没有，因为编译器会自动产生一个，并销毁内嵌的 CString 对象。CNoTrackObject类的定义位于Afxtls_.h中，主要是重载new 和 delete操作符，它的实现位于Afxtls.cpp中。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;五、线程局部数据&lt;/P&gt;
&lt;P&gt;和进程局部数据类似，线程局部数据是指必须和指定线程相关的局部数据，也就是说，不同线程访问同一个数据的时候，要为每个线程准备一份数据的实例。假设有一个CString对象，可以通过把它嵌入 CThreadLocal模板，使它成为线程局部数据:&lt;/P&gt;
&lt;P&gt;struct CMyThreadData : public CNoTrackObject&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; CString strThread;&lt;BR&gt;};&lt;BR&gt;CThreadLocal&amp;lt;CMyThreadData&amp;gt; threadData;&lt;/P&gt;
&lt;P&gt;void MakeRandomString()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp; // 一种洗牌方式，52张牌，效率很低，不实用&lt;BR&gt;&amp;nbsp;&amp;nbsp; CString&amp;amp; str = threadData-&amp;gt;strThread;&lt;BR&gt;&amp;nbsp;&amp;nbsp; str.Empty();&lt;BR&gt;&amp;nbsp;&amp;nbsp; while (str.GetLength() != 52)&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TCHAR ch = rand() % 52 + 1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (str.Find(ch) &amp;lt; 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; str += ch; &lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;如果从两个不同的线程调用 MakeRandomString ，则每个线程都会打乱字符串的顺序，而且相互之间没有影响。这是因为每个线程都有一个strThread实例对象，而不是只有一个全局对象。&lt;/P&gt;
&lt;P&gt;上述代码中使用了一个引用，而不是在循环中使用 threadData-&amp;gt;strThread，避免循环调用-&amp;gt;操作符，这样可以提高代码的效率。&lt;/P&gt;
&lt;P&gt;-------------------&lt;BR&gt;End. iwaswzq 2005/5/23&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6923.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>第一章、调试和错误处理 [2] 错误处理</title><link>http://blog.vckbase.com/iwaswzq/articles/6922.html</link><pubDate>Mon, 20 Jun 2005 09:59:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/articles/6922.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6922.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/articles/6922.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6922.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6922.html</trackback:ping><description>&lt;P&gt;第一章、调试和错误处理 [2] 错误处理&lt;/P&gt;
&lt;P&gt;设计良好的程序通常包含错误处理代码，出现异常错误后，程序可以平稳的恢复。当错误出现后，程序也许需要通知用户，让用户决定如何处理，或者自己直接处理错误，并恢复运行。在极端情况下，出现错误的程序会导致系统关闭。&lt;/P&gt;
&lt;P&gt;一、关于错误处理&lt;/P&gt;
&lt;P&gt;1、处理错误的模式&lt;/P&gt;
&lt;P&gt;每个进程都有一个错误处理模式，指出遇到严重错误的时候程序该如何处理。这些严重错误包括磁盘错误，设备未准备好错误，数据未对齐错误以及未处理的异常等。程序可以让系统显示一个消息框给用户，说明出现了错误。也可以自己处理该错误。要不惊动用户，自己处理错误，程序可以调用 SetErrorMode ，设置相关标志，则出现错误后，系统不显示相关的错误信息框。&lt;/P&gt;
&lt;P&gt;2、最后的错误码。&lt;/P&gt;
&lt;P&gt;当出现一个错误后，许多Win32函数返回一个错误码，通常是 FALSE, NULL, 0xFFFFFFFF,或者&amp;#8211;1。许多函数还同时设置内部错误码，叫做最后错误码，如果函数成功，则不设置它。这个错误码是线程相关的，一个线程的错误码不会影响其它线程的错误码。程序可以通过函数GetLastError 取得这个错误码。并根据该错误码了解更多的错误信息。&lt;/P&gt;
&lt;P&gt;用函数 SetLastError设置当前线程的最后错误码。SetLastErrorEx 还允许设置一个错误类型，从而表示错误的严重级别。&lt;/P&gt;
&lt;P&gt;系统定义了一套错误代码，可以直接使用。错误码是一个32位值， (31位通常是符号位)。位29保留作为用户自定义错误码使用。系统错误码不使用这一位(设置成0)。如果在程序中要定义自己的错误码，则可以把这一位设置成1，详细参考 WINERROR.H 中的定义。&lt;/P&gt;
&lt;P&gt;3、通知用户&lt;/P&gt;
&lt;P&gt;当某些错误出现后，为了通知用户，许多程序通过使用函数Beep或者 MessageBeep发出一个声音，或者用函数FlashWindow/FlashWindowEx 闪烁窗口。程序也可以用这些函数首先引起用户注意，然后显示一个消息框，给出错误的详细信息。&lt;/P&gt;
&lt;P&gt;4、错误信息表&lt;/P&gt;
&lt;P&gt;错误信息表是一个字符串资源，在资源文件中用MESSAGETABLE格式定义。要访问资源字符串，可以使用函数FormatMessage 。&lt;/P&gt;
&lt;P&gt;系统为所有 Win32错误码提供了一个信息表。要获得某个错误码的相关错误信息字符串，可以调用FormatMessage，指定 FORMAT_MESSAGE_FROM_SYSTEM 标志。&lt;/P&gt;
&lt;P&gt;要在你的程序中提供一个信息表，可以按照&amp;#8220; About Message Text Files&amp;#8221;中给出的步骤。要获取自定义的信息表字符串，调用FormatMessage的时候指定 FORMAT_MESSAGE_FROM_HMODULE标志。&lt;/P&gt;
&lt;P&gt;5、致命错误退出&lt;/P&gt;
&lt;P&gt;函数 FatalAppExit显示一个消息框，然后在用户关闭消息框后中止当前程序。由于它不释放内存和程序打开的文件，所以尽量少使用。&lt;/P&gt;
&lt;P&gt;二、编写错误信息的规则&lt;/P&gt;
&lt;P&gt;定义这个规则的目的是为了有助于写出更加清楚的、更有帮助性的错误信息。&lt;/P&gt;
&lt;P&gt;特别是，规则强调解决如下三方面的常见问题：&lt;/P&gt;
&lt;P&gt;(1) 用户常常误解弹出的错误。&lt;BR&gt;(2) 管理员很难理解某些事件记录信息&lt;BR&gt;(3) 因为对错误信息的误解导致技术支持过多。&lt;/P&gt;
&lt;P&gt;1、基本规则&lt;/P&gt;
&lt;P&gt;(1)不要人性化。从不假设程序或者硬件自己会思考或者感觉。下面给出一个例子：&lt;BR&gt;正确的：&lt;BR&gt;Node [node name] cannot use Windows protocols.&lt;BR&gt;错误的：&lt;BR&gt;Node [node name] does not speak any of our protocols. &lt;/P&gt;
&lt;P&gt;(2)避免使用 &amp;#8220;错误&amp;#8221;(Bad)这个词。尝试找到更具说明性的词汇，告诉用户那里出了问题。&lt;/P&gt;
&lt;P&gt;例如，避免下列信息: &lt;BR&gt;bad link&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 连接错误&lt;BR&gt;bad size&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 尺寸错误&lt;BR&gt;bad argument&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 参数错误&lt;BR&gt;bad address&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 地址错误&lt;BR&gt;bad CRC&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // CRC错误&lt;/P&gt;
&lt;P&gt;(3)使用完整的句子。&lt;/P&gt;
&lt;P&gt;例如，使用 "Binding is too long." 而不是 "Binding too long." &lt;/P&gt;
&lt;P&gt;(4)在信息字符串的开始使用关键词。避免用冠词(the, a, 或者an)开头。永远不要用占位符号开头，例如 %1 或者 %2 。因为不利于用户查找错误。&lt;/P&gt;
&lt;P&gt;例如：&lt;BR&gt;正确的&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;错误的&lt;BR&gt;Log file %1 is full. &amp;nbsp;&amp;nbsp;%1 log file is full. &lt;BR&gt;Listen failed in %1. &amp;nbsp;&amp;nbsp;%1: Listen failed.&amp;nbsp; &lt;BR&gt;Computer name %1 is a &amp;nbsp;&amp;nbsp;%1 is a domain controller of domain %2. &lt;BR&gt;domain controller of domain %2. &lt;/P&gt;
&lt;P&gt;2、可查找的信息&lt;/P&gt;
&lt;P&gt;把与错误有关的词放在信息字符串的开头。&lt;/P&gt;
&lt;P&gt;例如：&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;正确&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;错误&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Recycle Bin cannot store &amp;nbsp;&amp;nbsp;Some of the items you are about to&lt;BR&gt;some of the items you are &amp;nbsp;&amp;nbsp;delete cannot be stored in the Recycle Bin. &lt;BR&gt;about to delete.&amp;nbsp; &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Endpoint was not found. &amp;nbsp;&amp;nbsp;No endpoint was found. &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Move you specified requires &amp;nbsp;&amp;nbsp;That move requires moving text cards. &lt;BR&gt;moving text cards.&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Computer name as entered is not valid.&amp;nbsp;The computer name you typed is invalid.&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;或者 Invalid computer name.&amp;nbsp; &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Chat application cannot start. &amp;nbsp;&amp;nbsp;The application cannot start in Chat component. &lt;BR&gt;---------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;3、风格上的考虑&lt;/P&gt;
&lt;P&gt;用户通常喜欢使用简洁的表达或者过去式和主动语态的句子。例如：&lt;/P&gt;
&lt;P&gt;---------------------------------------------------------------------&lt;BR&gt;正确&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;错误&lt;BR&gt;Registry Editor cannot create &amp;nbsp;&amp;nbsp;Could not create the subkey. &lt;BR&gt;the subkey.&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Setup cannot start Program Manager. &amp;nbsp;Setup was unable to activate &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Program Manager. &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Floppy disk sector ID field does &amp;nbsp;Mismatch between the floppy disk &lt;BR&gt;not match floppy disk controller &amp;nbsp;sector ID field and the floppy &lt;BR&gt;track address.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;disk controller track address. &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;CHKDSK encountered an error during... &amp;nbsp;An error was encountered during... &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Cannot find %1. &amp;nbsp;&amp;nbsp;&amp;nbsp;Could not find %1.&amp;nbsp; &lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Modem does not respond. &amp;nbsp;&amp;nbsp;Modem not responding.&amp;nbsp; &lt;BR&gt;---------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;但是，使用主动语态和简单句型没有第二条重要，如果需要把关键词放在句子的开头，可以考虑使用被动语态和较复杂的句型。例如：&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;正确&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;错误&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Log file size cannot be adjusted. &amp;nbsp;Cannot adjust the size of the log file. &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;或者&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Was unable to adjust the size of the log file.&lt;BR&gt;---------------------------------------------------------------------&lt;BR&gt;Printing of %1 cannot resume. &amp;nbsp;&amp;nbsp;Cannot resume printing %1. &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;或者 Could not resume printing %1.&lt;BR&gt;---------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;4、信息字符串的长度&lt;/P&gt;
&lt;P&gt;是否采用冗长的信息取决于如下规则: &lt;/P&gt;
&lt;P&gt;(1)对于那些可能破坏安装过程或者导致系统崩溃的操作信息字符串，应该包含详细的解释和操作指导。例如Setup 和 STOP 之类的信息。&lt;/P&gt;
&lt;P&gt;(2)经常显示给技术性较差的用户(和管理员或者技术支持者相比)看的字符串，也可以考虑长一些。例如，打印管理器或者 IIS信息。&lt;/P&gt;
&lt;P&gt;(3)大多数情况下显示给技术用户看的信息应该短一些。信息帮助文件中的解释和相关操作可以详细说明。例如 WINS 和 TCP/IP 信息应该简洁而且中肯。&lt;/P&gt;
&lt;P&gt;5、对话框和弹出信息的规则&lt;/P&gt;
&lt;P&gt;用户常常对弹出的信息感到烦恼。这些信息出现在屏幕上，而且用户必须单击OK 或者 Cancel 按钮才能关闭，用户会感觉它们&amp;#8220;打断了&amp;#8221;什么东西，而且对下一步该如何做感到手足无措。&lt;/P&gt;
&lt;P&gt;如果要写这些类型的信息，考虑如下规则: &lt;/P&gt;
&lt;P&gt;(1)这个信息是否可以给用户一个清晰的、非技术性的解释？&lt;/P&gt;
&lt;P&gt;(2)这个信息是否包含明确的步骤或者清晰的解释，避免再次出现这样的问题？&lt;/P&gt;
&lt;P&gt;(3)如果需要一个较长的解释，指导用户去帮助文件中查找详细的信息。&lt;/P&gt;
&lt;P&gt;(4)如果是一个严重错误，记住使用事件记录，并保存错误代码。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;三、使用错误处理&lt;/P&gt;
&lt;P&gt;1、通知用户错误的例子：&lt;/P&gt;
&lt;P&gt;FlashWindow(hwnd, TRUE);&lt;BR&gt;Sleep(500);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;FlashWindow(hwnd, TRUE);&lt;BR&gt;MessageBeep(MB_ICONEXCLAMATION); &lt;/P&gt;
&lt;P&gt;2、获取最后错误码&lt;/P&gt;
&lt;P&gt;许多系统函数失败后，都会设置最终错误码（可以看函数相关文档），如果程序需要更详细的信息，可以获取这个错误码。&lt;/P&gt;
&lt;P&gt;void error(LPSTR lpszFunction) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CHAR szBuf[80]; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DWORD dw = GetLastError(); &lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; sprintf(szBuf, "%s failed: GetLastError returned %u\n", &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lpszFunction, dw); &lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; MessageBox(NULL, szBuf, "ERROR", MB_OK); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ExitProcess(dw); &lt;BR&gt;} &lt;/P&gt;
&lt;P&gt;-------------------------------&lt;BR&gt;END IWASWZQ 2005/6/15&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6922.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>第一章、调试和错误处理 [1] 调试</title><link>http://blog.vckbase.com/iwaswzq/articles/6921.html</link><pubDate>Mon, 20 Jun 2005 09:55:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/articles/6921.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6921.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/articles/6921.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6921.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6921.html</trackback:ping><description>&lt;P&gt;第一章、调试和错误处理 [1] 调试&lt;/P&gt;
&lt;P&gt;所谓调试器，是程序开发者用来观察和修改程序错误的一个程序，本文讲述 WIN32 API对调试器的支持。&lt;/P&gt;
&lt;P&gt;Win32 API提供了几个函数，可以用来构建一个基础的、事件驱动的调试器。事件驱动的意思是每次当所调试的进程出现特定事件后，就通知调试器，从而允许调试器作适当的响应。&lt;/P&gt;
&lt;P&gt;某些调试所需的函数，实际上是进程、线程、或者异常处理结构的一部分。下面介绍这些函数。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;1、支持调试的进程相关函数&lt;/P&gt;
&lt;P&gt;CreateProcess允许一个调试器启动一个进程然后调试它。参数 fdwCreate 用于给出调试操作的类型。如果设置了 DEBUG_PROCESS标志，则可以调试这个新的进程和它创建的子进程（线程）。除非创建子进程（线程）的时候没有给出这个标志。如果fdwCreate同时给出DEBUG_ONLY_THIS_PROCESS 标志，则仅仅调试当前进程。&lt;/P&gt;
&lt;P&gt;一个调试器可以调试另外一个调试器，只需创建相应的进程，并且指定 DEBUG_PROCESS 标志。新创建的被调试进程必须重新创建一个新的进程并且指定该标志。&lt;/P&gt;
&lt;P&gt;调试器可以通过 OpenProcess函数获取一个进程的id，然后函数DebugActiveProcess使用这个id将调试器和该进程相关联。一般，调试器打开进程的时候需要 PROCESS_VM_READ 和 PROCESS_VM_WRITE 标志，从而允许调试器读写该进程的虚拟内存空间（通过函数 ReadProcessMemory 和 WriteProcessMemory）&lt;/P&gt;
&lt;P&gt;2、支持调试的线程相关函数&lt;/P&gt;
&lt;P&gt;CreateThread 函数用来创建一个进程的新线程。调试器一般需要检查和改变线程寄存器的内容。要做到这一点，调试器必须用函数DuplicateHandle 复制线程的句柄，并且指定合适的访问权利(THREAD_GET_CONTEXT， THREAD_SET_CONTEXT，或者两者都用). &lt;/P&gt;
&lt;P&gt;如果一个进程具有对一个线程的合适的访问权限，则它可以通过 GetThreadContext/ SetThreadContext 访问线程的寄存器。&lt;/P&gt;
&lt;P&gt;对所调试的线程还可以设置 THREAD_SUSPEND_RESUME 权限，从而允许调试器通过函数 SuspendThread /ResumeThread 控制线程的运行。&lt;/P&gt;
&lt;P&gt;3、用于调试的异常处理函数&lt;/P&gt;
&lt;P&gt;如果一个正在被调试的进程内部出现异常，系统将异常传递给调试器，这叫做&amp;#8220;第一次通知&amp;#8221;( first-chance notification)，系统然后挂起该进程的所有线程。&lt;/P&gt;
&lt;P&gt;如果调试器没有处理这个异常，则系统开始查找合适的异常处理函数，如果无法找到，则系统再次通知调试器，这叫做&amp;#8220;第二次通知&amp;#8221;(last-chance notification)，如果调试器仍然不予处理，则系统会终止该进程。&lt;/P&gt;
&lt;P&gt;4、调试一个正在运行的进程&lt;/P&gt;
&lt;P&gt;要调试一个正在运行的进程，调试器首先要通过OpenProcess取得进程的id，然后用DebugActiveProcess将调试器和进程相关联。这种情况下，只能调试当前活动的进程，无法调试它的子进程。当然，调试器需要拥有对该进程的访问权限。当调试器和进程关联之后，系统会把该进程中（以及/或者它的任何子进程中）出现的所有调试事件通知调试器。&lt;/P&gt;
&lt;P&gt;5、写调试器的主循环&lt;/P&gt;
&lt;P&gt;调试器在它的主循环的开始调用 WaitForDebugEvent，该函数将阻塞调试器，直到有调试事件出现。调试事件出现后，系统挂起所调试进程的所有线程，然后通知调试器处理调试事件。调试器可以通过函数 SetDebugErrorLevel 设置错误处理的最小级别，控制系统所传递的调试事件。&lt;/P&gt;
&lt;P&gt;调试器可以通过下面的函数和用户交互，或者改变所调试的进程的状态。&lt;BR&gt;GetThreadContext&lt;BR&gt;GetThreadSelectorEntry&amp;nbsp;//返回指定选择子或者线程的描述表入口&lt;BR&gt;ReadProcessMemory&lt;BR&gt;SetThreadContext&lt;BR&gt;WriteProcessMemory&lt;/P&gt;
&lt;P&gt;调试器利用描述表入口完成 段偏移地址到线性虚拟地址的转换。因为ReadProcessMemory和WriteProcessMemory 需要使用线性虚拟地址。&lt;/P&gt;
&lt;P&gt;调试器会不断读取被调试的进程的内存，并把内存地址中保存的指令写入处理器的指令缓冲区。在写入指令后，调试器可以调用FlushInstructionCache 来执行缓冲区中的指令。&lt;/P&gt;
&lt;P&gt;调试器在它的主循环的末尾调用函数ContinueDebugEvent ，该函数使被调试的进程继续执行。&lt;/P&gt;
&lt;P&gt;6、和调试器通讯&lt;/P&gt;
&lt;P&gt;被调试的进程可以通过函数 OutputDebugString 发送一个字符串给调试器，它产生一个OUTPUT_DEBUG_STRING_EVENT 调试事件。进程可以通过函数 IsDebuggerPresent判断是否存在一个调试器正在调试它。&lt;/P&gt;
&lt;P&gt;函数 DebugBreak用来在当前进程中产生一个断点异常。一个断点是程序中的一个位置，程序运行到这个位置将停止运行，并允许调试器检查程序的代码、变量、以及寄存器值，然后进行修改，继续或者终止运行。如果当前这个进程没有被调试，则系统开始查找标准异常处理函数。大多数情况下，系统会报告&amp;#8220;unhandled breakpoint exception&amp;#8221;错误，并终止该进程。&lt;/P&gt;
&lt;P&gt;函数 FatalExit 中止当前进程，然后把执行权力交给调试器。与 DebugBreak不同的是，它不产生异常。但是由于它常常无法释放进程的内存或者关闭文件等资源，所以尽可能不要使用它。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;BR&gt;7、调试事件：&lt;/P&gt;
&lt;P&gt;调试事件是指导致系统通知调试器的进程内事件，包括创建进程、创建线程、加载dll，卸载dll，发送调试字符串以及产生异常等。&lt;/P&gt;
&lt;P&gt;如果调试器正在等待的时候产生了一个调试事件，则系统用描述该事件的信息填充调试器调用的WaitForDebugEvent函数中所给出的那个 DEBUG_EVENT 结构变量。 &lt;/P&gt;
&lt;P&gt;系统通知调试器的同时，它挂起所调试进程的所有线程，直到调试器调用ContinueDebugEvent才恢复该进程的运行。&lt;/P&gt;
&lt;P&gt;调试一个进程，可能出现下面的调试事件：&lt;BR&gt;-----------------------------------------&lt;BR&gt;调试事件&amp;nbsp;&amp;nbsp;&lt;BR&gt;-----------------------------------------&lt;BR&gt;CREATE_PROCESS_DEBUG_EVENT &lt;BR&gt;当在所调试进程中创建新的进程，或者调试器开始调试一个活动进程时，出现这个事件。系统在进程开始运行于用户模式之前产生这个事件。DEBUG_EVENT 结构包含一个 CREATE_PROCESS_DEBUG_INFO结构，里面包含新进程的句柄，进程影像文件的句柄，进程初始线程的句柄以及其它进程相关信息。&lt;BR&gt;进程句柄具有PROCESS_VM_READ和PROCESS_VM_WRITE访问权利。如果调试器拥有对线程的这些访问权限，则它可以通过ReadProcessMemory 和 WriteProcessMemory&amp;nbsp; 读写进程的内存。&lt;BR&gt;进程影像文件句柄具有GENERIC_READ访问权利。&lt;BR&gt;初始线程句柄具有THREAD_GET_CONTEXT，THREAD_SET_CONTEXT，和THREAD_SUSPEND_RESUME访问权利。如果调试器拥有对线程的这些访问权限，则它可以通过GetThreadContext和SetThreadContext 读写线程的寄存器，通过SuspendThread 和 ResumeThread 挂起和恢复线程。&lt;BR&gt;-----------------------------------------&lt;BR&gt;CREATE_THREAD_DEBUG_EVENT &lt;BR&gt;&amp;nbsp;当所调试的进程中创建一个线程或者调试器开始调试活动进程的时候，当线程开始进入user模式之前，产生这个事件。&lt;/P&gt;
&lt;P&gt;DEBUG_EVENT 结构包含一个CREATE_THREAD_DEBUG_INFO 结构，给出新的线程的句柄以及线程的起始地址。句柄具有THREAD_GET_CONTEXT，THREAD_SET_CONTEXT和THREAD_SUSPEND_RESUME访问权利。如果调试器拥有对线程的这些访问权限，则它可以通过GetThreadContext和SetThreadContext 读写线程的寄存器，通过SuspendThread 和 ResumeThread 挂起和恢复线程。&lt;/P&gt;
&lt;P&gt;-----------------------------------------&lt;BR&gt;EXCEPTION_DEBUG_EVENT &lt;/P&gt;
&lt;P&gt;当所调试的进程中出现异常的时候，产生这个事件，可能的异常包括非法内存访问、执行断点、除以0或者其他结构化异常。&amp;nbsp; &lt;BR&gt;DEBUG_EVENT结构包含一个EXCEPTION_DEBUG_INFO结构，里面给出了导致该事件的异常信息。除了标准异常外，在调试控制台进程的过程中，如果输入ctrl+c，系统会产生一个DBG_CONTROL_C异常，这个异常通常不是给程序处理的，它是专门给控制台程序的调试器的。程序一般不要使用异常处理函数来处理这个异常。&lt;/P&gt;
&lt;P&gt;如果进程没有被调试，或者调试器没有处理DBG_CONTROL_C （通过命令 gn），则系统会搜索程序的异常处理函数表，详细参考SetConsoleCtrlHandler函数。&lt;/P&gt;
&lt;P&gt;如果调试器处理了DBG_CONTROL_C (通过gh命令)，则程序不会收到 ctrl+c。除非程序这么做：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;while ((inputChar = getchar()) != EOF) ...&lt;BR&gt;或者&lt;BR&gt;&amp;nbsp;while (gets(inputString)) ...&lt;/P&gt;
&lt;P&gt;调试器无法停止这样的读循环等待。&lt;BR&gt;----------------------------------------&lt;BR&gt;EXIT_PROCESS_DEBUG_EVENT &lt;BR&gt;&amp;nbsp;&lt;BR&gt;当所调试进程的最后一个线程退出后，在内核卸载进程的dll，并且更新进程的退出码之后，产生这个事件。&lt;/P&gt;
&lt;P&gt;DEBUG_EVENT结构包含一个EXIT_PROCESS_DEBUG_INFO结构，里面给出进程的退出码（返回值）&lt;/P&gt;
&lt;P&gt;调试器收到这个事件后，删除为该进程分配的所有内部结构变量。系统关闭调试器使用的该进程的句柄，以及该进程的所有线程的句柄。&lt;BR&gt;----------------------------------&lt;BR&gt;EXIT_THREAD_DEBUG_EVENT &lt;/P&gt;
&lt;P&gt;当被调试进程的某个线程退出后，在系统更新该线程的退出码后立刻产生这个事件。&lt;BR&gt;DEBUG_EVENT结构包含一个EXIT_THREAD_DEBUG_INFO结构，给出退出码。&lt;/P&gt;
&lt;P&gt;调试器收到这个事件后，删除与该线程相关的所有内部结构变量，系统关闭调试器使用的该线程的句柄。&lt;/P&gt;
&lt;P&gt;如果这个线程是进程的最后一个线程，则不产生这个事件，而是产生EXIT_PROCESS_DEBUG_EVENT &lt;BR&gt;--------------------------------------&lt;BR&gt;LOAD_DLL_DEBUG_EVENT &lt;/P&gt;
&lt;P&gt;当一个被调试的进程加载一个DLL时，（包括隐式链接方式通过系统加载或者调用LoadLibrary进行显式加载）产生这个事件。&lt;/P&gt;
&lt;P&gt;DEBUG_EVENT结构包含一个LOAD_DLL_DEBUG_INFO 结构，给出该DLL的句柄，起始地址以及其它DLL相关信息。&lt;BR&gt;&amp;nbsp;&lt;BR&gt;一般的做法是，调试器收到这个事件后，加载该dll的符号表。&lt;BR&gt;---------------------------------------&lt;BR&gt;OUTPUT_DEBUG_STRING_EVENT &lt;/P&gt;
&lt;P&gt;当被调试的进程调用OutputDebugString函数的时候产生这个事件，DEBUG_EVENT 结构包含一个OUTPUT_DEBUG_STRING_INFO 结构，给出调试字符串的地址、长度和格式。&lt;BR&gt;------------------------------------&lt;BR&gt;UNLOAD_DLL_DEBUG_EVENT &lt;/P&gt;
&lt;P&gt;当被调试的进程使用FreeLibrary卸载一个DLL的时候，产生这个事件。注意只有当该DLL真正从进程的地址空间中卸载的时候(即它的使用计数为0)，才产生这个事件。 &lt;/P&gt;
&lt;P&gt;DEBUG_EVENT结构包含一个UNLOAD_DLL_DEBUG_INFO结构，给出DLL的基地址。一般的做法是，调试器收到这个事件后，卸载与该 DLL相关的符号表。&lt;/P&gt;
&lt;P&gt;当一个进程退出后，系统自动卸载它的DLL，但是不产生UNLOAD_DLL_DEBUG_EVENT事件。&lt;/P&gt;
&lt;P&gt;8、使用调试支持&lt;/P&gt;
&lt;P&gt;下面的例子代码使用WaitForDebugEvent和 ContinueDebugEvent，给出了一个简单的调试器的基本框架结构：&lt;/P&gt;
&lt;P&gt;DEBUG_EVENT DebugEv;&amp;nbsp;//调试事件信息&lt;BR&gt;DWORD dwContinueStatus = DBG_CONTINUE; //异常&lt;BR&gt;&amp;nbsp;&lt;BR&gt;for(;;) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;&lt;BR&gt;//等待调试事件出现，第二个参数给出无限期等待&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WaitForDebugEvent(&amp;amp;DebugEv, INFINITE); &lt;BR&gt;&amp;nbsp;&lt;BR&gt;//处理调试事件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; switch (DebugEv.dwDebugEventCode) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXCEPTION_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //处理异常。记住设置dwContinueStatus。该参数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //被函数 ContinueDebugEvent使用&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXCEPTION_ACCESS_VIOLATION: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第一次：传递给系统处理&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第二次：显示一个合适的错误信息&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXCEPTION_BREAKPOINT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第一次：显示当前的指令和寄存器值&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXCEPTION_DATATYPE_MISALIGNMENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第一次：传递给系统处理&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第二次：显示一个合适的错误信息&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXCEPTION_SINGLE_STEP: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第一次：更新当前指令和寄存器值的显示&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case DBG_CONTROL_C: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第一次：传递给系统处理&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 第二次：显示一个合适的错误信息&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //处理其它异常&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case CREATE_THREAD_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 根据所需，使用函数GetThreadContext&lt;BR&gt;&amp;nbsp;// 或者SetThreadContext &lt;BR&gt;&amp;nbsp;// 检查或者改变线程的寄存器&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 或者通过函数SuspendThread/ResumeThread &lt;BR&gt;&amp;nbsp;// 挂起或者恢复线程的运行。&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case CREATE_PROCESS_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 如果需要，通过函数GetThreadContext和&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // SetThreadContext 改变进程的初始线程的寄存器值，&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 通过函数ReadProcessMemory和&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // WriteProcessMemory读写进程的虚拟内存空间。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 通过函数SuspendThread /ResumeThread &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 挂起或者恢复线程的运行&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXIT_THREAD_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 显示线程的退出码&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case EXIT_PROCESS_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 显示进程的退出码&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case LOAD_DLL_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 读当前加载的dll的调试信息&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case UNLOAD_DLL_DEBUG_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 显示DLL卸载信息&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case OUTPUT_DEBUG_STRING_EVENT: &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 显示输出的调试字符串&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;BR&gt;&amp;nbsp;&lt;BR&gt;// 恢复线程的运行&lt;BR&gt;&amp;nbsp;&lt;BR&gt;ContinueDebugEvent(DebugEv.dwProcessId, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DebugEv.dwThreadId, dwContinueStatus); &lt;BR&gt;&amp;nbsp;&lt;BR&gt;} &lt;BR&gt;------------------------------------&lt;BR&gt;END IWASWZQ 2005/6/15&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6921.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>坐标系统与坐标变换</title><link>http://blog.vckbase.com/iwaswzq/articles/6407.html</link><pubDate>Sat, 11 Jun 2005 12:37:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/articles/6407.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/6407.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/articles/6407.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/6407.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/6407.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;第五章 坐标系统与坐标变换&lt;/P&gt;
&lt;P&gt;by iwaswzq 2005/1/22&lt;/P&gt;
&lt;P&gt;经常有朋友提问关于编程过程中遇到的坐标变换问题。我抽了点时间从msdn摘译了一些东西，并加了一些自己的理解，希望能有助于对程序中坐标变换的理解。鉴于我水平有限，可能某些概念的理解有些错误或者解释不够准确，欢迎指正。不足的地方，以后有时间会继续丰富此文。&lt;/P&gt;
&lt;P&gt;win32程序使用坐标系统之间的变换完成图形的缩放、旋转、平移等输出操作。win32下面总共使用四个坐标空间：世界坐标系、页面坐标系、设备坐标系和物理坐标系（包括客户区、桌面或打印纸等）。每个坐标空间都是一个线性空间，用两个相互垂直的坐标轴定位两维的物体。&lt;BR&gt;我们把改变一个物体的大小、方向和形状的算法称作&amp;#8220;变换&amp;#8221;。一个图形物体从一个坐标空间映射到另一个坐标空间的过程就是一个变换。最终，物体显示在一个物理设备上，通常是屏幕或者打印机。&lt;/P&gt;
&lt;P&gt;1、四个坐标系的定义&lt;/P&gt;
&lt;P&gt;坐标系&amp;nbsp;&amp;nbsp;描述 &lt;BR&gt;世界坐标系&amp;nbsp;可选，用于图形转换的起始坐标空间。最大尺寸是 2^32单位高和 2^32 单位宽。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;支持缩放、平移、旋转、变形、投射等转换操作。&lt;/P&gt;
&lt;P&gt;页面坐标系&amp;nbsp;作为世界坐标系之后的第二个坐标系使用，也可以作为变换的起始坐标系。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;最大尺寸是 2^32单位高和 2^32 单位宽。可以设置映射模式。&lt;/P&gt;
&lt;P&gt;设备坐标系&amp;nbsp;用于页面坐标系之后。仅仅允许平移操作。保证设备坐标系的原点位于正确的&lt;BR&gt;&amp;nbsp;&amp;nbsp;物理设备空间中合适的位置上。最大尺寸是2^27单位高和 2^27单位宽。&lt;BR&gt;&amp;nbsp; &lt;BR&gt;物理坐标系&amp;nbsp;图形变换后的最终的输出空间。通常指程序窗口的客户区。也可以是整个桌面、&lt;BR&gt;&amp;nbsp;&amp;nbsp;整个窗口区域或者打印机、绘图仪的某一页，取决于程序获得的DC的句柄。物&lt;BR&gt;&amp;nbsp;&amp;nbsp;理设备的尺寸取决于显卡、打印机等的设置。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;页面空间和设备空间一起工作，在这两个空间下，程序可以使用设备相关的单位，例如毫米和英寸。但是在世界坐标系和页面坐标系下，都认为是逻辑坐标系，单位是逻辑单位1。&lt;/P&gt;
&lt;P&gt;任何程序的绘图代码中使用的坐标都是从世界坐标系开始，直到物理坐标系，最后得到（看到）输出结果。每两个坐标系之间，系统都采用一种变换方法，从前面的坐标空间复制（或者映射）一个矩形区域到下一个坐标空间。为了便于处理，如果程序调用了SetWorldTransform函数，则这个映射过程从世界坐标系开始，否则从页面坐标系开始。用图形表示如下：&lt;/P&gt;
&lt;P&gt;+-----------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------------+&lt;BR&gt;| 绘图代码&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; (窗口)&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; (视口)&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | (屏幕/打印机等)&amp;nbsp; |&lt;BR&gt;| 世界坐标系| ---〉 | 页面坐标系 | -----〉| 设备坐标系 | ----〉|&amp;nbsp;&amp;nbsp; 物理坐标系&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;+-----------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +------------------+&lt;/P&gt;
&lt;P&gt;举个例子说，如果我们在DC上面有个画线函数:&lt;/P&gt;
&lt;P&gt;&amp;nbsp; MoveTo(hDC, 0, 0);&lt;BR&gt;&amp;nbsp; LineTo(hDC, 10,10);&lt;/P&gt;
&lt;P&gt;则如果我们没有使用SetWordTransform函数，可以认为我们的画线操作就是在页面坐标系下面，0，10都是页面坐标系下的坐标。如果我们使用了SetWorldTransform函数，则画线操作是在世界坐标系下面。页面坐标系中对应的这个线段是世界坐标系下面的线段(0,0) -&amp;gt; (10,10) 通过两个坐标空间之间的变换矩阵变换得到，结果可能还是(0,0)点到(10,10)点，也可能是(5,9)点到(-20,14)点，这取决于两个坐标系之间的变换矩阵。&lt;/P&gt;
&lt;P&gt;同样道理，对于上述四个坐标系，当系统从一个坐标系中复制指定矩形区域内的某个点到下一个坐标系时，它使用这两个坐标系之间的变换算法，根据点的原坐标计算得到点的像坐标。因此，一个图形在不同坐标系下，其尺寸、方向和形状都可能不同。注意一点，虽然这个变换是两个坐标系之间的，是针对物体整体而言的变换，但在系统在操作的时候，是逐点、逐行操作的。&lt;/P&gt;
&lt;P&gt;虽然很少用到SetWorldTransform函数，但是应该掌握最基本的坐标系之间的线性变换矩阵，形式如下：&lt;/P&gt;
&lt;P&gt;| eM11 eM12 0 | &lt;BR&gt;| eM21 eM22 0 |&amp;nbsp;&amp;nbsp; 即： x' = x * eM11 + y * eM21 + eDx, &lt;BR&gt;| eDx&amp;nbsp; eDy&amp;nbsp; 1 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; y' = x * eM12 + y * eM22 + eDy,&lt;/P&gt;
&lt;P&gt;函数采用逻辑单位，缺省的变换矩阵是单位阵：&lt;BR&gt;|&amp;nbsp; 1&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 0&amp;nbsp; |&lt;BR&gt;|&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp; 0&amp;nbsp; |&amp;nbsp;&amp;nbsp; 变换关系就是 x' = x ;&amp;nbsp;&amp;nbsp; y' = y ; 相当于没有变换&lt;BR&gt;|&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 1&amp;nbsp; |&lt;/P&gt;
&lt;P&gt;四个eM参数给出旋转和缩放变换系数，eDx/eDy给出平移变换系数。&lt;/P&gt;
&lt;P&gt;注意SetWorldTransform函数要求DC的图形模式是GM_ADVANCED，可以用SetGraphicsMode设置，仅Windows NT/ 2000下面支持。缺省图形模式是GM_COMPATIBLE，兼容16位windows，这个模式下不能使用该函数。下面简单介绍这两个坐标系之间的变换矩阵。&lt;/P&gt;
&lt;P&gt;2、世界坐标系到页面坐标系的变换&lt;/P&gt;
&lt;P&gt;（1）平移。物体上每个点进行水平和垂直的移动，eDx 和eDy 参数分别给出移动的尺寸。具体算法是：&lt;/P&gt;
&lt;P&gt;x' = x + Dx &lt;BR&gt;y' = y + Dy &lt;BR&gt;其中 x'，y' 是新的坐标， x，y 是源坐标。 Dx是水平移动距离，Dy是垂直移动距离。 &lt;/P&gt;
&lt;P&gt;平移矩阵是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |1&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 0| &lt;BR&gt;|x' y' 1| = |x y 1| * |0&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |Dx&amp;nbsp; Dy&amp;nbsp; 1| &lt;BR&gt;（2）缩放。组成物体的每个水平行或者垂直行进行拉伸或者压缩。算法公式是：&lt;/P&gt;
&lt;P&gt;y' = y * Dy &lt;BR&gt;x' = x * Dx &lt;BR&gt;其中 Dy，Dx是缩放系数。用矩阵表示为：&lt;/P&gt;
&lt;P&gt;|x' y' 1| = |x y 1| * |Dx&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 0|&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |0&amp;nbsp;&amp;nbsp; Dy&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |0&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp; 1|&lt;/P&gt;
&lt;P&gt;（3）旋转。组成物体的每个点都相对于坐标原点旋转一个角度。算法公式是：&lt;/P&gt;
&lt;P&gt;x' = (x * cos A) - (y * sin A) &lt;BR&gt;y' = (x * sin A) + (y * cos A) &lt;/P&gt;
&lt;P&gt;A表示绕原点逆时针旋转的角度，用矩阵表示如下：&lt;/P&gt;
&lt;P&gt;|x' y' 1| = |x y 1| * | cos A&amp;nbsp;&amp;nbsp; sin A&amp;nbsp;&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |-sin A&amp;nbsp;&amp;nbsp; cos A&amp;nbsp;&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1|&lt;/P&gt;
&lt;P&gt;（4）变形。分为水平变形和垂直变形两种，举个例子说，一个矩形通过变形成为一个平行四边形。算法公式分别是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; x' = x + (Sx * y) &lt;BR&gt;&amp;nbsp; y' = y + (Sy * x) &lt;/P&gt;
&lt;P&gt;其中Sx, Sy分别是变形系数。用矩阵表示为：&lt;/P&gt;
&lt;P&gt;|x' y' 1| = |x y 1| * |&amp;nbsp; 1&amp;nbsp;&amp;nbsp; Sx&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; | Sy&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp; 0&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&amp;nbsp; 1|&lt;/P&gt;
&lt;P&gt;（5）镜像映射。例如水平翻转的公式是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; x' = &amp;#8211;x &lt;/P&gt;
&lt;P&gt;用矩阵表示是：|-1&amp;nbsp;&amp;nbsp;&amp;nbsp; 0| &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1|&lt;/P&gt;
&lt;P&gt;线性变换可以是上面几种变换中任意若干种的组合。最终的变换矩阵是一个3x3的矩阵。可以调用CombineTransform进行两种变换的组合，也可以自己按照公式计算出变换矩阵。&lt;/P&gt;
&lt;P&gt;强调一点，虽然可以通过SetWorldTransform函数调用设置世界坐标系到页面坐标系的变换矩阵，但通常情况下，在我们的程序中，图形图像的变换并不是通过系统来完成的。而是程序自己完成的，因为自己做，可以更加灵活，容易控制，效率更高。因此不推荐使用SetWorldTransfor函数，如果我们要做图形变换的显示，建议自己先用变换算法计算好，然后直接在页面坐标系下面作图。&lt;/P&gt;
&lt;P&gt;3、页面坐标系到设备坐标系之间的变换&lt;/P&gt;
&lt;P&gt;这个变换决定了与特定DC相联系的映射模式，影响该DC上的所有图形输出。映射模式本身就是一个缩放变换，决定了画图操作中一个单位的尺寸，映射模式也可以用于平移变换，某些情形下，映射模式会改变x,y轴的坐标原点。首先来了解几个映射模式：&lt;/P&gt;
&lt;P&gt;（1）映射模式说明&lt;/P&gt;
&lt;P&gt;-------------------------------------------------------------------------&lt;BR&gt;映射模式&amp;nbsp;描述&lt;BR&gt;-------------------------------------------------------------------------&lt;BR&gt;MM_ANISOTROPIC&amp;nbsp;每个页面空间的单位映射为程序定义的设备空间的单位。两个坐标&lt;BR&gt;&amp;nbsp;&amp;nbsp;轴的缩放尺寸可以不一致(例如，一个世界坐标系下面的园在指定&lt;BR&gt;&amp;nbsp;&amp;nbsp;设备上可能显示为椭圆)。坐标轴的方向也是程序定义的。&lt;/P&gt;
&lt;P&gt;MM_HIENGLISH&amp;nbsp;每个页面空间的单位映射成设备空间中的0.001英寸。x轴向右，y轴向上。&lt;/P&gt;
&lt;P&gt;MM_HIMETRIC&amp;nbsp;每个页面空间的单位映射成设备空间中的0.01毫米。x轴向右，y轴向上。&lt;BR&gt;&amp;nbsp;&lt;BR&gt;MM_ISOTROPIC &amp;nbsp;每个页面空间的单位映射为程序定义的设备空间的单位。两个坐标轴&lt;BR&gt;&amp;nbsp;&amp;nbsp;的缩放尺寸一样。坐标轴的方向由程序定义。&lt;/P&gt;
&lt;P&gt;MM_LOENGLISH &amp;nbsp;每个页面空间的单位映射成设备空间中的0.01英寸。x轴向右，y轴向上。&lt;/P&gt;
&lt;P&gt;MM_LOMETRIC &amp;nbsp;每个页面空间的单位映射成设备空间中的0.1毫米。x轴向右，y轴向上。&lt;/P&gt;
&lt;P&gt;MM_TEXT &amp;nbsp;每个页面空间的单位映射成一个像素。就是说无缩放。如果也没有平移变换，&lt;BR&gt;&amp;nbsp;&amp;nbsp;则本映射模式下的页面空间和物理设备坐标空间等价。x轴向右，y轴向下。&lt;/P&gt;
&lt;P&gt;MM_TWIPS &amp;nbsp;每个页面空间的单位映射成打印机点的1/20(1/1440英寸)。x轴向右，y轴向上。&lt;BR&gt;------------------------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;要设置映射模式，调用SetMapMode，要获得当前的映射模式，调用GetMapMode.&lt;/P&gt;
&lt;P&gt;页面空间到设备空间的变换涉及到窗口或者视口中的点，从这个意义上讲，窗口反映了页面空间中的逻辑坐标系统，而视口代表设备空间的设备坐标系统。窗口和视口都包含一个坐标原点和x/y轴。窗口中使用的参数是逻辑坐标，视口中使用的参数是设备坐标（像素）。系统根据坐标原点生成变换矩阵。这就意味着，窗口和视口分别负责给出从页面坐标空间到设备坐标空间映射变换矩阵的一半参数。&lt;/P&gt;
&lt;P&gt;根据窗口和视口的坐标轴尺寸（最大坐标值），可以建立一个比例或者缩放系数，用于页面空间到设备空间的变换。对于上面六种预定一映射模式，当调用SetMapMode函数的时候，坐标轴的最大尺寸是由系统设置的，无法更改。其他两种映射模式MM_ISOTROPIC和 MM_ANISOTROPIC下，需要定义坐标轴的最大尺寸，因此调用SetMapMode之后，必须调用SetWindowExtEx和SetViewportExtEx进行设置。特别是在MM_ISOTROPIC映射模式下。必须注意先调用SetWindowExtEx然后调用SetViewportExtEx。&lt;/P&gt;
&lt;P&gt;根据窗口和视口的坐标原点，可以建立页面空间到设备空间的线性变换的平移关系。通过函数SetWindowOrgEx和SetViewportOrgEx来设置原点。坐标原点和坐标轴的尺寸没有关系，因此任何映射模式下都可以设置坐标原点，改变映射模式也不会影响当前的坐标原点。由于这两个函数是相关的，所以通常使用一个即可，不必两个都调用。记住一点，无论是否调用这两个函数，设备坐标(0,0)永远是左上角。也可以用函数OffsetWindowOrgEx或者OffsetViewportOrgEx改变坐标原点。下面的公式给出了页面坐标空间到设备坐标空间之间的点的映射关系：&lt;/P&gt;
&lt;P&gt;Dx = ((Lx - WOx) * VEx / WEx) + VOx &lt;/P&gt;
&lt;P&gt;Dx&amp;nbsp;&amp;nbsp;&amp;nbsp; 设备空间中的点（或者说单位）&lt;BR&gt;Lx&amp;nbsp;&amp;nbsp;&amp;nbsp; 逻辑单位 x (或者说页面空间中的单位) &lt;BR&gt;WOx&amp;nbsp;&amp;nbsp; 窗口的 x 原点 &lt;BR&gt;VOx&amp;nbsp;&amp;nbsp; 视口中 x 原点&lt;BR&gt;WEx&amp;nbsp;&amp;nbsp; 窗口的 x轴尺寸 &lt;BR&gt;VEx&amp;nbsp;&amp;nbsp; 视口的 x轴尺寸 &lt;/P&gt;
&lt;P&gt;对于y方向的公式也是一样的。&lt;/P&gt;
&lt;P&gt;函数LPtoDP 和 DPtoLP可以用来完成两个坐标空间点的变换的计算。&lt;/P&gt;
&lt;P&gt;（2）关于预定义映射模式&lt;/P&gt;
&lt;P&gt;6种预定义映射模式中，只有MM_TEXT是设备相关的，其余都是设备无关的。缺省的映射模式是MM_TEXT，即一个逻辑单位等于一个像素。逻辑到设备的映射仅仅是一个平移关系，和程序设置的窗口和视口的坐标原点有关。视口和窗口的坐标轴尺寸都设置成1，从而形成一一映射。如果程序要显示准确的几何图形，可以使用MM_LOENGLIST模式，保证图形的形状在任何显示器和打印机下面都是一样的。如果仍然使用MM_TEXT，则可能在VGA显示器上面显示的一个圆，到了EGA显示器下就变成椭圆了，如果用300dpi的激光打印机输出，则结果是很小的一个圆。&lt;/P&gt;
&lt;P&gt;（3）自定义映射模式&lt;/P&gt;
&lt;P&gt;MM_ISOTROPIC 和 MM_ANISOTROPIC两个映射模式用于自定义。MM_ISOTROPIC可以保证逻辑单位在x和y方向是一致的。MM_ANISOTROPIC下允许设置成不同。例如一个 CAD 或者绘图程序可以用MM_ISOTROPIC模式，将逻辑单位设置成工程常用的1/64英寸，代码如下：&lt;/P&gt;
&lt;P&gt;SetMapMode(hDC, MM_ISOTROPIC); &lt;BR&gt;SetWindowExtEx(hDC, 64, 64, NULL); &lt;BR&gt;SetViewportExtEx(hDC, GetDeviceCaps(hDC, LOGPIXELSX), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GetDeviceCaps(hDC, LOGPIXELSY), NULL); &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;4、设备坐标空间到物理设备的变换&lt;/P&gt;
&lt;P&gt;设备空间到物理设备的变换在很多情况下是唯一的，是由系统控制的，主要的目的是保证设备坐标系的原点准确映射到物理设备的合适位置。比如屏幕上面显示的某个程序，窗口显示的位置要和窗口矩形在显存中的位置对应起来，移动窗口就是改变这个控制窗口输出的矩形在显存中的起点，反映在显示器屏幕上面就是程序窗口的移动。如果是在打印机dc上面画图，则物理设备就是纸张了。这个变换通常是由系统负责控制的。因此没有函数用于设置这个变换，也没有函数获取相关的变换数据。&lt;/P&gt;
&lt;P&gt;5、缺省的变换&lt;/P&gt;
&lt;P&gt;如果程序创建了一个DC，并立刻开始调用GDI绘图函数，使用的变换过程就是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; 缺省的页面空间 -〉设备空间 -〉客户区(物理设备空间)的变换。&lt;/P&gt;
&lt;P&gt;除非程序调用SetGraphicsMode 和SetWorldTransform函数，否则世界坐标空间-〉页面坐标空间是单位变换，可以认为没有变换。&lt;/P&gt;
&lt;P&gt;页面空间-〉设备空间在MM_TEXT模式下是一一映射，即给定页面空间中的一点，对应设备空间中的相同点。前面已经指出，这个变换不是通过一个矩阵，而是通过用视口的宽度/窗口的宽度，视口的高度/窗口的高度两个公式来计算的。缺省情况下，视口的尺寸是1X1像素。窗口的尺寸是1X1页。&lt;/P&gt;
&lt;P&gt;设备空间-〉物理设备（客户区，桌面或者打印机）的变换通常也是一一映射，即设备空间中的一点对应客户区或者打印机输出中的一个单位，目的是保证无论程序窗口如何在屏幕上面移动，最终的输出始终准确地反映设备空间中的图形。&lt;/P&gt;
&lt;P&gt;注意MM_TEXT模式比较特殊，它的Y坐标轴是向下的，其它映射模式的Y坐标轴都是向上的。&lt;/P&gt;
&lt;P&gt;6、下面用一个例子考察每个坐标变换函数的意义。用classwizard生成一个sdi工程，视类选择从CScrollView派生，然后添加如下代码：&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;//用绿色填充一个圆形区域(中心[200,200],半径150)&lt;BR&gt;&amp;nbsp;CRect rect;&lt;BR&gt;&amp;nbsp;rect.SetRect(50,50,350,350);&lt;BR&gt;&amp;nbsp;CBrush bru;&lt;BR&gt;&amp;nbsp;bru.CreateSolidBrush(RGB(0,127,0));&lt;BR&gt;&amp;nbsp;CBrush *pBrushOld = pDC-&amp;gt;SelectObject(&amp;amp;bru);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;Ellipse(&amp;amp;rect);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;SelectObject(pBrushOld);&lt;BR&gt;&amp;nbsp;bru.DeleteObject();&lt;BR&gt;&amp;nbsp;//输出坐标原点&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;TextOut(0,0,"(0,0)");&lt;BR&gt;&amp;nbsp;//画出坐标轴&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(0,0);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(500,0); //x轴&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(490,5); //箭头&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(500,0);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(490,-5);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(0,0);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(0,500); //y轴&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(5,490);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(0,500);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(-5,490);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnInitialUpdate()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;CScrollView::OnInitialUpdate();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;CSize sizeTotal;&lt;BR&gt;&amp;nbsp;//设置整个窗口尺寸为1000x1000&lt;BR&gt;&amp;nbsp;sizeTotal.cx = sizeTotal.cy = 1000;&lt;BR&gt;&amp;nbsp;SetScrollSizes(MM_TEXT, sizeTotal);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;实际上我们所有的画图操作都是在世界坐标系下面，由于没有使用SetWorldTransform，所以世界坐标系和页面坐标系等价，可以认为我们的画图操作就是在页面坐标系中。下面分别添加相应的函数调用，考察每个函数对输出的影响。&lt;/P&gt;
&lt;P&gt;1、SetWindowOrg / SetViewportOrg (CDC类成员函数，对应api函数SetWindowOrgEx / SetViewportOrgEx&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;SetWindowOrg(-100,-50);//或者 pDC-&amp;gt;SetViewportOrg(100,50);&lt;BR&gt;&amp;nbsp;//&lt;BR&gt;&amp;nbsp; ...&amp;nbsp;//原来的代码&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;窗口没有滚动之前，视口坐标系的原点和窗口坐标系的原点重合，如果滚动窗口，相当于改变视口原点的位置，因此显示在屏幕上面的部分（视口里面的东西）就发生变化。现在我们不滚动窗口，而是调用SetWindowOrg改变窗口原点的坐标，看看发生的变化。&lt;/P&gt;
&lt;P&gt;SetWindowOrg(-100,-50)函数调用的意思是把窗口（页面坐标系）中的(-100,-50)点设置成窗口的原点，由于窗口和视口的原点永远是重合的，所以视口的(0,0)点现在就和窗口的(-100,-50)重合，而视口的(0,0)点就是程序客户区的左上角，因此设置的后果就是：绘图的输出向x/y轴的正方向移动了。编译运行以后可以看到，字符串"(0,0)"向右下平移了，好像向上、向左滚动了窗口一样。同样道理，如果SetWindowOry里面使用的是(100,50)，则效果等同于向下、向右滚动了窗口。&lt;BR&gt;对应图形如下：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /-----视口的原点&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (-100,-50) +-------------+&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; /-----视口的原点&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; (视口)&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; +-------------+-------+----&amp;gt;页面坐标系x&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------+-----------+-----&amp;gt;页面坐标系x&lt;BR&gt;&amp;nbsp;&amp;nbsp; |(0,0)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; | (0,0)&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; (视口)&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; | (屏幕上的)&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; | (部分&amp;nbsp;&amp;nbsp;&amp;nbsp; )&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp; 设置SetWindowOrg后&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp; ---------------------〉+----+--------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; +-------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; | 整个窗口比视口大&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp; 窗口&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; | 有些部分需要滚动&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; | 才能显示出来&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp;&amp;nbsp; +---------------------+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; +--------------------+&lt;BR&gt;&amp;nbsp;&amp;nbsp; |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; |&lt;BR&gt;&amp;nbsp; \/ Y&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; \/ Y&lt;/P&gt;
&lt;P&gt;从效果上SetWindowOrg(-100,-50)和SetViewportOrg(100,50)是等价的。但是使用一下就会发现SetViewportOrg(100,50)以后如果滚动窗口的话，窗口的刷新有些问题，所以在CScrollView里面用SetWindowOrg比较好，对于非滚动形式的窗口，使用SetViewportOrg比较直观一些。窗口对应的就是页面坐标系，也就是逻辑坐标系，视口对应的是设备坐标系。&lt;/P&gt;
&lt;P&gt;2、关于映射模式，上面的例子用的是缺省的映射模式MM_TEXT，现在改变一下映射模式，看看有什么变化。去掉设置原点的代码，加上：&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;/P&gt;
&lt;P&gt;&amp;nbsp;pDC-&amp;gt;SetMapMode(MM_LOMETRIC);&lt;BR&gt;&amp;nbsp;//用绿色填充一个圆形区域(中心[200,200],半径150)&lt;BR&gt;... //原来代码不变&lt;BR&gt;}&lt;BR&gt;运行一下看看，怎么Y坐标轴和圆都不见了，原来这个模式下面，Y轴是向上的。把程序里面的Y坐标都改成负值：&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;SetMapMode(MM_LOMETRIC);&lt;BR&gt;&amp;nbsp;//用绿色填充一个圆形区域(中心[200,200],半径150)&lt;BR&gt;&amp;nbsp;CRect rect;&lt;BR&gt;&amp;nbsp;rect.SetRect(50,-50,350,-350);&lt;BR&gt;&amp;nbsp;...&lt;BR&gt;&amp;nbsp;//用蓝色输出坐标原点&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;TextOut(0,0,"(0,0)");&lt;BR&gt;&amp;nbsp;//画出坐标轴&lt;BR&gt;&amp;nbsp;...&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(0,0);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(0,-500); //y轴&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(5,-490);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;MoveTo(0,-500);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LineTo(-5,-490);&lt;BR&gt;}&lt;BR&gt;运行一看，OK都出来了，但是尺寸比原来小多了。原来每个逻辑单位被映射成0.1毫米。那么圆的直径是300，应该对应30毫米，用尺子在屏幕上面量一下吧，几乎就是30毫米啊:)。不相信，设置成MM_HIMETRIC，天啊，看不到了，可能太小了？把圆的半径加大：&lt;/P&gt;
&lt;P&gt;rect.SetRect(50,-50,1350,-1350); &lt;/P&gt;
&lt;P&gt;嗯，出来了，直径好像是1300*0.01 = 13毫米。既然这样MM_HIENGLISH和MM_LOENGLISH以及MM_TWIPS就不测试了。需要注意一点，SetMapMode函数调用后，仅仅影响后续的画图函数，而它前面的画图函数仍然按照原来的映射模式输出。所以同一个绘图函数中，可以调用多次SetMapMode改变映射模式，比如绘图单位在英寸和厘米之间，绘图的精度在0.01厘米和0.1厘米之间可以时刻根据需要进行切换。&lt;/P&gt;
&lt;P&gt;3、SetWindowExt 和 SetViewportExt。由于它们仅仅在MM_ISOTROPIC 模式下有效，所以这样做：&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;SetMapMode(MM_ISOTROPIC&amp;nbsp; );&lt;BR&gt;&amp;nbsp;CSize sizeOrg = pDC-&amp;gt;SetWindowExt(200,100);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//查看返回值可以发现SetViewportExt返回的是当前屏幕设置的分辨率1024 x 768，不过y是负值&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //因为MM_ISOTROPIC模式下，Y轴是向下的。所以记得所有画图代码中的Y坐标用正值！&lt;BR&gt;&amp;nbsp;sizeOrg =pDC-&amp;gt;SetViewportExt(200,100); &lt;/P&gt;
&lt;P&gt;&amp;nbsp;...//画图代码&lt;BR&gt;}&lt;BR&gt;通过改变两个Set函数中的参数值，发现系统自动管理x/y的比率关系，使圆形保持正确形状。而且图形的大小和参数有关：&lt;BR&gt;假设SetWindowExt(xWin,yWin); SetViewportExt(xView,yView);则系统使用xView/xWin , yView/yWin 两个比值中较小的一个作为x/y两个方向共同的压缩比例。最后图形的大小仅仅和这个缩放系数有关。如果两个系数都大于1，则系统使用1:1比例，并不放大图形。&lt;/P&gt;
&lt;P&gt;4、DPtoLP 和 LPtoDP 。这两个函数用于逻辑坐标和设备坐标之间的转换。在MM_TEXT模式下两个坐标是一样的。现在设置成MM_LOMETRIC模式，看看它们的作用。&lt;/P&gt;
&lt;P&gt;void CSdiscroView::OnDraw(CDC* pDC)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;//每个逻辑单位对应0.1毫米设备单位&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;SetMapMode(MM_LOMETRIC );&lt;/P&gt;
&lt;P&gt;&amp;nbsp;CPoint p(100,200);&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;DPtoLP(&amp;amp;p,1);&lt;BR&gt;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;str.Format(" Use DPtoLP : (100,200) -&amp;gt; (%d,%d)\n",p.x,p.y);&lt;BR&gt;&amp;nbsp;TRACE(str);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;p.x = 100; p.y = 200;&lt;BR&gt;&amp;nbsp;pDC-&amp;gt;LPtoDP(&amp;amp;p,1);&lt;BR&gt;&amp;nbsp;str.Format(" Use LPtoDP : (100,200) -&amp;gt; (%d,%d)\n",p.x,p.y);&lt;BR&gt;&amp;nbsp;TRACE(str);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//调试窗口输出&lt;BR&gt;Use DPtoLP : (100,200) -&amp;gt; (313,-625)&lt;BR&gt;Use LPtoDP : (100,200) -&amp;gt; (32,-64)&lt;/P&gt;
&lt;P&gt;可见设备坐标系中的(100,200)对应的是逻辑坐标系（窗口）中的(313,-625)一点，逻辑坐标系下面的(100,200)对应设备坐标系下面的(32,-64)点。注意这个变换结果是设备相关的。对于不同的dc得到不同的结果。设置相同的都用屏幕dc，用不同计算机测试，不同的显示器，不同的显示模式设置也会得到不同的变换结果。&lt;/P&gt;
&lt;P&gt;这是什么意思呢？就是说窗口中，逻辑坐标(313,-625)在MM_LOMETRIC模式下，对应设备坐标系中X方向距离原点31.3毫米，Y方向距离原点62.5毫米的一个点，那个点在设备坐标系中坐标是(100,200),其实就是MM_TEXT模式下，逻辑坐标系下面的那个(100,200)点。同样道理，逻辑坐标(100,200)点，映射到设备坐标系中，是x轴方向距离原点10mm，y轴方向距离原点-20mm的点（注意方向），那个点的逻辑坐标是(32,-64)。也就是MM_TEXT模式下逻辑坐标系中的(32,-64)点。&lt;/P&gt;
&lt;P&gt;最后要说明一点，OnEraseBkgnd(CDC* pDC)里面的DC和OnDraw(CDC* pDC)里面的DC有所不同啊。窗口的滚动对前者没有影响，也就是说无论窗口如何滚动，在OnEraseBkgnd函数中输出的东西永远在视口固定的位置上，不受滚动影响。所以画图的时候，不要把背景和前景混淆了，什么函数就是干什么工作。&lt;/P&gt;
&lt;P&gt;-------------------------------------------------------------&lt;BR&gt;End. iwaswzq 2005/1/22&lt;BR&gt;-------------------------------------------------------------&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/6407.html" width = "1" height = "1" /&gt;</description></item></channel></rss>