﻿<?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>博客园-ILove's Dev Home - 休息的时候不要忘记 别人还在奔跑</title><link>http://www.cnblogs.com/ILove/</link><description /><language>zh-cn</language><lastBuildDate>Fri, 05 Dec 2008 14:20:00 GMT</lastBuildDate><pubDate>Fri, 05 Dec 2008 14:20:00 GMT</pubDate><ttl>60</ttl><item><title>log4net 使用手记</title><link>http://www.cnblogs.com/ILove/archive/2008/11/10/1330272.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Mon, 10 Nov 2008 04:02:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/11/10/1330272.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1330272.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/11/10/1330272.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1330272.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1330272.html</trackback:ping><description><![CDATA[<p>Log4net中，最常用的对象有 Logger、Appender 以及 Layout 等。Logger 是日志记录器，是记录日志的入口。每个Logger可以绑定一个或多个 Appender，Appender对象描述&#8220;向哪里写日志&#8221;，比如ConsoleAppender指示将日志写入到控制台，FileAppender指示将日志写入文件等。你也许还需要为 Appender 绑定一个 Layout：用来描述&#8220;怎样格式化日志&#8221;，比如<font face="Verdana">XmlLayout</font>指定应该将日志对象序列化为一段Xml文本。</p>
<p>1、Logger</p>
<p>每个Logger对象都有一个Name。这些Logger对象全部维护在一个Map中，我们可以通过LogManager.GetLogger(string name)来获取一个Logger对象。GetLogger方法内部会查找这个Map，如果找到名字相同的就返回，否则使用LoggerFactory创建一个新的Logger对象，并注册在Map中。当获得Logger对象之后，我们就可以使用ILog接口中的方法记录日志了：</p>
<p>public interface ILog : ILoggerWrapper<br />
{<br />
&nbsp;&nbsp;void Debug(...);<br />
&nbsp;&nbsp;void Error(...);<br />
&nbsp;&nbsp;void Fatal(...);<br />
&nbsp;&nbsp;void Info(...);<br />
&nbsp;&nbsp;void Warn(...);<br />
}</p>
<p>ILog接口里面只是默认实现了五个Level的日志，如果你觉得不够，还可以通过获取ILog上的ILogger对象实现更加丰富的控制。</p>
<p>Logger对象有个Level参数。如果给Logger对象设置了Level属性，则只有日志级别大于或等于该级别时，日志才会被记录下来，否则Log方法是直接返回的，就好像调用了一个空函数一样。这样给我们带来的好处就是，我们可以尽情的在代码中记录一些Debug日志，当部署时将Level级别设置为Info或更高就可以了，完全不必担心这些代码消耗性能，也不用使用#ifdef #endif 这类代码了。</p>
<p>log4net中的日志Level使用一个整数来表示级别，因此理论上可以设置int.MinValue~int.MaxValue任何一个级别。不过log4net已经默认为我们定义了十几种日志级别（详见<font face="Verdana">log4net.Core.Logger的静态实例</font>），而且ILogger对象中也默认只使用Debug、Info、Warn、Error、Fatal五种。除了这五种之外，还有两个常用的Level：<font face="Verdana">Off（</font>int.MaxValue）和All（int.MinValue）。如果将Logger对象的Level设置为Off，则表示关闭任何级别的日志（严格来讲是关闭任何级别小于int.MaxValue的日志）；如果设置为All，则表示所有级别的日志都会被记录。</p>
<p>对于这些常用的日志级别来讲，他们的级别顺序是：OFF &gt; FATAL &gt; ERROR &gt; WARN &gt; INFO &gt; DEBUG &gt; ALL。</p>
<p>在log4net中，Logger是可以继承的，比如我们定义了三个Logger：A、A.B、A.B.C。则A.B.C被视为A.B的子Logger对象，A.B又被视为A的子对象。同时隐含的，A是Root的对象，因为在log4net中将所有的Logger对象都视为一个名字为&#8220;Root&#8221;的Logger。子Logger继承父Logger的Appender列表，这一点是需要注意的。如果我们在Root中设置日志写入到Console、数据库，在A中设置日志写入到FileAppender，则实际使用A时，日志将同时被写入Console、数据库和文件。</p>
<p>2、Appender</p>
<p>Appender用来描述将日志写道哪里去。log4net默认提供了许多Appender对象，可以写入到文件、到控制台、到邮件服务器等。当然我们也可以实现自己的Appender，按照我们自己的逻辑记录日志。</p>
<p>具体的Appender这里就不再列举，各位可以参考log4net帮助文档中的例子，或者查看sdk。</p>
<p>3、Layout</p>
<p>Layout用来描述怎样格式化日志文本。常用的是PatternLayout，具体用法同样参考帮助文档中的例子，或者查看sdk。</p>
<p>除了使用默认提供的Layout，我们也可以实现自定义Layout。</p>
<p>PatternLayout在序列化%message时，会将MessageObject转换为字符串。该类会判断MessageObject的类型，如果是哈希表，就序列化成{key=value,key=value}这种形式，同样的，PatternLayout会自动处理数组。</p>
<p>log4net通过注册<font face="Verdana">PatternConverter类，将&#8220;message&#8221;与&#8220;<font face="Verdana">MessagePatternConverter</font>&#8221;类绑定在一起，这样当PatternLayout遇到%message时，就会创建一个<font face="Verdana">MessagePatternConverter</font>类，利用这个类将MessageObject转换为字符串。同理，PatternLayout中的类似%n、%l等参数，都是这样通过实现的。</font></p>
<p>因此，假设我们要实现自己的Layout更改%message的格式化方式，则只需要创建一个自己的PatternLayout类，并在构造方法中调用<font face="Verdana">AddConverter</font>方法，将message重新注册为我们自己的&#8220;My<font face="Verdana">MessagePatternConverter</font>&#8221;就可以了。</p>
<p>4、Object<font face="Verdana">Render</font></p>
<p>除了通过Layout控制日志文本的格式化方式，我们还可以通过注册对象的Render方式，让log4net在遇到指定类型的对象时，自动使用对应的ObjectRender来格式化日志。</p>
<p>比如：如果我们觉得log4net输出的异常信息不够详细。我们更想使用类似微软企业类库中的异常格式化方式。那么我们可以自定义一个ExceptionRender，在这个类里面使用企业类库中的类将异常格式化为文本，然后注册ExceptionRender：</p>
<p><font face="Verdana">&nbsp;&nbsp;&nbsp;ExceptionRender exceptionRender = new ExceptionRender();<br />
&nbsp;&nbsp;&nbsp;foreach(ILoggerRepository i in LogManager.GetAllRepositories())<br />
&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;i.RendererMap.Put(typeof(Exception), exceptionRender);<br />
&nbsp;&nbsp;&nbsp;}</font></p>
<p><font face="Verdana">5、配置文件实例：</font></p>
<p><font face="Verdana">下面是一个配置文件实例。</p>
</font>
<p>&nbsp;</p>
<div class="cnblogs_code"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008080">&nbsp;1</span>&nbsp;<span style="color: #0000ff">&lt;?</span><span style="color: #ff00ff">xml&nbsp;version="1.0"&nbsp;encoding="utf-8"</span><span style="color: #0000ff">?&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;2</span>&nbsp;<span style="color: #0000ff">&lt;</span><span style="color: #800000">configuration</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;3</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">configSections</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;4</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">section&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="log4net"</span><span style="color: #ff0000">&nbsp;type</span><span style="color: #0000ff">="log4net.Config.Log4NetConfigurationSectionHandler,&nbsp;log4net"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;5</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">configSections</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;6</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">log4net</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;7</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">root</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;8</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">level&nbsp;</span><span style="color: #ff0000">value</span><span style="color: #0000ff">="All"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">&nbsp;9</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender-ref&nbsp;</span><span style="color: #ff0000">ref</span><span style="color: #0000ff">="ConsoleAppender"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">10</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender-ref&nbsp;</span><span style="color: #ff0000">ref</span><span style="color: #0000ff">="RollingFileAppender"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">11</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender-ref&nbsp;</span><span style="color: #ff0000">ref</span><span style="color: #0000ff">="******AlertAppender"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">12</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">root</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">13</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="ConsoleAppender"</span><span style="color: #ff0000">&nbsp;type</span><span style="color: #0000ff">="log4net.Appender.ConsoleAppender,&nbsp;log4net"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">14</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">layout&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializationLayout,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">15</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="ConversionPattern"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="===========================&nbsp;%d{yyyy-MM-dd&nbsp;HH:mm:ss.fff}&nbsp;===========================%nLevel:&nbsp;%p%nAppDomain:&nbsp;%a%nLocation:&nbsp;%l%nMessage:%n%m%exception"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">16</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">serializer&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializer,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">17</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="TabString"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="&nbsp;&nbsp;&nbsp;&nbsp;"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">18</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">serializer</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">19</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">layout</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">20</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">appender</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">21</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="RollingFileAppender"</span><span style="color: #ff0000">&nbsp;type</span><span style="color: #0000ff">="log4net.Appender.RollingFileAppender,&nbsp;log4net"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">22</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="StaticLogFileName"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="false"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">23</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="RollingStyle"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="Date"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">24</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="File"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="Log\"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">25</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="DatePattern"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="yyyy-MM-dd&amp;quot;.log&amp;quot;"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">26</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="AppendToFile"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="true"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">27</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">LockingModel&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="log4net.Appender.FileAppender+MinimalLock,&nbsp;log4net"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">28</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">layout&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializationLayout,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">29</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="ConversionPattern"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="===========================&nbsp;%d{yyyy-MM-dd&nbsp;HH:mm:ss.fff}&nbsp;===========================%nLevel:&nbsp;%p%nAppDomain:&nbsp;%a%nLocation:&nbsp;%l%nMessage:%n%m%exception"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">30</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">serializer&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializer,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">31</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="TabString"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="&nbsp;&nbsp;&nbsp;&nbsp;"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">32</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">serializer</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">33</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">layout</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">34</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">appender</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">35</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">appender&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="******AlertAppender"</span><span style="color: #ff0000">&nbsp;type</span><span style="color: #0000ff">="******.Common.Logging.******AlertAppender,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">36</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="Title"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="log4net&nbsp;接收到异常"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">37</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="EmailAsFrom"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="Admin@Itil.com"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">38</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="EmailTo"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="librading"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">39</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="EmailCopyTo"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="xingkaiwang"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">40</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="EmailBodyFormat"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="Text"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">41</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="RtxAsFrom"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="xingkaiwang"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">42</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="RtxInfo"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="log4net&nbsp;接收到异常，详细信息请查阅邮件"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">43</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="RtxTo"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="librading"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">44</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="SmsTo"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">=""</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">45</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">filter&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="log4net.Filter.LevelRangeFilter"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">46</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="LevelMin"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="Error"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">47</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">filter</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">48</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">layout&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializationLayout,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">49</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="ConversionPattern"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="===========================&nbsp;%d{yyyy-MM-dd&nbsp;HH:mm:ss.fff}&nbsp;===========================%nLevel:&nbsp;%p%nAppDomain:&nbsp;%a%nLocation:&nbsp;%l%nMessage:%n%m%exception"</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">50</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">serializer&nbsp;</span><span style="color: #ff0000">type</span><span style="color: #0000ff">="******.Common.Logging.ReflectionSerializer,&nbsp;******.Common.Logging"</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">51</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;</span><span style="color: #800000">param&nbsp;</span><span style="color: #ff0000">name</span><span style="color: #0000ff">="TabString"</span><span style="color: #ff0000">&nbsp;value</span><span style="color: #0000ff">="&nbsp;&nbsp;&nbsp;&nbsp;"</span><span style="color: #ff0000">&nbsp;</span><span style="color: #0000ff">/&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">52</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">serializer</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">53</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">layout</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">54</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">appender</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">55</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;</span><span style="color: #0000ff">&lt;/</span><span style="color: #800000">log4net</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">56</span>&nbsp;<span style="color: #0000ff">&lt;/</span><span style="color: #800000">configuration</span><span style="color: #0000ff">&gt;</span><span style="color: #000000"><br />
</span><span style="color: #008080">57</span>&nbsp;</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;1）、注册ConfigSection</p>
<p>使用log4net配置文件时，必须告诉应用程序配置文件中的&lt;log4net&gt;*&lt;/log4net&gt;节点需要使用那个配置节解析类去处理。因此，必须在配置文件的configSections节添加注册。</p>
<p>2）、Logger设置</p>
<p>本实例中只设置了一个Logger，即名字为Root的Logger，其日志级别为All（即记录所有的日志），并关联了三个Appender。</p>
<p>3）、RollingFileAppender设置</p>
<p>本例中使用RollingFileAppender将日志以非独占的方式写入到了滚动文件中，文件的滚动方式为按日期滚动，文件名格式为&#8220;Log/yyyy-MM-dd.log&#8221;注意DatePattern中的<font face="Verdana">&amp;quot;.log&amp;quot;</font>部分：如果不用&amp;quot;将&#8220;.log&#8221;括起来，最终的文件名可能变成乱码。使用&amp;quot;将yyyy-MM-dd这样的日期格式之外的部分括起来可以避免文件名出错。</p>
<p>在使用FileAppender时，默认是独占日志文件的，即程序运行时会一直打开文件并禁止其他用户读文件。这样对于查阅日志是不便的。可以通过指定FileAppender的<font face="Verdana">LockingModel</font>为<font face="Verdana">MinimalLock</font>即可，比如文件中的第27行。</p>
<p>4）、Filter</p>
<p>在该配置文件的第45行，为AlertAppender设置了一个Filter，指定只有日志级别大于或等于Error时，该Appender才会生效。</p>
<p>5）、给对象设置属性</p>
<p>在上面的例子中，有很多param节点等。log4net在读取配置节时，会使用反射的方式去查看对象是否有一个同名的属性，如果有就会将指定的值绑定到对象的属性中去。如果是简单属性，使用param节就可以了；否则就要像第16行一样，添加一个同名的节点并且设置type属性。</p>
<p>&nbsp;</p>
  <img src ="http://www.cnblogs.com/ILove/aggbug/1330272.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43812/" target="_blank">[新闻]Google操作系统已开始内部测试？</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>虚函数的调用机制</title><link>http://www.cnblogs.com/ILove/archive/2008/04/24/1168454.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Wed, 23 Apr 2008 17:11:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/24/1168454.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1168454.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/24/1168454.html#Feedback</comments><slash:comments>21</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1168454.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1168454.html</trackback:ping><description><![CDATA[摘要: 前几天在看《.Net框架程序设计》的时候，好像记得书中有提到说每个对象在创建后都会有一个字段保存了一个内存地址，这个内存地址指向对象实际类型的方法表，其中维护了类型每个方法的签名以及他们的入口地址的对应关系。每次调用方法的时候会到这个表中去查找方法入口地址。而根据我之前对于程序的了解，只有虚函数才会需要保存在这个&#8220;函数指针表&#8221;中，而非虚方法因为在编译时就已经知道了函数入口地&nbsp;&nbsp;<a href='http://www.cnblogs.com/ILove/archive/2008/04/24/1168454.html'>阅读全文</a><img src ="http://www.cnblogs.com/ILove/aggbug/1168454.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43811/" target="_blank">[新闻]Google阅读器界面升级 全新改版</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>.Net，你为什么会慢</title><link>http://www.cnblogs.com/ILove/archive/2008/04/17/1157229.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Wed, 16 Apr 2008 17:35:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/17/1157229.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1157229.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/17/1157229.html#Feedback</comments><slash:comments>75</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1157229.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1157229.html</trackback:ping><description><![CDATA[<p>&nbsp;自打使用<span lang="EN-US">.Net</span>以来，他给我的印象就一直是：慢。不过这几天看了一下<span lang="EN-US">.Net</span>程序运行时的原理，才明白了我们平时的<span lang="EN-US">.Net</span>程序是为什么慢的，也明白了在某些情况下其实<span lang="EN-US">.Net</span>程序运行起来也不比非托管程序慢。<span lang="EN-US"><br />
<br />
</span>要看托管程序慢的原因，就得说说应用程序加载的过程。<span lang="EN-US"><br />
<br />
</span>应用程序文件的格式是有规律的。不管是托管程序还是非托管程序，可执行文件的内部都包含一个<span lang="EN-US">PE</span>文件（包含在<span lang="EN-US">exe</span>文件或者<span lang="EN-US">dll</span>文件的内部），系统也正是根据<span lang="EN-US">PE</span>文件里面的信息来启动这些可执行程序的。系统根据<span lang="EN-US">PE</span>文件中的信息，找到入口函数，接着将控制调转到这个函数中，从而启动这个程序。不过托管程序的文件中还有一个<span lang="EN-US">CLR</span>表头文件以及其他<span lang="EN-US">CLR</span>需要的信息。（有关<span lang="EN-US">PE</span>文件的信息，请<a href="http://www.cnblogs.com/ILove/archive/2008/04/15/1157228.html">点击这里</a>。个人认为要真正理解托管程序为啥慢，下功夫了解PE文件及其作用还是很重要的）<span lang="EN-US"><br />
<br />
</span>首先看看非托管程序。非托管程序的可执行文件都是二进制文件，是直接被编译成<span lang="EN-US">CPU</span>指令的。在非托管程序的可执行文件中，编译器在编译的时候已经把对方法的调用直接编译成了<span lang="EN-US">CPU</span>指令：因为在编译的时候就知道方法在代码段里的相对地址，也就是偏移量。当系统加载了可执行文件后，我们通过将可执行文件的基地址加上这个偏移量就可以计算出方法在内存中的实际地址。这样只要通过这种方法修改<span lang="EN-US">JMP</span>指令，就可以直接运行整个程序。<span lang="EN-US"><br />
<br />
</span>但托管程序不同。因为托管程序编译的结果是<span lang="EN-US">IL</span>中间代码，而这个<span lang="EN-US">IL</span>代码是由<span lang="EN-US">CLR</span>实时编译的，所以在启动这个程序之前，必须先加载<span lang="EN-US">CLR</span>，并由<span lang="EN-US">CLR</span>负责处理<span lang="EN-US">IL</span>代码中的方法调用。<span lang="EN-US"><br />
<br />
</span>那么，操作系统是如何知道一个应用程序需要加载<span lang="EN-US">CLR</span>的呢？也许有人会说因为托管程序的文件中还有一个<span lang="EN-US">CLR</span>头部，看到这个就知道是托管程序。这个说法当然不对。最新的操作系统也许能够认出<span lang="EN-US">CLR</span>头部，但<span lang="EN-US">2000</span>之前的系统，他们如何会认得出<span lang="EN-US">CLR</span>头部？要知道当这些系统出来的时候，还根本没有<span lang="EN-US">CLR</span>这个玩意儿呢。<span lang="EN-US"><br />
<br />
</span>实际上，系统启动一个托管程序，最开始的步骤都是一样的：检查<span lang="EN-US">PE</span>文件，然后执行<span lang="EN-US">PE</span>文件中<span lang="EN-US">.text</span>段（也就是代码段）中的代码。但托管程序在编译时，<span lang="EN-US">.text</span>段里面增加了一条<span lang="EN-US">JMP _CorExeMain</span>或者<span lang="EN-US">JMP _CorDllMain</span>的指令（根据是<span lang="EN-US">exe</span>文件还是<span lang="EN-US">dll</span>文件不同）。也正是从这里开始，托管程序的加载与非托管程序的加载产生了区别。这时候如果是非托管程序，就已经进入到入口函数中去了；但托管程序此时却跳转到了另一个函数中。那么这个函数是哪里的呢？这个函数在一个叫做<span lang="EN-US">MSCorEE.dll</span>的动态链接库文件中，当安装了<span lang="EN-US">.net</span>框架时就会被复制在系统目录下。系统会根据托管程序<span lang="EN-US">PE</span>文件中的信息找到这个<span lang="EN-US">DLL</span>，然后通过<span lang="EN-US">MSCorEE.dll</span>的<span lang="EN-US">PE</span>文件信息找到这个<span lang="EN-US">_CorExeMain</span>函数的入口地址，然后修改刚才的<span lang="EN-US">JMP</span>指令要跳转的地址，从而将控制跳转到了<span lang="EN-US">_CorExeMain</span>这个函数里面去。然后，在这个函数里面，<span lang="EN-US">CLR</span>被启动了，并做了若干的初始化工作，然后再通过托管程序的<span lang="EN-US">CLR</span>表头找到托管程序的入口地址，并将控制跳转到这里，于是托管程序开始运行。</p>
<p style="margin-bottom: 12pt; line-height: 150%">不过，上述过程在最新的操作系统上不同，因为这些新的操作系统认得托管程序的标志（也就是说知道根据<span lang="EN-US">PE</span>文件中的标志去判断是否是托管程序），因此在加载时就会直接调用<span lang="EN-US">_CorExeMain</span>，<span lang="EN-US">JMP</span>指令直接被跳过的。<br />
<br />
刚才说了托管程序在启动时的一些特殊处理：系统在进入入口函数前会首先调用<span lang="EN-US">MSCorEE.dll</span>中的代码来启动<span lang="EN-US">CLR</span>并做一些初始工作，然后再进入入口函数。但还有个问题没提到：那就是刚才我们有说到托管程序的编译结果是<span lang="EN-US">IL</span>代码，这个<span lang="EN-US">IL</span>代码是在运行时被<span lang="EN-US">CLR</span>实时编译的。那么，这个实时编译的过程又是怎样的呢？<br />
<br />
实际上，<span lang="EN-US">IL</span>中的方法并不是每次被调用时都会被重新编译一次，而是就像我们平时采用的&#8220;<span lang="EN-US">LazyLoad</span>&#8221;一样，他只有在第一次被调用的时候才会被编译。即时编译器保存有一个映射表。当调用一个方法时，即时编译器如果发现在这个映射表中没有标记这个方法，就会将这个方法的<span lang="EN-US">IL</span>代码编译成<span lang="EN-US">CPU</span>指令，然后分配在一个内存空间上，然后在这个映射表中记录下这个方法名和方法入口对应的内存地址，然后通过<span lang="EN-US">JMP</span>指令跳转到函数中去。当下次再产生对这个方法的调用时，即时编译器因为已经知道了这个方法对应的内存地址，因此就会直接通过<span lang="EN-US">JMP</span>指令跳转，而不会再次编译这段代码。<br />
<br />
因此可以看出，只要程序所有的代码都被执行过一次了，那么整个程序就都会被编译成<span lang="EN-US">CPU</span>指令保存在内存中。在此之后托管程序跟非托管程序的执行效率就基本上没什么区别了——当然，托管程序需要从那个映射表中取函数地址，而非托管程序中方法的地址是已知的。<br />
<br />
因此，理论上在某些情况下（比如<span lang="EN-US">win service</span>、<span lang="EN-US">iis</span>等长期循环执行的程序），托管程序的性能并不会比非托管程序的性能差多少。而且，非托管程序因为要考虑兼容性必须兼容标准指令，而非托管程序因为是运行时编译的，非常清楚操作系统环境，因此可以做针对性的优化。<br />
<br />
不过，因为即时编译的结果是保存在内存中的，因此对于那些会频繁启动的程序来讲，其启动过程是会比较慢的——因为每次启动都需要加载<span lang="EN-US">CLR</span>并做一次即时编译。<br />
<br />
至此，在了解到了托管程序与非托管程序在加载、执行时的区别，我们就可以更加清楚怎样才能充分利用非托管程序的优点、避免其缺点，从而发挥他的最大价值、避免使用时走入误区。</p>
 <img src ="http://www.cnblogs.com/ILove/aggbug/1157229.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43810/" target="_blank">[新闻]微软官方下载：Windows Vista SP2 Beta测试版</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>PE文件结构 zz</title><link>http://www.cnblogs.com/ILove/archive/2008/04/15/1157228.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Mon, 14 Apr 2008 17:32:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/15/1157228.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1157228.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/15/1157228.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1157228.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1157228.html</trackback:ping><description><![CDATA[<div class="postcontent">Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中（Specs and Strategy, Specifications, Windows NT File Format Specifications），但是它非常之晦涩。 <br />
　　 然而这一的文档并未提供足够的信息，所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题，它会对整个的PE文件格式作一个十分彻底的解释，另外，本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。 <br />
　　 为了获得PE文件中所包含的重要信息，我编写了一个名为PEFILE.DLL的动态链接库，本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中（译注：示例程序请在MSDN中寻找，本站恕不提供），你可以在你自己的应用程序中使用这个DLL；同样，你亦可以依你所愿地使用并构建它的源码。在本文末尾，你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。 <br />
<br />
<strong>介绍</strong> <br />
<br />
　　 Windows操作系统家族最近增加的Windows NT为开发环境和应用程序本身带来了很大的改变，这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范，同时为了保证与旧版本MS-DOS及Windows操作系统的兼容，PE文件格式也保留了MS-DOS中那熟悉的MZ头部。 <br />
　　 在本文之中，PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中，本文会详细讨论PE文件的每一个组成部分。 <br />
　　 许多单独的文件成分定义都来自于Microsoft Win32 SDK开发包中的WINNT.H文件，在这个文件中你会发现用来描述文件头部和数据目录等各种成分的结构类型定义。但是，在WINNT.H中缺少对PE文件结构足够的定义，在这种情况下，我定义了自己的结构来存取文件数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义，整套的PEFILE.H开发文件包含在PEFile示例程序之中。 <br />
　　 本文配套的示例程序除了PEFILE.DLL示例代码之外，还有一个单独的Win32示例应用程序，名为EXEVIEW.EXE。创建这一示例目的有二：首先，我需要测试PEFILE.DLL的函数，并且某些情况要求我同时查看多个文件；其次，很多解决PE文件格式的工作和直接观看数据有关。例如，要弄懂导入地址名称表是如何构成的，我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体，而EXEVIEW.EXE就是查看这些信息的最佳示例。 <br />
　　 闲话少叙，让我们开始吧。 <br />
<br />
<strong>PE文件结构 </strong><br />
<br />
　　 PE文件格式被组织为一个线性的数据流，它由一个MS-DOS头部开始，接着是一个是模式的程序残余以及一个PE文件标志，这之后紧接着PE文件头和可选头部。这些之后是所有的段头部，段头部之后跟随着所有的段实体。文件的结束处是一些其它的区域，其中是一些混杂的信息，包括重分配信息、符号表信息、行号信息以及字串表数据。我将所有这些成分列于图1。<br />
<img height="413" alt="" src="http://www.vckbase.com/document/journal/vckbase38/images/pe1.gif" width="104" /><br />
<strong>图1.PE文件映像结构</strong> <br />
　　 从MS-DOS文件头结构开始，我将按照PE文件格式各成分的出现顺序依次对其进行讨论，并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文件。这些示例都利用了Windows NT最酷的特色之一——内存映射文件，这一特色允许用户使用一个简单的指针来存取文件中所包含的数据，因此所有的示例都使用了内存映射文件来存取PE文件中的数据。 <br />
　　 注意：请查阅本文末尾关于如何使用PEFILE.DLL的那一段。 <br />
<br />
<strong>MS-DOS头部/实模式头部</strong> <br />
<br />
　　 如上所述，PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中，它并非一个新概念，因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是，当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候，操作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说，当你在MS-DOS 6.0下运行一个Windows NT可执行文件时，你会得到这样一条消息：&#8220;This program cannot be run in DOS mode.&#8221;如果MS-DOS头部不是作为PE文件格式的第一部分的话，操作系统装载文件的时候就会失败，并提供一些完全没用的信息，例如：&#8220;The name specified is not recognized as an internal or external command, operable program or batch file.&#8221; <br />
　　 MS-DOS头部占据了PE文件的头64个字节，描述它内容的结构如下：
<pre>//WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
USHORT e_magic; // 魔术数字
USHORT e_cblp; // 文件最后页的字节数
USHORT e_cp; // 文件页数
USHORT e_crlc; // 重定义元素个数
USHORT e_cparhdr; // 头部尺寸，以段落为单位
USHORT e_minalloc; // 所需的最小附加段
USHORT e_maxalloc; // 所需的最大附加段
USHORT e_ss; // 初始的SS值（相对偏移量）
USHORT e_sp; // 初始的SP值
USHORT e_csum; // 校验和
USHORT e_ip; // 初始的IP值
USHORT e_cs; // 初始的CS值（相对偏移量）
USHORT e_lfarlc; // 重分配表文件地址
USHORT e_ovno; // 覆盖号
USHORT e_res[4]; // 保留字
USHORT e_oemid; // OEM标识符（相对e_oeminfo）
USHORT e_oeminfo; // OEM信息
USHORT e_res2[10]; // 保留字
LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</pre>
第一个域e_magic，被称为魔术数字，它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D，表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部，就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用，但是对于Windows NT来说，这个结构中只有一个有用的域——最后一个域e_lfnew，一个4字节的文件偏移量，PE文件头部就是由它定位的。对于Windows NT的PE文件来说，PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。 <br />
<br />
<strong>实模式残余程序</strong> <br />
<br />
　　 实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。对于一个MS-DOS的可执行映像文件，应用程序就是从这里执行的。对于Windows、OS/2、Windows NT这些操作系统来说，MS-DOS残余程序就代替了主程序的位置被放在这里。这种残余程序通常什么也不做，而只是输出一行文本，例如：&#8220;This program requires Microsoft Windows v3.1 or greater.&#8221;当然，用户可以在此放入任何的残余程序，这就意味着你可能经常看到像这样的东西：&#8220;You can''t run a Windows NT application on OS/2, it''s simply not possible.&#8221; <br />
　　 当为Windows 3.1构建一个应用程序的时候，链接器将向你的可执行文件中链接一个名为WINSTUB.EXE的默认残余程序。你可以用一个基于MS-DOS的有效程序取代WINSTUB，并且用STUB模块定义语句指示链接器，这样就能够取代链接器的默认行为。为Windows NT开发的应用程序可以通过使用-STUB:链接器选项来实现。 <br />
<br />
<strong>PE文件头部与标志</strong> <br />
<br />
　　 PE文件头部是由MS-DOS头部的e_lfanew域定位的，这个域只是给出了文件的偏移量，所以要确定PE头部的实际内存映射地址，就需要添加文件的内存映射基地址。例如，以下的宏是包含在PEFILE.H源文件之中的： <br />
<pre>//PEFILE.H
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))</pre>
在处理PE文件信息的时候，我发现文件之中有些位置需要经常查阅。既然这些位置仅仅是对文件的偏移量，那么用宏来实现这些定位就比较容易，因为它们较之函数有更好的表现。 <br />
　　 请注意这个宏所获得的是PE文件标志，而并非PE文件头部的偏移量。那是由于自Windows与OS/2的可执行文件开始，.EXE文件都被赋予了目标操作系统的标志。对于Windows NT的PE文件格式而言，这一标志在PE文件头部结构之前。在Windows和OS/2的某些版本中，这一标志是文件头的第一个字。同样，对于PE文件格式，Windows NT使用了一个DWORD值。 <br />
　　 以上的宏返回了文件标志的偏移量，而不管它是哪种类型的可执行文件。所以，文件头部是在DWORD标志之后，还是在WORD标志处，是由这个标志是否Windows NT文件标志所决定的。要解决这个问题，我编写了ImageFileType函数（如下），它返回了映像文件的类型： <br />
<pre>//PEFILE.C
DWORD WINAPI ImageFileType (LPVOID lpFile)
{
/* 首先出现的是DOS文件标志 */
if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
{
/* 由DOS头部决定PE文件头部的位置 */
if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
IMAGE_OS2_SIGNATURE ||
LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
IMAGE_OS2_SIGNATURE_LE)
return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
else if (*(DWORD *)NTSIGNATURE (lpFile) ==
IMAGE_NT_SIGNATURE)
return IMAGE_NT_SIGNATURE;
else
return IMAGE_DOS_SIGNATURE;
}
else
/* 不明文件种类 */
return 0;
}
</pre>
以上列出的代码立即告诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型并且返回一个适当的文件种类来说，这个宏就会使这两件事变得非常简单。WINNT.H之中定义的四种不同文件类型有：
<pre>//WINNT.H
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
　　</pre>
首先，Windows的可执行文件类型没有出现在这一列表中，这一点看起来很奇怪。但是，在稍微研究一下之后，就能得到原因了：除了操作系统版本规范的不同之外，Windows的可执行文件和OS/2的可执行文件实在没有什么区别。这两个操作系统拥有相同的可执行文件结构。 <br />
　　 现在把我们的注意力转向Windows NT PE文件格式，我们会发现只要我们得到了文件标志的位置，PE文件之后就会有4个字节相跟随。下一个宏标识了PE文件的头部：
<pre>//PEFILE.C
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
SIZE_OF_NT_SIGNATURE))
　　</pre>
这个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不幸的是，这个常量并未定义在WINNT.H之中，于是我将它定义在了PEFILE.H中，它是一个DWORD的大小。 <br />
　　 既然我们知道了PE文件头的位置，那么就可以检查头部的数据了。我们只需要把这个位置赋值给一个结构，如下：
<pre>PIMAGE_FILE_HEADER pfh;
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile);</pre>
在这个例子中，lpFile表示一个指向可执行文件内存映像基地址的指针，这就显出了内存映射文件的好处：不需要执行文件的I/O，只需使用指针pfh就能存取文件中的信息。PE文件头结构被定义为：
<pre>//WINNT.H
typedef struct _IMAGE_FILE_HEADER {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER 20
</pre>
请注意这个文件头部的大小已经定义在这个包含文件之中了，这样一来，想要得到这个结构的大小就很方便了。但是我觉得对结构本身使用sizeof运算符（译注：原文为&#8220;function&#8221;）更简单一些，因为这样的话我就不必记住这个常量的名字IMAGE_SIZEOF_FILE_HEADER，而只需要记住结构IMAGE_FILE_HEADER的名字就可以了。另一方面，记住所有结构的名字已经够有挑战性的了，尤其在是这些结构只有WINNT.H中才有的情况下。 <br />
　　 PE文件中的信息基本上是一些高级信息，这些信息是被操作系统或者应用程序用来决定如何处理这个文件的。第一个域是用来表示这个可执行文件被构建的目标机器种类，例如DEC(R) Alpha、MIPS R4000、Intel(R) x86或一些其它处理器。系统使用这一信息来在读取这个文件的其它数据之前决定如何处理它。 <br />
　　 Characteristics域表示了文件的一些特征。比如对于一个可执行文件而言，分离调试文件是如何操作的。调试器通常使用的方法是将调试信息从PE文件中分离，并保存到一个调试文件（.DBG）中。要这么做的话，调试器需要了解是否要在一个单独的文件中寻找调试信息，以及这个文件是否已经将调试信息分离了。我们可以通过深入可执行文件并寻找调试信息的方法来完成这一工作。要使调试器不在文件中查找的话，就需要用到IMAGE_FILE_DEBUG_STRIPPED这个特征，它表示文件的调试信息是否已经被分离了。这样一来，调试器可以通过快速查看PE文件的头部的方法来决定文件中是否存在着调试信息。 <br />
　　 WINNT.H定义了若干其它表示文件头信息的标记，就和以上的例子差不多。我把研究这些标记的事情留给读者作为练习，由你们来看看它们是不是很有趣，这些标记位于WINNT.H中的IMAGE_FILE_HEADER结构之后。 <br />
　　 PE文件头结构中另一个有用的入口是NumberOfSections域，它表示如果你要方便地提取文件信息的话，就需要了解多少个段——更明确一点来说，有多少个段头部和多少个段实体。每一个段头部和段实体都在文件中连续地排列着，所以要决定段头部和段实体在哪里结束的话，段的数目是必需的。以下的函数从PE文件头中提取了段的数目：
<pre>PEFILE.C
int WINAPI NumOfSections(LPVOID lpFile)
{
/* 文件头部中所表示出的段数目 */
return (int)((PIMAGE_FILE_HEADER)
PEFHDROFFSET (lpFile))-&gt;NumberOfSections);
}
</pre>
如你所见，PEFHDROFFSET以及其它宏用起来非常方便。<br />
<br />
<strong>PE可选头部</strong> <br />
<br />
　　 PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是&#8220;可选头部&#8221;，但是请确信：这个头部并非&#8220;可选&#8221;，而是&#8220;必需&#8221;的。OPTHDROFFSET宏可以获得指向可选头部的指针：
<pre>//PEFILE.H
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
SIZE_OF_NT_SIGNATURE + \
sizeof(IMAGE_FILE_HEADER)))
　　</pre>
可选头部包含了很多关于可执行映像的重要信息，例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。IMAGE_OPTIONAL_HEADER结构如下：
<pre>//WINNT.H
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// 标准域
//
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONG BaseOfData;
//
// NT附加域
//
ULONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Reserved1;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONG SizeOfStackReserve;
ULONG SizeOfStackCommit;
ULONG SizeOfHeapReserve;
ULONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
</pre>
如你所见，这个结构中所列出的域实在是冗长得过分。为了不让你对所有这些域感到厌烦，我会仅仅讨论有用的——就是说，对于探究PE文件格式而言有用的。 <br />
<br />
<strong>标准域</strong> <br />
<br />
　　 首先，请注意这个结构被划分为&#8220;标准域&#8221;和&#8220;NT附加域&#8221;。所谓标准域，就是和UNIX可执行文件的COFF格式所公共的部分。虽然标准域保留了COFF中定义的名字，但是Windows NT仍然将它们用作了不同的目的——尽管换个名字更好一些。 <br />
　　 &#183;Magic。我不知道这个域是干什么的，对于示例程序EXEVIEW.EXE示例程序而言，这个值是0x010B或267（译注：0x010B为.EXE，0x0107为ROM映像，这个信息我是从eXeScope上得来的）。 <br />
　　 &#183;MajorLinkerVersion、MinorLinkerVersion。表示链接此映像的链接器版本。随Window NT build 438配套的Windows NT SDK包含的链接器版本是2.39（十六进制为2.27）。 <br />
　　 &#183;SizeOfCode。可执行代码尺寸。 <br />
　　 &#183;SizeOfInitializedData。已初始化的数据尺寸。 <br />
　　 &#183;SizeOfUninitializedData。未初始化的数据尺寸。 <br />
　　 &#183;AddressOfEntryPoint。在标准域中，AddressOfEntryPoint域是对PE文件格式来说最为有趣的了。这个域表示应用程序入口点的位置。并且，对于系统黑客来说，这个位置就是导入地址表（IAT）的末尾。以下的函数示范了如何从可选头部获得Windows NT可执行映像的入口点。
<pre>//PEFILE.C
LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
{
PIMAGE_OPTIONAL_HEADER poh;
poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
if (poh != NULL)
return (LPVOID)poh-&gt;AddressOfEntryPoint;
else
return NULL;
}
</pre>
&#183;BaseOfCode。已载入映像的代码（&#8220;.text&#8221;段）的相对偏移量。 <br />
　　 &#183;BaseOfData。已载入映像的未初始化数据（&#8220;.bss&#8221;段）的相对偏移量。 <br />
<br />
<strong>Windows NT附加域</strong> <br />
<br />
　　 添加到Windows NT PE文件格式中的附加域为Windows NT特定的进程行为提供了装载器的支持，以下为这些域的概述。 <br />
　　 &#183;ImageBase。进程映像地址空间中的首选基地址。Windows NT的Microsoft Win32 SDK链接器将这个值默认设为0x00400000，但是你可以使用-BASE:linker开关改变这个值。 <br />
　　 &#183;SectionAlignment。从ImageBase开始，每个段都被相继的装入进程的地址空间中。SectionAlignment则规定了装载时段能够占据的最小空间数量——就是说，段是关于SectionAlignment对齐的。 <br />
　　 Windows NT虚拟内存管理器规定，段对齐不能少于页尺寸（当前的x86平台是4096字节），并且必须是成倍的页尺寸。4096字节是x86链接器的默认值，但是它可以通过-ALIGN: linker开关来设置。 <br />
　　 &#183;FileAlignment。映像文件首先装载的最小的信息块间隔。例如，链接器将一个段实体（段的原始数据）加零扩展为文件中最接近的FileAlignment边界。早先提及的2.39版链接器将映像文件以0x200字节的边界对齐，这个值可以被强制改为512到65535这么多。 <br />
　　 &#183;MajorOperatingSystemVersion。表示Windows NT操作系统的主版本号；通常对Windows NT 1.0而言，这个值被设为1。 <br />
　　 &#183;MinorOperatingSystemVersion。表示Windows NT操作系统的次版本号；通常对Windows NT 1.0而言，这个值被设为0。 <br />
　　 &#183;MajorImageVersion。用来表示应用程序的主版本号；对于Microsoft Excel 4.0而言，这个值是4。 <br />
　　 &#183;MinorImageVersion。用来表示应用程序的次版本号；对于Microsoft Excel 4.0而言，这个值是0。 <br />
　　 &#183;MajorSubsystemVersion。表示Windows NT Win32子系统的主版本号；通常对于Windows NT 3.10而言，这个值被设为3。 <br />
　　 &#183;MinorSubsystemVersion。表示Windows NT Win32子系统的次版本号；通常对于Windows NT 3.10而言，这个值被设为10。 <br />
　　 &#183;Reserved1。未知目的，通常不被系统使用，并被链接器设为0。 <br />
　　 &#183;SizeOfImage。表示载入的可执行映像的地址空间中要保留的地址空间大小，这个数字很大程度上受SectionAlignment的影响。例如，考虑一个拥有固定页尺寸4096字节的系统，如果你有一个11个段的可执行文件，它的每个段都少于4096字节，并且关于65536字节边界对齐，那么SizeOfImage域将会被设为11 * 65536 = 720896（176页）。而如果一个相同的文件关于4096字节对齐的话，那么SizeOfImage域的结果将是11 * 4096 = 45056（11页）。这只是个简单的例子，它说明每个段需要少于一个页面的内存。在现实中，链接器通过个别地计算每个段的方法来决定SizeOfImage确切的值。它首先决定每个段需要多少字节，并且最后将页面总数向上取整至最接近的SectionAlignment边界，然后总数就是每个段个别需求之和了。 <br />
　　 &#183;SizeOfHeaders。这个域表示文件中有多少空间用来保存所有的文件头部，包括MS-DOS头部、PE文件头部、PE可选头部以及PE段头部。文件中所有的段实体就开始于这个位置。 <br />
　　 &#183;CheckSum。校验和是用来在装载时验证可执行文件的，它是由链接器设置并检验的。由于创建这些校验和的算法是私有信息，所以在此不进行讨论。 <br />
　　 &#183;Subsystem。用于标识该可执行文件目标子系统的域。每个可能的子系统取值列于WINNT.H的IMAGE_OPTIONAL_HEADER结构之后。 <br />
　　 &#183;DllCharacteristics。用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记。 <br />
　　 &#183;SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit。这些域控制要保留的地址空间数量，并且负责栈和默认堆的申请。在默认情况下，栈和堆都拥有1个页面的申请值以及16个页面的保留值。这些值可以使用链接器开关-STACKSIZE:与-HEAPSIZE:来设置。 <br />
　　 &#183;LoaderFlags。告知装载器是否在装载时中止和调试，或者默认地正常运行。 <br />
　　 &#183;NumberOfRvaAndSizes。这个域标识了接下来的DataDirectory数组。请注意它被用来标识这个数组，而不是数组中的各个入口数字，这一点非常重要。 <br />
　　 &#183;DataDirectory。数据目录表示文件中其它可执行信息重要组成部分的位置。它事实上就是一个IMAGE_DATA_DIRECTORY结构的数组，位于可选头部结构的末尾。当前的PE文件格式定义了16种可能的数据目录，这之中的11种现在在使用中。 <br />
<br />
<strong>数据目录</strong> <br />
<br />
WINNT.H之中所定义的数据目录为：
<pre>//WINNT.H
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器值（MIPS GP）
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
　　</pre>
基本上，每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本身是相同的，但是每个特定的目录种类却是完全唯一的。每个数据目录的定义在本文的以后部分被描述为&#8220;预定义段&#8221;。
<pre>//WINNT.H
typedef struct _IMAGE_DATA_DIRECTORY {
ULONG VirtualAddress;
ULONG Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
</pre>
每个数据目录入口指定了该目录的尺寸和相对虚拟地址。如果你要定义一个特定的目录的话，就需要从可选头部中的数据目录数组中决定相对的地址，然后使用虚拟地址来决定该目录位于哪个段中。一旦你决定了哪个段包含了该目录，该段的段头部就会被用于查找数据目录的精确文件偏移量位置。 <br />
　　 所以要获得一个数据目录的话，那么首先你需要了解段的概念。我在下面会对其进行描述，这个讨论之后还有一个有关如何定位数据目录的示例。 <br />
<br />
<strong>PE文件段</strong> <br />
<br />
　　 PE文件规范由目前为止定义的那些头部以及一个名为&#8220;段&#8221;的一般对象组成。段包含了文件的内容，包括代码、数据、资源以及其它可执行信息，每个段都有一个头部和一个实体（原始数据）。我将在下面描述段头部的有关信息，但是段实体则缺少一个严格的文件结构。因此，它们几乎可以被链接器按任何的方法组织，只要它的头部填充了足够能够解释数据的信息。 <br />
<br />
<strong>段头部</strong> <br />
<br />
　　 PE文件格式中，所有的段头部位于可选头部之后。每个段头部为40个字节长，并且没有任何的填充信息。段头部被定义为以下的结构：
<pre>//WINNT.H
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
　　</pre>
你如何才能获得一个特定段的段头部信息？既然段头部是被连续的组织起来的，而且没有一个特定的顺序，那么段头部必须由名称来定位。以下的函数示范了如何从一个给定了段名称的PE映像文件中获得一个段头部：
<pre>//PEFILE.C
BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
PIMAGE_SECTION_HEADER psh;
int nSections = NumOfSections (lpFile);
int i;
if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
!= NULL)
{
/* 由名称查找段 */
for (i = 0; i &lt; nSections; i++)
{
if (!strcmp(psh-&gt;Name, szSection))
{
/* 向头部复制数据 */
CopyMemory((LPVOID)sh, (LPVOID)psh,
sizeof(IMAGE_SECTION_HEADER));
return TRUE;
}
else
psh++;
}
}
return FALSE;
}</pre>
这个函数通过SECHDROFFSET宏将第一个段头部定位，然后它开始在所有段中循环，并将要寻找的段名称和每个段的名称相比较，直到找到了正确的那一个为止。当找到了段的时候，函数将内存映像文件的数据复制到传入函数的结构中，然后IMAGE_SECTION_HEADER结构的各域就能够被直接存取了。 <br />
<br />
<strong>段头部的域</strong> <br />
<br />
　　 &#183;Name。每个段都有一个8字符长的名称域，并且第一个字符必须是一个句点。 <br />
　　 &#183;PhysicalAddress或VirtualSize。第二个域是一个union域，现在已不使用了。 <br />
　　 &#183;VirtualAddress。这个域标识了进程地址空间中要装载这个段的虚拟地址。实际的地址由将这个域的值加上可选头部结构中的ImageBase虚拟地址得到。切记，如果这个映像文件是一个DLL，那么这个DLL就不一定会装载到ImageBase要求的位置。所以一旦这个文件被装载进入了一个进程，实际的ImageBase值应该通过使用GetModuleHandle来检验。 <br />
　　 &#183;SizeOfRawData。这个域表示了相对FileAlignment的段实体尺寸。文件中实际的段实体尺寸将少于或等于FileAlignment的整倍数。一旦映像被装载进入了一个进程的地址空间，段实体的尺寸将会变得少于或等于FileAlignment的整倍数。 <br />
　　 &#183;PointerToRawData。这是一个文件中段实体位置的偏移量。 <br />
　　 &#183;PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。这些域在PE格式中不使用。 <br />
　　 &#183;Characteristics。定义了段的特征。这些值可以在WINNT.H及本光盘（译注：MSDN的光盘）的PE格式规范中找到。 <br />
<br />
值 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;定义 <br />
0x00000020 代码段 <br />
0x00000040 已初始化数据段 <br />
0x00000080 未初始化数据段 <br />
0x04000000 该段数据不能被缓存 <br />
0x08000000 该段不能被分页 <br />
0x10000000 共享段 <br />
0x20000000 可执行段 <br />
0x40000000 可读段 <br />
0x80000000 可写段 <br />
<br />
<strong>定位数据目录</strong> <br />
<br />
　　 数据目录存在于它们相应的数据段中。典型地来说，数据目录是段实体中的第一个结构，但不是必需的。由于这个缘故，如果你需要定位一个指定的数据目录的话，就需要从段头部和可选头部中获得信息。 <br />
　　 为了让这个过程简单一点，我编写了以下的函数来定位任何一个在WINNT.H之中定义的数据目录。
<pre>// PEFILE.C
LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile,
DWORD dwIMAGE_DIRECTORY)
{
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
int nSections = NumOfSections(lpFile);
int i = 0;
LPVOID VAImageDir;
/* 必须为0到(NumberOfRvaAndSizes-1)之间 */
if (dwIMAGE_DIRECTORY &gt;= poh-&gt;NumberOfRvaAndSizes)
return NULL;
/* 获得可选头部和段头部的偏移量 */
poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile);
/* 定位映像目录的相对虚拟地址 */
VAImageDir = (LPVOID)poh-&gt;DataDirectory
[dwIMAGE_DIRECTORY].VirtualAddress;
/* 定位包含映像目录的段 */
while (i++ &lt; nSections)
{
if (psh-&gt;VirtualAddress &lt;= (DWORD)VAImageDir &amp;&amp;
psh-&gt;VirtualAddress +
psh-&gt;SizeOfRawData &gt; (DWORD)VAImageDir)
break;
psh++;
}
if (i &gt; nSections)
return NULL;
/* 返回映像导入目录的偏移量 */
return (LPVOID)(((int)lpFile +
(int)VAImageDir. psh-&gt;VirtualAddress) +
(int)psh-&gt;PointerToRawData);
}
　　</pre>
该函数首先确认被请求的数据目录入口数字，然后它分别获取指向可选头部和第一个段头部的两个指针。它从可选头部决定数据目录的虚拟地址，然后它使用这个值来决定数据目录定位在哪个段实体之中。如果适当的段实体已经被标识了，那么数据目录特定的位置就可以通过将它的相对虚拟地址转换为文件中地址的方法来找到。<br />
<br />
<strong>预定义段</strong> <br />
<br />
　　 一个Windows NT的应用程序典型地拥有9个预定义段，它们是.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。一些应用程序不需要所有的这些段，同样还有一些应用程序为了自己特殊的需要而定义了更多的段。这种做法与MS-DOS和Windows 3.1中的代码段和数据段相似。事实上，应用程序定义一个独特的段的方法是使用标准编译器来指示对代码段和数据段的命名，或者使用名称段编译器选项-NT——就和Windows 3.1中应用程序定义独特的代码段和数据段一样。 <br />
　　 以下是一个关于Windows NT PE文件之中一些有趣的公共段的讨论。 <br />
<br />
<strong>可执行代码段，.text</strong> <br />
<br />
　　 Windows 3.1和Windows NT之间的一个区别就是Windows NT默认的做法是将所有的代码段（正如它们在Windows 3.1中所提到的那样）组成了一个单独的段，名为&#8220;.text&#8221;。既然Windows NT使用了基于页面的虚拟内存管理系统，那么将分开的代码放入不同的段之中的做法就不太明智了。因此，拥有一个大的代码段对于操作系统和应用程序开发者来说，都是十分方便的。 <br />
　　 .text段也包含了早先提到过的入口点。IAT亦存在于.text段之中的模块入口点之前。（IAT在.text段之中的存在非常有意义，因为这个表事实上是一系列的跳转指令，并且它们的跳转目标位置是已固定的地址。）当Windows NT的可执行映像装载入进程的地址空间时，IAT就和每一个导入函数的物理地址一同确定了。要在.text段之中查找IAT，装载器只用将模块的入口点定位，而IAT恰恰出现于入口点之前。既然每个入口拥有相同的尺寸，那么向后退查找这个表的起始位置就很容易了。 <br />
<br />
<strong>数据段，.bss、.rdata、.data</strong> <br />
<br />
　　 .bss段表示应用程序的未初始化数据，包括所有函数或源模块中声明为static的变量。 <br />
　　 .rdata段表示只读的数据，比如字符串文字量、常量和调试目录信息。 <br />
　　 所有其它变量（除了出现在栈上的自动变量）存储在.data段之中。基本上，这些是应用程序或模块的全局变量。 <br />
<br />
<strong>资源段，.rsrc</strong> <br />
<br />
　　 .rsrc段包含了模块的资源信息。它起始于一个资源目录结构，这个结构就像其它大多数结构一样，但是它的数据被更进一步地组织在了一棵资源树之中。以下的IMAGE_RESOURCE_DIRECTORY结构形成了这棵树的根和各个结点。
<pre>//WINNT.H
typedef struct _IMAGE_RESOURCE_DIRECTORY {
ULONG Characteristics;
ULONG TimeDateStamp;
USHORT MajorVersion;
USHORT MinorVersion;
USHORT NumberOfNamedEntries;
USHORT NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
　　</pre>
请看这个目录结构，你将会发现其中竟然没有指向下一个结点的指针。但是，在这个结构中有两个域NumberOfNamedEntries和NumberOfIdEntries代替了指针，它们被用来表示这个目录附有多少入口。附带说一句，我的意思是目录入口就在段数据之中的目录后边。有名称的入口按字母升序出现，再往后是按数值升序排列的ID入口。 <br />
　　 一个目录入口由两个域组成，正如下面IMAGE_RESOURCE_DIRECTORY_ENTRY结构所描述的那样：
<pre>// WINNT.H
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
ULONG Name;
ULONG OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
</pre>
根据树的层级不同，这两个域也就有着不同的用途。Name域被用于标识一个资源种类，或者一种资源名称，或者一个资源的语言ID。OffsetToData与常常被用来在树之中指向兄弟结点——即一个目录结点或一个叶子结点。 <br />
　　 叶子结点是资源树之中最底层的结点，它们定义了当前资源数据的尺寸和位置。IMAGE_RESOURCE_DATA_ENTRY结构被用于描述每个叶子结点：
<pre>// WINNT.H
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
ULONG OffsetToData;
ULONG Size;
ULONG CodePage;
ULONG Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
</pre>
OffsetToData和Size这两个域表示了当前资源数据的位置和尺寸。既然这一信息主要是在应用程序装载以后由函数使用的，那么将OffsetToData作为一个相对虚拟的地址会更有意义一些。——幸甚，恰好是这样没错。非常有趣的是，所有其它的偏移量，比如从目录入口到其它目录的指针，都是相对于根结点位置的偏移量。 <br />
　　 要更清楚地了解这些内容，请参考图2。 <br />
<img height="423" alt="" src="http://www.cnblogs.com/document/journal/vckbase38/images/pe2.gif" width="491" /><br />
<strong>图2.一个简单的资源树结构</strong> <br />
　　 图2描述了一个非常简单的资源树，它包含了仅仅两个资源对象：一个菜单和一个字串表。更深一层地来说，它们各自都有一个子项。然而，你仍然可以看到资源树有多么复杂——即使它像这个一样只有一点点资源。 <br />
　　 在树的根部，第一个目录有一个文件中包含的所有资源种类的入口，而不管资源种类有多少。在图2中，有两个由树根标识的入口，一个是菜单的，另一个是字串表的。如果文件中拥有一个或多个对话框资源，那么根结点会再拥有一个入口，因此，就有了对话框资源的另一个分支。 <br />
　　 WINUSER.H中标识了基本的资源种类，我将它们列到了下面：
<pre>//WINUSER.H
/*
* 预定义的资源种类
*/
#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)
　　</pre>
在树的第一层级，以上列出的MAKEINTRESOURCE值被放置在每个种类入口的Name处，它标识了不同的资源种类。 <br />
　　 每个根目录的入口都指向了树中第二层级的一个兄弟结点，这些结点也是目录，并且每个都拥有它们自己的入口。在这一层级，目录被用来以给定的种类标识每一个资源种类。如果你的应用程序中有多个菜单，那么树中的第二层级会为每个菜单都准备一个入口。 <br />
　　 你可能意识到了，资源可以由名称或整数标识。在这一层级，它们是通过目录结构的Name域来分辨的。如果如果Name域最重要的位被设置了，那么其它的31个位就会被用作一个到IMAGE_RESOURCE_DIR_STRING_U结构的偏移量。
<pre>// WINNT.H
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
USHORT Length;
WCHAR NameString[1];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
　　</pre>
这个结构仅仅是由一个2字节长的Length域和一个UNICODE字符Length组成的。 <br />
　　 另一方面，如果Name域最重要的位被清空，那么它的低31位就被用于表示资源的整数ID。图2示范的就是菜单资源作为一个命名的资源，以及字串表作为一个ID资源。 <br />
　　 如果有两个菜单资源，一个由名称标识，另一个由资源标识，那么它们二者就会在菜单资源目录之后拥有两个入口。有名称的资源入口在第一位，之后是由整数标识的资源。目录域NumberOfNamedEntries和NumberOfIdEntries将各自包含值1，表示当前的1个入口。 <br />
　　 在第二层级的下面，资源树就不再更深一步地扩展分支了。第一层级分支至表示每个资源种类的目录中，第二层级分支至由标识符表示的每个资源的目录中，第三层级是被个别标识的资源与它们各自的语言ID之间一对一的映射。要表示一个资源的语言ID，目录入口结构的Name域就被用来表示资源的主语言ID和子语言ID了。Windows NT的Win32 SDK开发包中列出了默认的值资源，例如对于0x0409这个值来说，0x09表示主语言LANG_ENGLISH，0x04则被定义为子语言的SUBLANG_ENGLISH_CAN。所有的语言ID值都定义于Windows NT Win32 SDK开发包的文件WINNT.H中。 <br />
　　 既然语言ID结点是树中最后的目录结点，那么入口结构的OffsetToData域就是到一个叶子结点（即前面提到过的IMAGE_RESOURCE_DATA_ENTRY结构）的偏移量。 <br />
　　 再回过头来参考图2，你会发现每个语言目录入口都对应着一个数据入口。这个结点仅仅表示了资源数据的尺寸以及资源数据的相对虚拟地址。 <br />
　　 在资源数据段（.rsrc）之中拥有这么多结构有一个好处，就是你可以不存取资源本身而直接可以从这个段收集很多信息。例如，你可以获得有多少种资源、哪些资源（如果有的话）使用了特别的语言ID、特定的资源是否存在以及单独种类资源的尺寸。为了示范如何利用这一信息，以下的函数说明了如何决定一个文件中包含的不同种类的资源：
<pre>// PEFILE.C
int WINAPI GetListOfResourceTypes(LPVOID lpFile, HANDLE hHeap, char **pszResTypes)
{
PIMAGE_RESOURCE_DIRECTORY prdRoot;
PIMAGE_RESOURCE_DIRECTORY_ENTRY prde;
char *pMem;
int nCnt, i;
/* 获得资源树的根目录 */
if ((prdRoot = (PIMAGE_RESOURCE_DIRECTORY)ImageDirectoryOffset
(lpFile, IMAGE_DIRECTORY_ENTRY_RESOURCE)) == NULL)
return 0;
/* 在堆上分配足够的空间来包括所有类型 */
nCnt = prdRoot-&gt;NumberOfIdEntries * (MAXRESOURCENAME + 1);
*pszResTypes = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
nCnt);
if ((pMem = *pszResTypes) == NULL)
return 0;
/* 将指针指向第一个资源种类的入口 */
prde = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)prdRoot +
sizeof (IMAGE_RESOURCE_DIRECTORY));
/* 在所有的资源目录入口类型中循环 */
for (i = 0; i &lt; prdRoot-&gt;NumberOfIdEntries; i++)
{
if (LoadString(hDll, prde-&gt;Name, pMem, MAXRESOURCENAME))
pMem += strlen(pMem) + 1;
prde++;
}
return nCnt;
}
　　</pre>
这个函数将一个资源种类名称的列表写入了由pszResTypes标识的变量中。请注意，在这个函数的核心部分，LoadString是使用各自资源种类目录入口的Name域来作为字符串ID的。如果你查看PEFILE.RC，你会发现我定义了一系列的资源种类的字符串，并且它们的ID与它们在目录入口中的定义完全相同。PEFILE.DLL还有有一个函数，它返回了.rsrc段中的资源对象总数。这样一来，从这个段中提取其它的信息，借助这些函数或另外编写函数就方便多了。 <br />
<br />
<strong>导出数据段，.edata</strong> <br />
<br />
　　 .edata段包含了应用程序或DLL的导出数据。在这个段出现的时候，它会包含一个到达导出信息的导出目录。
<pre>// WINNT.H
typedef struct _IMAGE_EXPORT_DIRECTORY {
ULONG Characteristics;
ULONG TimeDateStamp;
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Name;
ULONG Base;
ULONG NumberOfFunctions;
ULONG NumberOfNames;
PULONG *AddressOfFunctions;
PULONG *AddressOfNames;
PUSHORT *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
　　</pre>
导出目录中的Name域标识了可执行模块的名称。NumberOfFunctions域和NumberOfNames域表示模块中有多少导出的函数以及这些函数的名称。 <br />
　　 AddressOfFunctions域是一个到导出函数入口列表的偏移量。AddressOfNames域是到一个导出函数名称列表起始处偏移量的地址，这个列表是由null分隔的。AddressOfNameOrdinals是一个到相同导出函数顺序值（每个值2字节长）列表的偏移量。 <br />
　　 三个AddressOf...域是当模块装载时进程地址空间中的相对虚拟地址。一旦模块被装载，那么要获得进程地质空间中的确切地址的话，就应该在相对虚拟地址上加上模块的基地址。可是，在文件被装载前，仍然可以决定这一地址：只要从给定的域地址中减去段头部的虚拟地址（VirtualAddress），再加上段实体的偏移量（PointerToRawData），这个结果就是映像文件中的偏移量了。以下的例子解说了这一技术： <br />
<pre>// PEFILE.C
int WINAPI GetExportFunctionNames(LPVOID lpFile, HANDLE hHeap, char **pszFunctions)
{
IMAGE_SECTION_HEADER sh;
PIMAGE_EXPORT_DIRECTORY ped;
char *pNames, *pCnt;
int i, nCnt;
/* 获得.edata域中的段头部和指向数据目录的指针 */
if ((ped = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryOffset
(lpFile, IMAGE_DIRECTORY_ENTRY_EXPORT)) == NULL)
return 0;
GetSectionHdrByName (lpFile, &amp;sh, ".edata");
/* 决定导出函数名称的偏移量 */
pNames = (char *)(*(int *)((int)ped-&gt;AddressOfNames -
(int)sh.VirtualAddress + (int)sh.PointerToRawData +
(int)lpFile) - (int)sh.VirtualAddress +
(int)sh.PointerToRawData + (int)lpFile);
/* 计算出要为所有的字符串分配多少内存 */
pCnt = pNames;
for (i = 0; i &lt; (int)ped-&gt;NumberOfNames; i++)
while (*pCnt++);
nCnt = (int)(pCnt.pNames);
/* 在堆上为函数名称分配内存 */
*pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nCnt);
/* 将所有字符串复制到缓冲区 */
CopyMemory((LPVOID)*pszFunctions, (LPVOID)pNames, nCnt);
return nCnt;
}</pre>
请注意，在这个函数之中，变量pNames是由决定偏移量地址和当前偏移量位置的方法来赋值的。偏移量的地址和偏移量本身都是相对虚拟地址，因此在使用之前必须进行转换——函数之中体现了这一点。虽然你可以编写一个类似的函数来决定顺序值或函数入口点，但是我为什么不为你做好呢？——GetNumberOfExportedFunctions、GetExportFunctionEntryPoints和GetExportFunctionOrdinals已经存在于PEFILE.DLL之中了。 <br />
<br />
<strong>导入数据段，.idata</strong> <br />
<br />
　　 .idata段是导入数据，包括导入库和导入地址名称表。虽然定义了IMAGE_DIRECTORY_ENTRY_IMPORT，但是WINNT.H之中并无相应的导入目录结构。作为代替，其中有若干其它的结构，名为IMAGE_IMPORT_BY_NAME、IMAGE_THUNK_DATA与IMAGE_IMPORT_DESCRIPTOR。在我个人看来，我实在不知道这些结构是如何和.idata段发生关联的，所以我花了若干个小时来破译.idata段实体并且得到了一个更简单的结构，我名之为IMAGE_IMPORT_MODULE_DIRECTORY。
<pre>// PEFILE.H
typedef struct tagImportDirectory
{
DWORD dwRVAFunctionNameList;
DWORD dwUseless1;
DWORD dwUseless2;
DWORD dwRVAModuleName;
DWORD dwRVAFunctionAddressList;
} IMAGE_IMPORT_MODULE_DIRECTORY, *PIMAGE_IMPORT_MODULE_DIRECTORY;
</pre>
和其它段的数据目录不同的是，这个是作为文件中的每个导入模块重复出现的。你可以将它看作模块数据目录列表中的一个入口，而不是一个整个数据段的数据目录。每个入口都是一个指向特定模块导入信息的目录。 <br />
　　 IMAGE_IMPORT_MODULE_DIRECTORY结构中的一个域dwRVAModuleName是一个相对虚拟地址，它指向模块的名称。结构中还有两个dwUseless参数，它们是为了保持段的对齐。PE文件格式规范提到了一些东西，关于导入标记、时间/日期标志以及主/次版本，但是在我的实验中，这两个域自始而终都是空的，所以我仍然认为它们没有什么用处。 <br />
　　 基于这个结构的定义，你便可以获得可执行文件中导入的所有模块和函数名称了。以下的函数示范了如何获得特定的PE文件中的所有导入函数名称：
<pre>//PEFILE.C
int WINAPI GetImportModuleNames(LPVOID lpFile, HANDLE hHeap, char **pszModules)
{
PIMAGE_IMPORT_MODULE_DIRECTORY pid;
IMAGE_SECTION_HEADER idsh;
BYTE *pData;
int nCnt = 0, nSize = 0, i;
char *pModule[1024];
char *psz;
pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset
(lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
pData = (BYTE *)pid;
/* 定位.idata段头部 */
if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
return 0;
/* 提取所有导入模块 */
while (pid-&gt;dwRVAModuleName)
{
/* 为绝对字符串偏移量分配缓冲区 */
pModule[nCnt] = (char *)(pData +
(pid-&gt;dwRVAModuleName-idsh.VirtualAddress));
nSize += strlen(pModule[nCnt]) + 1;
/* 增至下一个导入目录入口 */
pid++;
nCnt++;
}
/* 将所有字符串赋值到一大块的堆内存中 */
*pszModules = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nSize);
psz = *pszModules;
for (i = 0; i &lt; nCnt; i++)
{
strcpy(psz, pModule[i]);
psz += strlen (psz) + 1;
}
return nCnt;
}
</pre>
这个函数非常好懂，然而有一点值得指出——注意while循环。这个循环当pid-&gt;dwRVAModuleName为0的时候终止，这就暗示了在IMAGE_IMPORT_MODULE_DIRECTORY结构列表的末尾有一个空的结构，这个结构拥有一个0值，至少dwRVAModuleName域为0。这便是我在对文件的实验中以及之后在PE文件格式中研究的行为。 <br />
　　 这个结构中的第一个域dwRVAFunctionNameList是一个相对虚拟地址，这个地址指向一个相对虚拟地址的列表，这些地址是文件中的一些文件名。如下面的数据所示，所有导入模块的模块和函数名称都列于.idata段数据中了：
<pre>E6A7 0000 F6A7 0000 08A8 0000 1AA8 0000 ................
28A8 0000 3CA8 0000 4CA8 0000 0000 0000 (...&lt;...L.......
0000 4765 744F 7065 6E46 696C 654E 616D ..GetOpenFileNam
6541 0000 636F 6D64 6C67 3332 2E64 6C6C eA..comdlg32.dll
0000 2500 4372 6561 7465 466F 6E74 496E ..%.CreateFontIn
6469 7265 6374 4100 4744 4933 322E 646C directA.GDI32.dl
6C00 A000 4765 7444 6576 6963 6543 6170 l...GetDeviceCap
7300 C600 4765 7453 746F 636B 4F62 6A65 s...GetStockObje
6374 0000 D500 4765 7454 6578 744D 6574 ct....GetTextMet
7269 6373 4100 1001 5365 6C65 6374 4F62 ricsA...SelectOb
6A65 6374 0000 1601 5365 7442 6B43 6F6C ject....SetBkCol
6F72 0000 3501 5365 7454 6578 7443 6F6C or..5.SetTextCol
6F72 0000 4501 5465 7874 4F75 7441 0000 or..E.TextOutA..
</pre>
以上的数据是EXEVIEW.EXE示例程序.idata段的一部分。这个特别的段表示了导入模块列表和函数名称列表的起始处。如果你开始检查数据中的这个段，你应该认出一些熟悉的Win32 API函数以及模块名称。从上往下读的话，你可以找到GetOpenFileNameA，紧接着是COMDLG32.DLL。然后你能发现CreateFontIndirectA，紧接着是模块GDI32.DLL，以及之后的GetDeviceCaps、GetStockObject、GetTextMetrics等等。 <br />
　　 这样的式样会在.idata段中重复出现。第一个模块是COMDLG32.DLL，第二个是GDI32.DLL。请注意第一个模块只导出了一个函数，而第二个模块导出了很多函数。在这两种情况下，函数和模块的排列的方法是首先出现一个函数名，之后是模块名，然后是其它的函数名（如果有的话）。 <br />
　　 以下的函数示范了如何获得指定模块的所有函数名。
<pre>// PEFILE.C
int WINAPI GetImportFunctionNamesByModule(LPVOID lpFile, HANDLE hHeap,
char *pszModule, char **pszFunctions)
{
PIMAGE_IMPORT_MODULE_DIRECTORY pid;
IMAGE_SECTION_HEADER idsh;
DWORD dwBase;
int nCnt = 0, nSize = 0;
DWORD dwFunction;
char *psz;
/* 定位.idata段的头部 */
if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
return 0;
pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset
(lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
dwBase = ((DWORD)pid. idsh.VirtualAddress);
/* 查找模块的pid */
while (pid-&gt;dwRVAModuleName &amp;&amp; strcmp (pszModule,
(char *)(pid-&gt;dwRVAModuleName+dwBase)))
pid++;
/* 如果模块未找到，就退出 */
if (!pid-&gt;dwRVAModuleName)
return 0;
/* 函数的总数和字符串长度 */
dwFunction = pid-&gt;dwRVAFunctionNameList;
while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
*(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
{
nSize += strlen ((char *)((*(DWORD *)(dwFunction +
dwBase)) + dwBase+2)) + 1;
dwFunction += 4;
nCnt++;
}
/* 在堆上分配函数名称的空间 */
*pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
psz = *pszFunctions;
/* 向内存指针复制函数名称 */
dwFunction = pid-&gt;dwRVAFunctionNameList;
while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
*((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)))
{
strcpy (psz, (char *)((*(DWORD *)(dwFunction + dwBase)) +
dwBase+2));
psz += strlen((char *)((*(DWORD *)(dwFunction + dwBase))+
dwBase+2)) + 1;
dwFunction += 4;
}
return nCnt;
}
　　</pre>
就像GetImportModuleNames函数一样，这一函数依靠每个信息列表的末端来获得一个置零的入口。这在种情况下，函数名称列表就是以零结尾的。 <br />
　　 最后一个域dwRVAFunctionAddressList是一个相对虚拟地址，它指向一个虚拟地址表。在文件装载的时候，这个虚拟地址表会被装载器置于段数据之中。但是在文件装载前，这些虚拟地址会被一些严密符合函数名称列表的虚拟地址替换。所以在文件装载之前，有两个同样的虚拟地址列表，它们指向导入函数列表。 <br />
<br />
<strong>调试信息段，.debug </strong><br />
<br />
　　 调试信息位于.debug段之中，同时PE文件格式也支持单独的调试文件（通常由.DBG扩展名标识）作为一种将调试信息集中的方法。调试段包含了调试信息，但是调试目录却位于早先提到的.rdata段之中。这其中每个目录都涉及了.debug段之中的调试信息。调试目录的结构IMAGE_DEBUG_DIRECTORY被定义为：
<pre>// WINNT.H
typedef struct _IMAGE_DEBUG_DIRECTORY {
ULONG Characteristics;
ULONG TimeDateStamp;
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Type;
ULONG SizeOfData;
ULONG AddressOfRawData;
ULONG PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
</pre>
这个段被分为单独的部分，每个部分为不同种类的调试信息数据。对于每个部分来说都是一个像上边一样的调试目录。不同的调试信息种类如下：
<pre>// WINNT.H
#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
　　</pre>
每个目录之中的Type域表示该目录的调试信息种类。如你所见，在上边的表中，PE文件格式支持很多不同的调试信息种类，以及一些其它的信息域。对于那些来说，IMAGE_DEBUG_TYPE_MISC信息是唯一的。这一信息被添加到描述可执行映像的混杂信息之中，这些混杂信息不能被添加到PE文件格式任何结构化的数据段之中。这就是映像文件中最合适的位置，映像名称则肯定会出现在这里。如果映像导出了信息，那么导出数据段也会包含这一映像名称。 <br />
　　 每种调试信息都拥有自己的头部结构，该结构定义了它自己的数据。这些结构都列于WINNT.H之中。关于IMAGE_DEBUG_DIRECTORY一件有趣的事就是它包括了两个标识调试信息的域。第一个是AddressOfRawData，为相对文件装载的数据虚拟地址；另一个是PointerToRawData，为数据所在PE文件之中的实际偏移量。这就使得定位指定的调试信息相当容易了。 <br />
　　 作为最后的例子，请你考虑以下的函数代码，它从IMAGE_DEBUG_MISC结构中提取了映像名称。
<pre>//PEFILE.C
int WINAPI RetrieveModuleName(LPVOID lpFile, HANDLE hHeap, char **pszModule)
{
PIMAGE_DEBUG_DIRECTORY pdd;
PIMAGE_DEBUG_MISC pdm = NULL;
int nCnt;
if (!(pdd = (PIMAGE_DEBUG_DIRECTORY)ImageDirectoryOffset(lpFile,
IMAGE_DIRECTORY_ENTRY_DEBUG)))
return 0;
while (pdd-&gt;SizeOfData)
{
if (pdd-&gt;Type == IMAGE_DEBUG_TYPE_MISC)
{
pdm = (PIMAGE_DEBUG_MISC)((DWORD)pdd-&gt;PointerToRawData + (DWORD)lpFile);
nCnt = lstrlen(pdm-&gt;Data) * (pdm-&gt;Unicode ? 2 : 1);
*pszModule = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nCnt+1);
CopyMemory(*pszModule, pdm-&gt;Data, nCnt);
break;
}
pdd ++;
}
if (pdm != NULL)
return nCnt;
else
return 0;
}
</pre>
你看到了，调试目录结构使得定位一个特定种类的调试信息变得相对容易了些。只要定位了IMAGE_DEBUG_MISC结构，提取映像名称就如同调用CopyMemory函数一样简单。 <br />
　　 如上所述，调试信息可以被剥离到单独的.DBG文件中。Windows NT SDK包含了一个名为REBASE.EXE的程序可以实现这一目的。例如，以下的语句可以将一个名为TEST.EXE的调试信息剥离： <br />
　　 rebase -b 40000 -x c:\samples\testdir test.exe <br />
　　 调试信息被置于一个新的文件中，这个文件名为TEST.DBG，位于c:\samples\testdir之中。这个文件起始于一个单独的IMAGE_SEPARATE_DEBUG_HEADER结构，接着是存在于原可执行映像之中的段头部的一份拷贝。在段头部之后，是.debug段的数据。也就是说，在段头部之后，就是一系列的IMAGE_DEBUG_DIRECTORY结构及其相关的数据了。调试信息本身保留了如上所描述的常规映像文件调试信息。 <br />
<br />
<strong>PE文件格式总结</strong> <br />
<br />
　　 Windows NT的PE文件格式向熟悉Windows和MS-DOS环境的开发者引入了一种全新的结构。然而熟悉UNIX环境的开发者会发现PE文件格式与COFF规范很相像（如果它不是以COFF为基础的话）。 <br />
　　 整个格式的组成：一个MS-DOS的MZ头部，之后是一个实模式的残余程序、PE文件标志、PE文件头部、PE可选头部、所有的段头部，最后是所有的段实体。 <br />
　　 可选头部的末尾是一个数据目录入口的数组，这些相对虚拟地址指向段实体之中的数据目录。每个数据目录都表示了一个特定的段实体数据是如何组织的。 <br />
　　 PE文件格式有11个预定义段，这是对Windows NT应用程序所通用的，但是每个应用程序可以为它自己的代码以及数据定义它自己独特的段。 <br />
　　 .debug预定义段也可以分离为一个单独的调试文件。如果这样的话，就会有一个特定的调试头部来用于解析这个调试文件，PE文件中也会有一个标志来表示调试数据被分离了出去。 <br />
<br />
<strong>PEFILE.DLL函数描述</strong> <br />
<br />
　　 PEFILE.DLL主要由一些函数组成，这些函数或者被用来获得一个给定的PE文件中的偏移量，或者被用来把文件中的一些数据复制到一个特定的结构中去。每个函数都有一个需求——第一个参数是一个指针，这个指针指向PE文件的起始处。也就是说，这个文件必须首先被映射到你进程的地址空间中，然后映射文件的位置就可以作为每个函数第一个参数的lpFile的值来传入了。 <br />
　　 我意在使函数的名称使你能够一见而知其意，并且每个函数都随一个详细描述其目的的注释而列出。如果在读完函数列表之后，你仍然不明白某个函数的功能，那么请参考EXEVIEW.EXE示例来查明这个函数是如何使用的。以下的函数原型列表可以在PEFILE.H中找到：
<pre>// PEFILE.H
/* 获得指向MS-DOS MZ头部的指针 */
BOOL WINAPI GetDosHeader(LPVOID, PIMAGE_DOS_HEADER);
/* 决定.EXE文件的类型 */
DWORD WINAPI ImageFileType(LPVOID);
/* 获得指向PE文件头部的指针 */
BOOL WINAPI GetPEFileHeader(LPVOID, PIMAGE_FILE_HEADER);
/* 获得指向PE可选头部的指针 */
BOOL WINAPI GetPEOptionalHeader(LPVOID, PIMAGE_OPTIONAL_HEADER);
/* 返回模块入口点的地址 */
LPVOID WINAPI GetModuleEntryPoint(LPVOID);
/* 返回文件中段的总数 */
int WINAPI NumOfSections(LPVOID);
/* 返回当可执行文件被装载入进程地址空间时的首选基地址 */
LPVOID WINAPI GetImageBase(LPVOID);
/* 决定文件中一个特定的映像数据目录的位置 */
LPVOID WINAPI ImageDirectoryOffset(LPVOID, DWORD);
/* 获得文件中所有段的名称 */
int WINAPI GetSectionNames(LPVOID, HANDLE, char **);
/* 复制一个特定段的头部信息 */
BOOL WINAPI GetSectionHdrByName(LPVOID, PIMAGE_SECTION_HEADER, char *);
/* 获得由空字符分隔的导入模块名称列表 */
int WINAPI GetImportModuleNames(LPVOID, HANDLE, char **);
/* 获得一个模块由空字符分隔的导入函数列表 */
int WINAPI GetImportFunctionNamesByModule(LPVOID, HANDLE, char *, char **);
/* 获得由空字符分隔的导出函数列表 */
int WINAPI GetExportFunctionNames(LPVOID, HANDLE, char **);
/* 获得导出函数总数 */
int WINAPI GetNumberOfExportedFunctions(LPVOID);
/* 获得导出函数的虚拟地址入口点列表 */
LPVOID WINAPI GetExportFunctionEntryPoints(LPVOID);
/* 获得导出函数顺序值列表 */
LPVOID WINAPI GetExportFunctionOrdinals(LPVOID);
/* 决定资源对象的种类 */
int WINAPI GetNumberOfResources (LPVOID);
/* 返回文件中所使用的所有资源对象的种类 */
int WINAPI GetListOfResourceTypes(LPVOID, HANDLE, char **);
/* 决定调试信息是否已从文件中分离 */
BOOL WINAPI IsDebugInfoStripped(LPVOID);
/* 获得映像文件名称 */
int WINAPI RetrieveModuleName(LPVOID, HANDLE, char **);
/* 决定文件是否是一个有效的调试文件 */
BOOL WINAPI IsDebugFile(LPVOID);
/* 从调试文件中返回调试头部 */
BOOL WINAPI GetSeparateDebugHeader(LPVOID, PIMAGE_SEPARATE_DEBUG_HEADER);
　　除了以上所列的函数之外，本文中早先提到的宏也定义在了PEFILE.H中，完整的列表如下：
/* PE文件标志的偏移量 */
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))
/* MS操作系统头部标识了双字的NT PE文件标志；PE文件头部就紧跟在这个双字之后 */
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
SIZE_OF_NT_SIGNATURE))
/* PE可选头部紧跟在PE文件头部之后 */
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
SIZE_OF_NT_SIGNATURE + \
sizeof(IMAGE_FILE_HEADER)))
/* 段头部紧跟在PE可选头部之后 */
#define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
SIZE_OF_NT_SIGNATURE + \
sizeof(IMAGE_FILE_HEADER) + \
sizeof(IMAGE_OPTIONAL_HEADER)))
　　</pre>
要使用PEFILE.DLL，你只用包含PEFILE.H文件并在应用程序中链接到这个DLL即可。所有的这些函数都是互斥性的函数，但是有些函数的功能可以相互支持以获得文件信息。例如，GetSectionNames可以用于获得所有段的名称，这样一来，为了获得一个拥有独特段名称（在编译期由应用程序开发者定义的）的段头部，你就需要首先获得所有名称的列表，然后再对那个准确的段名称调用函数GetSectionHeaderByName了。现在，你可以享受我为你带来的这一切了！ </div>
 <img src ="http://www.cnblogs.com/ILove/aggbug/1157228.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43809/" target="_blank">[新闻]微软发布PC Live单机游戏客户端</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>理解 Thread.Sleep 函数</title><link>http://www.cnblogs.com/ILove/archive/2008/04/07/1140419.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Mon, 07 Apr 2008 06:45:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/07/1140419.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1140419.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/07/1140419.html#Feedback</comments><slash:comments>59</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1140419.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1140419.html</trackback:ping><description><![CDATA[<p><font face="Verdana">我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间。那么你有没有正确的理解这个函数的用法呢？思考下面这两个问题：<br />
1、假设现在是 2008-4-7 12:00:00.000，如果我调用一下 Thread.Sleep(1000) ，在 2008-4-7 12:00:01.000 的时候，这个线程会 不会被唤醒？<br />
2、某人的代码中用了一句看似莫明其妙的话：Thread.Sleep(0) 。既然是 Sleep 0 毫秒，那么他跟去掉这句代码相比，有啥区别么？</font></p>
<p><font face="Verdana">我们先回顾一下操作系统原理。<br />
操作系统中，CPU竞争有很多种策略。Unix系统使用的是时间片算法，而Windows则属于抢占式的。</font></p>
<p><font face="Verdana">在时间片算法中，所有的进程排成一个队列。操作系统按照他们的顺序，给每个进程分配一段时间，即该进程允许运行的时间。如果在 时间片结束时进程还在运行，则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束，则CPU当即进行切换。调度程 序所要做的就是维护一张就绪进程列表，，当进程用完它的时间片后，它被移到队列的末尾。</font></p>
<p><font face="Verdana">所谓抢占式操作系统，就是说如果一个进程得到了 CPU 时间，除非它自己放弃使用 CPU ，否则将完全霸占 CPU 。因此可以看出，在抢 占式操作系统中，操作系统假设所有的进程都是&#8220;人品很好&#8221;的，会主动退出 CPU 。</font></p>
<p><font face="Verdana">在抢占式操作系统中，假设有若干进程，操作系统会根据他们的优先级、饥饿时间（已经多长时间没有使用过 CPU 了），给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后，操作系统就会重新计算一 次所有进程的总优先级，然后再挑一个优先级最高的把 CPU 控制权交给他。</font></p>
<p><font face="Verdana">我们用分蛋糕的场景来描述这两种算法。假设有源源不断的蛋糕（源源不断的时间），一副刀叉（一个CPU），10个等待吃蛋糕的人（10 个进程）。</font></p>
<p><font face="Verdana">如果是 Unix操作系统来负责分蛋糕，那么他会这样定规矩：每个人上来吃 1 分钟，时间到了换下一个。最后一个人吃完了就再从头开始。于是，不管这10个人是不是优先级不同、饥饿程度不同、饭量不同，每个人上来的时候都可以吃 1 分钟。当然，如果有人本来不太饿，或者饭量小，吃了30秒钟之后就吃饱了，那么他可以跟操作系统说：我已经吃饱了（挂起）。于是操作系统就会让下一个人接着来。</font></p>
<p><font face="Verdana">如果是 Windows 操作系统来负责分蛋糕的，那么场面就很有意思了。他会这样定规矩：我会根据你们的优先级、饥饿程度去给你们每个人计算一个优先级。优先级最高的那个人，可以上来吃蛋糕——吃到你不想吃为止。等这个人吃完了，我再重新根据优先级、饥饿程度来计算每个人的优先级，然后再分给优先级最高的那个人。<br />
这样看来，这个场面就有意思了——可能有些人是PPMM，因此具有高优先级，于是她就可以经常来吃蛋糕。可能另外一个人是个丑男，而去很ws，所以优先级特别低，于是好半天了才轮到他一次（因为随着时间的推移，他会越来越饥饿，因此算出来的总优先级就会越来越高，因此总有一天会轮到他的）。而且，如果一不小心让一个大胖子得到了刀叉，因为他饭量大，可能他会霸占着蛋糕连续吃很久很久，导致旁边的人在那里咽口水。。。<br />
而且，还可能会有这种情况出现：操作系统现在计算出来的结果，5号PPMM总优先级最高，而且高出别人一大截。因此就叫5号来吃蛋糕。5号吃了一小会儿，觉得没那么饿了，于是说&#8220;我不吃了&#8221;（挂起）。因此操作系统就会重新计算所有人的优先级。因为5号刚刚吃过，因此她的饥饿程度变小了，于是总优先级变小了；而其他人因为多等了一会儿，饥饿程度都变大了，所以总优先级也变大了。不过这时候仍然有可能5号的优先级比别的都高，只不过现在只比其他的高一点点——但她仍然是总优先级最高的啊。因此操作系统就会说：5号mm上来吃蛋糕&#8230;&#8230;（5号mm心里郁闷，这不刚吃过嘛&#8230;&#8230;人家要减肥&#8230;&#8230;谁叫你长那么漂亮，获得了那么高的优先级）。</font></p>
<p><font face="Verdana">那么，Thread.Sleep 函数是干吗的呢？还用刚才的分蛋糕的场景来描述。上面的场景里面，5号MM在吃了一次蛋糕之后，觉得已经有8分饱了，她觉得在未来的半个小时之内都不想再来吃蛋糕了，那么她就会跟操作系统说：在未来的半个小时之内不要再叫我上来吃蛋糕了。这样，操作系统在随后的半个小时里面重新计算所有人总优先级的时候，就会忽略5号mm。Sleep函数就是干这事的，他告诉操作系统&#8220;在未来的多少毫秒内我不参与CPU竞争&#8221;。</font></p>
<p><font face="Verdana">看完了 Thread.Sleep 的作用，我们再来想想文章开头的两个问题。</font></p>
<p><font face="Verdana">对于第一个问题，答案是：不一定。因为你只是告诉操作系统：在未来的1000毫秒内我不想再参与到CPU竞争。那么1000毫秒过去之后，这时候也许另外一个线程正在使用CPU，那么这时候操作系统是不会重新分配CPU的，直到那个线程挂起或结束；况且，即使这个时候恰巧轮到操作系统进行CPU 分配，那么当前线程也不一定就是总优先级最高的那个，CPU还是可能被其他线程抢占去。</font></p>
<p><font face="Verdana">与此相似的，Thread有个Resume函数，是用来唤醒挂起的线程的。好像上面所说的一样，这个函数只是&#8220;告诉操作系统我从现在起开始参与CPU竞争了&#8221;，这个函数的调用并不能马上使得这个线程获得CPU控制权。</font></p>
<p><font face="Verdana">对于第二个问题，答案是：有，而且区别很明显。假设我们刚才的分蛋糕场景里面，有另外一个PPMM 7号，她的优先级也非常非常高（因为非常非常漂亮），所以操作系统总是会叫道她来吃蛋糕。而且，7号也非常喜欢吃蛋糕，而且饭量也很大。不过，7号人品很好，她很善良，她没吃几口就会想：如果现在有别人比我更需要吃蛋糕，那么我就让给他。因此，她可以每吃几口就跟操作系统说：我们来重新计算一下所有人的总优先级吧。不过，操作系统不接受这个建议——因为操作系统不提供这个接口。于是7号mm就换了个说法：&#8220;在未来的0毫秒之内不要再叫我上来吃蛋糕了&#8221;。这个指令操作系统是接受的，于是此时操作系统就会重新计算大家的总优先级——注意这个时候是连7号一起计算的，因为&#8220;0毫秒已经过去了&#8221;嘛。因此如果没有比7号更需要吃蛋糕的人出现，那么下一次7号还是会被叫上来吃蛋糕。</font></p>
<p><font face="Verdana">因此，Thread.Sleep(0)的作用，就是&#8220;触发操作系统立刻重新进行一次CPU竞争&#8221;。竞争的结果也许是当前线程仍然获得CPU控制权，也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ，因为这样就给了其他线程比如Paint线程获得CPU控制权的权力，这样界面就不会假死在那里。</font></p>
<p><font face="Verdana">末了说明一下，虽然上面提到说&#8220;除非它自己放弃使用 CPU ，否则将完全霸占 CPU&#8221;，但这个行为仍然是受到制约的——操作系统会监控你霸占CPU的情况，如果发现某个线程长时间霸占CPU，会强制使这个线程挂起，因此在实际上不会出现&#8220;一个线程一直霸占着 CPU 不放&#8221;的情况。至于我们的大循环造成程序假死，并不是因为这个线程一直在霸占着CPU。实际上在这段时间操作系统已经进行过多次CPU竞争了，只不过其他线程在获得CPU控制权之后很短时间内马上就退出了，于是就又轮到了这个线程继续执行循环，于是就又用了很久才被操作系统强制挂起。。。因此反应到界面上，看起来就好像这个线程一直在霸占着CPU一样。<br />
<br />
末了再说明一下，文中线程、进程有点混乱，其实在Windows原理层面，CPU竞争都是线程级的，本文中把这里的进程、线程看成同一个东西就好了。</font></p>
<img src ="http://www.cnblogs.com/ILove/aggbug/1140419.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43808/" target="_blank">[新闻]Firefox遭“独家”恶意软件攻击</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>Refactoring to, towards, and away from pattern</title><link>http://www.cnblogs.com/ILove/archive/2008/04/07/1139686.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Sun, 06 Apr 2008 17:15:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/07/1139686.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1139686.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/07/1139686.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1139686.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1139686.html</trackback:ping><description><![CDATA[<div style="text-indent: 2em; line-height: 1.6em" align="center"><strong><font style="font-size: medium; line-height: 1.3em">Refactoring to, towards, and away from pattern</font><wbr><wbr> </strong></div>
<center style="text-indent: 2em; line-height: 1.6em"><strong><wbr><font style="font-size: medium; line-height: 1.3em">（通过重构实现模式、趋向模式和去除模式）<br />
</font></strong><span style="font-size: 10pt">《重构与模式》1～3章读后感</span></center>
<p style="text-indent: 2em; line-height: 1.6em">
<p style="text-indent: 2em; line-height: 1.6em"><strong>1、设计过度和设计不足同样不可取
<p style="text-indent: 2em; line-height: 1.6em"></strong>所谓的过度设计，顾名思议就是代码的灵活性、复杂性超出所需。这个问题在一些没有项目责任心的人身上是不会发生的，因为他不会去为了让项目更灵活、功能更强大而去仔细的设计这个系统，反而在一些有心将项目设计好的人身上经常发生。产生这种问题的原因，其实还是对于模式这个东西的真正意义没有理解。模式是用来将系统变简单的，而不是用来将系统变复杂的。
<p style="text-indent: 2em; line-height: 1.6em">设计一个灵活的、能够满足未来需求变化的系统这个出发点是很好的，但是必须正视现实：如果对未来的需求预计不正确，预期中的需求根本不会变成现实，那么按此编写的代码又怎样呢？毕竟我们都无法未卜先知。
<p style="text-indent: 2em; line-height: 1.6em">如果我们遇见某个模块将来可能会发生需求变化，我们需要根据这个可能性的概率、要投入的成本以及将带来的收益去衡量一下。
<p style="text-indent: 2em; line-height: 1.6em">一般的系统，生命周期达到3年而不做大重构的，已经非常不容易了。那么，我们在做一般软件的时候，就完全没有必要考虑到5年、10年以后的问题。距离现在越远，需求的可测量性就越小，自己对需求的猜测正确性就越低，现在的设计还能满足需求的概率就越小。
<p style="text-indent: 2em; line-height: 1.6em">况且，我们可以先做一些小的工作，使得将来一旦需求变化，我们只需要重构模块内部而不会对周围模块产生大的影响，就可以了。
<p style="text-indent: 2em; line-height: 1.6em">事实上，设计模式并没有标准的模型，每种模式在不同的场景下实现的途径都可能会不同，怎样实现模式与实际环境密不可分。因此，有意义的模式都是通过重构产生的，而重构往往会产生模式。
<p style="text-indent: 2em; line-height: 1.6em">这，也就是&#8220;演进式设计&#8221;。
<p style="text-indent: 2em; line-height: 1.6em">就好像我们要改造一个房子，我们要把一堵墙全部换成隔音砖。那，我们是一小块一小块区域的换呢，还是一下子把一堵墙拆了重新砌墙呢？呵呵，小心房子倒掉哦～～^_^
<p style="text-indent: 2em; line-height: 1.6em">
<p style="text-indent: 2em; line-height: 1.6em"><strong>2、设计欠账
<p style="text-indent: 2em; line-height: 1.6em"></strong>假设你去请求开发经理给你一些时间以重构现有代码以&#8220;改善其设计&#8221;，他会有何反应呢？可能是斩钉截铁的说不：应付没完没了的需求和bug就已经够难了，哪里还有时间让你&#8220;一段时间啥功能都没完成&#8221;。
<p style="text-indent: 2em; line-height: 1.6em">使用重构的语言和大多数管理人员沟通，效果往往都不好。不过，我们可以用设计欠账来隐喻。
<p style="text-indent: 2em; line-height: 1.6em">人类无法一次性就写出完美的代码。我们会自然而然的累积设计欠账。所以问题就变成&#8220;应该什么时候偿还设计欠账&#8221;？
<p style="text-indent: 2em; line-height: 1.6em">用金融术语来说，如果不偿还债务，就会产生滞纳金。如果不偿还滞纳金，滞纳金就会越来越多，而去是按照复利计算的。随着时间的转移，债务就像滚雪球或者我们所说的驴打滚一样，越来越多。设计欠账也是如此。
<p style="text-indent: 2em; line-height: 1.6em">因此，定期的清理设计欠账，是必要的。
<p style="text-indent: 2em; line-height: 1.6em">
<p style="text-indent: 2em; line-height: 1.6em"><strong>3、模式的真正价值只是一种思想，而不是一个框图。</strong>
<p style="text-indent: 2em; line-height: 1.6em">对设计模式过于痴迷的人往往会走向另外一个极端。如果一个简单的HelloWorld程序，有人用了Factory、Command、Observer、Templete Method来实现，你看了会如何呢？说到这里，使我想起了微软的PetShop网站。这个网站，用了大量的设计模式——实际上这个网站就是微软设计模式系列在线培训的例程。之前面试的很多人，都提到说看过PetShop。但问及里面的模式是用来解决什么问题的，就一问三不知了。甚至有人说，仿造PetShop做过系统——这个有用么？数据库教材里面有提到第二范式第三范式甚至第N范式，我们在设计数据库的时候也要遵循第N范式么？那样设计出来的，不是系统，而是花瓶。
<p style="text-indent: 2em; line-height: 1.6em">实际上，PetShop只是用来演示&#8220;模式能够达到什么目的&#8221;的，而不是用来演示&#8220;模式应该在什么时候用&#8221;的。
<p style="text-indent: 2em; line-height: 1.6em">之前面试时很多人在问到设计模式的时候，就会把设计模式教程上那些框图说出来。事实上，设计模式只是一个思想，他只是一些最佳实践，真正的价值在于告诉你&#8220;遇到某种情景，可以怎样解决&#8221;，而不是告诉你&#8220;遇到某种情景，应该怎样解决&#8221;。真正优秀的系统设计，往往是跟教程上那些框图对应不起来的。
<p style="text-indent: 2em; line-height: 1.6em">
<p style="text-indent: 2em; line-height: 1.6em"><strong>4、模式是否会使得代码更加复杂
<p style="text-indent: 2em; line-height: 1.6em"></strong>本书的作者提到了一个现实中我们经常遇到的相同的问题，呵呵。
<p style="text-indent: 2em; line-height: 1.6em">某人A使用设计模式很好的改造了一个模块。在作者看来，这个改造使得这个模块变得灵活、可扩展、清晰简单。而另外一个不了解设计模式的B看到这个重构后，大叫&#8220;现在的代码太复杂了！我更喜欢原来的代码！&#8221;。而在作者教B学习和掌握了这个模式后，B勉强同意代码没有他原来以为的那么复杂，但仍然表示重构后的代码并不比原先的更好。
<p style="text-indent: 2em; line-height: 1.6em">是啊。作者对于设计模式的熟悉，使得在作者的眼里这个重构是优秀的。这件事说明，人们对于模式的熟悉程度决对于他们如何看待基于模式的重构将起到决定性的作用。因此，开发小组需要进一步学习这些模式，另一方面，某些模式的实现确实会使得代码过分复杂，如果出现这种情况，返回或者进一步重构就非常有必要。 </p>
<img src ="http://www.cnblogs.com/ILove/aggbug/1139686.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43808/" target="_blank">[新闻]Firefox遭“独家”恶意软件攻击</a><br/><a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;<a href="http://space.cnblogs.com" target="_blank">社区</a>&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻频道</a>&nbsp;<a href="http://space.cnblogs.com/group.htm" target="_blank">小组</a>&nbsp;<a href="http://space.cnblogs.com/q" target="_blank">博问</a>&nbsp;<a href="http://wz.cnblogs.com/" target="_blank">网摘</a>&nbsp;<a href="http://space.cnblogs.com/ing" target="_blank">闪存</a>]]></description></item><item><title>在.Net中使用异步（二）</title><link>http://www.cnblogs.com/ILove/archive/2008/04/06/1139446.html</link><dc:creator>没有昵称</dc:creator><author>没有昵称</author><pubDate>Sun, 06 Apr 2008 12:09:00 GMT</pubDate><guid>http://www.cnblogs.com/ILove/archive/2008/04/06/1139446.html</guid><wfw:comment>http://www.cnblogs.com/ILove/comments/1139446.html</wfw:comment><comments>http://www.cnblogs.com/ILove/archive/2008/04/06/1139446.html#Feedback</comments><slash:comments>15</slash:comments><wfw:commentRss>http://www.cnblogs.com/ILove/comments/commentRss/1139446.html</wfw:commentRss><trackback:ping>http://www.cnblogs.com/ILove/services/trackbacks/1139446.html</trackback:ping><description><![CDATA[摘要: 在上一篇文章中，我们探讨了使用Thread类实现异步的方法。在整个过程中，可以发现Delegate这个东西出现了很多次。而仔细研究Delegate，我们发现每一个Delegate类型都自动产生了Invoke、BeginInvoke、EndInvoke等方法。而BeginInvoke、EndInvoke这两个方法，我们马上就可以猜到这是用来实现异步的～～那么我们现在就看一下怎样使用委托来实现异步。D&nbsp;&nbsp;<a href='http://www.cnblogs.com/ILove/archive/2008/04/06/1139446.html'>阅读全文</a><img src ="http://www.cnblogs.com/ILove/aggbug/1139446.html?type=1" width = "1" height = "1" /><br><br><a href="http://news.cnblogs.com/n/43806/" target="_blank">[新闻]Python 3.0正式发布</a