﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><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>C++博客-漫步者-文章分类-Windows编程</title><link>http://www.cppblog.com/Walker/category/15672.html</link><description>先学会转文章，在仔细读文章，最后自己写点东西........</description><language>zh-cn</language><lastBuildDate>Sun, 01 Apr 2012 04:38:49 GMT</lastBuildDate><pubDate>Sun, 01 Apr 2012 04:38:49 GMT</pubDate><ttl>60</ttl><item><title>How to: Convert Between Various String Types</title><link>http://www.cppblog.com/Walker/articles/147908.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 01 Jun 2011 22:43:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/147908.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/147908.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/147908.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/147908.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/147908.html</trackback:ping><description><![CDATA[<a href="http://msdn.microsoft.com/en-us/library/ms235631.aspx">http://msdn.microsoft.com/en-us/library/ms235631.aspx</a><br /><pre>// convert_from_char.cpp
// compile with: /clr /link comsuppw.lib

#include &lt;iostream&gt;
#include &lt;stdlib.h&gt;
#include &lt;string&gt;

#include "atlbase.h"
#include "atlstr.h"
#include "comutil.h"

using namespace std;
using namespace System;

int main()
{
    // Create and display a C style string, and then use it 
    // to create different kinds of strings.
    char *orig = "Hello, World!";
    cout &lt;&lt; orig &lt;&lt; " (char *)" &lt;&lt; endl;

    // newsize describes the length of the 
    // wchar_t string called wcstring in terms of the number 
    // of wide characters, not the number of bytes.
    size_t newsize = strlen(orig) + 1;

    // The following creates a buffer large enough to contain 
    // the exact number of characters in the original string
    // in the new format. If you want to add more characters
    // to the end of the string, increase the value of newsize
    // to increase the size of the buffer.
    wchar_t * wcstring = new wchar_t[newsize];

    // Convert char* string to a wchar_t* string.
    size_t convertedChars = 0;
    mbstowcs_s(&amp;convertedChars, wcstring, newsize, orig, _TRUNCATE);
    // Display the result and indicate the type of string that it is.
    wcout &lt;&lt; wcstring &lt;&lt; _T(" (wchar_t *)") &lt;&lt; endl;

    // Convert the C style string to a _bstr_t string.
    _bstr_t bstrt(orig);
    // Append the type of string to the new string
    // and then display the result.
    bstrt += " (_bstr_t)";
    cout &lt;&lt; bstrt &lt;&lt; endl;

    // Convert the C style string to a CComBSTR string.
    CComBSTR ccombstr(orig);
    if (ccombstr.Append(_T(" (CComBSTR)")) == S_OK)
    {
        CW2A printstr(ccombstr);
        cout &lt;&lt; printstr &lt;&lt; endl;
    }

    // Convert the C style string to a CstringA and display it.
    CStringA cstringa(orig);
    cstringa += " (CStringA)";
    cout &lt;&lt; cstringa &lt;&lt; endl;

    // Convert the C style string to a CStringW and display it.
    CStringW cstring(orig);
    cstring += " (CStringW)";
    // To display a CStringW correctly, use wcout and cast cstring
    // to (LPCTSTR).
    wcout &lt;&lt; (LPCTSTR)cstring &lt;&lt; endl;

    // Convert the C style string to a basic_string and display it.
    string basicstring(orig);
    basicstring += " (basic_string)";
    cout &lt;&lt; basicstring &lt;&lt; endl;

    // Convert the C style string to a System::String and display it.
    String ^systemstring = gcnew String(orig);
    systemstring += " (System::String)";
    Console::WriteLine("{0}", systemstring);
    delete systemstring;
}
</pre>
<h3 class="subHeading" xmlns="http://www.w3.org/1999/xhtml">Output</h3>
<div class="subsection" xmlns="http://www.w3.org/1999/xhtml">
<div class="LW_CodeSnippetContainer" xmlns=""><a name="CodeSpippet1"></a>
<div class="LW_CodeSnippetContainerCodeCollection">
<div class="LW_CodeSnippetToolBar">
<div style="valign: top" class="LW_CodeSnippetToolBarText"><a style="display: block" title="Copy to clipboard." href="javascript:CodeSnippet_CopyCode('CodeSnippetContainerCode1');" name="CodeSnippetname">Copy</a> </div></div>
<div id="CodeSnippetContainerCode1" class="LW_CodeSnippetContainerCode">
<div style="color: black"><pre>Hello, World! (char *)
Hello, World! (wchar_t *)
Hello, World! (_bstr_t)
Hello, World! (CComBSTR)
Hello, World! (CStringA)
Hello, World! (CStringW)
Hello, World! (basic_string)
Hello, World! (System::String)
</pre></div></div></div></div></div><img src ="http://www.cppblog.com/Walker/aggbug/147908.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-06-02 06:43 <a href="http://www.cppblog.com/Walker/articles/147908.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字符编码</title><link>http://www.cppblog.com/Walker/articles/147443.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Fri, 27 May 2011 14:54:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/147443.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/147443.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/147443.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/147443.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/147443.html</trackback:ping><description><![CDATA[<h5 class="posthead"><a id="ctl04_TitleUrl" href="http://www.cnblogs.com/KevinYang/archive/2010/06/18/1760597.html"><font color="#006ff7" size="5">关于字符编码，你所需要知道的</font></a> </h5>
<div class="postText">
<div id="cnblogs_post_body">
<p>&nbsp;</p>
<p>字符编码的问题看似很小，经常被技术人员忽视，但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识，希望对大家有所帮助。</p>
<h2>还是得从ASCII码说起</h2>
<p>&nbsp;</p>
<p>说到字符编码，不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题，后来人们发现，计算机还可以做更多的事，例如文本处理。但由于计算机只识&#8220;数&#8221;，因此人们必须告诉计算机哪个数字来代表哪个特定字符，例如65代表字母&#8216;A&#8217;，66代表字母&#8216;B&#8217;，以此类推。但是<strong><span style="color: #008000">计算机之间字符-数字的对应关系必须得一致，否则就会造成同一段数字在不同计算机上显示出来的字符不一样</span></strong>。因此美国国家标准协会ANSI制定了一个标准，规定了常用字符的集合以及每个字符对应的编号，这就是ASCII字符集（Character Set），也称ASCII码。</p>
<p>当时的计算机普遍使用8比特字节作为最小的存储和处理单元，加之当时用到的字符也很少，26个大小写英文字母还有数字再加上其他常用符号，也不到100个，因此使用7个比特位就可以高效的存储和处理ASCII码，剩下最高位1比特被用作一些通讯系统的奇偶校验。</p>
<blockquote>
<p>注意，字节代表系统能够处理的最小单位，不一定是8比特。只是现代计算机的事实标准就是用8比特来代表一个字节。在很多技术规格文献中，为了避免产生歧义，更倾向于使用8位组（Octet）而不是字节（Byte）这个术语来强调8个比特的二进制流。下文中为了便于理解，我会延用大家熟悉的&#8220;字节&#8221;这个概念。</p></blockquote>
<p><img style="display: inline" border="0" alt="ASCII table" src="http://www.joelonsoftware.com/pictures/unicode/ascii.png" width="274" height="146" /></p>
<p>ASCII字符集由95个可打印字符（0x20-0x7E）和33个控制字符（0x00-0x19，0x7F）组成。可打印字符用于显示在输出设备上，例如荧屏或者打印纸上，控制字符用于向计算机发出一些特殊指令，例如0x07会让计算机发出哔的一声，0x00通常用于指示字符串的结束，0x0D和0x0A用于指示打印机的打印针头退到行首（回车）并移到下一行（换行）。</p>
<p>那时候的字符编解码系统非常简单，就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备，只需要在ASCII字符集中依次找到字符对应的字节，然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。</p>
<h2>OEM字符集的衍生</h2>
<p>当计算机开始发展起来的时候，人们逐渐发现，ASCII字符集里那可怜的128个字符已经不能再满足他们的需求了。人们就在想，一个字节能够表示的数字（编号）有256个，而ASCII字符只用到了0x00~0x7F，也就是占用了前128个，后面128个数字不用白不用，因此很多人打起了后面这128个数字的主意。可是问题在于，很多人同时有这样的想法，但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符，却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。</p>
<p>下面这张表是IBM-PC机推出的其中一个OEM字符集，字符集的前128个字符和ASCII字符集的基本一致（为什么说基本一致呢，是因为前32个控制字符在某些情况下会被IBM-PC机当作可打印字符解释），后面128个字符空间加入了一些欧洲国家用到的重音字符，以及一些用于画线条画的字符。</p>
<p><img style="display: inline" title="IBM-PC OEM字符集" border="0" alt="IBM-PC OEM字符集" src="http://www.joelonsoftware.com/pictures/unicode/oem.png" width="271" height="209" /></p>
<p>事实上，大部分OEM字符集是兼容ASCII字符集的，也就是说，大家对于0x00~0x7F这个范围的解释基本是相同的，而对于后半部分0x80~0xFF的解释却不一定相同。甚至有时候同样的字符在不同OEM字符集中对应的字节也是不同的。</p>
<p>不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历r&#233;sum&#233;s给职员乙，结果职员乙看到的却是r<img border="0" alt="ג" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />sum<img border="0" alt="ג" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />s，因为&#233;字符在职员甲机器上的OEM字符集中对应的字节是0x82，而在职员乙的机器上，由于使用的OEM字符集不同，对0x82字节解码后得到的字符却是<img border="0" alt="ג" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />。</p>
<h2>多字节字符集（MBCS）和中文字符集</h2>
<p>上面我们提到的字符集都是基于单字节编码，也就是说，一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题，因为他们通过扩展第8个比特，就可以得到256个字符了，足够用了。但是对于亚洲国家来说，256个字符是远远不够用的。因此这些国家的人为了用上电脑，又要保持和ASCII字符集的兼容，就发明了多字节编码方式，相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码（DBCS，Double Byte Character Set）。</p>
<p>对于单字节字符集来说，代码页中只需要有一张码表即可，上面记录着256个数字代表的字符。程序只需要做简单的查表操作就可以完成编解码的过程。</p>
<blockquote>
<p>代码页是字符集编码的具体实现，你可以把他理解为一张&#8220;字符-字节&#8221;映射表，通过查表实现&#8220;字符-字节&#8221;的翻译。下面会有更详细的描述。</p></blockquote>
<p>而对于多字节字符集，代码页中通常会有很多码表。那么程序怎么知道该使用哪张码表去解码二进制流呢？答案是，<strong><span style="color: #008000">根据第一个字节来选择不同的码表进行解析</span></strong>。</p>
<p>例如目前最常用的中文字符集GB2312，涵盖了所有简体字符以及一部分其他字符；GBK（K代表扩展的意思）则在GB2312的基础上加入了对繁体字符等其他非简体字符（GB18030字符集不是双字节字符集，我们在讲Unicode的时候会提到）。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候，如果遇到字节的最高位是0的话，那么就使用936代码页中的第1张码表进行解码，这就和单字节字符集的编解码方式一致了。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_d9018f68-cc7b-46bc-8851-f8e72fb4b6f2.png" width="416" height="305" /> </p>
<p>当字节的高位是1的时候，确切的说，当第一个字节位于0x<code>81</code>&#8211;0x<code>FE之间时，根据第一个字节不同找到代码页中的相应的码表，例如当第一个字节是0x81，那么对应936中的下面这张码表：</code></p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_d246dec5-4706-4776-9dd0-f9d3c0d1ec0b.png" width="442" height="374" /> </p>
<p>（关于936代码页中完整的码表信息，参见MSDN：<a href="http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx"><font color="#006ff7">http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx</font></a>.）</p>
<p>按照936代码页的码表，当程序遇到连续字节流0x81 0x40的时候，就会解码为&#8220;丂&#8221;字符。 </p>
<h2>ANSI标准、国家标准、ISO标准</h2>
<p>不同ASCII衍生字符集的出现，让文档交流变得非常困难，因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码（注意，<strong><span style="color: #008000">我们现在通常说到ANSI编码，通常指的是平台的默认编码，例如英文操作系统中是ISO-8859-1，中文系统是GBK</span></strong>），ISO组织制定的各种ISO标准字符编码，还有各国也会制定一些国家标准字符集，例如中国的GBK，GB2312和GB18030。</p>
<p>操作系统在发布的时候，通常会往机器里预装这些标准的字符集还有平台专用的字符集，这样只要你的文档是使用标准字符集编写的，通用性就比较高了。例如你用GB2312字符集编写的文档，在中国大陆内的任何机器上都能正确显示。同时，我们也可以在一台机器上阅读多个国家不同语言的文档了，前提是本机必须安装该文档使用的字符集。</p>
<h2>Unicode的出现</h2>
<p>虽然通过使用不同字符集，我们可以在一台机器上查阅不同语言的文档，但是我们仍然无法解决一个问题：<strong><span style="color: #008000">在一份文档中显示所有字符</span></strong>。为了解决这个问题，我们需要一个全人类达成共识的巨大的字符集，这就是Unicode字符集。</p>
<h3>Unicode字符集概述</h3>
<p>Unicode字符集涵盖了目前人类使用的所有字符，并为每个字符进行统一编号，分配唯一的字符码（Code Point）。Unicode字符集将所有字符按照使用上的频繁度划分为17个层面（Plane），每个层面上有2<sup>16</sup>=65536个字符码空间。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_2d8a0259-48da-46a1-864c-b16301c745e5.png" width="556" height="224" /></p>
<p>其中第0个层面BMP，基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字，要么是留作扩展。我们平常用到的Unicode字符，一般都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。 </p>
<h3>编码系统的变化</h3>
<p>在Unicode出现之前，所有的字符集都是和具体编码方案绑定在一起的，都是直接将字符和最终字节流绑定死了，例如ASCII编码系统规定使用7比特来编码ASCII字符集；GB2312以及GBK字符集，限定了使用最多2个字节来编码所有字符，并且规定了字节序。这样的编码系统通常用简单的查表，也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子：</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_110e73b2-f02e-45f7-be70-15a93c78fcda.png" width="373" height="275" /></p>
<p>这种方式的缺点在于，字符和字节流之间耦合得太紧密了，从而限定了字符集的扩展能力。假设以后火星人入住地球了，要往现有字符集中加入火星文就变得很难甚至不可能了，而且很容易破坏现有的编码规则。</p>
<p>因此Unicode在设计上考虑到了这一点，将字符集和字符编码方案分离开。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="字符编码系统" border="0" alt="字符编码系统" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_05943adf-b8bf-47ab-b188-60144dad31ff.png" width="330" height="265" /></p>
<p>也就是说，<strong><span style="color: #008000">虽然每个字符在Unicode字符集中都能找到唯一确定的编号（字符码，又称Unicode码），但是决定最终字节流的却是具体的字符编码</span></strong>。例如同样是对Unicode字符&#8220;A&#8221;进行编码，UTF-8字符编码得到的字节流是0x41，而UTF-16（大端模式）得到的是0x00 0x41。</p>
<h3>常见的Unicode编码</h3>
<p><strong>UCS-2/UTF-16</strong></p>
<p>如果要我们来实现Unicode字符集中BMP字符的编码方案，我们会怎么实现？由于BMP层面上有2<sup>16</sup>=65536个字符码，因此我们只需要两个字节就可以完全表示这所有的字符了。</p>
<p>举个例子，&#8220;中&#8221;的Unicode字符码是0x4E2D(01001110 00101101)，那么我们可以编码为01001110 00101101（大端）或者00101101 01001110 （小端）。</p>
<p>UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示，并且编码得到的结果完全一致。不同之处在于，<strong><span style="color: #008000">UCS-2最初设计的时候只考虑到BMP字符，因此使用固定2个字节长度，也就是说，他无法表示Unicode其他层面上的字符，而UTF-16为了解除这个限制，支持Unicode全字符集的编解码，采用了变长编码，最少使用2个字节，如果要编码BMP以外的字符，则需要4个字节结对</span></strong>，这里就不讨论那么远，有兴趣可以参考维基百科：<a title="UTF-16/UCS-2" href="http://en.wikipedia.org/wiki/UTF-16/UCS-2" target="_blank"><font color="#006ff7">UTF-16/UCS-2</font></a>。</p>
<p>Windows从NT时代开始就采用了UTF-16编码，很多流行的编程平台，例如.Net，Java，Qt还有Mac下的Cocoa等都是使用UTF-16作为基础的字符编码。例如代码中的字符串，在内存中相应的字节流就是用UTF-16编码过的。</p>
<p><strong>UTF-8</strong></p>
<p>UTF-8应该是目前应用最广泛的一种Unicode编码方案。由于UCS-2/UTF-16对于ASCII字符使用两个字节进行编码，存储和处理效率相对低下，并且由于ASCII字符经过UTF-16编码后得到的两个字节，高字节始终是0x00，很多C语言的函数都将此字节视为字符串末尾从而导致无法正确解析文本。因此一开始推出的时候遭到很多西方国家的抵触，大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码，解决了这个问题。</p>
<p>UTF-8编码方案采用1-4个字节来编码字符，方法其实也非常简单。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_ecdb5f75-3393-4816-8b8b-293c3b992a13.png" width="492" height="345" /></p>
<p>（上图中的x代表Unicode码的低8位，y代表高8位） </p>
<p><strong><span style="color: #008000">对于ASCII字符的编码使用单字节，和ASCII编码一摸一样，这样所有原先使用ASCII编解码的文档就可以直接转到UTF-8编码了。对于其他字符，则使用2-4个字节来表示，其中，首字节前置1的数目代表正确解析所需要的字节数，剩余字节的高2位始终是10。例如首字节是1110yyyy，前置有3个1，说明正确解析总共需要3个字节，需要和后面2个以10开头的字节结合才能正确解析得到字符</span></strong>。</p>
<p>关于UTF-8的更多信息，参考维基百科：<a title="UTF-8" href="http://en.wikipedia.org/wiki/UTF-8" target="_blank"><font color="#006ff7">UTF-8</font></a>。</p>
<p><strong>GB18030</strong></p>
<p>任何能够将Unicode字符映射为字节流的编码都属于Unicode编码。中国的GB18030编码，覆盖了Unicode所有的字符，因此也算是一种Unicode编码。只不过他的编码方式并不像UTF-8或者UTF-16一样，将Unicode字符的编号通过一定的规则进行转换，而只能通过查表的手段进行编码。</p>
<p>关于GB18030的更多信息，参考：<a title="GB18030" href="http://en.wikipedia.org/wiki/GB18030" target="_blank"><font color="#006ff7">GB18030</font></a>。</p>
<h3>Unicode相关的常见问题</h3>
<p><strong>Unicode是两个字节吗？</strong></p>
<p>Unicode只是定义了一个庞大的、全球通用的字符集，并为每个字符规定了唯一确定的编号，具体存储为什么样的字节流，取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。</p>
<p><strong>带签名的UTF-8指的是什么意思？</strong></p>
<p>带签名指的是字节流以BOM标记开始。很多软件会&#8220;智能&#8221;的探测当前字节流使用的字符编码，这种探测过程出于效率考虑，通常会提取字节流前面若干个字节，看看是否符合某些常见字符编码的编码规则。由于UTF-8和ASCII编码对于纯英文的编码是一样的，无法区分开来，因此通过在字节流最前面添加BOM标记可以告诉软件，当前使用的是Unicode编码，判别成功率就十分准确了。但是需要注意，不是所有软件或者程序都能正确处理BOM标记，例如PHP就不会检测BOM标记，直接把它当普通字节流解析了。因此如果你的PHP文件是采用带BOM标记的UTF-8进行编码的，那么有可能会出现问题。</p>
<p><strong>Unicode编码和以前的字符集编码有什么区别？</strong></p>
<p>早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码，936代码页，实际上说的是同个东西。但是对于Unicode则不同，Unicode字符集只是定义了字符的集合和唯一编号，Unicode编码，则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已，并不是具体的编码方案。所以当需要用到字符编码的时候，你可以写gb2312，codepage936，utf-8，utf-16，但请不要写unicode（看过别人在网页的meta标签里头写charset=unicode，有感而发）。</p>
<p>&nbsp;</p>
<h2>乱码问题</h2>
<p>乱码指的是程序显示出来的字符文本无法用任何语言去解读。一般情况下会包含大量?或者�。乱码问题是所有计算机用户或多或少会遇到的问题。<span style="color: #008000"><strong>造成乱码的原因就是因为使用了错误的字符编码去解码字节流</strong></span><span style="color: #000000">，<strong><span style="color: #008000">因此当我们在思考任何跟文本显示有关的问题时，请时刻保持清醒：当前使用的字符编码是什么</span></strong>。只有这样，我们才能正确分析和处理乱码问题。</span></p>
<p>例如最常见的网页乱码问题。如果你是网站技术人员，遇到这样的问题，需要<span style="color: #000000">检查以下原因：</span></p>
<ul><li><span style="color: #000000">服务器返回的响应头Content-Type没有指明字符编码</span> </li><li>网页内是否使用META HTTP-EQUIV标签指定了字符编码 </li><li>网页文件本身存储时使用的字符编码和网页声明的字符编码是否一致 <span style="color: #000000">&nbsp;</span></li></ul>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_bff028b7-2846-4cb5-9f73-e8a7269b55c4.png" width="209" height="121" /> <img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_c878fc60-29a3-4488-85ce-1923774f692e.png" width="369" height="66" /> </p>
<p>注意，网页解析的过程如果使用的字符编码不正确，还可能会导致脚本或者样式表出错。具体细节可以参考我以前写过的文章：<a href="http://www.imkevinyang.com/2009/08/%E6%96%87%E6%A1%A3%E5%AD%97%E7%AC%A6%E9%9B%86%E5%AF%BC%E8%87%B4%E7%9A%84%E8%84%9A%E6%9C%AC%E9%94%99%E8%AF%AF.html"><font color="#006ff7">文档字符集导致的脚本错误</font></a>和<a href="http://www.imkevinyang.com/2009/11/asp-net%E9%A1%B5%E9%9D%A2%E7%9A%84%E7%BC%96%E7%A0%81%E9%97%AE%E9%A2%98.html"><font color="#006ff7">Asp.Net页面的编码问题</font></a>。</p>
<p>不久前看到某技术论坛有人反馈，WinForm程序使用Clipboard类的GetData方法去访问剪切板中的HTML内容时会出现乱码的问题，我估计也是由于WinForm在获取HTML文本的时候没有用对正确的字符编码导致的。Windows剪贴板只支持UTF-8编码，也就是说你传入的文本都会被UTF-8编解码。这样一来，只要两个程序都是调用Windows剪切板API编程的话，那么复制粘贴的过程中不会出现乱码。除非一方在获取到剪贴板数据之后使用了错误的字符编码进行解码，才会得到乱码（我做了简单的WinForm剪切板编程实验，发现GetData使用的是系统默认编码，而不是UTF-8编码）。</p>
<p>关于乱码中出现?或者�，这里需要额外提一下，<strong><span style="color: #008000">当程序使用特定字符编码解析字节流的时候，一旦遇到无法解析的字节流时，就会用?或者�来替代。因此，一旦你最终解析得到的文本包含这样的字符，而你又无法得到原始字节流的时候，说明正确的信息已经彻底丢失了，尝试任何字符编码都无法从这样的字符文本中还原出正确的信息来</span></strong>。</p>
<h2>必要的术语解释</h2>
<p><strong>字符集（Character Set）</strong>，字面上的理解就是字符的集合，例如ASCII字符集，定义了128个字符；GB2312定义了7445个字符。而<strong><span style="color: #008000">计算机系统中提到的字符集准确来说，指的是已编号的字符的有序集合（不一定是连续）</span></strong>。</p>
<p><strong>字符码（Code Point）</strong>指的就是字符集中每个字符的数字编号。例如ASCII字符集用0-127这连续的128个数字分别表示128个字符；GBK字符集使用区位码的方式为每个字符编号，首先定义一个94X94的矩阵，行称为&#8220;区&#8221;，列称为&#8220;位&#8221;，然后将所有国标汉字放入矩阵当中，这样每个汉字就可以用唯一的&#8220;区位&#8221;码来标识了。例如&#8220;中&#8221;字被放到54区第48位，因此字符码就是5448。而Unicode中将字符集按照一定的类别划分到0~16这17个层面（Planes）中，每个层面中拥有2<sup>16</sup>=65536个字符码，因此Unicode总共拥有的字符码，也即是Unicode的字符空间总共有17*65536=1114112。</p>
<p><sup></sup></p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://images.cnblogs.com/cnblogs_com/KevinYang/WindowsLiveWriter/0a35b862ad7b_1A8D/image_b8e6f3f9-ac55-4536-b096-2fa51e1edc12.png" width="218" height="199" /> </p>
<p><strong>编码</strong>的过程是将字符转换成字节流。</p>
<p><strong>解码</strong>的过程是将字节流解析为字符。</p>
<p><strong>字符编码（Character Encoding）</strong>是将字符集中的字符码映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如&#8216;A&#8217;的编号是65，用单字节表示就是0x41，因此写入存储设备的时候就是b&#8217;01000001&#8217;。GBK编码则是将区位码（GBK的字符码）中的区码和位码的分别加上0xA0（160）的偏移（之所以要加上这样的偏移，主要是为了和ASCII码兼容），例如刚刚提到的&#8220;中&#8221;字，区位码是5448，十六进制是0x3630，区码和位码分别加上0xA0的偏移之后就得到0xD6D0，这就是&#8220;中&#8221;字的GBK编码结果。</p>
<p><strong>代码页（Code Page）</strong>一种字符编码具体形式。早期字符相对少，因此通常会使用类似表格的形式将字符直接映射为字节流，然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代码页实现GBK字符集的编码，名字虽然不一样，但对于同一汉字的编码肯定是一样的。</p>
<p><strong>大小端</strong>的说法源自《格列佛游记》。我们知道，鸡蛋通常一端大一端小，小人国的人们对于剥蛋壳时应从哪一端开始剥起有着不一样的看法。同样，计算机界对于传输多字节字（由多个字节来共同表示一个数据类型）时，是先传高位字节（大端）还是先传低位字节（小端）也有着不一样的看法，这就是计算机里头大小端模式的由来了。无论是写文件还是网络传输，实际上都是往流设备进行写操作的过程，而且这个写操作是从流的低地址向高地址开始写（这很符合人的习惯），对于多字节字来说，如果先写入高位字节，则称作大端模式。反之则称作小端模式。也就是说，大端模式下，字节序和流设备的地址顺序是相反的，而小端模式则是相同的。一般网络协议都采用大端模式进行传输。</p>
<p style="text-align: right">&#8212;&#8212;<a title="关于字符编码，你所需要知道的" href="http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html"><em><strong><font color="#006ff7">Kevin Yang</font></strong></em></a></p>
<p>参考链接：</p>
<ul><li><a title="The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" href="http://www.joelonsoftware.com/printerFriendly/articles/Unicode.html" target="_blank"><font color="#006ff7">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</font></a> </li><li><a href="http://developers.sun.com/dev/gadc/technicalpublications/articles/gb18030.html"><font color="#006ff7">http://developers.sun.com/dev/gadc/technicalpublications/articles/gb18030.html</font></a> </li><li><a href="http://en.wikipedia.org/wiki/Universal_Character_Set"><font color="#006ff7">http://en.wikipedia.org/wiki/Universal_Character_Set</font></a> </li><li><a href="http://en.wikipedia.org/wiki/Code_page"><font color="#006ff7">http://en.wikipedia.org/wiki/Code_page</font></a> </li></ul></div></div><img src ="http://www.cppblog.com/Walker/aggbug/147443.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-05-27 22:54 <a href="http://www.cppblog.com/Walker/articles/147443.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>dll</title><link>http://www.cppblog.com/Walker/articles/147052.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Tue, 24 May 2011 12:39:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/147052.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/147052.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/147052.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/147052.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/147052.html</trackback:ping><description><![CDATA[<h1>&nbsp;</h1>
<h1>Def<span style="font-family: 宋体">和</span>__declspec(dllexport)</h1>
<p><span style="font-family: 宋体">其实</span>def<span style="font-family: 宋体">的功能相当于</span>extern &#8220;C&#8221; __declspec(dllexport)<span style="font-family: 宋体">，所以它也仅能处理</span>C<span style="font-family: 宋体">函数，而不能处理重载函数。而</span>__declspec(dllexport)<span style="font-family: 宋体">和</span>__declspec(dllimport)<span style="font-family: 宋体">配合使用能够适应任何情况，因此</span>__declspec(dllexport)<span style="font-family: 宋体">是更为先进的方法。所以，目前普遍的看法是不使用</span>def<span style="font-family: 宋体">文件，我也同意这个看法。<br /><br /></p>
<h1>如何在dll中定义输出函数</h1>
<p><font size="2">总体来说有两种方法，<br />一种是添加一个def定义文件，在此文件中定义dll中要输出的函数；<br />第二种是在源代码中待输出的函数前加上__declspec(dllexport)关键字。</font></p>
<p>2)名字修饰约定 <br /><br />1、修饰名(Decoration &nbsp; name) <br /><br />&#8220;C&#8221;或者&#8220;C++&#8221;函数在内部（编译和链接）通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出&#8220;C++&#8221;重载函数、构造函数、析构函数，又如在汇编代码里调用&#8220;C&#8221;&#8221;或&#8220;C++&#8221;函数等。 <br /><br />修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。 <br /><br />2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同，下面分别说明。 <br /><br />a、C编译时函数名修饰约定规则： <br /><br />__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个&#8220;@&#8221;符号和其参数的字节数，格式为_functionname@number。 <br /><br />__cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。 <br /><br />__fastcall调用约定在输出函数名前加上一个&#8220;@&#8221;符号，后面也是一个&#8220;@&#8221;符号和其参数的字节数，格式为@functionname@number。 <br /><br />它们均不改变输出函数名中的字符大小写，这和PASCAL调用约定不同，PASCAL约定输出的函数名无任何修饰且全部大写。 <br /><br />b、C++编译时函数名修饰约定规则： <br /><br />__stdcall调用约定： <br />1、以&#8220;?&#8221;标识函数名的开始，后跟函数名； <br />2、函数名后面以&#8220;@@YG&#8221;标识参数表的开始，后跟参数表； <br />3、参数表以代号表示： <br />X--void &nbsp; ， <br />D--char， <br />E--unsigned &nbsp; char， <br />F--short， <br />H--int， <br />I--unsigned &nbsp; int， <br />J--long， <br />K--unsigned &nbsp; long， <br />M--float， <br />N--double， <br />_N--bool， <br />.... <br />PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以&#8220;0&#8221;代替，一个&#8220;0&#8221;代表一次重复； <br />4、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； &nbsp; <br />5、参数表后以&#8220;@Z&#8221;标识整个名字的结束，如果该函数无参数，则以&#8220;Z&#8221;标识结束。 <br /><br />其格式为&#8220;?functionname@@YG*****@Z&#8221;或&#8220;?functionname@@YG*XZ&#8221;，例如 <br />int &nbsp; Test1（char &nbsp; *var1,unsigned &nbsp; long）-----&#8220;?Test1@@YGHPADK@Z&#8221; <br />void &nbsp; Test2（） &nbsp; -----&#8220;?Test2@@YGXXZ&#8221; <br /><br />__cdecl调用约定： <br />规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YA&#8221;。 <br /><br />__fastcall调用约定： <br />规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YI&#8221;。 <br /><br />VC++对函数的省缺声明是 "__cedcl ",将只能被C/C++调用. <br /><br /><br />大家都应该知道，函数调用都有修饰符，有__cdecl,__stdcall,__fastcall等等，不同修饰符意味着不同的参数出栈方式以及不同的函数名称变化。 <br />对于C语言，默认是__cdecl，但如果我们要做DLL需要其它语言例如Delphi、VB等调用，通常都建议声明为__stdcall。win32的api基本都是__stdcall方式（就是那个WINAPI宏）。 <br /><br />问题就出在这个stdcall了。对于VC来说，__cdecl修饰的函数，在DLL的导出名字一般是没有变化的，但对于__stdcall就会做一些改变，在前面加一个下划线，后面再补上一个&#8220;@&#8221;，再加上参数的字节长度总和。 <br /><br />例如，对于函数 int add(int a, int b)，如果int __cdecl add(int a, int b)，在DLL的导出名字还是&#8220;add&#8221;，但如果是int __stdcall add(int a, int b)，在DLL的导出名字就是&#8220;_add@8&#8221;（查看导出名字可以用VC自带的dumpbin命令，方法是&#8220;dumpbin /exports aaa.dll&#8221;）。 <br /><br />好了，说了一大堆废话，开始进主题啦。 <br /><br />开始的时候，我做了一个test.dll，例如包含一个导出的函数 int __stdcall build(int n)。 <br />在delphi的时候，如果用 </p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools">Java代码 <a title="复制代码" href="#"><img alt="复制代码" src="http://www.cppblog.com/images/icon_copy.gif" /></a>&nbsp;<a title="收藏这段代码" href="javascript:void()"><img class="star" alt="收藏代码" src="http://www.cppblog.com/images/icon_star.png" /><img style="display: none" class="spinner" alt="" src="http://www.cppblog.com/images/spinner.gif" /></a></div></div>
<ol class="dp-j"><li><span>function&nbsp;build(n:Integer):&nbsp;integer;&nbsp;stdcall;&nbsp;external&nbsp;</span><span class="string">'test.dll'</span><span>&nbsp;name&nbsp;</span><span class="string">'build'</span><span>;&nbsp;&nbsp;</span></li></ol></div>
<p>&nbsp;</p><pre style="display: none" class="java" title="C的修饰符__stdcall在导出Dll时候的问题" pre_index="0" source_url="http://www.iteye.com/topic/577571#1342140" codeable_id="1342140" codeable_type="Post" name="code">function build(n:Integer): integer; stdcall; external 'test.dll' name 'build';</pre>
<p>这种声明方式的话，导入会出错，因为找不到build这个函数，这里的build已经改成名字是_build@4。当然，如果我们直接写&#8220;external 'test.dll' name '_build@4';&#8221;应该是没有问题的。不过不爽。为什么win api的函数就没有这些乱七八糟的东西？ <br /><br />后来在网上找了一遍，写了一个def文件。 <br /><br /></p>
<div class="dp-highlighter">
<div class="bar">
<div class="tools">Java代码 <a title="复制代码" href="#"><img alt="复制代码" src="http://www.cppblog.com/images/icon_copy.gif" /></a>&nbsp;<a title="收藏这段代码" href="javascript:void()"><img class="star" alt="收藏代码" src="http://www.cppblog.com/images/icon_star.png" /><img style="display: none" class="spinner" alt="" src="http://www.cppblog.com/images/spinner.gif" /></a></div></div>
<ol class="dp-j"><li><span>LIBRARY&nbsp;&nbsp;&nbsp;test.dll &nbsp;&nbsp;</span></li><li><span>&nbsp;&nbsp;</span></li><li><span>EXPORTS &nbsp;&nbsp;</span></li><li><span>build&nbsp;&nbsp;</span></li></ol></div>
<p>&nbsp;</p><pre style="display: none" class="java" title="C的修饰符__stdcall在导出Dll时候的问题" pre_index="1" source_url="http://www.iteye.com/topic/577571#1342140" codeable_id="1342140" codeable_type="Post" name="code">LIBRARY   test.dll

EXPORTS
build</pre>
<p><br /><br />这样生成的dll的导出名字就没有那些乱七八糟的修饰符了。不过这个时候又轮到C这边有问题了。原因是C这边在链接的时候找不到_build@4这个函数。真是晕倒，两边不讨好。 <br /><br />我就觉得奇怪，怎么win api的函数，即没有那些修饰符，但又可以让delphi、C、VB等正确调用呢？奇怪啊。<br /><br />今天写线程函数时，发现msdn中对ThreadProc的定义有要求：DWORD WINAPI ThreadProc(LPVOID lpParameter); <br /><br />不解为什么要用WINAPI宏定义，查了后发现下面的定义。于是乎需要区别__stdcall和__cdecl两者的区别； #define CALLBACK __stdcall <br />#define WINAPI __stdcall <br />#define WINAPIV __cdecl <br />#define APIENTRY WINAPI <br />#define APIPRIVATE __stdcall <br />#define PASCAL __stdcall <br />#define cdecl _cdecl <br />#ifndef CDECL <br />#define CDECL _cdecl <br />#endif <br /><br />几乎我们写的每一个WINDOWS API函数都是__stdcall类型的，首先，需要了解两者之间的区别： WINDOWS的函数调用时需要用到栈（STACK，一种先入后出的存储结构）。当函数调用完成后，栈需要清楚，这里就是问题的关键，如何清除？？如果我们的函数使用了_cdecl，那么栈的清除工作是由调用者，用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题，不同的编译器产生栈的方式不尽相同，那么调用者能否正常的完成清除工作呢？答案是不能。如果使用__stdcall，上面的问题就解决了，函数自己解决清除工作。所以，在跨（开发）平台的调用中，我们都使用__stdcall（虽然有时是以 WINAPI的样子出现）。那么为什么还需要_cdecl呢？当我们遇到这样的函数如fprintf()它的参数是可变的，不定长的，被调用者事先无法知道参数的长度，事后的清除工作也无法正常的进行，因此，这种情况我们只能使用_cdecl。到这里我们有一个结论，如果你的程序中没有涉及可变参数，最好使用__stdcall关键字。 <br /><br />2. <br /><br />__cdecl,__stdcall是声明的函数调用协议.主要是传参和弹栈方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API) <br /><br />__cdecl 是C/C++和MFC程序默认使用的调用约定，也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br />__stdcall调用约定用于调用Win32 API函数。采用__stdcall约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，函数参数个数固定。由于函数体本身知道传进来的参数个数，因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。 <br />__fastcall 约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节（DWORD）的参数分别放在ECX和EDX寄存器，其余的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall <br /><br />3. <br /><br />__stdcall: <br /><br />_stdcall 调用约定相当于16位动态库中经常使用的PASCAL调用约定。 <br /><br />&nbsp; <br />在32位的VC++5.0中PASCAL调用约定不再被支持（实际上它已被定义为 __stdcall。除了__pascal外，__fortran和__syscall也不被支持），取而代之的是__stdcall调用约定。两者实质上是一致的，即函数的参数自右向左通过栈传递，被调用的函数在返回前清理传送参数的内存栈，但不同的是函数名的修饰部分（关于函数名的修饰部分在后面将详细说明）。 <br /><br />_stdcall是Pascal程序的缺省调用方式，通常用于Win32 Api中，函数采用从右到左的压栈方式，自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀，在函数名后加上"@"和参数的字节数。 <br /><br />_cdecl: <br /><br />_cdecl c调用约定, 按从右至左的顺序压参数入栈，由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的（正因为如此，实现可变参数的函数只能使用该调用约定）。另外，在函数名修饰约定方面也有所不同。 <br /><br />_cdecl是C和C＋＋程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码，所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。 <br /><br />__fastcall: <br /><br />__fastcall调用约定是"人"如其名，它的主要特点就是快，因为它是通过寄存器来传送参数的（实际上，它用ECX和EDX传送前两个双字（DWORD）或更小的参数，剩下的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的内存栈），在函数名修饰约定方面，它和前两者均不同。 <br /><br />_fastcall方式的函数采用寄存器传递参数，VC将函数编译后会在函数名前面加上"@"前缀，在函数名后加上"@"和参数的字节数。 <br /><br />thiscall: <br /><br />thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器，参数从右到左压。thiscall不是关键词，因此不能被程序员指定。 <br /><br />naked call: <br /><br />采用1-4的调用约定时，如果必要的话，进入函数时编译器会产生代码来保存ESI，EDI，EBX，EBP寄存器，退出函数时则产生代码恢复这些寄存器的内容。 <br /><br />naked call不产生这样的代码。naked call不是类型修饰符，故必须和_declspec共同使用。 <br /><br />另附: <br /><br />关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前，也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时，直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、 /Gd和/Gr。缺省状态为/Gd，即__cdecl。 <br /><br />要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定，至于函数名修饰约定，可以通过其它方法模仿。还有一个值得一提的是WINAPI宏，Windows.h支持该宏，它可以将出函数翻译成适当的调用约定，在WIN32中，它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。 <br /><br />名字修饰约定 <br /><br />1、修饰名(Decoration name) <br />&#8220;C&#8221; 或者&#8220;C++&#8221;函数在内部（编译和链接）通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出&#8220;C++&#8221;重载函数、构造函数、析构函数，又如在汇编代码里调用&#8220;C&#8221;&#8221;或&#8220;C++&#8221;函数等。 <br /><br />修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。 <br /><br />2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同，下面分别说明。 <br /><br />a、C编译时函数名修饰约定规则： <br /><br />__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个&#8220;@&#8221;符号和其参数的字节数，格式为_functionname@number。 <br /><br />__cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。 <br /><br />__fastcall调用约定在输出函数名前加上一个&#8220;@&#8221;符号，后面也是一个&#8220;@&#8221;符号和其参数的字节数，格式为@functionname@number。 <br /><br />它们均不改变输出函数名中的字符大小写，这和PASCAL调用约定不同，PASCAL约定输出的函数名无任何修饰且全部大写。 <br /><br />b、C++编译时函数名修饰约定规则： <br /><br />__stdcall调用约定： <br />1、以&#8220;?&#8221;标识函数名的开始，后跟函数名； <br />2、函数名后面以&#8220;@@YG&#8221;标识参数表的开始，后跟参数表； <br />3、参数表以代号表示： <br />X--void ， <br />D--char， <br />E--unsigned char， <br />F--short， <br />H--int， <br />I--unsigned int， <br />J--long， <br />K--unsigned long， <br />M--float， <br />N--double， <br />_N--bool， <br />.... <br />PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以&#8220;0&#8221;代替，一个&#8220;0&#8221;代表一次重复； <br />4、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； <br />5、参数表后以&#8220;@Z&#8221;标识整个名字的结束，如果该函数无参数，则以&#8220;Z&#8221;标识结束。 <br /><br />其格式为&#8220;?functionname@@YG*****@Z&#8221;或&#8220;?functionname@@YG*XZ&#8221;，例如 <br />int Test1（char *var1,unsigned long）-----&#8220;?Test1@@YGHPADK@Z&#8221; <br />void Test2（） -----&#8220;?Test2@@YGXXZ&#8221; <br /><br />__cdecl调用约定： <br />规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YA&#8221;。 <br /><br />__fastcall调用约定： <br />规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YI&#8221;。 <br />VC++对函数的省缺声明是&#8220;__cedcl&#8220;,将只能被C/C++调用. <br /><br />CB在输出函数声明时使用4种修饰符号 <br />//__cdecl <br />cb的默认值，它会在输出函数名前加_，并保留此函数名不变，参数按照从右到左的顺序依次传递给栈，也可以写成_cdecl和cdecl形式。 <br />//__fastcall <br />她修饰的函数的参数将尽肯呢感地使用寄存器来处理，其函数名前加@，参数按照从左到右的顺序压栈； <br />//__pascal <br />它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈； <br />//__stdcall <br />使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈，也可以是_stdcall； <br /><br />VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用. <br /><br />注意： <br /><br />1、_beginthread需要__cdecl的线程函数地址，_beginthreadex和CreateThread需要__stdcall的线程函数地址。 <br /><br />2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义： <br /><br />#define CALLBACK __stdcall <br /><br />#define WINAPI　 __stdcall <br /><br />3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b); <br /><br />&nbsp;&nbsp; typedef int (__cdecl*FunPointer)(int a, int b); <br /><br />&nbsp;&nbsp; 修饰符的书写顺序如上。 <br /><br />4、extern "C"的作用：如果Add(int a, int b)是在c语言编译器编译，而在c++文件使用，则需要在c++文件中声明：extern "C" Add(int a, int b)，因为c编译器和c++编译器对函数名的解释不一样（c++编译器解释函数名的时候要考虑函数参数，这样是了方便函数重载，而在c语言中不存在函数重载的问题），使用extern "C"，实质就是告诉c++编译器，该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。 <br /><br />一般象如下使用： <br /><br />#ifdef _cplusplus <br /><br />#define EXTERN_C extern "C" <br /><br />#else <br /><br />#define EXTERN_C extern <br /><br />#endif <br /><br />#ifdef _cplusplus <br /><br />extern "C"{ <br /><br />#endif <br /><br />EXTERN_C int func(int a, int b); <br /><br />#ifdef _cplusplus <br /><br />} <br /><br />#endif <br /><br />5、MFC提供了一些宏，可以使用AFX_EXT_CLASS来代替__declspec(DLLexport)，并修饰类名，从而导出类，AFX_API_EXPORT来修饰函数，AFX_DATA_EXPORT来修饰变量 <br /><br />AFX_CLASS_IMPORT：__declspec(DLLexport) <br /><br />AFX_API_IMPORT：__declspec(DLLexport) <br /><br />AFX_DATA_IMPORT：__declspec(DLLexport) <br /><br />AFX_CLASS_EXPORT：__declspec(DLLexport) <br /><br />AFX_API_EXPORT：__declspec(DLLexport) <br /><br />AFX_DATA_EXPORT：__declspec(DLLexport) <br /><br />AFX_EXT_CLASS：#ifdef _AFXEXT <br /><br />&nbsp;&nbsp; AFX_CLASS_EXPORT <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #else <br /><br />&nbsp;&nbsp; AFX_CLASS_IMPORT <br /><br />6、DLLMain负责初始化(Initialization)和结束 (Termination)工作，每当一个新的进程或者该进程的新的线程访问DLL时，或者访问DLL的每一个进程或者线程不再使用DLL或者结束时，都会调用DLLMain。但是，使用TerminateProcess或TerminateThread结束进程或者线程，不会调用DLLMain。 <br /><br />7、一个DLL在内存中只有一个实例 <br /><br />DLL程序和调用其输出函数的程序的关系： <br /><br />1)、DLL与进程、线程之间的关系 <br /><br />DLL模块被映射到调用它的进程的虚拟地址空间。 <br /><br />DLL使用的内存从调用进程的虚拟地址空间分配，只能被该进程的线程所访问。 <br /><br />DLL的句柄可以被调用进程使用；调用进程的句柄可以被DLL使用。 <br /><br />DLLDLL可以有自己的数据段，但没有自己的堆栈，使用调用进程的栈，与调用它的应用程序相同的堆栈模式。 <br /><br />2)、关于共享数据段 <br /><br />DLL定义的全局变量可以被调用进程访问；DLL可以访问调用进程的全局数据。使用同一 DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量，则需要使用同步机制；对一个DLL的变量，如果希望每个使用DLL的线程都有自己的值，则应该使用线程局部存储(TLS，Thread Local Strorage)。 </span></p><img src ="http://www.cppblog.com/Walker/aggbug/147052.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-05-24 20:39 <a href="http://www.cppblog.com/Walker/articles/147052.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C与C++相互调用</title><link>http://www.cppblog.com/Walker/articles/146992.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 23 May 2011 12:53:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/146992.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/146992.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/146992.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/146992.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/146992.html</trackback:ping><description><![CDATA[<p>&nbsp;在实际工作中可能经常要进行C和C++的混合编程，C++调用C语言的代码通常都比较容易，但也有一些细节需要注意。C要调用C++的代码就略为麻烦一些，因为C不支持面向对象的特征。<br />首先我们来看一下C++调用C语言的代码。要让你的C代码既能被C代码又能被C++调用虽说容易，但是还是有需要注意的地方。现有三个文件分别如下：<br />/* file TestC.h */<br />#ifndef TESTC_H<br />#define TESTC_H<br />&nbsp;<br />#ifdef __cplusplus<br />extern "C" {<br />#endif<br />&nbsp;<br />int add(int a, int b);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />#ifdef __cplusplus<br />}<br />#endif<br />&nbsp;<br />#endif /* TESTC_H */<br />&nbsp;<br />&nbsp;<br />/* file TestC.c */<br />&nbsp;<br />#include "TestC.h"<br />&nbsp;<br />int add(int a, int b)<br />{<br />&nbsp;&nbsp;&nbsp; return (a + b);<br />}<br />&nbsp;<br />&nbsp;<br />/* file TestCpp.cpp */<br />#include "stdio.h"<br />#include "TestC.h"<br />&nbsp;<br />int main()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("add = %d\n", add(2, 5));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />}<br />说明：<br />file TestC.h是C的头文件，file TestC.c是其实现文件，file TestCpp.cpp是调用C函数的C++文件。<br />文件TestC.h中的TESTC_H定义是为了头文件保护，&#8221; #ifdef __cplusplus&#8221;这个不能缺少，你可以去查看C的标准库头文件中都有这个，如&#8221;stdio.h&#8221;。有了这个宏编译器就知道现在是C还是C++在调用它。<br />为什么要区分C与C++调用呢？其深层次原因是因为C和C++编译器在编译和链接时对于函数的处理不一样。C++为了支持函数重载在编译时会加入函数参数及类型信息。如上面的add方法，C编译器编译后在符号库中的名字为_add，而C++编译器则会产生像_add_int_int之类的名字。C++正是依靠这种机制实现了函数的重载。<br />extern关键字表示将函数或变量声明为全局类型，与之相对应的是static。static限定函数或变量的作用域为本文件。extern还有一个作用就是与&#8221;C&#8221;连在一起使用，即extern &#8220;C&#8221;通知编译器将extern &#8220;C&#8221;所包含的代码按照C的方式编译和链接。<br />&nbsp;<br />下面我们就来看看如何在C语言中使用C++的代码（包括C++类的方法）。为了简单起见，我将类的定义和实现放在一个文件中(通常应该是将分别放在.h和.cpp文件中)。自定义类文件(这里省略了头文件保护等其它细节)如下：<br />//* file TestClass.h */<br />&nbsp;<br />class HJH<br />{<br />public:<br />&nbsp;&nbsp;&nbsp; int add(int a, int b)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (a + b);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />};<br />&nbsp;<br />将C++类封装为C函数的文件（为了简略也将声明和实现放在了同一个文件中）如下：<br />/* file TestCpp.cpp */<br />&nbsp;<br />#include "TestClass.h"<br />&nbsp;<br />extern "C" int add_cpp(int a, int b);<br />&nbsp;<br />int add_cpp(int a, int b)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HJH hjh;<br />&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return hjh.add(a, b);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />}<br />&nbsp;<br />实际调用C++代码的C文件如下：<br />/*file TestC.c */<br />#include "stdio.h"<br />&nbsp;<br />extern int add_cpp(int a, int b);<br />&nbsp;<br />int main()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("add_cpp = %d\n", add_cpp(2, 5));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br />}<br />上面的过程很清晰，就是用一个函数将C++类的使用封装起来，然后将它外部声明为C函数就可以了。<br />文件TestClass.h定义并实现了一个类，该类只有一个add方法。文件TestCpp.cpp定义并实现了一个函数add_cpp，函数中定义了一个HJH类对象并调用了该对象的add方法。然后将add_cpp函数进行外部声明为C。<br />TestC.c文件中为了使用add_cpp函数，也需要进行外部声明。这是为了通知编译器说明这个函数是在其他文件中实现（注意在C文件中的extern后面不可加&#8221;C&#8221;）。当这三个文件一起编译链接时，编译器就可以找到add_cpp的具体实现。</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/gobitan/archive/2007/03/18/1532769.aspx">http://blog.csdn.net/gobitan/archive/2007/03/18/1532769.aspx</a></p><img src ="http://www.cppblog.com/Walker/aggbug/146992.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-05-23 20:53 <a href="http://www.cppblog.com/Walker/articles/146992.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转GetUserDefaultLangID,GetSystemDefaultLangID,GetUserDefaultUILanguage比较</title><link>http://www.cppblog.com/Walker/articles/146720.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 18 May 2011 23:24:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/146720.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/146720.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/146720.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/146720.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/146720.html</trackback:ping><description><![CDATA[<p align="left">&nbsp;</p>
<p>这三个函数都是得到语言ID，但是使用的场合上却是有所不同，下面先给出三个函数的原型和解释：<br />GetUserDefaultLangID<br />Returns the language identifier for the current user locale. </p>
<p>LANGID GetUserDefaultLangID(void);<br />Parameters<br />This function has no parameters. </p>
<p>Return Values<br />Returns the language identifier for the current user locale. </p>
<p>GetSystemDefaultLangID<br />Returns the language identifier for the system locale. </p>
<p>LANGID GetSystemDefaultLangID(void);<br />Parameters<br />This function has no parameters. </p>
<p>Return Values<br />Returns the language identifier for the system locale. </p>
<p>GetUserDefaultUILanguage<br />Retrieves the user UI language for the current user. If the current user has not set a language, GetUserDefaultUILanguage returns the language identifier for the system default UI language.</p>
<p>LANGID GetUserDefaultUILanguage(void);<br />Parameters<br />This function has no parameters.</p>
<p>Return Value<br />Returns the language identifier for the user UI language for the current user.</p>
<p>要理解这三个函数的使用先要看看控制面板-&gt;区域和语言选项，<br />第一页代表数字货币时间，第二页是系统UI和软件界面，第三页高级是非unicode程序界面语言。<br />下面是这三个页面设置时，不同的函数的结果：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 区域选项&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 语言&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 高级<br />GetUserDefaultLangID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 改变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />GetSystemDefaultLangID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 改变<br />GetUserDefaultUILanguage&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 改变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 改变&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不变<br />由些可见，GetUserDefaultUILanguage会和系统UI的语言一致，所以我们建议在写UNICODE程序界面时用这个<br />函数会好些，和OS的UI一致，不会让客人觉得很奇怪。</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/nanjian011/archive/2009/10/23/4716903.aspx">http://blog.csdn.net/nanjian011/archive/2009/10/23/4716903.aspx</a></p>
<p align="left">&nbsp;</p><img src ="http://www.cppblog.com/Walker/aggbug/146720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-05-19 07:24 <a href="http://www.cppblog.com/Walker/articles/146720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调试Release发布版程序的Crash错误 （转）</title><link>http://www.cppblog.com/Walker/articles/146153.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Tue, 10 May 2011 23:53:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/146153.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/146153.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/146153.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/146153.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/146153.html</trackback:ping><description><![CDATA[<h2><a id=viewpost1_TitleUrl href="http://www.cppblog.com/woaidongmao/archive/2011/05/10/146092.html">调试Release发布版程序的Crash错误</a> </h2>
<div>
<p><a title=http://blog.sina.com.cn/s/blog_48f93b530100fsln.html href="http://blog.sina.com.cn/s/blog_48f93b530100fsln.html">http://blog.sina.com.cn/s/blog_48f93b530100fsln.html</a></p>
<p>&nbsp;</p>
<p><span>在<span>Windows</span>平台下用<span>C++</span>开发应用程序，最不想见到的情况恐怕就是程序崩溃，而要想解决引起问题的<span>bug</span>，最困难的应该就是调试<span>release</span>版本了。因为<span>release</span>版本来就少了很多调试信息，更何况一般都是发布出去由用户使用，<span>crash</span>的现场很难保留和重现。本文将给出几个解决方案，完成对<span>release</span>版应用程序<span>crash</span>错误的调试。（本文只讨论<span>Windows</span>平台<span>MSVC</span>环境下的调试，对于其他平台和开发环境没有关注，请大家自己借鉴和尝试。）</span></span></span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp; </span><wbr><strong><span>方案一：崩溃地址</span></strong><span> <span xml:lang="EN-US"><strong><span>+ MAP</span></span>文件</strong></span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>这种方案只能对<span xml:lang="EN-US"><span>VC7</span></span>以前的版本开发的程序使用。<span xml:lang="EN-US"><span>&nbsp;<wbr></span></span></p>
</span>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong>1</strong></span></span><strong><span>、崩溃地址</span></strong></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span>所谓崩溃地址就是引起程序崩溃的内存地址，在</span><span><span xml:lang="EN-US">WinXP</span></span><span>下应用程序</span><span><span xml:lang="EN-US">crash</span></span><span>的对话框如下图：</span></span></p>
<p align=center><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fsln&amp;url=http://s5.sinaimg.cn/orignal/48f93b53g79aad1fff694&amp;690" target=_blank><span><span><img title=clip_image001 border=0 alt=clip_image001 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image001_0ad42579-f1c9-44ae-b2f8-b50b8a737e0d.jpg" width=424 height=181 v:shapes="_x0000_i1025"></span></span></a></span></p>
<p align=center><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fsln&amp;url=http://s7.sinaimg.cn/orignal/48f93b53g79aad2c60546&amp;690" target=_blank><span><span><img title=clip_image002 border=0 alt=clip_image002 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image002_cb3f00fa-aa22-4301-a42c-92c12dff2ea4.jpg" width=494 height=120 v:shapes="_x0000_i1026" real_src="http://s7.sinaimg.cn/bmiddle/48f93b53g79aad2c60546&amp;690"></span></span></a></span></p>
<p align=center><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fsln&amp;url=http://s2.sinaimg.cn/orignal/48f93b53g79aad34a5521&amp;690" target=_blank><span><span><img title=clip_image003 border=0 alt=clip_image003 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image003_fea81ac9-41ea-42be-a24f-d0fe99dafa80.jpg" width=494 height=380 v:shapes="_x0000_i1027" real_src="http://s2.sinaimg.cn/bmiddle/48f93b53g79aad34a5521&amp;690"></span></span></a></span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span><span>上面第</span><span><span xml:lang="EN-US">2</span></span><span>张图中画红线的值为</span><span><span xml:lang="EN-US">crash</span></span><span>的代码偏移地址，第</span><span><span xml:lang="EN-US">3</span></span><span>张图为即</span><span><span xml:lang="EN-US">crash</span></span><span>绝对地址；一般引起</span><span><span xml:lang="EN-US">crash</span></span><span>的原因多为内存操作错误，我们用这两个地址和</span><span><span xml:lang="EN-US">MAP</span></span><span>文件就能定位出错的代码行。</span></span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong>2</strong></span></span><strong><span>、<span xml:lang="EN-US"><span>MAP</span></span>文件</span></strong></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> MAP</span></span><span>文件是记录应用程序信息的文件（文本文件），里面大概包含了程序的全局符号、源码模块名、源码文件和行号等信息，而这些信息能够帮助我们定位出错的代码行。</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>怎样生成<span xml:lang="EN-US"><span>MAP</span></span>文件呢？以<span xml:lang="EN-US"><span>VC6</span></span>为例，在 <span xml:lang="EN-US"><span>Project Settings -&gt; C/C++ <wbr>-&gt; Debug info</span></span>中，选择 <span xml:lang="EN-US"><span>Line Numbers Only</span> </span>；在 <span xml:lang="EN-US"><span>Project Settings -&gt; Link</span> </span>中，选择 <span xml:lang="EN-US"><span>Generate mapfile</span></span>项，并在<span xml:lang="EN-US"><span>Project Options</span> </span>里面输入 </span><span xml:lang="EN-US"><span>/MAPINFO:LINES</span></span><span> </span><span>和</span><span> <span><span xml:lang="EN-US">/MAPINFO:EXPORTS</span></span>，重新编译程序就会生成<span xml:lang="EN-US"><span>.map</span></span>文件。</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>以上设置对应的编译链接选项分别分：</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><strong><span xml:lang="EN-US"><span>/Zi</span></span></strong><span> </span><span>— 表示生成<span xml:lang="EN-US"><span>pdb</span></span>调试信息；</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><strong><span xml:lang="EN-US"><span>/MAP[:filename]</span></span></strong><span> </span><span>— 表示生成<span xml:lang="EN-US"><span>map</span></span>文件名；</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><strong><span xml:lang="EN-US"><span>/MAPINFO:EXPORTS <wbr></span></strong></span><span>— 表示生成的<span xml:lang="EN-US"><span>map</span></span>文件中加入<span xml:lang="EN-US"><span>exported functions</span></span>（生成<span xml:lang="EN-US"><span>DLL</span></span>文件时）；</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><strong><span xml:lang="EN-US"><span>/MAPINFO:LINES <wbr></span></strong></span><span>— 表示生成的<span xml:lang="EN-US"><span>map</span></span>文件中加入代码行信息。</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>由于<span xml:lang="EN-US"><span>/MAPINFO:LINES</span></span>选项在<span xml:lang="EN-US"><span>VC8</span></span>以后的版本中不再支持，因此通过<span xml:lang="EN-US"><span>MAP</span></span>文件中的信息和<span xml:lang="EN-US"><span>crash</span></span>地址定位出错代码行就比较困难了，所以这种方案只能在<span xml:lang="EN-US"><span>VC7</span></span>及以前的版本中使用。</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span>一个</span><span><span xml:lang="EN-US">MAP</span></span><span>文件片段示例如下：</span><span><span xml:lang="EN-US">&nbsp;<wbr></span></span></p>
</span>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fsln&amp;url=http://s4.sinaimg.cn/orignal/48f93b53g79aae50a0e23&amp;690" target=_blank><span><span><img title=clip_image004 border=0 alt=clip_image004 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image004_fbf07de5-1b63-4473-917f-f6695904c9be.jpg" width=494 height=229 v:shapes="_x0000_i1028" real_src="http://s4.sinaimg.cn/bmiddle/48f93b53g79aae50a0e23&amp;690"></span></span>&nbsp;<wbr></a>&nbsp;<wbr></span></p>
</span>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fsln&amp;url=http://s1.sinaimg.cn/orignal/48f93b53g79aae6db2060&amp;690" target=_blank><span><span><img title=clip_image005 border=0 alt=clip_image005 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image005_93f97fcb-0900-4f57-baf2-29a04f821fa2.jpg" width=494 height=227 v:shapes="_x0000_i1029" real_src="http://s1.sinaimg.cn/bmiddle/48f93b53g79aae6db2060&amp;690"></span></span></a></span></p>
</span>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span><span>图中</span><span><span xml:lang="EN-US">Rva+Base</span></span><span>列的地址为该行函数对应的函数绝对地址，</span><span><span xml:lang="EN-US">Address</span></span><span>列中冒号后面的地址为函数相对偏移地址。</span><span><span xml:lang="EN-US">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span></span></p>
</span>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong>3</strong></span></span><strong><span>、定位<span xml:lang="EN-US"><span>crash</span></span>代码</span></strong></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>有了上面的介绍，定位<span xml:lang="EN-US"><span>crash</span></span>代码就很简单了。用下面的公式来进行定位：</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><strong><span>崩溃行偏移 <span xml:lang="EN-US"><span>=</span> </span>崩溃地址 <span xml:lang="EN-US"><span>-</span> </span>崩溃函数绝对地址 <span xml:lang="EN-US"><span>+</span> </span>函数相对偏移</span></strong></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span> </span><span>我们首先根据崩溃地址（绝对地址），按照找到第<span xml:lang="EN-US"><span>2</span></span>张图中<span xml:lang="EN-US"><span>Rva+Base</span></span>列的地址找到发生崩溃的函数（即崩溃地址大于该函数行的<span xml:lang="EN-US"><span>Rva+Base</span></span>地址且小于下个函数的地址），然后找到该行对应的函数相对偏移地址，带入公式中，就得到了崩溃行偏移，该值表示崩溃行的代码相对于代码所在函数的偏移量。用该值去与第<span xml:lang="EN-US"><span>3</span></span>张图中对应函数冒号后面的偏移量去比较，最接近的值前面的那个十进制数即为代码所在函数中的行号。</span></p>
<p><span xml:lang="EN-US"><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ok</span></span><span>，到此我们已经成功找到了崩溃的代码行，只不过这种方法还是比较费力，并且限制比较多，我们看看下面的方案。</span></p>
<p><span>上篇给出的方案一还要补充几句。通过<span>&#8220;crash</span>地址<span> + MAP</span>文件<span>&#8221;</span>来定位出错代码位置虽然需要经过比较复杂的地址计算，但却是最简单实现的方式。如果仅仅想通过崩溃地址定位出错的函数，就更加方便了。我在网上找到一个解析<span>MAP</span>文件的小工具，可以非常清晰的列出每个函数的地址，并且可以将分析表格导出为<span>Excel</span>文件。工具下载地址：<span><a href="http://e.ys168.com/?tinyfun"><span>http://e.ys168.com/?tinyfun</span></a></span>，工具目录下<span>VCMapper.exe</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>另外上篇主要参考两篇文章：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://www.vckbase.com/document/viewdoc/?id=908"><span>http://www.vckbase.com/document/viewdoc/?id=908</span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://www.vckbase.com/document/viewdoc/?id=1473"><span>http://www.vckbase.com/document/viewdoc/?id=1473</span></a></span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><strong><span>方案二：崩溃地址<span> + MAP</span>文件<span> + COD</span>文件</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>由于<span>VC8</span>以后的版本都不再支持<span>MAP</span>文件中产生代码行信息，因此我们寻找另一种定位方式：<span>COD</span>文件。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>1</span></strong></span><strong><span>、<span>COD</span>文件</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> COD</span><span>文件是一个包含了汇编码、二进制机器码和源代码对应信息的文件，每一个<span>cpp</span>都对应一个<span>COD</span>文件。通过这个文件，我们可以非常方便地进行定位。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>在<span>VC6</span>中生成<span>COD</span>文件的设置方式为：<span>Project Settings -&gt; C/C++</span>，在<span> Category </span>中选<span> Listing Files</span>，在<span> Listing file type </span>组合框中选<span> Assembly</span>，<span>Machine code</span>，<span>and source</span>。在<span>VC8</span>中生成<span>COD</span>文件的设置方式为：<span>Project Properties -&gt; C/C++ -&gt; Output Files -&gt; Assembler Output </span>项，选择<span> Assembly</span>，<span>Machine code</span>，<span>and Source(/Facs)</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>2</span></strong></span><strong><span>、定位崩溃行</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>下面通过举例进行说明。现在我有一个基于对话框的<span>MFC</span>应用程序<span>CrashTest</span>，在<span>CCrashTestDlg::OnInitDialog</span>函数中写入导致<span>crash</span>的代码语句（第<span>99</span>行），源文件如下：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fv7y&amp;url=http://s16.sinaimg.cn/orignal/48f93b53g79bbc36cd95f&amp;690" target=_blank><span><span><img title=clip_image006 border=0 alt=clip_image006 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image006_887560ee-8b72-42ea-a623-220dcb47ac1f.jpg" width=360 height=169 v:shapes="_x0000_i1030"></span></span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>根据崩溃地址（<span>0x<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="4012" unitname="a">004012A</st1:chmetcnv>3</span>）以及<span>MAP</span>文件（定位片段图片如下），定位<span>crash</span>函数为<span>OnInitDialog</span>；并且我们可以很容易地计算出崩溃地址相对于崩溃函数的偏移量为<span> 0x<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="4012" unitname="a">004012A</st1:chmetcnv>3 - 0x004011E0 = 0xC3</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fv7y&amp;url=http://s11.sinaimg.cn/orignal/48f93b53g79bbc5f052da&amp;690" target=_blank><span><span><img title=clip_image007 border=0 alt=clip_image007 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image007_dc9962ce-f969-4c7f-aa0b-e973cac321ce.jpg" width=494 height=36 v:shapes="_x0000_i1031" real_src="http://s11.sinaimg.cn/bmiddle/48f93b53g79bbc5f052da&amp;690"></span></span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>再来看看<span>CrashTestDlg.cod</span>文件，我们根据文件中源码信息找到<span>OnInitDialog</span>函数信息片段：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fv7y&amp;url=http://s4.sinaimg.cn/orignal/48f93b53g79bc3c02d3f3&amp;690" target=_blank><span><span><img title=clip_image008 border=0 alt=clip_image008 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image008_00ac91bc-99ca-4d4f-86cd-b0d1d23bb10a.jpg" width=494 height=231 v:shapes="_x0000_i1032" real_src="http://s4.sinaimg.cn/bmiddle/48f93b53g79bc3c02d3f3&amp;690"></span></span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>可以看到图片中第一行为<span>OnInitDialog</span>函数汇编代码的起始行；找到<span>&#8220;int * p = NULL;&#8221;</span>这一句源码，其前面的<span>98</span>表示这行代码在源文件中的行号，下面的<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="0" unitname="C"><span>000c</span></st1:chmetcnv><span>1</span>表示相对于函数开始位置的偏移量，后面的<span>&#8220;<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="True" sourcevalue="33" unitname="C">33 c</st1:chmetcnv><st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="0" unitname="&#8221;">0&#8221;</st1:chmetcnv></span>为机器码，<span>&#8220;xor eax</span>，<span>eax&#8221;</span>为汇编码。那么我们根据前面算出来的偏移量<span>0xC3</span>，找到对应出错的语句为<span>99</span>行：<span>&#8220;*p = 5;&#8221;</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>总结一下定位步骤：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 1) </span><span>根据公式 <strong><span>崩溃语句在函数中偏移地址<span> = </span>崩溃地址<span> - </span>崩溃函数地址</span></strong> 计算出偏移量<span>X</span>；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 2) </span><span>根据公式 <strong><span>崩溃语句在<span>COD</span>文件中地址<span> = </span>崩溃函数在<span>COD</span>文件中地址<span> + <wbr>X</span></span></strong><span> </span>计算出地址<span>Y</span>。其中崩溃函数在<span>COD</span>文件中地址为<span>COD</span>文件中函数起始括号<span>&#8220;{&#8221;</span>后面表明的地址，一般情况下为<span>0x0000</span>；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 3) </span><span>根据<span>Y</span>在<span>COD</span>文件中找到对应代码行。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ok</span><span>，方案二介绍完了。这种方法最大的好处是没有<span>VC</span>开发环境版本限制，而且<span>COD</span>文件里面包含的信息更加丰富，不但可以帮助我们定位<span>crash</span>，还能帮我们分析很多东西。当然，这也导致编译生成了很多信息文件。</span></p>
<p><span>根据前面两篇博文，我们要定位崩溃行代码，必须要自己根据相关信息文件进行计算。如果需要处理的量比较大，恐怕会很费力气。有没有更简单快速的办法呢？</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>最直接的想法就是写一个小工具，根据规则和信息进行自动定位，不过开发起来也是要费一番功夫的。令人开心的是，我们可以找到类似的工具，而且是开源免费的！程序员的世界也许很多时候都是这么单纯而乐于分享！</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><strong><span>方案三：崩溃地址<span> + PDB</span>文件<span> + CrashFinder</span></span></strong></p>
<p><span>&nbsp;<wbr><wbr>&nbsp;<wbr><wbr>&nbsp;<wbr><wbr> CrashFinder</span><span>是一个开源工具，作者是<span>John Robbin</span>，大家可以去他的<span>blog</span>上去找关于<span>CrashFinder</span>的信息。我们这里以<span>CrashFinder2.5</span>版本为例介绍，相关文章链接为：<span><a href="http://www.wintellect.com/CS/blogs/jrobbins/archive/2006/04/19/crashfinder-returns.aspx"><span>http://www.wintellect.com/CS/blogs/jrobbins/archive/2006/04/19/crashfinder-returns.aspx</span></a></span></span></p>
<p><span>&nbsp;<wbr><wbr>&nbsp;<wbr><wbr>&nbsp;<wbr> <strong><span>1</span></strong></span><strong><span>、<span>PDB</span>文件</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PDB</span><span>（<span>Program Database</span>）文件中包含了<span>exe</span>程序所有的调试相关信息，具体可以查阅<span>MSDN</span>。当编译选项设置为<span>/Zi</span>，链接选项设置为<span>/DEBUG</span>，<span>/OPT:REF</span>时，就会生成工程的<span>.pdb</span>文件。具体到<span>VC2005</span>中，就是<span> Project Propertise -&gt; C/C++ -&gt; General -&gt; Debug Information Format </span>项设置为<span> Program Database</span>（<span>/Zi</span>），<span>Linker -&gt; Debugging -&gt; Generate Debug Info </span>项设置为<span> Yes</span>（<span>/Debug</span>），<span>Linker -&gt; Optimization -&gt; References </span><wbr>项设置为<span> Eliminate <wbr>Unreferenced Data</span>（<span>/OPT:REF</span>）。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>只要设置以上选项，<span>release</span>版本也能生成<span>PDB</span>文件。当然，对应的应用程序也会稍大。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>2</span></strong></span><strong><span>、<span>CrashFinder</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CrashFinder</span><span>能够运行需要两个条件：一是系统必须要有<span>dbghelp.dll</span>文件；二是<span>PDB</span>文件必须与<span>exe</span>文件在一个路径下。对于<span>dbghelp.dll</span>，一般在系统<span>system32</span>路径下都有，如果没有下载一个放到这个目录下就可以了。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>先看一下<span>CrashFinder</span>的界面。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr></p>
<p align=center><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fxvm&amp;url=http://s3.sinaimg.cn/orignal/48f93b53g7a37be0ea632&amp;690" target=_blank><span><span><img title=clip_image009 border=0 alt=clip_image009 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image009_60e666ee-6f31-4132-929c-3ef20c5a9fde.jpg" width=494 height=327 v:shapes="_x0000_i1033"></span></span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>用起来也非常简单。首先选择<span>File-&gt;New</span>或点击工具栏新建按钮，选择要调试的<span>exe</span>文件打开，会发现<span>exe</span>及所依赖的<span>dll</span>文件信息都已经加载进来。在下半部分的编辑框中输入崩溃地址（<span>16</span>进制），点右边的<span>&#8220;Find&#8221;</span>按钮，就会在下面显示崩溃的源文件路径、名称以及崩溃所在行号了，如下图所示。</span></p>
<p align=center><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100fxvm&amp;url=http://s10.sinaimg.cn/orignal/48f93b53g7a37da3b3c69&amp;690" target=_blank><span><span><img title=clip_image010 border=0 alt=clip_image010 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image010_4b25ce3c-1bea-4818-be58-230d6cba8a6a.jpg" width=494 height=370 v:shapes="_x0000_i1034" real_src="http://s10.sinaimg.cn/bmiddle/48f93b53g7a37da3b3c69&amp;690"></span></span></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>用<span>CrashFinder</span>进行<span>crash</span>定位真的非常方便。但是我在使用过程中发现了一个<span>bug</span>，每次启动程序后，直接新建的话加载进来的<span>exe</span>模块都显示叉，提示找不到<span>debug symbols</span>。但是用打开按钮随便打开一个文件失败后，再新建就能成功。猜测可能是直接新建，定位<span>PDB</span>文件时的路径不对引起的。有源码，但是懒的看了呵呵，大家感兴趣可以试一下。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>好了，方案三就介绍到这里，后面还有更加强大的方案<span> : )</span></span></p>
<p><span>前面几个方案都是直接定位<span>crash</span>的代码位置，但是在比较大型的程序中，只知道这个信息还是远远不够的，我们希望知道更多关于调用函数顺序及变量值等信息，也就是<span>crash</span>时调用堆栈信息。</span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><strong><span>方案四：<span>SetUnhandledExceptionFil<wbr>ter + StackWalker</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>这个方案需要自己动手往工程里添加代码了。要实现上面的想法，需要做两件事情：<span>1</span>、需要在<span>crash</span>时有机会对程序堆栈进行处理；<span>2</span>、对堆栈信息进行收集。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>1</span></strong></span><strong><span>、<span>SetUnhandleExceptionFilt<wbr>er</span>函数</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Windows</span><span>平台下的<span>C++</span>程序异常通常可分为两种：结构化异常（<span>Structured Exception</span>，可以理解为与操作系统相关的异常）和<span>C++</span>异常。对于结构化异常处理（<span>SEH</span>），可以找到很多资料，在此不细说。对于<span>crash</span>错误，一般由未被正常捕获的异常引起，<span>Windows</span>操作系统提供了一个<span>API</span>函数可以在程序<span>crash</span>之前有机会处理这些异常，就是<span>SetUnhandleExceptionFilt<wbr>er</span>函数。（<span>C++</span>也有一个类似函数<span>set_terminate</span>可以处理未被捕获的<span>C++</span>异常。）</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> SetUnhandleExceptionFilt<wbr>er</span><span>函数声明如下：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFil<wbr>ter(<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> LPTOP_LEVEL_EXCEPTION_FILTER <em>lpTopLevelExceptionFilte<wbr>r</em><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> );</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>其中<span> LPTOP_LEVEL_EXCEPTION_FILTER </span>定义如下：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in struct _EXCEPTION_POINTERS *ExceptionInfo<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> );<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>简单来说，<span>SetUnhandleExceptionFilt<wbr>er</span>允许我们设置一个自己的函数作为全局<span>SEH</span>过滤函数，当程序<span>crash</span>前会调用我们的函数进行处理。我们可以利用的是<span> _EXCEPTION_POINTERS </span>结构类型的变量<span>ExceptionInfo</span>，它包含了对异常的描述以及发生异常的线程状态，过滤函数可以通过返回不同的值来让系统继续运行或退出应用程序。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>关于<span> SetUnhandleExceptionFilt<wbr>er </span>函数的具体用法和示例请参考<span>MSDN</span>。</span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>2</span></strong></span><strong><span>、<span>StackWalker</span></span></strong><span><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>现在我们已经有机会可以在<span>crash</span>之前对程序状态信息进行处理了，只需要生成并保存堆栈信息就大功告成了。<span>Windows</span>的<span>dbghelp.dll</span>库提供了一个函数可以得到当前堆栈信息：<span>StackWalk64</span>（在<span>Win2K</span>以前版本中为<span>StackWalk</span>）。该函数声明如下：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> BOOL WINAPI StackWalk64(<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> DWORD <em>MachineType</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> HANDLE <em>hProcess</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> HANDLE <em>hThread</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in_out <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> LPSTACKFRAME64 <em>StackFrame</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in_out <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PVOID <em>ContextRecord</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PREAD_PROCESS_MEMORY_ROUTINE64 <em>ReadMemoryRoutine</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PFUNCTION_TABLE_ACCESS_ROUTINE64 <em>FunctionTableAccessRouti<wbr>ne</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PGET_MODULE_BASE_ROUTINE64 <em>GetModuleBaseRoutine</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PTRANSLATE_ADDRESS_ROUTINE64 <em>TranslateAddress</em><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> );<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>该函数的具体用法可以参考<span>MSDN</span>。在这里推荐一个牛人写好的<span>StackWalker</span>，可以直接拿来用，开源的。<span>StackWalker</span>提供了一个基类，给出了几个简单的接口，可以方便地生成堆栈信息，并且支持一系列<span>VC</span>版本，非常好用。我们可以自己写一个子类，并重载虚函数<span>OnOutput</span>，就可以将堆栈信息输出为特定格式了。<span>StackWalker</span>的地址为：<span><a href="http://www.codeproject.com/KB/threads/StackWalker.aspx"><span>http://www.codeproject.com/KB/threads/StackWalker.aspx</span></a></span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>不过对于<span>Release</span>版本来说，<span>StackWalk64</span>函数获得的堆栈信息有可能不完整。如果异常是由<span>MFC</span>的模块抛出，那么获得的堆栈可能缺少前面调用模块信息。另外，<span>StackWalk64</span>需要最新的<span>dbghelp.dll</span>文件支持才能工作；要正确输出<span>crash</span>的函数名和行号，需要要<span>pdb</span>文件支持。以上不足有可能影响输出信息的完整性和效果，而对于发布在外的程序，要带上<span>pdb</span>文件几乎不可能，因此这个方案还是有缺憾的，比较适用于本地的<span>release</span>版本调试。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>下一篇我们将介绍一个更加完善的解决方案</span></p>
<p><span>当我们把自己的<span>release</span>版本程序发布出去以后，一般都是在用户的机器上运行。这种情况下，对于第四种方案，因为需要<span>pdb</span>文件才能够正确生成堆栈调用的函数行号及代码行号，因此方案四只适用于本地<span>release</span>版的调试，否则只能生成不完整的堆栈信息。对于前三种方案，其实只需要用户告知崩溃地址，然后在本地查找<span>crash</span>地址就可以了，但是定位<span>crash</span>的过程非常不方便，如果<span>crash</span>的情况比较多，前三种方案都不合适。而且，前三种方案均不能生成堆栈调用信息，对于<span>debug</span>的作用有限。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>下面我们就来看一个更加完善的解决方案。</span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><strong><span>方案五：<span>SetUnhandledExceptionFil<wbr><wbr>ter + Minidump</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> SetUnhandleExceptionFilt<wbr>er</span><span>函数我们已经介绍过了，本方案的思路还是要利用我们自己的异常处理函数，来生成<span>minidump</span>文件。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>1</span></strong></span><strong><span>、<span>Minidump</span>概念</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> minidump</span><span>（小存储器转储）可以理解为一个<span>dump</span>文件，里面记录了能够帮助调试<span>crash</span>的最小有用信息。实际上，如果你在 系统属性<span> -&gt; </span>高级<span> -&gt; </span>启动和故障恢复<span> -&gt; </span>设置<span> -&gt; </span>写入调试信息 中选择<span>&#8220;</span>小内存转储<span>(64 KB)&#8221;</span>的话，当系统意外停止时都会在<span>C:\Windows\Minidump\</span>路径下生成一个<span>.dmp</span>后缀的文件，这个文件就是<span>minidump</span>文件，只不过这个是内核态的<span>minidump</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr><span>我们要生成的是用户态的<span>minidump</span>，文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其<span>mini</span>的特性，<span>dump</span>文件是压缩过的。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>2</span></strong></span><strong><span>、生成<span>minidump</span>文件</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>生成<span>minidump</span>文件的<span>API</span>函数是<span>MiniDumpWriteDump</span>，该函数需要<span>dbghelp.lib</span>支持，其原型如下<span>:</span></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> BOOL WINAPI MiniDumpWriteDump(<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> HANDLE <em>hProcess</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> DWORD <em>ProcessId</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> HANDLE <em>hFile</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> MINIDUMP_TYPE <em>DumpType</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PMINIDUMP_EXCEPTION_INFORMATION <em>ExceptionParam</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PMINIDUMP_USER_STREAM_INFORMATION <em>UserStreamParam</em>,<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> __in <wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> PMINIDUMP_CALLBACK_INFORMATION <em>CallbackParam</em><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> );</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>在我们的异常处理函数中加入以下代码：</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> HANDLE hFile = ::CreateFile( _T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> if( hFile != INVALID_HANDLE_VALUE)<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> {<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> MINIDUMP_EXCEPTION_INFORMATION einfo;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> einfo.ThreadId = ::GetCurrentThreadId();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> einfo.ExceptionPointers = pExInfo;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> einfo.ClientPointers = FALSE;</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, &amp;einfo, NULL, NULL);<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ::CloseHandle(hFile);<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> }</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>其中，<span>pExInfo</span>变量为异常处理函数<span>PEXCEPTION_POINTERS</span>类型的参数。具体请参考<span>MSDN</span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>3</span></strong></span><strong><span>、调试<span>minidump</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;</span><wbr><span>调试<span>dump</span>文件首先需要<span>pdb</span>文件，因此我们<span>build</span>程序时需要设置<span> Debug Infomation Format </span>为<span> &#8220;Program Database</span>（<span>/Zi</span>）<span>&#8221;</span>。其次，我们还要确保所用的<span>dump</span>文件与源代码、<span>exe</span>、<span>pdb</span>文件版本是一致的，这要求我们必须维护好程序版本信息。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>调试<span>minidump</span>最方便的环境就是<span>VS</span>了，我们只要将<span>.dmp</span>、<span>.exe</span>、<span>.pdb</span>文件放在一个路径下，保证源代码文件的路径与编译时的路径一致就可以了，剩下的就是<span>VS</span>帮我们完成。双击<span>.dmp</span>文件或者在文件打开工程中选择<span>&#8220;dump files&#8221;</span>，加载<span>dump</span>文件，然后按<span>F5</span>运行就能直接恢复<span>crash</span>时的现场了，你可以定位<span>crash</span>的代码，可以查看调用堆栈，可以查看线程和模块信息<span>...</span>一切都跟你设置断点调试一样，太强大了！看个截图吧。</span></p>
<p><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100g282&amp;url=http://s16.sinaimg.cn/orignal/48f93b53g7b0e4eff189f&amp;690" target=_blank><span><span><img title=clip_image012 border=0 alt=clip_image012 src="http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/ReleaseCrash_A881/clip_image012_3df8e4ef-d0e8-49d3-a9af-47f58cc713c6.jpg" width=616 height=469 v:shapes="_x0000_i1035"></span></span></a><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=48f93b530100g282&amp;url=http://s13.sinaimg.cn/orignal/48f93b53g7b0e4725426c&amp;690" target=_blank></a></span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>需要注意的是，对于<span>release</span>版的程序来说，很多代码是经过编译器优化过的，因此定位的时候可能会有所偏差，大家可以考虑设置选项去掉代码优化。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>其他可以调试<span>minidump</span>的工具还有<span>WinDbg</span>等，大家可以查阅相关资料。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>本文主要参考了这篇文章：<span><a href="http://vicchina.51.net/research/other/seh/minidumps/intro.htm"><span>http://vicchina.51.net/research/other/seh/minidumps/intro.htm</span></a></span>。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>下一篇，我们将给出一个调试<span>release</span>发布程序的完美解决方案，适合用户量较大的应用发布程序的调试。</span></p>
<p><span>上一篇我们已经给出了方案，能够非常方便的通过<span>dump</span>文件对<span>crash</span>错误进行调试和定位；从整个流程上看还差最后一步，即怎样拿到<span>crash</span>时产生的<span>dump</span>文件。如果可以让用户把文件发送过来自然不错，但对于类似免费共享软件等在互联网上发布的程序呢？我们的用户是不确定的，而且用户量有可能非常大，即使我们能想办法联系到用户，总不能挨个去收集<span>crash</span>信息吧。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>我们需要一种方案，能够提供<span>crash</span>信息汇报功能。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>我们可以架设一台服务器专门进行信息收集，只要客户端在<span>crash</span>时正确汇报即可，但是相应的维护成本和开发难度也不可忽视。有没有更简单的方法呢？还记得我的博文<span>&#8220;<a href="http://blog.sina.com.cn/s/blog_48f93b530100esmg.html" target=_blank><span><span>为程序添加自动发送Email</span></span><span><span>功能</span></span></a>&#8221;</span>吗？这就是简单有效的方法！</span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><strong><span>方案六：<span>minidump + email</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>我们只需要在异常处理时，先生成<span>minidump</span>信息文件，再用<span>email</span>方式将文件发送到指定邮箱就行了。剩下的就是我们每天查看邮箱，提取<span>dump</span>文件进行调试了。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>1</span></strong></span><strong><span>、<span>Email</span>功能</span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>首先我们来看一下<span>email</span>发送都需要哪些相关信息。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a</span><span>、发送端邮箱帐户；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> b</span><span>、接收端邮箱帐户；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> c</span><span>、<span>email</span>标题，一般应有软件名称及版本信息；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> d</span><span>、<span>email</span>正文，一般应有简单的<span>crash</span>信息提示，以区别不同原因造成的<span>crash</span>；</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>e</span><span>、<span>email</span>附件，当然就是我们的<span>dump</span>文件了，还可以加上软件生成的<span>log</span>文件等。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>当然，对于标题应该尽量多加一些信息区别引起<span>crash</span>的原因，比如将<span>crash</span>的地址信息加到标题中；因为当每天有成百上千的<span>crash</span>汇报上来，重复的<span>crash</span>占大多数，把时间都花在区分它们身上有点太浪费。由此看来，前面方案中提到的<span>StackWalker</span>还是有些用处的，我们可以用它来生成一些<span>crash</span>的文字描述信息，写到标题或正文中去。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> dump</span><span>文件的大小是否适合作为邮件的附件呢？实际上<span>minidump</span>产生的文件一般在几<span>K</span>到几十<span>K</span>之间，作为<span>email</span>的附件没有任何问题。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> </span><span>关于发送<span>email</span>相关技术细节，已经在<span>&#8220;<a href="http://blog.sina.com.cn/s/blog_48f93b530100esmg.html" target=_blank><span><span>为程序添加自动发送Email</span></span><span><span>功能</span></span></a>&#8221;</span>文中介绍了，大家可以参考。其实，对接受邮箱中邮件的处理还是很费时费力的，大家可以考虑写一些脚本将处理流程自动化，提高效率。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><span>2</span></strong></span><strong><span>、<span>google breakpad</span></span></strong></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> google breakpad</span><span>是一个开源的跨平台<span>crash report</span>系统，光从开源和跨平台这两个特点上来看，它就足以称的上是一个完善而有效的工具了。其实，<span>breakpad</span>在整个<span>crash report</span>层次上给出了一个系统级的解决方案，也就是说它几乎能适应各种软件、各种平台的应用要求。</span></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> breakpad</span><span>的整体思路跟上面介绍的方案是相似的，只不过最后提交<span>dump</span>文件的方式更加完善。大家有兴趣可以去它的官方网址查阅相关资料：<span><a href="http://code.google.com/p/google-breakpad/"><span>http://code.google.com/p/google-breakpad/</span></a></span>。</span></p>
<p>&nbsp;<wbr></p>
<p><span>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ok</span><span>，关于调试<span>release</span>发布程序的<span>crash</span>错误系列文章就写完了。这几篇文章给出的方案由简单到复杂，由简陋到完善，对<span>crash</span>调试有了一个比较全面的总结。当然，其中涉及到的概念和技术还很多，需要我们去不断学习和领悟，也希望大家能够互相交流。</span></p>
</div>
<img src ="http://www.cppblog.com/Walker/aggbug/146153.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-05-11 07:53 <a href="http://www.cppblog.com/Walker/articles/146153.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>dll pe 文件头</title><link>http://www.cppblog.com/Walker/articles/144121.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 13 Apr 2011 06:41:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/144121.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/144121.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/144121.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/144121.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/144121.html</trackback:ping><description><![CDATA[问：1.1<br>我知道程序中最重要的段是text段，请告诉我text 段在哪？<br>答：1.1<br>text 段在文件偏移0x400处，大小0x200字节，该区可运行，可读取，包含代码。<br>该区在内存中RVA 0x1000处，大小0x1000.<br><br>问：1.2<br>我用urtraedit 打开hello.exe 看了，在0x400处-0x600处，大部分都是0，为什么这样呢。<br>答：1.2<br><span class=highlight><strong><font color=#ff0000>pe</font></strong></span> 格式大部分文件都是这样，这是对齐所要求的，文件对齐为0x200, 内存对齐为0x1000<br>你可以在NT_Option_Header 的Section_Alignment, File_Alignment 域中看到这两个数据。<br><br>//<br>// Optional header format.<br>//<br><br>ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>def struct _IMAGE_OPTIONAL_HEADER {<br>//<br>// Standard fields.<br>//<br><br>WORD Magic;<br>BYTE MajorLinkerVersion;<br>BYTE MinorLinkerVersion;<br>DWORD SizeOfCode;<br>DWORD SizeOfInitializedData;<br>DWORD SizeOfUninitializedData;<br>DWORD AddressOfEntryPoint;<br>DWORD BaseOfCode;<br>DWORD BaseOfData;<br><br>//<br>// NT additional fields.<br>//<br><br>DWORD ImageBase;<br>DWORD SectionAlignment;<br>DWORD FileAlignment;<br>WORD MajorO<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>ratingSystemVersion;<br>WORD MinorO<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>ratingSystemVersion;<br>WORD MajorImageVersion;<br>WORD MinorImageVersion;<br>WORD MajorSubsystemVersion;<br>WORD MinorSubsystemVersion;<br>DWORD Win32VersionValue;<br>DWORD SizeOfImage;<br>DWORD SizeOfHeaders;<br>DWORD CheckSum;<br>WORD Subsystem;<br>WORD DllCharacteristics;<br>DWORD SizeOfStackReserve;<br>DWORD SizeOfStackCommit;<br>DWORD SizeOfHeapReserve;<br>DWORD SizeOfHeapCommit;<br>DWORD LoaderFlags;z<br>DWORD NumberOfRvaAndSizes;<br>IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];<br>} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;<br><br>问：1.3<br>慢点，别一下子贴那么多东西，我还没有找到 _IMAGE_OPTIONAL_HEADER 的位置呢，<br>告诉我怎样找<br>答：1.3<br>贴上那个_IMAGE_OPTIONAL_HEADER结构好说话，它的位置紧跟在 MAGE_FILE_HEADER 之后<br>告诉你个小技巧，那个Magic对NT x86来讲总是010B,在头文件找到那个010b,就是IMAGE_OPTIONAL_HEADER32 结构的地址。<br><br>问：1.4<br>问题越来越多了。<br>_IMAGE_OPTIONAL_HEADER 还没有说清呢，又出来一个IMAGE_FILE_HEADER。先不管IMAGE_FILE_HEADER<br>先按你的小技巧，在头部找到010b, 因为是little endial, 在ultraedit 中要找0b 01.<br>好，找到了，离那个 50 45 00 00 (ascii PE)相距不远，在偏移D8处，按你所说SectionAlignment和FileAlignment 应该在结构第9个，第10个DWORD 处。<br>好，找到了，在f8处有00001000， FC处为00 00 02 00 （我已经考虑了endian,以后不用提醒了）。<br>答：1.4<br>呀，进步不小吗？这样一下子你就把IMAGE_OPTIONAL_HEADER32 中所有的东西都找出来了。<br><br>问：1.5<br>是的，我可以把Optional header中所有东西都找出来，但我现在除了刚才介绍的第9个DWORD为内存对齐大小，第10个DWORD为文件对齐大小，其它我都不知道是干什么的？<br>答：1.5<br>别着急，其实还是很容易理解的，从字面意义就能猜大概。不过我们现在还不是通读Optional header的时候，还是拣我们最关心的问题插手吧。<br><br>问：1.6 <br>还是回到text 段上来吧，刚才你对text段大小，位置，属性分析的头头是到<br>你是从那看出来的？<br>答：1.6<br>是从section header 中看出来的，每一个section, 都有一个section header 描述其位置，大小，属性。<br>section header 的结构是这样定义的<br>#define IMAGE_SIZEOF_SHORT_NAME 8<br><br>ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>def struct _IMAGE_SECTION_HEADER {<br>BYTE Name[IMAGE_SIZEOF_SHORT_NAME];<br>union {<br>DWORD PhysicalAddress;<br>DWORD VirtualSize;<br>} Misc;<br>DWORD VirtualAddress;<br>DWORD SizeOfRawData;<br>DWORD PointerToRawData;<br>DWORD PointerToRelocations;<br>DWORD PointerToLinenumbers;<br>WORD NumberOfRelocations;<br>WORD NumberOfLinenumbers;<br>DWORD Characteristics;<br>} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;<br><br>问：1.7<br>呦，慢点，怎么又往外甩结构，我很菜！ 哦，不太多，还行吧。<br>不过你还是告诉我具体位置在哪吧，我好拿结构和数据对对号。<br>答：1.7<br>好，正是这种<span class=highlight><strong><font color=#ff0000>学习</font></strong></span>方法。你一定能学会的。<br>节表头是一个数组，它把所有节的位置，长度，属性放在了一起<br>紧跟在option header 之后，所以你从文件头部往下找就可以了。<br>看到IMAGE_SECTION_HEADER结构的第一个成员了吗，它是<br>BYTE Name【8】<br>这是节名称，你要找的text 段名字就是 .text, 你看ultraedit<br>ascii 码区离文件开始不远的地方，有一个.text, 对应的二进制<br>数据是2E 74 65 78 74, 这就是text 端IMAGE_SECTION_HEADER处<br><br>问：1.8<br>原来玄机在这里呀。我试试看。哦，看见了，在1B8处。 前8个<br>字节是节名称。后面的00 00 00 28 到底是物理地址还是虚拟大小，<br>（偷偷的，虚拟大小，表示内存中只有0x28个字节有效，其它全是0），在后面00 00 10 00 是虚拟相对地址 俗称RVA, 就是在内存中相对与起始地址的偏移。再后面00 00 02 00 为SizeOfRawData， 就是文件中大小，再后面 00 00 04 00 是<br>PointerToRawData，是文件的偏移 后面有三个DWORD 全是0，他们<br>是重定位信息和行号，很好，EXE文件可以不用管这些。最后一个<br>60 00 00 20 代表属性可读，可写，是代码。好，我终于理解你的第一句话了。<br>不解释一下，我怎么能一下子听的懂呢！ 谢谢你。<br>那么我又有问题了。那程序针真是搜索这个.text字符串找到Text 节表头吗？<br>答：1.8<br>不是。前面说过，节表头紧随Optional header 之后。<br><br>问：1.9<br>Optional header 结构变量太多，我数了一下都没数清，到底占多少个字节呢？<br>答：1.9<br>正等着你这一问呢？是啊，数都数不清，纵是现在记住了将来也容易忘。<br>估计微软也想到了这一点，他把OPTION header 的大小放到了 _IMAGE_FILE_HEADER 的一个变量中，<br>下面是_IMAGE_FILE_HEADER 的定义<br>ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>def struct _IMAGE_FILE_HEADER {<br>WORD Machine;<br>WORD NumberOfSections;<br>DWORD TimeDateStamp;<br>DWORD PointerToSymbolTable;<br>DWORD NumberOfSymbols;<br>WORD SizeOfOptionalHeader;<br>WORD Characteristics;<br>} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;<br>SizeOfOptionalHeader 一般总是0xE0<br><br>问：1.10<br>我今天已经学了不少东西了，看样子后面还很多的样子。再问最后一个问题。<br>FILE_HEADER 在文件什么位置呢。<br>答：1.10<br>这个简单，就在PE标识符后面。看到了吗，在C0处，ascii 是PE. 二进制是50 45 00 00<br><br>代学生：<br>哦，看到了，今天10个问题已经满了，我还想学，可是有点累了。。。<br>代老师：<br>今天就到这里吧，好好休息一下。<br><br><br>引言： 上一次以hello.exe 为例，介绍了<span class=highlight><strong><font color=#ff0000>pe</font></strong></span> 文件头，节表和导入表。<br>这一次我们以 count.dll 为例，介绍导出表和重定位表<br>count.dll 是罗云斌win32汇编编程中的例子程序，因其短小，故被选中。<br><br>问5.1：dll 为什么叫动态连接库，与平常的静态连接有什么不同。<br>答5.1：静态连接库在编译连接时由link 程序把库文件直接添加到运行程序中。<br>动态连接库在编译连接时只是把插桩加到代码里。运行时由加载器载入<br>内存，修改插桩代码使指向正确的地址。这个过程在上一讲中已经说过了。<br><br>问5.2：既然是库函数，就会有一堆函数构成，那么是否每个函数都可以被外边调用呢？<br>答5.2：库函数可分为三类，一个是库入口函数。<br>一类为可被外部调用，叫导出库函数。<br>一类不能被外部调用，我们叫它私有函数吧。因为它没有向外提供接口。<br><br>问5.3：导出函数是怎样向外提供接口的呢？ 或者说我们怎样才能使用导出函数呢？<br>答5.3： 这个问题是我们的重点，我们结合实例来吧，慢慢把它讲清楚。<br>用ultraedit 打开这个dll 文件。<br>ultraedit看到的是最原始的文件，其它众多的<span class=highlight><strong><font color=#ff0000>pe</font></strong></span> 分析软件都是从原始文件分析得到的。哦，当然，这话说的多余了。<br>大概浏览一下这个文件。<br>区分一下dos头， PE 头， 节表， 有几个块组成。这些都是很明显的。上一讲中已经说过了。<br>哦，上一讲讲的是exe, 这里是dll, 不过它们都是PE 文件， 格式是一样的。<br><br>顺便复习一下上次内容，这可以说是上次4讲的精华了，却是以count.dll 为例，同样通俗易懂：<br>dos 头： 标记 "MZ"<br>00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ?........ ..<br>00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ?......@.......<br>00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................<br>00000030 00 00 00 00 00 00 00 00 00 00 00 00 C0 00 00 00 ............?..<br>PE 头： 标记 "PE"<br>000000C0 50 45 00 00 4C 01 04 00 F6 34 EB 3C 00 00 00 00 PE..L...??....<br>000000D0 00 00 00 00 E0 00 0E 21 0B 01 05 0C 00 02 00 00 ....?.!........ <br>从dos头 0x3c 处也能看出PE 头位置。<br>节表： 有明显的字符串标记，此处是".text"<br>000001B0 2E 74 65 78 74 00 00 00 .text...<br>000001C0 70 00 00 00 00 10 00 00 00 02 00 00 00 04 00 00 p...............<br>000001D0 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`<br>000001E0 2E 72 64 61 74 61 00 00 BC 00 00 00 00 20 00 00 .rdata..?... ..<br>000001F0 00 02 00 00 00 06 00 00 00 00 00 00 00 00 00 00 ................<br>00000200 00 00 00 00 40 00 00 40 2E 64 61 74 61 00 00 00 ....@..@.data...<br>00000210 04 00 00 00 00 30 00 00 00 00 00 00 00 00 00 00 .....0..........<br>00000220 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0 ............@..?<br>00000230 2E 72 65 6C 6F 63 00 00 2C 00 00 00 00 40 00 00 .reloc..,....@..<br>00000240 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 ................<br>00000250 00 00 00 00 40 00 00 42 ....@..B........ <br>数一数节表有4个<br>从PE 头 0xc7处也能看出来。<br>大致浏览一下后面的数据块划分，块与块之间很容易识别，因为每一块之间都有很多0，<br>它们是以512字节对齐填充的。<br>咦！ 怎么只看到了3块， 节表头中不是说4块吗？<br>再仔细对照一下节表头：跟我一块找找。<br>00000400 55 8B EC B8 01 00 00 00 C9 C2 0C 00 55 8B EC 6A U嬱?...陕..U嬱j<br>00000410 01 FF 75 10 FF 75 0C FF 75 08 E8 4B 00 00 00 C9 . . . .鐺...?<br>00000420 C2 0C 00 55 8B EC FF 05 00 30 00 10 FF 35 00 30 ?.U嬱 .0.. .0<br>00000430 00 10 FF 75 0C FF 75 08 E8 CF FF FF FF A1 00 30 .. . .柘 0<br>00000440 00 10 C9 C2 08 00 55 8B EC FF 0D 00 30 00 10 FF ..陕..U嬱 .0.. br /&gt; 00000450 35 00 30 00 10 FF 75 0C FF 75 08 E8 AC FF FF FF 5.0.. . .璎 br /&gt; 00000460 A1 00 30 00 10 C9 C2 08 00 CC FF 25 00 20 00 10 ?0..陕..?%. ..<br>00000470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ <br>这段是 text 段，因为节表已经说了，text 段内存地址0x1000,大小0x70<br>在文件中处于偏移0x400, 占用文件大小0x200 字节。<br><br>00000600 38 20 00 00 00 00 00 00 30 20 00 00 00 00 00 00 8 ......0 ......<br>00000610 00 00 00 00 48 20 00 00 00 20 00 00 00 00 00 00 ....H ... ......<br>00000620 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................<br>00000630 38 20 00 00 00 00 00 00 27 02 53 65 74 44 6C 67 8 ......'.SetDlg<br>00000640 49 74 65 6D 49 6E 74 00 55 53 45 52 33 32 2E 64 ItemInt.USER32.d<br>00000650 6C 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ll..............<br>00000660 00 00 00 00 F6 34 EB 3C 00 00 00 00 9C 20 00 00 ....??....?..<br>00000670 01 00 00 00 02 00 00 00 02 00 00 00 88 20 00 00 ............?..<br>00000680 90 20 00 00 98 20 00 00 46 10 00 00 23 10 00 00 ?..?..F...#...<br>00000690 A8 20 00 00 B2 20 00 00 00 00 01 00 43 6F 75 6E ?..?......Coun<br>000006A0 74 65 72 2E 64 6C 6C 00 5F 44 65 63 43 6F 75 6E ter.dll._DecCoun<br>000006B0 74 00 5F 49 6E 63 43 6F 75 6E 74 00 00 00 00 00 t._IncCount.....<br>000006C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ <br>这段是 rdata 段，因为节表已经说了，rdata 段内存地址0x2000,大小0xbc<br>在文件中处于偏移0x600, 占用文件大小0x200 字节。<br><br><br>00000800 00 10 00 00 18 00 00 00 28 30 2E 30 3E 30 4B 30 ........(0.0&gt;0K0<br>00000810 51 30 61 30 6C 30 00 00 00 00 00 00 00 00 00 00 Q0a0l0..........<br>00000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................<br>这段是 reloc 段，因为节表已经说了，reloc 段内存地址0x4000,大小0x20<br>在文件中处于偏移0x800, 占用文件大小0x200 字节。<br><br>哦！也！文件分析完了。<br>呦，不是说看看丢了哪一块吗？ 是data 段丢了。看看节表怎么说：<br>节表已经说了，data 段内存地址0x4000,大小0x4<br>在文件中处于偏移0x0, 占用文件大小0x0 字节。<br>怪不得文件中找不到它的踪影，原来它不存在。但内存中还是给它留了位置。<br>不过这里是个特例，一般文件都会有data 段的存在。<br><br>（小声说）别高兴太早了，这只是划分了各个块，把每块的具体内容分析完才算完<br>哦，也.<br>下面我们再详细分析一下各个段功能。<br>text 段为核心，其它段都是为它服务的。<br>text 段<br>即代码段，由指令集构成。你可以反汇编出这部分内容，就知道它们的功能了。<br>为了本帖的完整性，我把它贴过来，并不长。<br>10001000 EntryPoint:<br>10001000 55 push ebp<br>10001001 8BEC mov ebp,esp<br>10001003 B801000000 mov eax,00000001h<br>10001008 C9 leave<br>10001009 C20C00 retn 000Ch<br>;----------------------------------------------------------------------------------------------------<br>1000100C SUB_L1000100C:<br>1000100C 55 push ebp<br>1000100D 8BEC mov ebp,esp<br>1000100F 6A01 push 00000001h<br>10001011 FF7510 push [ebp+10h]<br>10001014 FF750C push [ebp+0Ch]<br>10001017 FF7508 push [ebp+08h]<br>1000101A E84B000000 call jmp_USER32.dll!SetDlgItemInt<br>1000101F C9 leave<br>10001020 C20C00 retn 000Ch<br>;----------------------------------------------------------------------------------------------------<br>10001023 _IncCount:<br>10001023 55 push ebp<br>10001024 8BEC mov ebp,esp<br>10001026 FF0500300010 inc [L10003000]<br>1000102C FF3500300010 push [L10003000]<br>10001032 FF750C push [ebp+0Ch]<br>10001035 FF7508 push [ebp+08h]<br>10001038 E8CFFFFFFF call SUB_L1000100C<br>1000103D A100300010 mov eax,[L10003000]<br>10001042 C9 leave<br>10001043 C20800 retn 0008h<br>;----------------------------------------------------------------------------------------------------<br>10001046 _DecCount:<br>10001046 55 push ebp<br>10001047 8BEC mov ebp,esp<br>10001049 FF0D00300010 dec [L10003000]<br>1000104F FF3500300010 push [L10003000]<br>10001055 FF750C push [ebp+0Ch]<br>10001058 FF7508 push [ebp+08h]<br>1000105B E8ACFFFFFF call SUB_L1000100C<br>10001060 A100300010 mov eax,[L10003000]<br>10001065 C9 leave<br>10001066 C20800 retn 0008h<br>;----------------------------------------------------------------------------------------------------<br>10001069 CC Align 2<br>1000106A jmp_USER32.dll!SetDlgItemInt:<br>1000106A FF2500200010 jmp [********] //故意把名字隐含了<br>;----------------------------------------------------------------------------------------------------<br>代码分三类：<br>第一类与地址无关，它们二进制代码已经定下来了<br>第二类与地址有关，它们二进制代码也定下来了，如果dll 加载到它的默认地址，代码不用修改<br>第三类是代码还没有确定，用插桩来表示。如：<br>jmp [********], 它的插桩是 10002000 地址<br><br>先来解决插桩问题吧，这就是hello.exe 讲座中的导入表问题。<br>1. 内存地址 10002000-10000000(image_base) = 2000(RVA)<br>说实话，image_base 我记不清位置，也没有明显标记，每次要查结构偏移。<br>好在dll 通常是10000000,exe 通常是400000，不查也没有问题。<br>我又查了一边，偏移是PE 标识后（不包括PE00标识）第13 个DWORD 偏移处。加深点印象，跟IAT在目录项偏移一样<br><br>2. RVA -&gt; offset: 从节表知 RVA 0x2000== offset 0x600：<br>3. [0x600] == 2038, RVA 2038==offset 638, [0x638]== "0207 setDlgItemInt", 前面是导出序号，后面是导出名称<br>; ---------------------------------------------------------------------------<br>loader 解决插桩的问题是从导入表开始的。导入表是目录项的第二项：<br>00000140 08 20 00 00 28 00 00 00 . ..(...<br><br>导入表指向导入函数库数组。<br>RVA 2008 == offset 608, length=0x28 (一个导入表结构为5个DWORD-0x14, 故0x28为两项，一个有效项，一个全0尾标识）<br>第一项：<br>originFirstThunk == 2030, RVA 2030==offset 630 (IAT 的备份，供你看的）<br>Name = 2048, RVA 2048 = offset 648 == "USER32.dll"<br>FirstThunk == 2000， RVA 2000 == offset 600 (IAT loader 加载时会更改这一部分，以完成插桩）<br>导入表给出了"USER32.dll"，插桩处2038 给出了函数名"setDlgItemInt", loader 根据这些信息完成插桩。<br>; ---------------------------------------------------------------------------<br>至此我们已经复习了hello.exe 中讲过的东西了。<br><br>问5.4：其实话说的越多越不清楚，越少越容易扼要，把导入表部分再概括一下把。<br>答5.4：导入表在rdata 域。<br>第一部分为导入地址表，这部分loader 在加载时会修改其数值完成插桩。<br>第二部分为导入表。导入表是为IAT 服务的，loader 要修改IAT, 必须要知道导入函数的名称。<br>这由导入表提供。导入表同时还提供，该函数名负责IAT 表中哪一个区间。<br>所以，导入表是导入函数库数组。每个结构有5个DWORD 变量，2个没用。<br>3个变量全是RVA, 一个指向函数名，一个指向IAT, 另一个也指向IAT，在结构最前面,但这个IAT loader 不会更改它。<br>第三部分按地理位置划分是IAT 的备份表，就是导入表中Origin_FirstThunk指向的部分。<br>第四部分是导入函数名表，前面是序号导出，后面紧跟名称导出。修改插桩当然要用这些信息<br>第五部分为函数库名称区域。因为导入表只提供RVA, RVA指向的是这个区域。<br><br>问5.5：好，这样一看rdata 的上半部分意义就明白了，那下面还有一部分内容呢？<br>答5.5：这就是还没开始讲的导出表了。导出表比导入表简单。因为导入表可能从好几个库中导入，<br>而导出表只是将自己的相关函数导出。<br><br>问5.6：为什么要把导出表和导入表放到一起呢。<br>答5.6：因为它们的属性相同。看节表rdata. 0x40000040,两个属性，我查了一下文档，前面那个1代表可读，<br>后面那个1代表代码包含初始化数据。<br><br>问5.7： 具体导出表要包含那些内容才算把函数导出了呢？<br>答：呦，拖课了！ 今天主要是复习了一下前边讲的内容，下一课我们再讲导出表！
<div style="LINE-HEIGHT: 18px" class=bword>问5.7： dll 和 exe 文件都是PE 文件， 在PE 文件中是怎样区分的呢？<br>答5.7：有一些小差别。<br>例如， exe 通常被加载到0x400000, 而dll 默认加载是0x10000000<br>exe 通常不含reloc 段，而dll 包含。<br>exe 通常不含export 段， 而dll 包含。<br>exe 属性010f, 最后1bit 说它没有重定位信息 <br>dll 属性210e. 最高位的2 说它是dll, 这个好像是最关键差别了吧。<br><br>问5.8：exe 的 entrypoint 是程序执行的起始点，dll 的 entrypoint 是什么呢？<br>答5.8：这个地方是dll 的入口函数地址，它是不可以省略的。<br>dll 在加载，卸载以及线程加载，卸载时都会<br>到这里执行程序。 它的通用结构是这样的。<br>BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved)<br>{<br>switch(fswReason)<br>case DLL_PROCESS_ATTACH<br>......<br>case DLL_THREAD_ATTACH<br>.....<br>case DLL_THREAD_DETACH<br>.....<br>case DLL_PROCESS_DETACH<br>.... <br>return (TRUE or False) // true , 成功，false 失败， loader 会把它从内存卸掉。<br>} <br>count.dll 中没有按这种结构，它只是简单的返回一个TRUE,因为它不需要申请内存和释放内存等初始化操作。<br>count.dll entrypoint 是10001000， PE 标识后第10 DWORD 地址，记不住用工具查。<br><br>问5.9：前面说过dll 有入口函数， 导出函数和非导出函数。又回到上次未讲的问题<br>导出表是怎样把函数导出的。<br>答5.9：我们先猜一猜导出函数的关键要素吧。<br>1. 导出库名称<br>2. 函数导出序号。（提供序号导出）<br>3. 导出函数名 （提供函数名导出）<br>4. 序号或函数名对应的地址。<br>它向系统报告这些信息已经足够了。<br><br>问5.10： 结合例子和结构定义具体说一下吧。<br>答5.10： 看count.dll 目录项第一项<br>00000130 60 20 00 00 5C 00 00 00 ` ..\... <br>位置 RVA 2060 == offset 660<br>大小 0x5c<br>00000660 00 00 00 00 F6 34 EB 3C 00 00 00 00 9C 20 00 00 ....??....?..<br>00000670 01 00 00 00 02 00 00 00 02 00 00 00 88 20 00 00 ............?..<br>00000680 90 20 00 00 98 20 00 00 46 10 00 00 23 10 00 00 ?..?..F...#...<br>00000690 A8 20 00 00 B2 20 00 00 00 00 01 00 43 6F 75 6E ?..?......Coun<br>000006A0 74 65 72 2E 64 6C 6C 00 5F 44 65 63 43 6F 75 6E ter.dll._DecCoun<br>000006B0 74 00 5F 49 6E 63 43 6F 75 6E 74 00 t._IncCount. <br><br>那么这是一个什么样的数据结构呢？我们先猜猜看，这里也叫逆向<span class=highlight><strong><font color=#ff0000>学习</font></strong></span>方法吧。<br>首先函数名称：count.dll（offset 69c), 函数名称_DecCount(offset 6a8), _IncCount(6b2) 都已经看到了。<br>转换为RVA. offset 69c =RVA 209c<br>offset 6a8 =RVA 20a8<br>offset 6b2 =RVA 20b2<br>很高兴在数据中找到了9c 20, a8 20, b2 20. 关注一下这些地址。<br>从660 在滤一下，感觉后面的00000002 可能是个数吧。<br>后边的2088， 2090，2098 <br>RVA 2088 == offset 688 [688] == 46 10 00 00 23 10 00 00 // 像函数地址,赶紧确认一下（在上一篇），正是。<br>RVA 2090 == offset 690 [690] == A8 20 00 00 B2 20 00 00 // 函数名称地址<br>RVA 2098 == offset 690 [690] == 00 00 01 00 // 像hint<br>这样划分后，看不懂的就不多了，<span class=highlight><strong><font color=#ff0000>学习</font></strong></span>数据结构是时候了。<br>ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>def struct _IMAGE_EXPORT_DIRECTORY {<br>DWORD Characteristics;<br>DWORD TimeDateStamp;<br>WORD MajorVersion;<br>WORD MinorVersion;<br>DWORD Name;<br>DWORD Base;<br>DWORD NumberOfFunctions;<br>DWORD NumberOfNames;<br>DWORD AddressOfFunctions; // RVA from base of image<br>DWORD AddressOfNames; // RVA from base of image<br>DWORD AddressOfNameOrdinals; // RVA from base of image<br>} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;<br>我们分析的是对的，那个base 是什么东西 ？是导出函数的基序号。所以导出函数序号不是0，1，而是1，2 了<br>很明显，1指的是1046地址，2指的是1023地址<br>问5.11 再总结一下导出表吧。<br>答5.11 导出表有两种导出方法，一种是按名称导出，一种是按序号导出。其中序号导出的个数总是大于等于名称导出的个数。<br>导出的函数地址按4字节一字排开。每一个函数索引号就是+Base 值就是导出序号。<br>序号不直观，所以有些函数用名称导出，名称导出最终还是要找到函数序号。所以把名称所在的名称数组的位置为索引<br>去从名称序号数组中拿到序号（此为索引号），由索引号取到函数地址。<br><br>问5.12 假如loader 要插桩本函数 _IncCount 地址，它是怎样操作的。<br>答5.12<br>1. 它首先要加载我们的dll. 用loadlibrary<br>2. 找到我们的导出表。<br>3. 再找到导出表中AddressOfNames。<br>4. 遍历该表找到_IncCount 函数，记下它的索引<br>5. 从AddressOfNameOrdinals数组中，取到该索引对应的函数地址序号<br>6. 从导出表中找到AddressOfFunctions， 用得到的序号去取到它的地址。<br>7. 将该地址去填充到IAT 的对应位置上。<br><br>这样插桩就完成了。哇！这个小插桩要经过这么多步骤哇，有没有办法简化一下啦... 等着你去实现呢！<br><br>问5.13 count.dll 中还有一个reloc 节，讲讲它是怎样构成的。<br>答5.13 reloc 也是为text 段服务的，前面说过，若dll 加载到它默认位置，可以不用reloc 段。<br>当不能加载到默认地址时， 某些于地址有关的指令需要重新定位。就是说要修改指令中地址<br>使其指向正确的地址。<br>看目录项中reloc 表，第6个表（索引号为5）：<br>00000160 00 40 00 00 18 00 00 00 .@......<br>RVA 4000 = offset 800, size=0x18<br>哦，纵使不用reloc 目录项，直接目视也看见它了。这个程序很小，是这样的。<br>00000800 00 10 00 00 18 00 00 00 28 30 2E 30 3E 30 4B 30 ........(0.0&gt;0K0<br>00000810 51 30 61 30 6C 30 00 00 00 00 00 00 00 00 00 00 Q0a0l0..........<br>我们也像export 表一样，先猜猜reloc 结构应该有那些重要元素。<br>1. 内存地址， 4byte, 我们要知道对哪的指令进行重定位。即where 问题<br>2. 替换方法。 是替换一字节，2字节还是4字节， 是how 的问题，估计有几个bit 就够了。<br>3. 用什么替换。 是一个what 的问题。 这个问题就不用考虑了，这个what,就是加载地址与默认地址偏移。<br>这样看起来一个reloc 项至少也要 5 bytes 了。如果有很多项，那这很多项就构成一个数组。<br>我这里介绍的方法是一种逆向的<span class=highlight><strong><font color=#ff0000>学习</font></strong></span>方法，或者是原始的思考方法。因为我想最初设计这个PE 结构的人也会<br>这么想。<br>现在使用的PE 结构在这个想法的基础上进行了优化，使得reloc表占用较少的字节，<br>具体说是它让一个reloc 项占用2byte,下面看它的方法：<br><br>1.<br>ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>def struct _IMAGE_BASE_RELOCATION {<br>DWORD VirtualAddress;<br>DWORD SizeOfBlock;<br>} IMAGE_BASE_RELOCATION;<br><br>它的意思是说，它要重定位VirtualAddress=1000这块区域， 该区域所占的重定位信息大小为SizeOfBlock=0x18<br>后面紧跟的每2 个bytes 构成一个重定位项， 其中低12 bit为重定位地址， 高4bits 为重定位类型。<br>我这里把重定位类型copy 过来，其中有的在x86 上是用不到的。<br>//<br>// Based relocation ty<span class=highlight><strong><font color=#ff0000>pe</font></strong></span>s.<br>//<br><br>#define IMAGE_REL_BASED_ABSOLUTE 0<br>#define IMAGE_REL_BASED_HIGH 1<br>#define IMAGE_REL_BASED_LOW 2<br>#define IMAGE_REL_BASED_HIGHLOW 3<br>#define IMAGE_REL_BASED_HIGHADJ 4<br>#define IMAGE_REL_BASED_MIPS_JMPADDR 5<br>#define IMAGE_REL_BASED_SECTION 6<br>#define IMAGE_REL_BASED_REL32 7<br><br>#define IMAGE_REL_BASED_MIPS_JMPADDR16 9<br>#define IMAGE_REL_BASED_IA64_IMM64 9<br>#define IMAGE_REL_BASED_DIR64 10<br>#define IMAGE_REL_BASED_HIGH3ADJ 11<br><br>地址只有12为意味着你只能管理4K 范围，是的，如果超过了4K范围，我们重新定义一个IMAGE_BASE_RELOCATION<br>变量就可以了，它又能管理下一个4K的范围。后续没16bit 为一个reloc 项， 最尾部以0000 结尾。当然，由于<br>重定位块大小有定义，纵使不用0000标识结尾也没有问题，把这里的冗余姑且叫双保险吧，就是浪费了2bytes<br>count.dll 中，我们先算算有几个重定位项。哦，不用算，有7个，一查就查出来了。<br>但当程序大的时候还是要计算的。(0x18-8)/2-1=7 个。<br>计算公式：(SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2-1<br><br>问：5.14 那这7个重定位项到底是怎样重定位的呢？还是给出具体结果更彻底。<br>答：5.14 好，做事做到底，现在我们就开始吧。<br>第一项：28 30 其内容为 0x3028 重定位类型为3， 地址为0x28<br>其它重定位类型都是3， 地址不同而已。<br>#define IMAGE_REL_BASED_HIGHLOW 3<br>那么这个BASE_HIGHLOW 是什么意思呢？我们先看看0x28处的指令。<br>10001026 FF0500300010 inc [L10003000]<br>假如我们的dll 不是被加载到0x10000000，而是加载到0x30000000, 即向上提高了0x20000000<br>我们只要把0x28地址处也加上0x20000000,就可以了。这样这条指令就变成了<br>10001026 FF0500300030 inc [L30003000]<br>看明白了吧！ 窗户纸就是这样被捅破的<br><br><br>好了，本帖就到这里结束吧！</div>
<img src ="http://www.cppblog.com/Walker/aggbug/144121.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-13 14:41 <a href="http://www.cppblog.com/Walker/articles/144121.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Services</title><link>http://www.cppblog.com/Walker/articles/144118.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 13 Apr 2011 05:53:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/144118.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/144118.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/144118.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/144118.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/144118.html</trackback:ping><description><![CDATA[&nbsp;
<p align=left><strong><span>Services</span></strong><strong></strong></p>
<p align=left><strong><span>1 </span></strong><strong><span>概述</span></strong><strong></strong></p>
<p align=left><span>1</span><span>服务程序遵循</span><span>Service Control Manager(SCM)</span><span>的接口规则。</span><span> </span><span>启动方式由</span><span>3</span><span>种：</span><span>1 </span><span>系统启动时自动启动；</span><span>2 </span><span>通过服务控制面板</span><span>;3 </span><span>通过使用服务函数。</span><span>2</span><span>服务可以在没有用户登录到系统的情况下运行。</span></p>
<p align=left><span>2 </span><span>驱动服务</span><span>：</span><span>驱动服务遵循设备驱动协议，它与服务程序类似，但是不与</span><span>SCM</span><span>交互。</span></p>
<p align=left><span>3 </span><span>The SCM processes service control notifications in a serial fashion</span></p>
<p align=left><span>4</span><span> The default security descriptor allows the <a href="http://msdn.microsoft.com/en-us/library/ms684190(v=VS.85).aspx"><span>LocalSystem account</span></a>, and members of the Administrators and Power Users groups to stop and start services.</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>2 </span></strong><strong><span>服务在</span></strong><strong><span>Win7</span></strong><strong><span>中的新特性</span></strong><strong></strong></p>
<p align=left><span>1 </span><span>可以注册一个服务，在一个特定事件发生时启动或者停止服务。</span></p>
<p align=left><span>更新的函数</span></p>
<table border=1 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms681987(v=VS.85).aspx"><strong><span>ChangeServiceConfig</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Changes the configuration parameters of a service. This function supports managed service accounts and virtual accounts. For more information, see <a href="http://go.microsoft.com/fwlink/?LinkId=147314" target=_blank><span>Service Accounts Step-by-Step Guide</span></a>.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms681988(v=VS.85).aspx"><strong><span>ChangeServiceConfig2</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Changes the optional configuration parameters of a service. This function supports new configuration information levels for processor groups and service trigger events.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682450(v=VS.85).aspx"><strong><span>CreateService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Creates a service object and adds it to the specified service control manager database. This function supports managed service accounts and virtual accounts. For more information, see <a href="http://go.microsoft.com/fwlink/?LinkId=147314" target=_blank><span>Service Accounts Step-by-Step Guide</span></a>.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms683241(v=VS.85).aspx"><strong><span>HandlerEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>An application-defined callback function used with the <a href="http://msdn.microsoft.com/en-us/library/ms685058(v=VS.85).aspx"><strong><span>RegisterServiceCtrlHandlerEx</span></strong></a> function. This callback function supports new extended control codes for system time changes and service trigger events.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684935(v=VS.85).aspx"><strong><span>QueryServiceConfig2</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the optional configuration parameters of a service. This function supports new configuration information levels for processor groups and service trigger events.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms686241(v=VS.85).aspx"><strong><span>SetServiceStatus</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Updates the service control manager's status information for the calling service. This function supports new extended control codes for system time changes and service trigger events.</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<p><span>新的结构</span></p>
<table border=1 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/dd405511(v=VS.85).aspx"><strong><span>SERVICE_TIMECHANGE_INFO</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Contains system time change settings. </span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/dd405512(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Represents a service trigger event.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/dd405514(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER_INFO</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Contains trigger event information for a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/dd405515(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER_SPECIFIC_DATA_ITEM</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Contains trigger-specific data for a service trigger event.</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left><strong><span>Service Changes for Windows <st1:place w:st="on">Vista</st1:place></span></strong></p>
<p>&nbsp;</p>
<p><span>为了提高性能、可靠性、安全性、可管理性等，在</span><span>Vista</span><span>之后，服务提供了很多增强的特性。</span></p>
<p>&nbsp;</p>
<p><span>Session 0 Isolation</span></p>
<p align=left><span>服务总是运行在</span><span>Session 0</span><span>，在</span><span>Vista</span><span>之前，第一个用户也是运行在</span><span>Session0</span><span>，在</span><span>Vista</span><span>之后，第一个用户运行在</span><span>Session1</span><span>，第二个用户运行在</span><span>Session2</span><span>，</span><span> </span><span>等等，这样用户程序和服务就始终运行在不同的</span><span>Session</span><span>上。</span><span>Session 0 </span><span>不支持与用户交互的进程。这就意味着</span><span>Service</span><span>不能向应用程序发送消息，应用程序也不能像</span><span>Service</span><span>发送消息。除此之外，</span><span>Service</span><span>还不能显示</span><span>GUI</span><span>，如对话框，但是</span><span>Service</span><span>可以使用</span><span><a href="http://msdn.microsoft.com/en-us/library/aa383842(v=VS.85).aspx"><strong><span>WTSSendMessage</span></strong></a> </span><span>函数在另一个</span><span>Session</span><span>中显示对话框。</span></p>
<p align=left><span>Delayed AutoStart </span><span>在系统启动的一小段时间（</span><span>shortly after</span><span>）内启动服务。</span></p>
<p align=left><span>Failure Detection and Recovery</span><span>：如果服务失败，</span><span>SCM</span><span>可以执行失败</span><span>action</span><span>，如重启服务。</span></p>
<p align=left><span>Preshutdown Notifications</span><span>：是服务有足够的时间来优雅的关闭。</span></p>
<p align=left><span>Restricted Network Acess</span><span>：</span></p>
<p align=left><span>Running with Least Privilage</span><span>：</span></p>
<p>&nbsp;</p>
<p align=left><strong><span>关于服务</span></strong><strong></strong></p>
<p align=left><span>SCM</span><span>维护者已安装的服务和驱动服务的数据库，并且提供了统一的安全的控制它们的方法。数据库信息包括每个服务如何被启动。</span></p>
<p align=left><span>下面类型的程序使用</span><span>SCM</span><span>提供的函数：</span></p>
<p align=left><span>服务程序（</span><span>Service Program</span><span>）：一个为一个或者多个服务提供可执行代码的程序。服务程序使用连接到</span><span>SCM</span><span>的函数以及发送状态信息给</span><span>SCM</span><span>的函数。</span></p>
<p align=left><span>服务配置程序（</span><span>Service configuration program</span><span>）：一个查询和修改服务数据库的程序。服务配置程序使用打开数据库的函数，在数据库中安装和删除服务，对安装的服务查询和修改配置参数，安全参数。服务配置程序管理服务以及驱动服务。</span></p>
<p align=left><span>服务控制程序（</span><span>Service Control Program</span><span>）：一个启动并控制服务以及驱动服务的程序。服务控制程序使用发送请求到</span><span>SCM</span><span>的函数。</span></p>
<p align=left><strong><span>Service Control Manager(</span></strong><strong><span>服务控制管理器</span></strong><strong><span>)</span></strong></p>
<p align=left><span>1 </span><span>系统启动时启动；它是一个</span><span>RPC (Remote procedure call) </span><span>服务器。所以服务配置和服务控制程序可以在远程机器上管理服务。</span></p>
<p align=left><span>提供接口功能：</span></p>
<p align=left><span>管理安装服务的数据库；</span></p>
<p align=left><span>启动服务（系统启动时，或者需要时）</span></p>
<p align=left><span>枚举安装的服务</span></p>
<p align=left><span>为运行的服务维护状态信息</span></p>
<p align=left><span>传输控制请求（</span><span>control request</span><span>）给正在运行的服务</span></p>
<p align=left><span>锁定和解锁服务数据库。</span></p>
<p align=left><span>Database of installed service</span></p>
<p align=left><span>在注册表中维护着已安装的服务列表。注册表如下</span></p>
<p align=left><strong><span>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services</span></strong><span>.</span></p>
<p align=left><span>这个键值为每一个安装的服务包含一个子键，子键的名字就是服务名。</span></p>
<p align=left><span>Database</span><span>又叫作</span><span>ServiceActive databse</span><span>或者</span><span>SCM database</span><span>。你必须使用</span><span>SCM</span><span>提供的函数，而不能直接修改</span><span>databse</span><span>，即不能直接修改注册表。</span></p>
<p align=left><span>自动启动服务</span></p>
<p align=left><span>在系统启动的时候</span><span>,SCM</span><span>将启动所有自启动的服务以及它们依赖的服务。</span></p>
<p align=left><span>启动顺序：</span></p>
<p align=left><span>1 </span><span>按加载启动组列表中的顺序；这个信息保存在</span><strong><span>ServiceGroupOrder</span></strong><strong><span>值中，</span></strong><strong></strong></p>
<p align=left><span>HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control</span></p>
<p align=left><span>2 GroupOrderList</span></p>
<p align=left><span>HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control</span></p>
<p align=left><span>3 </span><span>每个服务的依赖列表</span></p>
<p align=left><span>当启动完成后，系统执行启动认证程序（</span><span>boot verification program</span><span>）根据</span><span>BootVerificationProgram</span></p>
<p align=left><span>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control.</span></p>
<p align=left><span>默认情况下，这个值是没有设置的。你可以提供程序来检测系统，调用</span><span><a href="http://msdn.microsoft.com/en-us/library/ms684274(v=VS.85).aspx"><span>NotifyBootConfigStatus</span></a></span><span>通知</span><span>SCM</span><span>，报告</span><span>boot </span><span>状态。</span></p>
<p align=left><span>在成功重启后，系统会将组册表复制一份到下面目录下</span><span>last-known-good (LKG) configuration</span></p>
<p align=left><span>HKEY_LOCAL_MACHINE\SYSTEM\Co<strong>ntrolSet</strong><em>XXX</em><strong>\Services</strong></span></p>
<p align=left><span>按需启动服务</span></p>
<p align=left><span>当服务启动之后，</span><span>SCM</span><span>执行下列步骤：</span></p>
<p align=left><span>获取存储在数据库中的帐户信息</span></p>
<p align=left><span>登录到服务帐户</span></p>
<p align=left><span>加载用户配置</span></p>
<p align=left><span>在暂停状态下创建服务</span></p>
<p align=left><span>给进程分配一个</span><span>Logon</span><span>的</span><span>token</span></p>
<p align=left><span>允许进程执行</span></p>
<p align=left><span>Service Record List</span></p>
<p align=left><span>由于每个服务实体是从注册表数据库中读取的，因此</span><span>SCM</span><span>为每个服务创建了一个</span><span>Service Record</span><span>，</span></p>
<p align=left><span>一个</span><span>Service Record</span><span>包括：</span></p>
<p align=left><span>服务名</span></p>
<p align=left><span>启动类型</span></p>
<p align=left><span>服务状态（</span><span>SERVICE_STATUS</span><span>结构）</span></p>
<p align=left><span>指向依赖服务列表的指针</span></p>
<p align=left><span>服务在安装的时候用户名和密码是指定好的。</span><span>SCM</span><span>在注册表中存放用户名，在</span><span>Local Security Authority</span><span>（</span><span>LSA</span><span>）中存放密码。</span></p>
<p align=left><span>SCM</span><span>保存两份用户密码。一个当前密码和一个备份密码。在服务第一次安装的时候使用当前密码，备份密码是未初始化的。当用当前密码运行服务成功后，才会将当前密码写入到备份密码。</span><span>SCM</span><span>在收到服务状态通知后更新服务状态。</span></p>
<p align=left><span>驱动服务的状态是通过查询</span><span>IO</span><span>系统获得的，而不是通过状态通知，这点是与</span><span>Service</span><span>不同的。</span></p>
<p align=left><span>SCM Handles</span></p>
<p align=left><span>SCM</span><span>支持句柄（</span><span>Handle</span><span>）来访问下面的对象。</span></p>
<p align=left><span>已安装的服务的数据库：用</span><span>SCManager object</span><span>来表示</span></p>
<p align=left><span>服务：由一个安装的服务代表服务对象。</span></p>
<p align=left><span>数据库锁</span></p>
<p align=left><span>Service Progr<strong>ams</strong></span><strong><span>服务程序</span></strong><strong></strong></p>
<p align=left><span>Main</span><span>函数：</span></p>
<p align=left><span>调用</span><span>StartServiceCtrlDispatcher</span><span>函数连接到</span><span>SCM</span><span>并启动</span><span>control dispatcher</span><span>线程。</span><span>Control dispatcher</span><span>线程循环</span><span>loop</span><span>，等待在</span><span>dispatch table</span><span>中指定的服务的请求。</span></p>
<p align=left><span>例子：</span></p>
<p align=left><span>void __cdecl _tmain(int argc, TCHAR *argv[]) </span></p>
<p align=left><span>{ </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// If command-line parameter is "install", install the service. </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Otherwise, the service is probably being started by the SCM.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if( lstrcmpi( argv[1], TEXT("install")) == 0 )</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SvcInstall();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// TO_DO: Add any additional services for the process to this table.</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>SERVICE_TABLE_ENTRY DispatchTable[] = </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{ </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{ NULL, NULL } </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>}; </span></p>
<p align=left><span>&nbsp;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// This call returns when the service has stopped. </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// The process should simply terminate when the call returns.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if (!StartServiceCtrlDispatcher( DispatchTable )) </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{ </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SvcReportEvent(TEXT("StartServiceCtrlDispatcher")); </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>} </span></p>
<p align=left><span>} <strong></strong></span></p>
<p align=left><span>ServiceMain</span><span>函数：</span></p>
<p align=left><span>首先调用</span><span>RegisterServiceCtrlHandler</span><span>函数来注册</span><span>SvcCtrHandler</span><span>函数作为服务的处理函数，然后开始初始化。</span></p>
<p align=left><span>VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )</span></p>
<p align=left><span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Register the handler function for the service</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatusHandle = RegisterServiceCtrlHandler( </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SVCNAME, </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SvcCtrlHandler);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if( !gSvcStatusHandle )</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{ </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SvcReportEvent(TEXT("RegisterServiceCtrlHandler")); </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return; </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>} </span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// These SERVICE_STATUS members remain as set here</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwServiceSpecificExitCode = 0;<span>&nbsp;&nbsp;&nbsp; </span></span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Report initial status to the SCM</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Perform service-specific initialization and work.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>SvcInit( dwArgc, lpszArgv );</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span></p>
<p align=left><span>// Purpose: </span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>The service code</span></p>
<p align=left><span>//</span></p>
<p align=left><span>// Parameters:</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>dwArgc - Number of arguments in the lpszArgv array</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>lpszArgv - Array of strings. The first string is the name of</span></p>
<p align=left><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>the service and subsequent strings are passed by the process</span></p>
<p align=left><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>that called the StartService function to start the service.</span></p>
<p align=left><span>// </span></p>
<p align=left><span>// Return value:</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>None</span></p>
<p align=left><span>//</span></p>
<p align=left><span>VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)</span></p>
<p align=left><span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// TO_DO: Declare and set any required variables.</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>//<span>&nbsp;&nbsp; </span>Be sure to periodically call ReportSvcStatus() with </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>//<span>&nbsp;&nbsp; </span>SERVICE_START_PENDING. If initialization fails, call</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>//<span>&nbsp;&nbsp; </span>ReportSvcStatus with SERVICE_STOPPED.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Create an event. The control handler function, SvcCtrlHandler,</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// signals this event when it receives the stop control code.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>ghSvcStopEvent = CreateEvent(</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>NULL,<span>&nbsp;&nbsp;&nbsp; </span>// default security attributes</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>TRUE,<span>&nbsp;&nbsp;&nbsp; </span>// manual reset event</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>FALSE,<span>&nbsp;&nbsp; </span>// not signaled</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>NULL);<span>&nbsp;&nbsp; </span>// no name</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if ( ghSvcStopEvent == NULL)</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Report running status when initialization is complete.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// TO_DO: Perform work until service stops.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>while(1)</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// Check whether to stop the service.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>WaitForSingleObject(ghSvcStopEvent, INFINITE);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//</span></p>
<p align=left><span>// Purpose: </span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>Sets the current service status and reports it to the SCM.</span></p>
<p align=left><span>//</span></p>
<p align=left><span>// Parameters:</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>dwCurrentState - The current state (see SERVICE_STATUS)</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>dwWin32ExitCode - The system error code</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>dwWaitHint - Estimated time for pending operation, </span></p>
<p align=left><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>in milliseconds</span></p>
<p align=left><span>// </span></p>
<p align=left><span>// Return value:</span></p>
<p align=left><span>//<span>&nbsp;&nbsp; </span>None</span></p>
<p align=left><span>//</span></p>
<p align=left><span>VOID ReportSvcStatus( DWORD dwCurrentState,</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>DWORD dwWin32ExitCode,</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DWORD dwWaitHint)</span></p>
<p align=left><span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>static DWORD dwCheckPoint = 1;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Fill in the SERVICE_STATUS structure.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwCurrentState = dwCurrentState;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwWaitHint = dwWaitHint;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if (dwCurrentState == SERVICE_START_PENDING)</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwControlsAccepted = 0;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>if ( (dwCurrentState == SERVICE_RUNNING) ||</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>(dwCurrentState == SERVICE_STOPPED) )</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>gSvcStatus.dwCheckPoint = 0;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>else gSvcStatus.dwCheckPoint = dwCheckPoint++;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>// Report the status of the service to the SCM.</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp; </span>SetServiceStatus( gSvcStatusHandle, &amp;gSvcStatus );</span></p>
<p align=left><span>}</span></p>
<p align=left><strong>&nbsp;</strong></p>
<p align=left><span>Control Handler</span><span>函数：</span></p>
<p align=left><span>该函数是由</span><span>dispatcher thread</span><span>调用的，它处理在</span><span>OpCode</span><span>参数中传进来的控制码，然后调用</span><span>ReportSvcStatus</span><span>函数来更新服务状态。当</span><span>Handler</span><span>收到控制码时，只有当收到的控制码引起服务状态变化时才报告服务状态。</span></p>
<p align=left><span>VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )</span></p>
<p align=left><span>{</span></p>
<p align=left><span><span>&nbsp;&nbsp; </span>// Handle the requested control code. </span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp; </span>switch(dwCtrl) </span></p>
<p align=left><span><span>&nbsp;&nbsp; </span>{&nbsp;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>case SERVICE_CONTROL_STOP: </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>// Signal the service to stop.</span></p>
<p align=left>&nbsp;</p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetEvent(ghSvcStopEvent);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p align=left><span>&nbsp;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>case SERVICE_CONTROL_INTERROGATE: </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>break; </span></p>
<p align=left><span>&nbsp;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>default: </span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>break;</span></p>
<p align=left><span><span>&nbsp;&nbsp; </span>} </span></p>
<p align=left><span><span>&nbsp;&nbsp; </span></span></p>
<p align=left><span>}</span></p>
<p align=left><span>Service Configuration Program Tasks</span><span>服务配置程序的任务</span></p>
<p align=left><span>安装服务</span></p>
<p align=left><span>删除服务</span></p>
<p align=left><span>改变服务配置</span></p>
<p align=left><span>查询服务配置</span></p>
<p align=left><span>Service Control Program Tasks</span><span>服务控制程序任务</span></p>
<p align=left><span>启动服务</span></p>
<p align=left><span>停止服务</span></p>
<p align=left><span>改变一个服务的</span><span>DACL</span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>Service Functions</span></strong></p>
<p align=left><span>The following functions are used or implemented by services.</span></p>
<table border=1 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td vAlign=top>
            <p align=left><span>Function</span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Description</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms683240(v=VS.85).aspx"><strong><span>Handler</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>An application-defined callback function used with the <a href="http://msdn.microsoft.com/en-us/library/ms685054(v=VS.85).aspx"><strong><span>RegisterServiceCtrlHandler</span></strong></a> function.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms683241(v=VS.85).aspx"><strong><span>HandlerEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>An application-defined callback function used with the <a href="http://msdn.microsoft.com/en-us/library/ms685058(v=VS.85).aspx"><strong><span>RegisterServiceCtrlHandlerEx</span></strong></a> function.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms685054(v=VS.85).aspx"><strong><span>RegisterServiceCtrlHandler</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Registers a function to handle service control requests.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms685058(v=VS.85).aspx"><strong><span>RegisterServiceCtrlHandlerEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Registers a function to handle extended service control requests.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms685138(v=VS.85).aspx"><strong><span>ServiceMain</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>An application-defined function that serves as the starting point for a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms686239(v=VS.85).aspx"><strong><span>SetServiceBits</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Registers a service type with the service control manager and the Server service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms686241(v=VS.85).aspx"><strong><span>SetServiceStatus</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Updates the service control manager's status information for the calling service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms686324(v=VS.85).aspx"><strong><span>StartServiceCtrlDispatcher</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Connects the main thread of a service process to the service control manager.</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>The following functions are used by programs that control or configure services.</span></p>
<table border=1 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td vAlign=top>
            <p align=left><span>Function</span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Description</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms681987(v=VS.85).aspx"><strong><span>ChangeServiceConfig</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Changes the configuration parameters of a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms681988(v=VS.85).aspx"><strong><span>ChangeServiceConfig2</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Changes the optional configuration parameters of a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682028(v=VS.85).aspx"><strong><span>CloseServiceHandle</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Closes the specified handle to a service control manager object or a service object.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682108(v=VS.85).aspx"><strong><span>ControlService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Sends a control code to a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682110(v=VS.85).aspx"><strong><span>ControlServiceEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Sends a control code to a service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682450(v=VS.85).aspx"><strong><span>CreateService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Creates a service object and adds it to the specified service control manager database.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682562(v=VS.85).aspx"><strong><span>DeleteService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Marks the specified service for deletion from the service control manager database.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682610(v=VS.85).aspx"><strong><span>EnumDependentServices</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the name and status of each service that depends on the specified service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms682640(v=VS.85).aspx"><strong><span>EnumServicesStatusEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Enumerates services in the specified service control manager database based on the specified information level.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms683228(v=VS.85).aspx"><strong><span>GetServiceDisplayName</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the display name of the specified service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms683229(v=VS.85).aspx"><strong><span>GetServiceKeyName</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the service name of the specified service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684274(v=VS.85).aspx"><strong><span>NotifyBootConfigStatus</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Reports the boot status to the service control manager.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684276(v=VS.85).aspx"><strong><span>NotifyServiceStatusChange</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Enables an application to receive notification when the specified service is created or deleted or when its status changes.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684323(v=VS.85).aspx"><strong><span>OpenSCManager</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Establishes a connection to the service control manager on the specified computer and opens the specified service control manager database.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684330(v=VS.85).aspx"><strong><span>OpenService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Opens an existing service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684932(v=VS.85).aspx"><strong><span>QueryServiceConfig</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the configuration parameters of the specified service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684935(v=VS.85).aspx"><strong><span>QueryServiceConfig2</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the optional configuration parameters of the specified service.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/aa379312(v=VS.85).aspx"><strong><span>QueryServiceObjectSecurity</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves a copy of the security descriptor associated with a service object.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms684941(v=VS.85).aspx"><strong><span>QueryServiceStatusEx</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Retrieves the current status of the specified service based on the specified information level.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/aa379589(v=VS.85).aspx"><strong><span>SetServiceObjectSecurity</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Sets the security descriptor of a service object.</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top>
            <p align=left><span><a href="http://msdn.microsoft.com/en-us/library/ms686321(v=VS.85).aspx"><strong><span>StartService</span></strong></a> </span></p>
            </td>
            <td vAlign=top>
            <p align=left><span>Starts a service.</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left><strong><span>Service Structures</span></strong></p>
<p align=left><span>The following structures are used with services:</span></p>
<ul type=disc>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms682651(v=VS.85).aspx"><strong><span>ENUM_SERVICE_STATUS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms682648(v=VS.85).aspx"><strong><span>ENUM_SERVICE_STATUS_PROCESS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms684950(v=VS.85).aspx"><strong><span>QUERY_SERVICE_CONFIG</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms684953(v=VS.85).aspx"><strong><span>QUERY_SERVICE_LOCK_STATUS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685126(v=VS.85).aspx"><strong><span>SC_ACTION</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685155(v=VS.85).aspx"><strong><span>SERVICE_DELAYED_AUTO_START_INFO</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685156(v=VS.85).aspx"><strong><span>SERVICE_DESCRIPTION</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685939(v=VS.85).aspx"><strong><span>SERVICE_FAILURE_ACTIONS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685937(v=VS.85).aspx"><strong><span>SERVICE_FAILURE_ACTIONS_FLAG</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685947(v=VS.85).aspx"><strong><span>SERVICE_NOTIFY</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685961(v=VS.85).aspx"><strong><span>SERVICE_PRESHUTDOWN_INFO</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685976(v=VS.85).aspx"><strong><span>SERVICE_REQUIRED_PRIVILEGES_INFO</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685987(v=VS.85).aspx"><strong><span>SERVICE_SID_INFO</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685996(v=VS.85).aspx"><strong><span>SERVICE_STATUS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms685992(v=VS.85).aspx"><strong><span>SERVICE_STATUS_PROCESS</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/ms686001(v=VS.85).aspx"><strong><span>SERVICE_TABLE_ENTRY</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/dd405512(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/dd405514(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER_INFO</span></strong></a> </span></li>
    <li><span><a href="http://msdn.microsoft.com/en-us/library/dd405515(v=VS.85).aspx"><strong><span>SERVICE_TRIGGER_SPECIFIC_DATA_ITEM</span></strong></a> </span></li>
</ul>
<img src ="http://www.cppblog.com/Walker/aggbug/144118.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-13 13:53 <a href="http://www.cppblog.com/Walker/articles/144118.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>64-bit C/C++ applications </title><link>http://www.cppblog.com/Walker/articles/144117.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 13 Apr 2011 05:14:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/144117.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/144117.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/144117.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/144117.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/144117.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Lessons on development of 64-bit C/C++ applications (single file)The course is devoted to creation of 64-bit applications in C/C++ language and is intended for the Windows developers who use Visual ...&nbsp;&nbsp;<a href='http://www.cppblog.com/Walker/articles/144117.html'>阅读全文</a><img src ="http://www.cppblog.com/Walker/aggbug/144117.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-13 13:14 <a href="http://www.cppblog.com/Walker/articles/144117.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>OnEraseBkGnd与OnPaint的联系是什么？</title><link>http://www.cppblog.com/Walker/articles/144098.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Wed, 13 Apr 2011 02:42:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/144098.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/144098.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/144098.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/144098.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/144098.html</trackback:ping><description><![CDATA[问题是这样产生的.在OnEraseBkGnd中,如果你不调用原来缺省 <br>的OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面, <br>由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd <br>函数,这时就和窗口缺省的背景刷相关了.缺省的 <br>OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况 <br>下是白刷),而随后你又自己重画背景造成屏幕闪动. <br>另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你 <br>调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含 <br>调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数 <br>是FALSE,则不会重刷背景. <br><br>所以解决方法有三个半: <br>1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数. <br>2.用OnPaint实现,同时重载OnEraseBkGnd,其中直接返回. <br>3.用OnPaint实现,创建窗口时设置背景刷为空 <br>4.用OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样 <br>的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一 <br>下,所以不是彻底的解决方法) <br>都挺简单的.<br>------------------------------------------------------<br>在MFC中 任何一个window组件的绘图 都是放在这两个member function中<br>在设定上 OnEraseBkgnd()是用来画底图的 而OnPaint()是用来画主要对象的<br>举例说明 一个按钮是灰色的 上面还有文字<br>则OnEraseBkgnd()所做的事就是把按钮画成灰色<br>而OnPaint()所做的事 就是画上文字<br><br>既然这两个member function都是用来画出组件的<br>那为何还要分OnPaint() 与 OnEraseBkgnd() 呢<br>其实OnPaint() 与 OnEraseBkgnd() 特性是有差的<br>1. OnEraseBkgnd()的要求是快速 在里面的绘图程序最好是不要太耗时间<br>因为 每当window组件有任何小变动 都会马上呼叫OnEraseBkgnd()<br>2. OnPaint() 是只有在程序有空闲的时候才会被呼叫<br>3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的<br>所以 OnPaint()被呼叫一次之前 可能会呼叫OnEraseBkgnd()好几次<br><br><br>如果我们是一个在做图形化使用者接口的人<br>常会需要把一张美美的图片设为我们dialog的底图<br>把绘图的程序代码放在OnPaint() 之中 可能会常碰到一些问题<br>比方说拖曳一个窗口在我们做的dialog上面一直移动<br>则dialog会变成灰色 直到动作停止才恢复<br>这是因为每次需要重绘的时候 程序都会马上呼叫OnEraseBkgnd()<br>OnEraseBkgnd()就把dialog画成灰色<br>而只有动作停止之后 程序才会呼叫OnPaint() 这时才会把我们要画的底图贴上去<br><br><br>这个问题的解法 比较差点的方法是把OnEraseBkgnd() 改写成不做事的function<br>如下所示<br>BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)<br>{<br>return TRUE;<br>}<br>以上本来是会呼叫CDialog::OnEraseBkgnd() 但是如果我们不呼叫的话<br>程序便不会画上灰色的底色了<br><br><br>比较好的做法是直接将绘图的程序从OnPaint()移到OnEraseBkgnd()来做<br>如下所示<br><br>// m_bmpBKGND 为一CBitmap对象 且事先早已加载我们的底图<br>// 底图的大小与我们的窗口client大小一致<br><br><br>BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)<br>{<br>CRect rc;<br>GetUpdateRect(&amp;rc);<br>CDC srcDC;<br>srcDC.CreateCompatibleDC(pDC);<br>srcDC.SelectObject(m_bmpBKGND);<br><br>pDC-&gt;BitBlt(rc.left,rc.top,rc.GetWidth(),<br>rc.GetHeight(),&amp;srcDC,rc.left,rc.top,SRCCOPY);<br>return TRUE;<br>}<br><br>特别要注意的是 取得重画大小是使用GetUpdateRect() 而不是GetClientRect()<br>如果使用GetClientRect() 会把不该重画的地方重画<br><br>另外不推荐使用GetUpdateRect()，很多情况下这个函数在OnEraseBkGnd里调用会有问题，建议使用GetClipBox()<br><br>首先，tr0j4n是好心人，写得这么长。但好像不对哦。<br><br>应该是这样的吧，当Windows确定客户区需要重绘时，它首先发送WM_ERASEBKGND消息给窗口过程，由WM_ERASEBKGND消息的默认处理用白色画刷刷除背景，然后再发送WM_PAINT消息给窗口过程，由WM_PAINT消息的响应程序负责绘画客户区内容。或者说，当Windows确定客户区需要重绘时，它分别发送WM_ERASEBKGND和WM_PAINT消息，由这两个消息的响应程序分别负责刷除背景和重画客户内容。<br><br>至闪烁的问题，是由于刷除背景以后，在WM_PAINT未执行完成之前，windows已把视频卡的缓存输出到屏幕上了。<br>&nbsp;<br><br><span csdnid="titleStyle"><!-- google_ad_section_start --><span style="COLOR: red">如何擦除之前的背景图片？</span></span><br><br>我明白问题所在了，你的问题是当窗口大小变化时，原来的背景没有被清除，造成图片重叠显示，你上面的代码没有问题，写在OnEraseBkgnd()中还是写在OnPaint()中的结果都是一样的，最主要的原因是，当我们改变窗口大小时，触发WM_SIZE消息，而默认的OnSize在内部调用Invalidate时用的参数是：Invalidate(FLASE);也就是不刷新背景的。所以原来的背景刷不掉<br>解决的方法是处理WM_SIZE，在OnSize()中，调用：Invalidate(TRUE);强制刷新背景就行了。<br>
<img src ="http://www.cppblog.com/Walker/aggbug/144098.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-13 10:42 <a href="http://www.cppblog.com/Walker/articles/144098.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转log4cplus</title><link>http://www.cppblog.com/Walker/articles/143404.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 04 Apr 2011 07:57:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/143404.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/143404.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/143404.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/143404.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/143404.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: log4cplus库（一）(简单使用) 一.简介&nbsp;&nbsp;&nbsp; log4cplus是一个日志记录的库，目的很简单，就是把合适的信息送到正确的位置上去。在服务器程序上使用非常方便。&nbsp;&nbsp;&nbsp; 开发库下载地址可以去baidu搜一下，是开源的哦！二.组成&nbsp; Log4cplus 由4部分组成：&nbsp;(1) Logger&nbsp;&nbs...&nbsp;&nbsp;<a href='http://www.cppblog.com/Walker/articles/143404.html'>阅读全文</a><img src ="http://www.cppblog.com/Walker/aggbug/143404.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-04 15:57 <a href="http://www.cppblog.com/Walker/articles/143404.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件工程配置规范（VC2005） 第二版 （转）</title><link>http://www.cppblog.com/Walker/articles/143403.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 04 Apr 2011 07:46:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/143403.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/143403.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/143403.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/143403.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/143403.html</trackback:ping><description><![CDATA[<div class=post>
<h2><a id=viewpost1_TitleUrl href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html"><font color=#223355>软件工程配置规范（VC2005） 第二版</font></a> </h2>
<div class=postbody><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html">转自http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html</a>
<p><br><strong></strong></p>
<p><strong></strong></p>
<p><strong>版</strong><strong> </strong><strong>本</strong><strong> </strong><strong>控</strong><strong> </strong><strong>制</strong><strong></strong>
<table border=1 cellSpacing=0 cellPadding=0>
    <tbody>
        <tr>
            <td vAlign=top width=121>
            <p><strong>版本号</strong><strong></strong></p>
            </td>
            <td vAlign=top width=122>
            <p><strong>日期</strong><strong></strong></p>
            </td>
            <td vAlign=top width=121>
            <p><strong>修改者</strong><strong></strong></p>
            </td>
            <td vAlign=top width=122>
            <p><strong>说明</strong><strong></strong></p>
            </td>
            <td vAlign=top width=122>
            <p><strong>备注</strong><strong></strong></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=121>
            <p>0.1</p>
            </td>
            <td vAlign=top width=122>
            <p>2010.07.13</p>
            </td>
            <td vAlign=top width=121>
            <p>phoenix</p>
            </td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
        </tr>
        <tr>
            <td vAlign=top width=121>
            <p>0.2</p>
            </td>
            <td vAlign=top width=122>
            <p>2011.01.12</p>
            </td>
            <td vAlign=top width=121>
            <p>phoenix</p>
            </td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
        </tr>
        <tr>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
        </tr>
        <tr>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
        </tr>
        <tr>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=121>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
            <td vAlign=top width=122>&nbsp;</td>
        </tr>
    </tbody>
</table>
</p>
<p><strong><br></strong></p>
<p>目 录</p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612780"><font color=#1d58d1>1. 引言... 1</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612781"><font color=#1d58d1>1.1. 编写目的.. 1</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612782"><font color=#1d58d1>1.2. 参考资料.. 1</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612783"><font color=#1d58d1>2. 目录结构... 1</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612784"><font color=#1d58d1>3. 工程配置... 2</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612785"><font color=#1d58d1>4. 属性配置... 2</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612786"><font color=#1d58d1>4.1. &#8220;常规&#8221;配置.. 2</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612787"><font color=#1d58d1>4.2. &#8220;调试&#8221;配置.. 2</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612788"><font color=#1d58d1>4.3. &#8220;C/C++&#8221;配置.. 3</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612789"><font color=#1d58d1>4.4. &#8220;链接器&#8221;配置.. 3</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612790"><font color=#1d58d1>4.5. &#8220;生成事件&#8221;配置.. 3</font></a></p>
<p><a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#_Toc282612791"><font color=#1d58d1>5. 附录：VC2005中可以使用的宏... 3</font></a></p>
<p><strong><font color=#1d58d1></font></strong></p>
<h3><a name=_Toc282612780><font color=#1d58d1>1.引言</font></a></h3>
<h4><a name=_Toc282612781><font color=#1d58d1>1.1.编写目的</font></a></h4>
<p>当开发人员开始进行编码工作以及编码工作的过程中，都需要根据工程的需要配置各种工作路径，如引入第三方库、输出生成的头文件和库文件等。以前部分开发人员对工程路径的定义比较随意，相当一部分使用的是绝对路径。如果只是单个开发人员完成一个单一的功能，这种做法看起来没有什么明显的缺点。但是，如果是数个开发人员来合作共同完成一个功能和结构都很复杂的工程时，这种做法的弊端就凸显出来。一个非常明显的问题就是：开发人员A将他的工程交给开发人员B编译时，如工程定义使用了绝对路径，除非开发人员B的计算机与A的计算机具有完全相同的文件组织结构，否则一次性编译通过是不可能。另外，当开发人员在一长串的&#8220;Can&#8217;t find &#8230;&#8221;或者&#8220;XXX undefined&#8221;的错误与警告中焦头烂额时，也许只是因为一个不起眼的地方使用了绝对路径，或者是大小写的差异。</p>
<p>因此，规范开发人员的工程配置是很有必要的。通过规范的工程配置，所有开发人员之间的工程是无缝衔接的，即任何一个开发人员的工程拿到其他人的工程时，无须修改任何一个配置便可以一次性的编译成功。这样的规范操作无异可以大大减少团队成员通过代码交流时的不必要成本。另外，规范的工程配置也便于SubVersion版本控制系统的引入，通过引入SubVersion版本控制系统，可以协调整个研发团队的工程进度，控制产品的里程碑发布。这一切，都从规范的工程配置开始。</p>
<h4><a name=_Toc282612782><font color=#1d58d1>1.2.参考资料</font></a></h4>
<p>1. 《OpenSource SubVersion规范》</p>
<p>2. 《Visual Studio 2005编程指南》</p>
<h3><a name=_Toc282612783><font color=#1d58d1>2.目录结构</font></a></h3>
<p>MyDevelopeFolder</p>
<p>├─Bin</p>
<p>│ ├─Debug</p>
<p>│ ├─Program</p>
<p>│ ├─Release</p>
<p>│ ├─UnicodeDebug</p>
<p>│ ├─UnicodeProgram</p>
<p>│ └─UnicodeRelease</p>
<p>├─MySDK</p>
<p>│ ├─Include</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ └─Lib</p>
<p>│ ├─Debug</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ ├─Program</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ ├─Release</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ ├─UnicodeDebug</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ ├─UnicodeProgram</p>
<p>│ │ ├─BCG</p>
<p>│ │ └─Boost</p>
<p>│ └─UnicodeRelease</p>
<p>│ ├─BCG</p>
<p>│ └─Boost</p>
<p>├─Project</p>
<p>│ ├─LibMyExample</p>
<p>│ │ └─Document</p>
<p>│ └─MyExampleApp</p>
<p>│ └─Document</p>
<p>├─Solution</p>
<p>└─Temp</p>
<p>├─Compile</p>
<p>└─Link</p>
<p>1. MyDevelopeFolder是开发工程的根目录</p>
<p>2. Bin目录存放所有动态链接库和可执行程序，包括自己的产出和第三方库，按编译配置名称包括对应的子目录，如Debug、Release。另外，程序运行过程中需要外部的数据文件和启动时需要的配置文件等等都可放于该目录</p>
<p>3. MySDK存放<strong>产品项目依赖、产出</strong>的.h文件和.lib文件。其中Lib目录下根据配置名称和第三方库名称对lib文件进行管理。如某项目使用BCG作为界面库，则BCG的头文件放置于&#8220;Include\BCG&#8221;下，不同版本的库文件置于&#8220;Lib\配置名\BCG&#8221;下；项目输出的lib文件直接位于&#8220;Lib\配置名&#8221;下。这样产品依赖的不同开发库所使用头文件与库文件和输出的头文件与库文件相互之间是独立、彼此不干涉的。</p>
<p>4. Project是工程目录，用于存放代码，按模块名组织次级目录。功能库工程一般的以&#8220;Lib&#8221;开头，以便与其它动态库区别。每个工程模块目录应包含&#8220;Document&#8221;子目录，该目录下按需要增加&#8220;DBM&#8221;、&#8220;DOC&#8221;、&#8220;UML&#8221;三个子目录。&#8220;DBM&#8221;用于存放与该模块相关的数据库设计文档，通常是PowerDesigner文档；&#8220;DOC&#8221;用于存放与该模块相关的一般性说明文档；&#8220;UML&#8221;用于存放与该模块相关的UML设计文档，通常是Rational Rose文档。</p>
<p>5. Solution是解决方案目录，用于存放产品的完整解决方案。通常使用解决方案可以生成该产品的所有版本。</p>
<p>6. Temp是用于编译生成的中间目录，主要存放编译过程中生成的各种中间文件。</p>
<h3><a name=_Toc282612784><font color=#1d58d1>3.工程配置</font></a></h3>
<p>根据调试信息与字符集的不同，一般的工程配置有六类，如下表所示
<table border=1 cellSpacing=0 cellPadding=0>
    <tbody>
        <tr>
            <td vAlign=top width=145>
            <p>名称</p>
            </td>
            <td vAlign=top width=145>
            <p>字符集</p>
            </td>
            <td vAlign=top width=145>
            <p>是否包含调试信息</p>
            </td>
            <td vAlign=top width=145>
            <p>是否包含代码优化</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>Debug</p>
            </td>
            <td vAlign=top width=145>
            <p>ANSI</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
            <td vAlign=top width=145>
            <p>NO</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>Release</p>
            </td>
            <td vAlign=top width=145>
            <p>ANSI</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>Program</p>
            </td>
            <td vAlign=top width=145>
            <p>ANSI</p>
            </td>
            <td vAlign=top width=145>
            <p>NO</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>UnicodeDebug</p>
            </td>
            <td vAlign=top width=145>
            <p>UNICODE</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
            <td vAlign=top width=145>
            <p>NO </p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>UnicodeRelease</p>
            </td>
            <td vAlign=top width=145>
            <p>UNICODE</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=145>
            <p>UnicodeProgram</p>
            </td>
            <td vAlign=top width=145>
            <p>UNICODE</p>
            </td>
            <td vAlign=top width=145>
            <p>NO</p>
            </td>
            <td vAlign=top width=145>
            <p>YES</p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<h3><a name=_Toc282612785><font color=#1d58d1>4.属性配置</font></a></h3>
<h4><a name=_Toc282612786><font color=#1d58d1>4.1.&#8220;常规&#8221;配置</font></a></h4>
<p>输出目录： ..\..\Temp\Link\$(ProjectName)\$(ConfigurationName)</p>
<p>中间目录：..\..\Temp\Compile\$(ProjectName)\$(ConfigurationName)</p>
<h4><a name=_Toc282612787><font color=#1d58d1>4.2.&#8220;调试&#8221;配置</font></a></h4>
<p>如果需要启动本模块进行调试，则&#8220;命令&#8221;为：..\..\Bin\$(ConfigurationName)\$(TargetFileName)</p>
<h4><a name=_Toc282612788><font color=#1d58d1>4.3.&#8220;C/C++&#8221;配置</font></a></h4>
<p>1) &#8220;附加包含目录&#8221;：..\..\MineSDK\Include ，如有其它目录请用&#8220;;&#8221;间隔，注意使用相对路径，<strong><font color=#ff0000>严禁使用绝对路径</font>。</strong></p>
<p>2) 动态库使用导出宏导出/导入时，应在&#8220;预处理器&#8221;中定义导出宏，不得在代码文件中定义。导出宏的一般格式为&#8220;LIB_XXXXX&#8221;。</p>
<p>3) 一般情况下不推荐使用预编译头。</p>
<h4><a name=_Toc282612789><font color=#1d58d1>4.4.&#8220;链接器&#8221;配置</font></a></h4>
<p>1) &#8220;输出文件&#8221;：</p>
<p>a) 若为Debug版时：$(OutDir)\$(ProjectName)D.dll；</p>
<p>b) 若为Release版时：$(OutDir)\$(ProjectName).dll。</p>
<p>2) &#8220;附加库目录&#8221;：..\..\Bin\$(ConfigurationName);..\..\MySDK\Lib，如有其它目录请用&#8220;;&#8221;间隔，注意使用相对路径，严格禁止使用本地绝对路径。</p>
<p>3) &#8220;模块定义文件&#8221;：一般不使用模块定义.def文件，本项可选择&#8220;从默认配置&#8230;&#8221;。</p>
<h4><a name=_Toc282612790><font color=#1d58d1>4.5.&#8220;生成事件&#8221;配置</font></a></h4>
<p>一般需要配置的是&#8220;生成后事件&#8221;。任何一个工程模块的生成后事件都应包括以下内容：</p>
<p>1) copy $(TargetPath) ..\..\Bin\$(ConfigurationName)</p>
<p>2) 若有导出的Lib库文件，需要增加：copy $(TargetDir)$(TargetName).lib ..\..\MySDK\Lib</p>
<p>3) 若有导出的头文件，需要增加： copy myhead.h ..\..\MySDK\Include </p>
<p>具体文件名与路径视情况而定，但是严禁使用绝对路径。</p>
<h3><a name=_Toc282612791><font color=#1d58d1>5.附录：VC2005</font></a>中可以使用的宏</h3>
<table border=1 cellSpacing=0 cellPadding=0>
    <tbody>
        <tr>
            <td vAlign=top width=163>
            <p>ConfigurationName</p>
            </td>
            <td vAlign=top width=405>
            <p>配置名字，通常是Debug或者Release</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>IntDir</p>
            </td>
            <td vAlign=top width=405>
            <p>编译器使用的中间目录，产出obj文件</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>OutDir</p>
            </td>
            <td vAlign=top width=405>
            <p>链接器使用的输出目录</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>ProjectDir</p>
            </td>
            <td vAlign=top width=405>
            <p>项目目录</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>ProjectName</p>
            </td>
            <td vAlign=top width=405>
            <p>项目名字</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>SolutionDir</p>
            </td>
            <td vAlign=top width=405>
            <p>解决方案目录</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>TargetDir</p>
            </td>
            <td vAlign=top width=405>
            <p>目标输出文件所在的目录</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>TargetExt</p>
            </td>
            <td vAlign=top width=405>
            <p>目标输出的扩展名</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>TargetFileName</p>
            </td>
            <td vAlign=top width=405>
            <p>目标输出文件名，包括扩展名</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>TargetName</p>
            </td>
            <td vAlign=top width=405>
            <p>目标输出名，不包括扩展名</p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=163>
            <p>TargetPath</p>
            </td>
            <td vAlign=top width=405>
            <p>目标输出文件的全路径名</p>
            </td>
        </tr>
    </tbody>
</table>
</div>
<p class=postfoot>posted on 2011-04-03 18:58 <a href="http://www.cppblog.com/phoenix8848cn/"><font color=#223355>西门有悔</font></a> 阅读(352) <a href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#Post"><font color=#223355>评论(4)</font></a> &nbsp;<a href="http://www.cppblog.com/phoenix8848cn/admin/EditPosts.aspx?postid=143354"><font color=#223355>编辑</font></a>&nbsp;<a href="http://www.cppblog.com/phoenix8848cn/AddToFavorite.aspx?id=143354"><font color=#223355>收藏</font></a> <a href="http://www.cppblog.com/phoenix8848cn/services/trackbacks/143354.aspx"><font color=#223355>引用</font></a> </p>
</div>
<img src="http://www.cppblog.com/phoenix8848cn/aggbug/143354.html?webview=1" width=1 height=1> <!--
<rdf:rdf xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
<rdf:description
rdf:about="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html"
dc:identifier="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html"
dc:title="软件工程配置规范（VC2005） 第二版"
trackback:ping="http://www.cppblog.com/phoenix8848cn/services/trackbacks/143354.aspx" />
</rdf:rdf>
--><script type=text/javascript>
//<![cdata[
Sys.WebForms.PageRequestManager._initialize('AjaxHolder$scriptmanager1', document.getElementById('Form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls(['tAjaxHolder$UpdatePanel1'], [], [], 90);
//]]&gt;
</script>
<div id=AjaxHolder_UpdatePanel1><a name=pagedcomment></a><a name=评论>
<div id=comments>
<h3>评论</h3>
<div class=post>
<h2><a title="permalink: re: 软件工程配置规范（VC2005） 第二版" href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#143360">#</a><font color=#000000>&nbsp;</font><a name=143360></a><font color=#000000>re: 软件工程配置规范（VC2005） 第二版&nbsp;&nbsp;</font><a onclick='return SetReplyAuhor("御用软件")' href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#post">回复</a><font color=#000000>&nbsp;&nbsp;</font><a title=查看该作者发表过的评论 href="http://www.cppblog.com/comment?author=%e5%be%a1%e7%94%a8%e8%bd%af%e4%bb%b6" target=_blank>更多评论</a><font color=#000000> </font><a id=AjaxHolder_Comments_CommentList_ctl00_DeleteLink href="javascript:__doPostBack('AjaxHolder$Comments$CommentList$ctl00$DeleteLink','')"></a><font color=#000000>&nbsp;&nbsp;</font><a id=AjaxHolder_Comments_CommentList_ctl00_EditLink></a><font color=#000000> </font></h2>
希望可以放出pdf版，这样方便使用&#8230;&#8230;
<div class=postfoot>2011-04-03 20:19 | <a id=AjaxHolder_Comments_CommentList_ctl00_NameLink href="http://yyrj.org/" target=_blank><font color=#223355>御用软件</font></a> </div>
</div>
<div class=post>
<h2><a title="permalink: re: 软件工程配置规范（VC2005） 第二版" href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#143370"><font color=#223355>#</font></a>&nbsp;<a name=143370></a>re: 软件工程配置规范（VC2005） 第二版&nbsp;&nbsp;<a onclick='return SetReplyAuhor("溪流")' href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#post"><font color=#223355>回复</font></a>&nbsp;&nbsp;<a title=查看该作者发表过的评论 href="http://www.cppblog.com/comment?author=%e6%ba%aa%e6%b5%81" target=_blank><font color=#223355>更多评论</font></a> <a id=AjaxHolder_Comments_CommentList_ctl01_DeleteLink href="javascript:__doPostBack('AjaxHolder$Comments$CommentList$ctl01$DeleteLink','')"></a>&nbsp;&nbsp;<a id=AjaxHolder_Comments_CommentList_ctl01_EditLink></a> </h2>
个人非常讨厌什么 bin，source，solution，。。。 <br>你要么就纯粹自己搞，bin、source 都可以，就别 solution、project 了，最后自己写脚本 <br>要么就用 solution、project，目录就大体上按默认的，一个project一个目录， <br>既用 solution、project，又独立搞一套目录体系，然后修改一大堆纯粹关于目录的配置参数，何必呢
<div class=postfoot>2011-04-03 21:52 | <a id=AjaxHolder_Comments_CommentList_ctl01_NameLink href="http://www.cppblog.com/Streamlet/" target=_blank><font color=#223355>溪流</font></a> </div>
</div>
<div class=post>
<h2><a title="permalink: re: 软件工程配置规范（VC2005） 第二版" href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#143376"><font color=#223355>#</font></a>&nbsp;<a name=143376></a>re: 软件工程配置规范（VC2005） 第二版&nbsp;&nbsp;<a onclick='return SetReplyAuhor("西门有悔")' href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#post"><font color=#223355>回复</font></a>&nbsp;&nbsp;<a title=查看该作者发表过的评论 href="http://www.cppblog.com/comment?author=%e8%a5%bf%e9%97%a8%e6%9c%89%e6%82%94" target=_blank><font color=#223355>更多评论</font></a> <a id=AjaxHolder_Comments_CommentList_ctl02_DeleteLink href="javascript:__doPostBack('AjaxHolder$Comments$CommentList$ctl02$DeleteLink','')"></a>&nbsp;&nbsp;<a id=AjaxHolder_Comments_CommentList_ctl02_EditLink></a> </h2>
@溪流<br>1.Bin里是所有生成的文件，包含了程序可以运行的最小资源，产品发布人员只需要将Bin里的文件打包就可以生成安装文件。而默认的配置会在bin目录里生成程序调试数据库等一些非运行时需要的文件。Bin是面向产品测试与发布人员的，开发人员只是将dll和exe输出到bin中进行调试。这样使产品开发与产品测试、发布分开。<br>2.Project与Solution分开是因为每个成员都是独立地开发一个或几个Project的，他把Bin与SDK从SVN上checkout出来，就可以进行自己的代码编写，而不必关心与其他开发人员所同时进行的project的依赖关系。Solution里包含的是整个产品的所有project以及project之间的依赖关系。打开solution就可以生成一个完整的产品到bin里，而且bin里没有任何多余的文件。<br><br>效果：采用了这套工程配置方法，整个团队代码与工程层面的交流明显顺畅多了，再也没有出现拿到别人的工程半天build不过的问题。而且开发与测试、发布之间的卸接也很顺利。开发人员每天都build后commit到svn。每周一开发部产品管理员用solution生成一个完成的bin并整理出track后发布到Svn上，测试人员用本周一的bin进行测试，到了Tag的时间点测试部产品管理员将bin打包成安装程序发布到svn上并通知实施部门有更新版本。形成一个完整的流程。再用bugzilla与dotproject对产品的bug和人员进行管理。<br><br>总结：这套工程配置应该算是不依赖于第三方工具，进行基于Svn的代码管理以及多个开发人员之间的合作开发。如果是一个人，或者project不多的时候就没必要如此复杂。而且修改工程配置是一次的，不需要每次都修改。可以说一劳永逸。<br><br>谢谢你的评论。<br>
<div class=postfoot>2011-04-04 00:43 | <a id=AjaxHolder_Comments_CommentList_ctl02_NameLink href="http://www.cppblog.com/phoenix8848cn/" target=_blank><font color=#223355>西门有悔</font></a> </div>
</div>
<div class=post>
<h2><a title="permalink: re: 软件工程配置规范（VC2005） 第二版" href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#143387"><font color=#223355>#</font></a>&nbsp;<a name=143387></a>re: 软件工程配置规范（VC2005） 第二版<a name=Post></a>&nbsp;&nbsp;<a onclick='return SetReplyAuhor("溪流")' href="http://www.cppblog.com/phoenix8848cn/archive/2011/04/03/143354.html#post"><font color=#223355>回复</font></a>&nbsp;&nbsp;<a title=查看该作者发表过的评论 href="http://www.cppblog.com/comment?author=%e6%ba%aa%e6%b5%81" target=_blank><font color=#223355>更多评论</font></a> <a id=AjaxHolder_Comments_CommentList_ctl03_DeleteLink href="javascript:__doPostBack('AjaxHolder$Comments$CommentList$ctl03$DeleteLink','')"></a>&nbsp;&nbsp;<a id=AjaxHolder_Comments_CommentList_ctl03_EditLink></a> </h2>
1、打包人员不该偷懒，他们应该知道完整的精确的文件清单，而不仅仅是&#8220;某个目录下的所有文件&#8221; <br>2、还是没有看出来把solution单独藏在一个目录的用意。 </div>
</div>
</div>
<img src ="http://www.cppblog.com/Walker/aggbug/143403.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-04-04 15:46 <a href="http://www.cppblog.com/Walker/articles/143403.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）Win7 UAC的安全、兼容及权限 </title><link>http://www.cppblog.com/Walker/articles/141814.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 14 Mar 2011 12:58:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/141814.html</guid><description><![CDATA[<div class=posthead>
<h2><a id=ctl01_TitleUrl class=singleposttitle href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html"><font color=#223355>1、Win7 UAC的安全、兼容及权限</font></a> </h2>
Posted on 2010-11-24 22:42 <a href="http://www.cnblogs.com/mydomain/"><font color=#223355>edwardlewiswe</font></a> 阅读(401) <a href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#commentform"><font color=#223355>评论(0)</font></a> <a onclick="open_link('http://www.cnblogs.com/mydomain/admin/EditPosts.aspx?postid=1887132')" href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#" rel=nofollow><font color=#223355>编辑</font></a> <a onclick="AddToWz(1887132);return false;" href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#"><font color=#223355>收藏</font></a> <img alt="" src="http://www.cnblogs.com/mydomain/aggbug/1887132.html?type=1&amp;webview=1" width=1 height=1> </div>
<div class=postbody>
<div id=cnblogs_post_body>
<div style="LAYOUT-GRID:  15.6pt none" class=Section0>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">网上关于这个问题讨论较多，但也不外乎几种方法。总结一下，如附中。顺便了解一个UAC。</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&nbsp;</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">UAC，全称User Account Control（用户帐户控制）</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&nbsp;</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">System Safe Monitor（主机入侵防御系统）</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">UAC是如何工作的[3]</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">我们可以简单的把UAC当作权限临时重分配的工具。在默认情况下，所有的非系统核心进程都只拥有标准权限，这一权限不能对系统关键区域进行修改。对于一个程序，如果它当中含有提权申请，则在运行时会弹出UAC窗口要求提权。如果用户允许，则程序暂时性的获得了最高权限，可以对系统关键区域进行更改；如果用户拒绝，则程序被拒绝执行。而如果程序中没有提权申请，则系统会让程序运行于标准权限下。同时，对于所有程序，都可以用&#8220;以管理员身份运行&#8221;的方式手动提权。而即便病毒感染了系统，它也处于UAC的监视之下，这使得病毒的反清除行为会受到很大阻碍。正是凭借这一机制，UAC成为了一道重要的系统防火墙。</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">在 Windows7（NT6.x系统）中，系统取消了对移动设备Autorun.inf的支持</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">用户界面特权隔离[5]</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">在早期的Windows操作系统中，在同一用户下运行的所有进程有着相同的安全等级，拥有相同的权限。例如，一个进程可以自由地发送一个Windows消息到另外一个进程的窗口。从Windows Vista开始，当然也包括Windows 7，对于某些Windows消息，这一方式再也行不通了。进程(或者其他的对象)开始拥有一个新的属性——特权等级(Privilege Level)。一个特权等级较低的进程不再可以向一个特权等级较高的进程发送消息，虽然他们在相同的用户权限下运行。这就是所谓的用户界面特权隔离 (User Interface Privilege Isolation，UIPI)。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">UIPI的引入，最大的目的是防止恶意代码发送消息给那些拥有较高权限的窗口以对其进行攻击，从而获取较高的权限等等。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">UIPI的运行机制</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">在Windows 7中，当UAC(User Account Control)启用的时候，UIPI的运行可以得到最明显的体现。在UAC中，当一个管理员用户登录系统后，操作系统会创建两个令牌对象(Token Object)：第一个是管理员令牌，拥有大多数特权(类似于Windows Vista之前的System中的用户)，而第二个是一个经过过滤后的简化版本，只拥有普通用户的权限。</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">默认情况下，以普通用户权限启动的进程拥有普通特权等级(UIPI的等级划分为低等级(low)，普通(normal)，高等级(high)，系统 (system))。同样的，以管理员权限运行的进程，例如，用户右键单击选择&#8220;以管理员身份运行&#8221;或者是通过添加&#8220;runas&#8221;参数调用 ShellExecute运行的进程，这样的进程就相应地拥有一个较高(high)的特权等级。</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">这将导致系统会运行两种不同类型，不同特权等级的进程(当然，从技术上讲这两个进程都是在同一用户下)。我们可以使用Windows Sysinternals工具集中的进程浏览器(Process Explorer)查看各个进程的特权等级。[6]</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">所以，当你发现你的进程之间Windows消息通信发生问题时，不妨使用进程浏览器查看一下两个进程之间是否有合适的特权等级。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">UIPI所带来的限制</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">正如我们前文所说，等级的划分，是为了防止以下犯上。所以，有了用户界面特权隔离，一个运行在较低特权等级的应用程序的行为就受到了诸多限制，它不可以：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">验证由较高特权等级进程创建的窗口句柄</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">通过调用SendMessage和PostMessage向由较高特权等级进程创建的窗口</span><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">发送Windows消息</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">使用线程钩子处理较高特权等级进程</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">使用普通钩子(SetWindowsHookEx)监视较高特权等级进程</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: 'Arial'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">►</span><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">向一个较高特权等级进程执行DLL注入</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">但是，一些特殊Windows消息是容许的。因为这些消息对进程的安全性没有太大影响。这些Windows消息包括：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x000 - WM_NULL</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x003 - WM_MOVE</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x005 - WM_SIZE</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x00D - WM_GETTEXT</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x00E - WM_GETTEXTLENGTH</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x033 - WM_GETHOTKEY</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x07F - WM_GETICON</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x305 - WM_RENDERFORMAT</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x308 - WM_DRAWCLIPBOARD</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x30D - WM_CHANGECBCHAIN</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x31A - WM_THEMECHANGED</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">0x313, 0x31B (WM_???)</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">修复UIPI问题</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">基于Windows Vista之前的操作系统行为所设计的应用程序，可能希望Windows消息能够在进程之间自由的传递，以完成一些特殊的工作。当这些应用程序在 Windows 7上运行时，因为UIPI机制，这种消息传递被阻断了，应用程序就会遇到兼容性问题。为了解决这个问题，Windows Vista引入了一个新的API函数ChangeWindowMessageFilter[7]。利用这个函数，我们可以添加或者删除能够通过特权等级隔离的 Windows消息。这就像拥有较高特权等级的进程，设置了一个过滤器，允许通过的Windows消息都被添加到这个过滤器的白名单，只有在这个白名单上的消息才允许传递进来。</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">如果我们想容许一个消息可以发送给较高特权等级的进程，我们可以在较高特权等级的进程中调用ChangeWindowMessageFilter函数，以 MSGFLT_ADD作为参数将消息添加进消息过滤器的白名单。同样的，我们也可以以MSGFLT_REMOVE作为参数将这个消息从白名单中删除。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">一个示例</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">对于系统消息的处理，接受消息的进程需要将该消息加入到白名单中，可以通过下面的代码实现：</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">需要在高权限程序开始的地方加入以下代码,指定什么消息可以接受</span></p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0>&nbsp;</p>
<div class=cnblogs_code onclick="cnblogs_code_show('0bf83cb4-b9ff-4a05-8bc3-5d12919a1837')"><img style="DISPLAY: none" id=code_img_closed_0bf83cb4-b9ff-4a05-8bc3-5d12919a1837 class=code_img_closed alt="" src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" jQuery1300107144859="2"><img id=code_img_opened_0bf83cb4-b9ff-4a05-8bc3-5d12919a1837 class=code_img_opened onclick="cnblogs_code_hide('0bf83cb4-b9ff-4a05-8bc3-5d12919a1837',event)" alt="" src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif"><span class=cnblogs_code_collapse>代码</span>
<div style="DISPLAY: block" id=cnblogs_code_open_0bf83cb4-b9ff-4a05-8bc3-5d12919a1837 class=cnblogs_code_hide jQuery1300107144859="1">
<pre>
<div><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="COLOR: #008080"> 1</span> <span style="COLOR: #000000">typedef BOOL (WINAPI </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">_ChangeWindowMessageFilter)( UINT , DWORD);<br></span><span style="COLOR: #008080"> 2</span> <span style="COLOR: #000000"><br></span><span style="COLOR: #008080"> 3</span> <span style="COLOR: #000000">BOOL CVistaMsgRecvApp::AllowMeesageForVista(UINT uMessageID, BOOL bAllow)</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">注册Vista全局消息</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080"> 4</span> <span style="COLOR: #008000">&nbsp;</span><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080"> 5</span> <span style="COLOR: #000000">BOOL bResult </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> FALSE;<br></span><span style="COLOR: #008080"> 6</span> <span style="COLOR: #000000">HMODULE hUserMod </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> NULL;<br></span><span style="COLOR: #008080"> 7</span> <span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">vista and later</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080"> 8</span> <span style="COLOR: #000000">hUserMod </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> LoadLibrary( L</span><span style="COLOR: #800000">"</span><span style="COLOR: #800000">user32.dll</span><span style="COLOR: #800000">"</span><span style="COLOR: #000000"> );<br></span><span style="COLOR: #008080"> 9</span> <span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">( NULL </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> hUserMod )<br></span><span style="COLOR: #008080">10</span> <span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">11</span> <span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> FALSE;<br></span><span style="COLOR: #008080">12</span> <span style="COLOR: #000000">}<br></span><span style="COLOR: #008080">13</span> <span style="COLOR: #000000">_ChangeWindowMessageFilter pChangeWindowMessageFilter </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (_ChangeWindowMessageFilter)GetProcAddress( hUserMod, </span><span style="COLOR: #800000">"</span><span style="COLOR: #800000">ChangeWindowMessageFilter</span><span style="COLOR: #800000">"</span><span style="COLOR: #000000"> );<br></span><span style="COLOR: #008080">14</span> <span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">( NULL </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> pChangeWindowMessageFilter )<br></span><span style="COLOR: #008080">15</span> <span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">16</span> <span style="COLOR: #000000">AfxMessageBox(_T(</span><span style="COLOR: #800000">"</span><span style="COLOR: #800000">create windowmessage filter failed</span><span style="COLOR: #800000">"</span><span style="COLOR: #000000">));<br></span><span style="COLOR: #008080">17</span> <span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> FALSE;<br></span><span style="COLOR: #008080">18</span> <span style="COLOR: #000000">}<br></span><span style="COLOR: #008080">19</span> <span style="COLOR: #000000">bResult </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> pChangeWindowMessageFilter( uMessageID, bAllow </span><span style="COLOR: #000000">?</span><span style="COLOR: #000000"> </span><span style="COLOR: #800080">1</span><span style="COLOR: #000000"> : </span><span style="COLOR: #800080">2</span><span style="COLOR: #000000"> );</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">MSGFLT_ADD: 1, MSGFLT_REMOVE: 2</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">20</span> <span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">( NULL </span><span style="COLOR: #000000">!=</span><span style="COLOR: #000000"> hUserMod )<br></span><span style="COLOR: #008080">21</span> <span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">22</span> <span style="COLOR: #000000">FreeLibrary( hUserMod );<br></span><span style="COLOR: #008080">23</span> <span style="COLOR: #000000">}<br></span><span style="COLOR: #008080">24</span> <span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> bResult;<br></span><span style="COLOR: #008080">25</span> <span style="COLOR: #000000">}</span></div>
</pre>
</div>
</div>
<span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">对于自定义消息，通常是指大于WM_USER的消息，我们首先必须在系统中注册该消息，然后在调用上面的代码:</span>
<p>&nbsp;</p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">
<div class=cnblogs_Highlighter>
<div id=highlighter_910031 class="syntaxhighlighter collapsed  cpp">
<div class="bar   ">
<div class=toolbar><a style="WIDTH: 16px; HEIGHT: 16px" class="item expandSource" title="show source" href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#expandSource" highlighterId="highlighter_910031" commandName="expandSource">show source</a><a style="WIDTH: 16px; HEIGHT: 16px" class="item viewSource" title="view source" href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#viewSource" highlighterId="highlighter_910031" commandName="viewSource">view source</a><a style="WIDTH: 16px; HEIGHT: 16px" class="item printSource" title=print href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#printSource" highlighterId="highlighter_910031" commandName="printSource">print</a><a style="WIDTH: 16px; HEIGHT: 16px" class="item about" title=? href="http://www.cnblogs.com/mydomain/archive/2010/11/24/1887132.html#about" highlighterId="highlighter_910031" commandName="about">?</a></div>
</div>
<div class=lines>
<div class="line alt1">
<table>
    <tbody>
        <tr>
            <td class=number><code>1</code></td>
            <td class=content><code class="cpp preprocessor"><font color=#808080>#define WM_MYNEWMESSAGE (WM_USER + 999)&lt;BR&gt;UINT uMsgBall=::RegisterWindowMessage (WM_MYNEWMESSAGE )&lt;BR&gt;if(!uMsgBall)&lt;BR&gt;return FALSE;&lt;BR&gt;</font></code></td>
        </tr>
    </tbody>
</table>
</div>
</div>
</div>
</div>
注册消息通过RegisterWindowMessage实现，函数的参数就是你需要注册的消息值。</span><font size=2 face=Verdana> </font>
<p>&nbsp;</p>
<p style="MARGIN-TOP: 0pt; TEXT-INDENT: 21pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">此时，低等级的进程就可以像高等级的进程发送消息了。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #ff0000; FONT-SIZE: 12pt; FONT-WEIGHT: bold; mso-spacerun: 'yes'">附 Win7下如何让应用程序以管理员身份进行安装执行</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">1、法一：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">runas /profile /env /user:mydomain\admin "mmc %windir%\system32\dsa.msc"</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">我感觉这种方法不靠谱。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">2、法二：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">通过manifest文件使VC应用程序获得管理员权限</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">这种方法还不错[1]。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&lt;security&gt;</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&lt;requestedPrivileges&gt;</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&lt;requestedExecutionLevel level="requireAdministrator" uiAccess="false"/&gt;</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&lt;/requestedPrivileges&gt;</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">&lt;/security&gt;</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">3、法三：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">起名为setup，win7会自己提升权限，增加manifest文件。还有一种方法是改注册表。HKEY_CURRENT_USER\Software \Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">添加一个字符串值 名称就是你的程序的路径和名字，值为RUNASADMIN。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">也可以做成服务。</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">4、其它方法：</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">还有的网友说：有个开源的项目叫做RunAs，就是用来以指定用户来运行程序的项目，可以参考</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">参考网址和更多阅读</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[1] </span><span><a href="http://hi.baidu.com/crowreturns/blog/item/f5e7cefd7546a284b801a07e.html"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://hi.baidu.com/crowreturns/blog/item/f5e7cefd7546a284b801a07e.html</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[2] </span><span><a href="http://www.cnblogs.com/sun8134/archive/2009/10/30/1593025.html"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://www.cnblogs.com/sun8134/archive/2009/10/30/1593025.html</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[3] 360 论坛</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[4] </span><span><a href="http://topic.csdn.net/u/20100203/14/d98d8310-4971-47d1-94b1-9cdfbf159b4f.html"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://topic.csdn.net/u/20100203/14/d98d8310-4971-47d1-94b1-9cdfbf159b4f.html</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[5] </span><span><a href="http://blog.csdn.net/jinhill/archive/2010/07/21/5752870.aspx"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://blog.csdn.net/jinhill/archive/2010/07/21/5752870.aspx</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[6] Sysinternals Utilities Index</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span><a href="http://technet.microsoft.com/en-us/sysinternals/bb545027.aspx"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://technet.microsoft.com/en-us/sysinternals/bb545027.aspx</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[7] ChangeWindowMessageFilter</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span><a href="http://msdn.microsoft.com/en-us/library/ms632675%28VS.85%29.aspx"><span style="FONT-FAMILY: '宋体-PUA'; COLOR: #0000ff; FONT-SIZE: 12pt; TEXT-DECORATION: underline; mso-spacerun: 'yes'" class=15>http://msdn.microsoft.com/en-us/library/ms632675%28VS.85%29.aspx</span></a></span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0><span style="FONT-FAMILY: '宋体-PUA'; FONT-SIZE: 12pt; mso-spacerun: 'yes'">[ Using the ChangeWindowMessageFilter function is not recommended, as it has process-wide scope. Instead, use the ChangeWindowMessageFilterEx function to control access to specific windows as needed. ChangeWindowMessageFilter may not be supported in future versions of Windows.]</span></p>
<p style="MARGIN-TOP: 0pt; MARGIN-BOTTOM: 0pt" class=p0>［8］http://it.chinawin.net/softwaredev/article-b6f9.html</p>
</div>
</div>
<script type=text/javascript>
if ($ != jQuery) {
$ = jQuery.noConflict();
}
var isLogined = false;
var cb_blogId = 73456;
var cb_entryId = 1887132;
var cb_blogApp = "mydomain";
var cb_blogUserGuid = "279c040c-9795-df11-ba8f-001cf0cd104b";
var cb_entryCreatedDate = '2010/11/24 22:42:00';
</script></div>
<img src ="http://www.cppblog.com/Walker/aggbug/141814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2011-03-14 20:58 <a href="http://www.cppblog.com/Walker/articles/141814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC调用外部程序接口 (转)</title><link>http://www.cppblog.com/Walker/articles/136660.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Thu, 16 Dec 2010 11:48:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/136660.html</guid><wfw:comment>http://www.cppblog.com/Walker/comments/136660.html</wfw:comment><comments>http://www.cppblog.com/Walker/articles/136660.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Walker/comments/commentRss/136660.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Walker/services/trackbacks/136660.html</trackback:ping><description><![CDATA[<span style="COLOR: #000000">(</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">)&nbsp;system()<br><br>函数名:&nbsp;system&nbsp;<br>功&nbsp;能:&nbsp;发出一个DOS命令&nbsp;<br>用&nbsp;法:&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;system(</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">command);&nbsp;<br>备&nbsp;注:&nbsp;system函数已经被收录在标准c库中，可以直接调用&nbsp;<br>返回值<br>　　</span><span style="COLOR: #000000">=-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">:出现错误&nbsp;<br>　　</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">:调用成功但是没有出现子进程&nbsp;<br>　　</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">:成功退出的子进程的id<br>样例:&nbsp;<br>system(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">D:\\game.exe</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br><br></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;system(<br>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">command&nbsp;<br>);<br></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;_wsystem(<br>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;wchar_t&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">command&nbsp;<br>);<br><br><br><br>(</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">)&nbsp;WinExec()<br>函数原型:<br>UINT&nbsp;WINAPI&nbsp;WinExec(<br>&nbsp;&nbsp;__in&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCSTR&nbsp;lpCmdLine,<br>&nbsp;&nbsp;__in&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UINT&nbsp;uCmdShow<br>);<br><br><br>参数说明：<br>　　lpCmdLine，　　</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;命令路径</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">　　uCmdShow，　&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;显示方式，共有11种，具体可以查阅MSDN的ShowWindow函数</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000"><br>返回值：<br>　　成功，返回值大于31<br>　　返回0表示内存或者资源溢出<br>　　返回ERROR_BAD_FORMAT表示exe文件非法或者已损坏。<br>　　返回ERROR_FILE_NOT_FOUND指定的文件没有找到。<br>　　返回ERROR_PATH_NOT_FOUND找不到指定路径<br>样例:<br>　　WinExec(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Notepad.exe</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,&nbsp;SW_SHOW);&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;打开记事本</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">　　WinExec(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">D:\\Program&nbsp;Files\\Test\\Test.exe</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,SW_SHOWMAXIMIZED);&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;以最大化的方式打开Test.exe（注意文件名的大小写也必须完全一样）</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000"><br><br>(</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">)&nbsp;ShellExecute()<br>函数原型:　　　<br><br>HINSTANCE&nbsp;ShellExecute(&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;HWND&nbsp;hwnd,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpOperation,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpFile,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpParameters,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpDirectory,<br>&nbsp;&nbsp;&nbsp;&nbsp;INT&nbsp;nShowCmd<br>);<br><br>用例:　ShellExecute(NULL,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">open</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">C:\\Test.txt</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,NULL,NULL,SW_SHOWNORMAL);&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">打开C:\Test.txt&nbsp;文件　　　　　　　　</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000"><br><br><br>(</span><span style="COLOR: #000000">4</span><span style="COLOR: #000000">)&nbsp;CreateProcess()　<br><br>函数原型:<br><br>BOOL&nbsp;CreateProcess(<br>&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpApplicationName,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">执行程序名</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPTSTR&nbsp;lpCommandLine,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;参数行<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">下面两个参数描述了所创建的进程和线程的安全属性，如果为NULL则使用默认的安全属性</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPSECURITY_ATTRIBUTES&nbsp;lpProcessAttributes,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">进程安全属性</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPSECURITY_ATTRIBUTES&nbsp;lpThreadAttributes,&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;线程安全属性</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;BOOL&nbsp;bInheritHandles,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;继承标志</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;dwCreationFlags,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;创建标志</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPVOID&nbsp;lpEnvironment,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;环境变量</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPCTSTR&nbsp;lpCurrentDirectory,&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;运行该进程的初始目录</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPSTARTUPINFO&nbsp;lpStartupInfo,&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;用于在创建子进程时设置各种属性</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;LPPROCESS_INFORMATION&nbsp;lpProcessInformation&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">用于在进程创建后接受相关信息</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">　　);&nbsp;<br><br>评论:<br>system()，主要用在DOS环境下.<br>WinExec(),简单实用，方便打开执行进程，但不能操作控制进程。<br>ShellExecute(),增强了操作能力，但对进程的控制还是不够用。<br>CreateProcess(),<br>目前最强劲的进程函数，通过设置StartupInfo结构体参数，来设置子进程的属性，子进程创建后的信息也保存在ProcessInformation结构体中，便于操作，功能强大，但参数过多.<br></span>
<img src ="http://www.cppblog.com/Walker/aggbug/136660.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2010-12-16 19:48 <a href="http://www.cppblog.com/Walker/articles/136660.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GDI+学习之线性渐变画刷</title><link>http://www.cppblog.com/Walker/articles/136411.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Tue, 14 Dec 2010 12:52:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/136411.html</guid><description><![CDATA[<p><br>&nbsp; GDI+学习笔记之GDI+环境初始化 收藏 <br>作者：朱金灿<br>来源：<a href="http://blog.csdn.net/clever101/">http://blog.csdn.net/clever101/</a> </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 尽管以前接触过一下GDI+，但决心从现在开始系统学习GDI+，所用的教材为《精通GDI编程》。在VS 2010以下版本的VS 编译器使用GDI+都需要对GDI+环境进行初始化操作（VS 2010中的MFC 10依赖GDI+，故不用初始化）。 </p>
<p>VS 2003、VS 2005和VS 2008的GDI+环境初始化操作步骤一样。 </p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在应用程序类添加一个保护权限的数据成员： </p>
<p>view plaincopy to clipboardprint?<br>ULONG_PTR m_gdiplusToken;&nbsp; <br>ULONG_PTR m_gdiplusToken; </p>
<p>&nbsp;</p>
<p><br>在应用程序类的实现文件包含gdi+的头文件： </p>
<p>&nbsp;</p>
<p><br>view plaincopy to clipboardprint?<br>#include &lt;GdiPlus.h&gt;&nbsp; <br>#include &lt;GdiPlus.h&gt; </p>
<p><br>在工程附加库加上：GdiPlus.lib </p>
<p>&nbsp;</p>
<p><br>然后在应用程序类的InitInstance加上下面初始化代码： </p>
<p>&nbsp;</p>
<p><br>view plaincopy to clipboardprint?<br>BOOL C***App::InitInstance()&nbsp;&nbsp; <br>{&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; Gdiplus::GdiplusStartupInput StartupInput;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; GdiplusStartup(&amp;m_gdiplusToken,&amp;StartupInput,NULL);&nbsp;&nbsp; <br>}&nbsp; <br>BOOL C***App::InitInstance()<br>{<br>&nbsp;Gdiplus::GdiplusStartupInput StartupInput;<br>&nbsp;GdiplusStartup(&amp;m_gdiplusToken,&amp;StartupInput,NULL);<br>}<br>&nbsp;</p>
<p>&nbsp;</p>
<p>上面代码的作用是初始化GDI+资源。 </p>
<p>&nbsp;</p>
<p><br>&nbsp; 在应用程序类的InitInstance加上下面代码： </p>
<p>&nbsp;</p>
<p><br>view plaincopy to clipboardprint?<br>int C***App::ExitInstance()&nbsp;&nbsp; <br>{&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // TODO: 在此添加专用代码和/或调用基类&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; Gdiplus::GdiplusShutdown(m_gdiplusToken);&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; return __super::ExitInstance();&nbsp;&nbsp; <br>}&nbsp; <br>int C***App::ExitInstance()<br>{<br>&nbsp;// TODO: 在此添加专用代码和/或调用基类<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;Gdiplus::GdiplusShutdown(m_gdiplusToken);<br>&nbsp;return __super::ExitInstance();<br>}<br>&nbsp;</p>
<p>&nbsp;</p>
<p>上面代码的作用是销毁GDI+资源。 </p>
<p><br>VC 6.0中使用GDI+库，请参考这篇文章：在VC6.0中使用GDI+的两种办法 </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp; 现在测试一下我们初始化GDI+环境是否成功。我们使用GDI+的类接口在视图客户区绘制一个字符串，具体代码如下： </p>
<p>&nbsp;</p>
<p><br>view plaincopy to clipboardprint?<br>CDC *pDC = pView-&gt;GetDC();&nbsp;&nbsp; <br>Gdiplus::Graphics graphics(pDC-&gt;m_hDC);&nbsp;&nbsp; <br>Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255));&nbsp;&nbsp; <br>Gdiplus::SolidBrush brush(Gdiplus::Color(255,0,0,255));&nbsp;&nbsp; <br>Gdiplus::FontFamily fontfm(L"宋体");&nbsp;&nbsp; <br>Gdiplus::Font font(&amp;fontfm,24,Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);&nbsp;&nbsp; <br>CRect rt;&nbsp;&nbsp; <br>pView-&gt;GetClientRect(&amp;rt);&nbsp;&nbsp; <br>Gdiplus::PointF pointF(rt.Width()/2,rt.Height()/2);&nbsp;&nbsp; <br>&nbsp;&nbsp; graphics.DrawString(L"GDI+程序示意",-1,&amp;font,pointF,&amp;brush);&nbsp;&nbsp; <br>&nbsp;&nbsp; graphics.ReleaseHDC(pDC-&gt;m_hDC);&nbsp;&nbsp; <br>&nbsp;&nbsp; pView-&gt;ReleaseDC(pDC);&nbsp; <br>&nbsp;CDC *pDC = pView-&gt;GetDC();<br>&nbsp;Gdiplus::Graphics graphics(pDC-&gt;m_hDC);<br>&nbsp;Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255));<br>&nbsp;Gdiplus::SolidBrush brush(Gdiplus::Color(255,0,0,255));<br>&nbsp;Gdiplus::FontFamily fontfm(L"宋体");<br>&nbsp;Gdiplus::Font font(&amp;fontfm,24,Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);<br>&nbsp;CRect rt;<br>&nbsp;pView-&gt;GetClientRect(&amp;rt);<br>&nbsp;Gdiplus::PointF pointF(rt.Width()/2,rt.Height()/2);<br>&nbsp;&nbsp;&nbsp; graphics.DrawString(L"GDI+程序示意",-1,&amp;font,pointF,&amp;brush);<br>&nbsp;&nbsp;&nbsp; graphics.ReleaseHDC(pDC-&gt;m_hDC);<br>&nbsp;&nbsp;&nbsp; pView-&gt;ReleaseDC(pDC);<br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>效果图如下： </p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>使用GDI+一些注意事项： </p>
<p>&nbsp;</p>
<p><br>1.在DLL中使用GDI+库，只需要包含GdiPlus.h和GdiPlus.lib，初始化GDI+环境的工作只需要在主调用程序做，否则在DLL初始化代码中初始化GDI+环境容易发生DLL重入的错误（以前我犯过这样的错误）。 </p>
<p>&nbsp;</p>
<p><br>2.GDI+接口参数使用的是unicode字符集，因为调用任何GDI+类接口时其字符串参数都必须确保是unicode字符。在多字节字符集环境下开发常量字符串可以通过L宏转换,变量多字节字符转unicode字符可以使用Windows API函数MultiByteToWideChar或ATL的A2W宏。 </p>
<p>&nbsp;</p>
<p><br>3. GDI+的对象和GDI句柄一样，同样会占用资源，一次使用过多的GDI+的对象甚至会发生程序崩溃的现象。因此必须随时将不必要的GDI+的对象占用的资源释放掉，如上例的：graphics.ReleaseHDC(pDC-&gt;m_hDC)。 </p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/clever101/archive/2010/12/08/6063670.aspx">http://blog.csdn.net/clever101/archive/2010/12/08/6063670.aspx</a><br>作者：朱金灿<br>来源：<a href="http://blog.csdn.net/clever101/">http://blog.csdn.net/clever101/</a> </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我发现使用GDI+来制作画图工具的调色板极为方便（这个工作如果让GDI来做不知要写多少代码）。下面我们学习一下GDI+的线性渐变画刷：LinearGradientBrush类的用法，具体代码如下： </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view plaincopy to clipboardprint?<br>&nbsp;&nbsp;&nbsp; CDC *pDC = pView-&gt;GetDC();&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; // 定义一个画图对象&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; Gdiplus::Graphics graphics(pDC-&gt;m_hDC);&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; // 获取视图客户区大小&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; CRect rt;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; pView-&gt;GetClientRect(&amp;rt);&nbsp;&nbsp; <br>// 定义一个线性渐变画刷，按红黄蓝绿的顺序四种颜色渐变&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,0,255));&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; Color colors[] = {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 0, 0),&nbsp;&nbsp; // red&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 255, 0), //yellow&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 0, 0, 255),&nbsp;&nbsp; // blue&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 0, 255, 0)};&nbsp; // green&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; REAL positions[] = {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.0f,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.33f,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.66f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.0f};&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; linGrBrush.SetInterpolationColors(colors, positions,4);&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // 填充指定区域矩形&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; graphics.FillRectangle(&amp;linGrBrush,rt.Width()/2,0,80,rt.Height()/2);&nbsp;&nbsp; <br>&nbsp;CDC *pDC = pView-&gt;GetDC();<br>&nbsp; &nbsp; // 定义一个画图对象<br>&nbsp;Gdiplus::Graphics graphics(pDC-&gt;m_hDC);<br>&nbsp;&nbsp; &nbsp;<br>&nbsp; // 获取视图客户区大小<br>&nbsp; CRect rt;<br>&nbsp; pView-&gt;GetClientRect(&amp;rt);<br>// 定义一个线性渐变画刷，按红黄蓝绿的顺序四种颜色渐变<br>&nbsp;&nbsp; &nbsp; LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,0,255));<br>&nbsp; Color colors[] = {<br>&nbsp;&nbsp; Color(255, 255, 0, 0),&nbsp;&nbsp; // red<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 255, 0), //yellow<br>&nbsp;&nbsp; Color(255, 0, 0, 255),&nbsp;&nbsp; // blue<br>&nbsp;&nbsp; Color(255, 0, 255, 0)};&nbsp; // green<br>&nbsp;&nbsp; REAL positions[] = {<br>&nbsp;&nbsp;&nbsp; 0.0f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 0.33f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 0.66f,<br>&nbsp;&nbsp;&nbsp; 1.0f};&nbsp; <br>&nbsp;linGrBrush.SetInterpolationColors(colors, positions,4);<br>&nbsp;// 填充指定区域矩形<br>&nbsp;graphics.FillRectangle(&amp;linGrBrush,rt.Width()/2,0,80,rt.Height()/2);&nbsp; </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp; 效果如下： </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这个线性渐变画刷很简单，就是按垂直方向（即y轴方向）渐变的。我感兴趣的是画刷的两个参数Point(100,0),Point(100,rt.Height()/2)，书上介绍的起点的颜色和终点颜色的位置和要填充的矩形之间的关系是怎样的？我们看到上面的画刷的起点和终点的高度和要填充的矩形的高度是一样的，都是rt.Height()/2。我们把画刷的高度缩小为原来的一般，看看有什么效果，即定义为： </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; view plaincopy to clipboardprint?<br>LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/4),Color(255,255,0,0),Color(255,0,0,255));&nbsp; <br>LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/4),Color(255,255,0,0),Color(255,0,0,255)); </p>
<p>效果图如下： </p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp; 我们发现上图等于连续用两个画刷填充这个矩形。假如填充的目标矩形的高度小于画刷的高度，又会是怎样的效果呢？代码改为： </p>
<p>&nbsp;</p>
<p><br>view plaincopy to clipboardprint?<br>LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,255,0));&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; Color colors[] = {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 0, 0),&nbsp;&nbsp; // red&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 255, 0), //yellow&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 0, 0, 255),&nbsp;&nbsp; // blue&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 0, 255, 0)};&nbsp; // green&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; REAL positions[] = {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.0f,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.33f,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.66f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.0f};&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; linGrBrush.SetInterpolationColors(colors, positions,4);&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // 填充指定区域矩形&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; graphics.FillRectangle(&amp;linGrBrush,rt.Width()/2,0,80,rt.Height()/4);&nbsp; <br>LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,255,0));<br>&nbsp; Color colors[] = {<br>&nbsp;&nbsp; Color(255, 255, 0, 0),&nbsp;&nbsp; // red<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Color(255, 255, 255, 0), //yellow<br>&nbsp;&nbsp; Color(255, 0, 0, 255),&nbsp;&nbsp; // blue<br>&nbsp;&nbsp; Color(255, 0, 255, 0)};&nbsp; // green<br>&nbsp;&nbsp; REAL positions[] = {<br>&nbsp;&nbsp;&nbsp; 0.0f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 0.33f,&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 0.66f,<br>&nbsp;&nbsp;&nbsp; 1.0f};&nbsp; <br>&nbsp;linGrBrush.SetInterpolationColors(colors, positions,4);<br>&nbsp;// 填充指定区域矩形<br>&nbsp;graphics.FillRectangle(&amp;linGrBrush,rt.Width()/2,0,80,rt.Height()/4);<br>&nbsp;</p>
<p>&nbsp;</p>
<p><br>效果图如下： </p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 我们看到这时矩形区域的填充只使用了画刷的一部分。这时或许我们可以得出一个简单的结论：用画刷填充多边形区域，有点类似于铺地砖，地砖好比画刷，空地好比要填充的区域区域。 </p>
<p>&nbsp;</p>
<p><br>思考题： </p>
<p>&nbsp;</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 线性渐变画刷的起点和终点的坐标值和要填充的矩形之间是什么关系？ </p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/clever101/archive/2010/12/08/6063697.aspx">http://blog.csdn.net/clever101/archive/2010/12/08/6063697.aspx</a></p>
<img src ="http://www.cppblog.com/Walker/aggbug/136411.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2010-12-14 20:52 <a href="http://www.cppblog.com/Walker/articles/136411.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>控件通知消息</title><link>http://www.cppblog.com/Walker/articles/136328.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 13 Dec 2010 12:30:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/136328.html</guid><description><![CDATA[&nbsp;&nbsp;<span>控件通知消息</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>在《深度解析<span>VC</span>中的消息（上）》中，我们提到了消息的分类有<span>3</span>种：窗口消息、命令消息和控件通知消息，我们这里要谈的是最后一种：控件通知消息。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>控件通知消息，是指这样一种消息，一个窗口内的子控件发生了一些事情，需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框，以及<span>Windows</span>公共控件如树状视图、列表视图等。例如，单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。她类似于命令消息，当用户与控件窗口交互时，那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令，而是为了让主窗口能够改变控件，例如加载、显示数据。例如按下一个按钮，他向父窗口发送的消息也可以看作是一个控件通知消息；单击鼠标所产生的消息可以由主窗口直接处理，然后交给控件窗口处理。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>控件通知消息主要由窗口类即直接或间接由<span>CWND</span>类派生类处理。</span>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>控件通知格式</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>控件通知经历了一个演变过程，因而<span>SendMessage( )</span>的变量<span>Message</span>、<span>wParam</span>和<span>lParam</span>有三种格式。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>第一控件通知格式<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>第一控件通知格式只是窗口消息的子集。它的特征格式如下：<span>WM_XXXX</span>。它主要来自下面的<span>3</span>种消息类型：<span><br>&nbsp;&nbsp;&nbsp;&nbsp; (1)</span>表示一个控件窗口要么已经被创建或销毁，要么已经被鼠标单击的消息：<span>WM_PARENTNOTIFY</span>；<span><br>&nbsp;&nbsp;&nbsp;&nbsp; (2)</span>发送到父窗口，用来绘制自身窗口的消息，例如：<span> WM_CTLCOLOR</span>、<span>WM_DRAWITEM</span>、<span>WM_MEASUREITEM</span>、<span>WM_DELETEITEM</span>、<span>WM_CHARTOITEM</span>、<span>WM_VKTOITEM</span>、<span>WM_COMMAND</span>和<span>WM_COMPAREITEM<br>&nbsp;&nbsp;&nbsp;&nbsp; (3)</span>有滚动调控件发送，通知父窗口滚动窗口的消息：<span>WM_VSCROLL</span>和<span>WM_HSCROLL<br>&nbsp;&nbsp;&nbsp;&nbsp; </span>第二控件通知格式<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>第二控件通知格式与命令消息共享，它的特征格式如下：<span>WM_COMMAND</span>。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>在<span>WM_COMMAND</span>中，<span>lParam</span>用来区分是命令消息还是控件通知消息：如果<span>lParam</span>为<span>NULL</span>，则这是个命令消息，否则<span>lParam</span>里面放的必然就是控件的句柄，是一个控件通知消息。<span>对于<span>wParam</span>则是低位放的是控件<span>ID</span>，高位放的是相应的消息事件。<span><br></span></span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>第三控件通知格式<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>这个才真正涉及到我们要讲的内容，同时他也是最为灵活的一种格式。它的特征格式如下：<span>WM_NOTIFY</span>。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>在<span>WM_NOTIFY</span>中，<span>lParam</span>中放的是一个称为<a name=baidusnap0></a><strong><span>NMHDR</span></strong>结构的指针。在<span>wParam</span>中放的则是控件的<span>ID</span>。</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><strong><span>NMHDR</span></strong><span>结构的由来</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp;<strong><span>NMHDR</span></strong></span><span>结构是很值得一提的，该结构包括有关制作该通知的控件的任何内容，而不受空间和类型的限制，他的来历也是很有意思的。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>在最初的<span>windows3.x</span>中，根本就不存在什么<span>WM_NOTIFY</span>，控件通知它们父窗口，如鼠标点击<span>,</span>控件背景绘制事件，通过发送一个消息到父窗口。简单的通知仅发送一个<span>WM_COMMAND</span>消息，包含一个通知码和一个在<span>wParam</span>中的控件<span>ID</span>及一个在<span>lPraram</span>中的控件句柄。这样一来，<span>wParam</span>和<span>lParam</span>就都被填充了，没有额外的空间来传递一些其它的消息，例如鼠标按下的位置和时间。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>为了克服这个困难，<span>windows3.x</span>就提出了一个比较低级的解决策略，那就是给一些消息添加一些附加消息，最为明显的就是控件自画用到的<span>DRAWITEMSTRUCT</span>。不知道大家对这个结构熟悉不，不过，如果你是老手，你应该非常清楚这个结构，这个结构包含了<span>9</span>个内容，几乎你需要控制的信息都给你提供了。为什么说它比较低级呢？因为不同的消息附加的内容不同，结果就是一盘散沙，非常混乱。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>在<span>win32</span>中，<span>MS</span>又提出了一个更好的解决方案：引进<strong><span>NMHDR</span></strong>结构。这个结构的引进就是消息统一起来，利用它可以传递复杂的信息。这个结构的布局如下：<span><br>&nbsp;&nbsp;&nbsp;&nbsp;<strong><span>NMHDR</span></strong><br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HWnd hWndFrom ; </span>相当于原<span>WM_COMMAND</span>传递方式的<span>lParam<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UINT idFrom ;&nbsp;&nbsp;&nbsp; </span>相当于原<span>WM_COMMAND</span>传递方式的<span>wParam</span>（<span>low-order</span>）<span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UINT code ;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>相当于原<span>WM_COMMAND</span>传递方式的<span>Notify Code(wParam"s high-order)<br>&nbsp;&nbsp;&nbsp;&nbsp; }</span>；<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>对于这个结构的应用于<span>WM_NOTIFY</span>信息结构，结果<span>WM_NOTIFY</span>就变成了：<span><br>&nbsp;&nbsp;&nbsp;&nbsp; A</span>、无附加信息。结构变得很简单，就是一个<strong><span>NMHDR</span></strong>结构。<span><br>&nbsp;&nbsp;&nbsp;&nbsp; B</span>、有附加信息。定义一个大的结构，它的第一个元素就是<strong><span>NMHDR</span></strong>结构，它的后面放置附加信息。<span><br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>WM_NOTIFY</span><span>结构的好处</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>除了上面我们所说的好处外，<span>WN_NOTIFY</span>还有自己的独特的好处：<span><br>&nbsp;&nbsp;&nbsp;&nbsp; </span>由于在大结构中，第一个成员为<strong><span>NMHDR</span></strong><span>,</span>这样一来，我们就可以利用指向<strong><span>NMHDR</span></strong>的指针来传递结构地址，根据指针的特性，无论消息有没有附加信息，这个指针都适用，也能够很方便的进行强制转换。</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>分析<span>ON_NOTIFY<br></span>　<span>&nbsp;&nbsp;</span></span><span>类向导可以创建<span>ON_NOTIFY</span>消息映射入口并提供一个处理函数的框架，来处理<span> WM_NOTIFY</span>类型的消息。<span>ON_NOTIFY</span>消息映射宏有如下语法<span>.<br></span>　　　<span>ON_NOTIFY(wNotifyCode,id,memberFxn)<br></span>　　其中：<span>wNotifyCode:</span>要处理的通知消息通知码。比如上面我们提到的<span>LVN_KEYDOWN</span>；<span>Id:</span>控件标识<span>ID</span>；<span>MemberFxn:</span>处理此消息的成员函数。<span><br></span>　　此成员函数有如下的原型声明<span>:<br></span>　　　<span>afx_msg void memberFxn( <strong><span>NMHDR</span></strong> * pNotifyStruct, LRESULT * result); <br></span>　　比如<span>:</span>假设你想成员函数<span>OnKeydownList1</span>处理<span>ClistCtrl(</span>标识<span>ID=IDC_LIST1</span>）的<span> LVN_KEYDOWN</span>消息<span>,</span>你可以使用类向导添加如下的消息映射<span>:<br></span>　　　<span>ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )<br></span>　　在上面的例子中<span>,</span>类向导提供如下函数<span>:<br></span>　　　<span>void CMessageReflectionDlg::OnKeydownList1(<strong><span>NMHDR</span></strong>* pNMHDR, LRESULT* pResult)<br></span>　　　　<span>{<br></span>　　　　　<span>LV_KEYDOWN* pLVKey= (LV_KEYDOWN*)pNMHDR;<br></span>　　　　　<span>*pResult = 0;<br></span>　　　　<span> }<br></span>　　这时类向导提供了一个适当类型的指针，你既可以通过<span>pNMHDR</span>，也可以通过<span> pLVKey</span>来访问这个通知结构。</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>ON_NOTIFY_RANGE<br></span><span>　　</span><span>有时我们可能需要为一组控件处理相同的<span>WM_NOTIFY</span>消息。这时需要使用<span>ON_NOTIFY_RANGE</span>而不是<span>ON_NOTIFY</span>。不过，很不幸的是，<span>VC6</span>的<span>ClassWizard</span>并不支持这个消息，所以我们必须手工添加。方法和一般的手工添加的消息一样，不过需要注意的是：<span><br>&nbsp;&nbsp;&nbsp;&nbsp; (1)</span>当你使用<span> ON_NOTIFY_RANGE</span>时<span>,</span>你需要指定控件的<span>ID</span>范围<span>.</span>其消息映射入口及函数原型如下<span>:<br></span>　　　<span>&nbsp;&nbsp; ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )<br></span>　　　　其中：<span>wNotifyCode:</span>消息通知码<span>.</span>比如<span>:LVN_KEYDOWN</span>。<span>id: </span>第一控件的标识<span>ID</span>。<span><br></span>　　　　　<span>idLast:</span>最后一个控件的标识<span>ID</span>。（标识值一定要连续）<span>memberFxn: </span>消息处理函数。<span><br></span>　　<span>(2)</span>成员函数必须有如下原型申明：<span>afx_msg void memberFxn( UINT id, <strong><span>NMHDR</span></strong> * pNotifyStruct, LRESULT * result );</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>结束语：</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp; WM_NOTIFY</span><span>是一个很值得思考的结构，另外他和<span>Reflect Message</span>联系非常紧密，我们接下来就要讨论一下反射消息。</span></p>
<img src ="http://www.cppblog.com/Walker/aggbug/136328.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2010-12-13 20:30 <a href="http://www.cppblog.com/Walker/articles/136328.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows消息（转）</title><link>http://www.cppblog.com/Walker/articles/136326.html</link><dc:creator>漫步者×&amp;……%￥</dc:creator><author>漫步者×&amp;……%￥</author><pubDate>Mon, 13 Dec 2010 11:47:00 GMT</pubDate><guid>http://www.cppblog.com/Walker/articles/136326.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;转贴:深度解析VC中的消息(上) 收藏 hustli（原作）转自csdn.net摘要：Windows编程和Dos编程，一个很大的区别就是，windows编程是事件驱动，消息传递的。所以，要做好windows编程，必须对消息机制有一个清楚的认识，本文希望能够对消息的传递做一个全面的论述，由于小生初学VC，里面可能有一些错误的地方，还往各位大虾批评、指正。&nbsp;注意：有些...&nbsp;&nbsp;<a href='http://www.cppblog.com/Walker/articles/136326.html'>阅读全文</a><img src ="http://www.cppblog.com/Walker/aggbug/136326.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Walker/" target="_blank">漫步者×&……%￥</a> 2010-12-13 19:47 <a href="http://www.cppblog.com/Walker/articles/136326.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>