<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>windam.log &#187; Windows平台开发</title>
	<atom:link href="http://www.windameister.org/blog/category/programming/win32dev/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.windameister.org/blog</link>
	<description>learn, think, share, communication</description>
	<lastBuildDate>Fri, 04 Nov 2011 16:28:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>自动构建二：CruiseControl.NET配置基础</title>
		<link>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/</link>
		<comments>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/#comments</comments>
		<pubDate>Sun, 12 Jun 2011 08:24:56 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[自动构建]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/06/12/%e8%87%aa%e5%8a%a8%e6%9e%84%e5%bb%ba%e4%ba%8c%ef%bc%9acruisecontrol-net%e9%85%8d%e7%bd%ae%e5%9f%ba%e7%a1%80/</guid>
		<description><![CDATA[在软件工程领域，CruiseControl是一个基于Java的持续集成框架。它包含了大量与持续集成有关的功能，如：邮件通知，Ant自动构建，以及支持多种源码管理工具。可以利用Web页面观察当前和以往的构建流程。它允许我们对软件开发中的很多流程进行持续的自动化控制。 CruiseControl是一个自由，开源软件，使用BSD类型的license。最早的时候，该软件是由ThoughtWorks公司的员工开发出来用于该公司自身内部项目的，随后则变成了一个独立的应用软件。 CruiseControl.NET是CruiseControl的.NET版本。还有一个Ruby版本的CruiseControl，被称为CruiseControl.rb。 对于使用Windows平台，以及Visual Studio集成开发环境的团队来说，使用CruiseControl.NET的好处在于他整合了MSBuild，NAnt，VSS等微软平台下常用的软件开发中的工具，并且非常易于在.NET平台下对他的功能进行扩展。 我们可以利用MSBuild来编写构建脚本，利用CruiseControl.NET配置实现持续集成，每日构建，自动化测试，邮件通知等功能。 安装CruiseControl.NET需要先安装.NET Framework 2.0以及支持ASP.NET的Web server（比较典型的如开启了ASP.NET的IIS服务器） CruiseControl.NET提供了两种查看和管理构建流程的方式： 1. 通过Web Dashboard，也就是通过网页查看 2. 通过CCTray客户端（该客户端是一个常驻程序，可以随时点开查看当前的构建状况，并且可以在构建成功或失败后弹出提示） 安装CruiseControl.NET 从ThoughtWorks的网站下载当前最新的Release版本： http://confluence.public.thoughtworks.org/display/CCNET/Download 在服务器上安装该CCNet安装程序（服务器需要是Windows的，并且有.NET2.0运行环境与支持ASP.NET的WebServer） 配置CruiseControl.NET： 安装目录下有名为ccnet.config的xml格式的配置文件，用于配置当前的CCNet服务器。 对于一个比较简单的项目，我们的持续集成需求包括： 1. 从源代码管理服务器上取下最新的源码 2. 构建 3. 构建成功后，将文件发布到我们指定的目录 我们还可能会需要每日构建，每天夜里0:00开始一次所有项目的构建，并将结果发布到指定位置。 项目之间往往存在互相依赖，如果被依赖项发生了更改，应该有办法让所有依赖了该项目的项目都依次构建。 下面以一个简单的例子描述如何配置CruiseControl.net服务器。 打开ccnet.config文件，该文件是一个xml格式的文件，可以在配置文件中编写多个project，每个project都对应着一个&#60;project&#62; &#60;/project&#62;块。 &#60;cruisecontrol&#62; &#60;project name=”MyProj1”&#62; &#60;/project&#62; &#60;project name=”MyProj2”&#62; &#60;/project&#62; &#60;/cruisecontrol&#62; 每个Project块的配置类似下面的例子： &#60;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&#62; &#60;workingDirectory&#62;yourWorkingDirectory&#60;/workingDirectory&#62; &#60;artifactDirectory&#62;yourArtifactDirectory&#60;/artifactDirectory&#62; &#60;category&#62;Category 1&#60;/category&#62; &#60;webURL&#62;http://server1/ccnet/server/local/project/testProject/ViewLatestBuildReport.aspx&#60;/webURL&#62; &#60;modificationDelaySeconds&#62;2&#60;/modificationDelaySeconds&#62; &#60;maxSourceControlRetries&#62;5&#60;/maxSourceControlRetries&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>在软件工程领域，CruiseControl是一个基于Java的持续集成框架。它包含了大量与持续集成有关的功能，如：邮件通知，Ant自动构建，以及支持多种源码管理工具。可以利用Web页面观察当前和以往的构建流程。它允许我们对软件开发中的很多流程进行持续的自动化控制。</p>
<p>CruiseControl是一个自由，开源软件，使用BSD类型的license。最早的时候，该软件是由ThoughtWorks公司的员工开发出来用于该公司自身内部项目的，随后则变成了一个独立的应用软件。</p>
<p>CruiseControl.NET是CruiseControl的.NET版本。还有一个Ruby版本的CruiseControl，被称为CruiseControl.rb。</p>
<p>对于使用Windows平台，以及Visual Studio集成开发环境的团队来说，使用CruiseControl.NET的好处在于他整合了MSBuild，NAnt，VSS等微软平台下常用的软件开发中的工具，并且非常易于在.NET平台下对他的功能进行扩展。</p>
<p><span id="more-272"></span></p>
<p>我们可以利用MSBuild来编写构建脚本，利用CruiseControl.NET配置实现持续集成，每日构建，自动化测试，邮件通知等功能。</p>
<p>安装CruiseControl.NET需要先安装.NET Framework 2.0以及支持ASP.NET的Web server（比较典型的如开启了ASP.NET的IIS服务器）</p>
<p>CruiseControl.NET提供了两种查看和管理构建流程的方式：</p>
<p>1. 通过Web Dashboard，也就是通过网页查看</p>
<p>2. 通过CCTray客户端（该客户端是一个常驻程序，可以随时点开查看当前的构建状况，并且可以在构建成功或失败后弹出提示）</p>
<p>安装CruiseControl.NET</p>
<p>从ThoughtWorks的网站下载当前最新的Release版本：</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Download">http://confluence.public.thoughtworks.org/display/CCNET/Download</a></p>
<p>在服务器上安装该CCNet安装程序（服务器需要是Windows的，并且有.NET2.0运行环境与支持ASP.NET的WebServer）</p>
<p>配置CruiseControl.NET：</p>
<p>安装目录下有名为ccnet.config的xml格式的配置文件，用于配置当前的CCNet服务器。</p>
<p>对于一个比较简单的项目，我们的持续集成需求包括：</p>
<p>1. 从源代码管理服务器上取下最新的源码</p>
<p>2. 构建</p>
<p>3. 构建成功后，将文件发布到我们指定的目录</p>
<p>我们还可能会需要每日构建，每天夜里0:00开始一次所有项目的构建，并将结果发布到指定位置。</p>
<p>项目之间往往存在互相依赖，如果被依赖项发生了更改，应该有办法让所有依赖了该项目的项目都依次构建。</p>
<p>下面以一个简单的例子描述如何配置CruiseControl.net服务器。</p>
<p>打开ccnet.config文件，该文件是一个xml格式的文件，可以在配置文件中编写多个project，每个project都对应着一个&lt;project&gt; &lt;/project&gt;块。</p>
<p>&lt;cruisecontrol&gt;</p>
<p>&lt;project name=”MyProj1”&gt;</p>
<p>&lt;/project&gt;</p>
<p>&lt;project name=”MyProj2”&gt;</p>
<p>&lt;/project&gt;</p>
<p>&lt;/cruisecontrol&gt;</p>
<p>每个Project块的配置类似下面的例子：</p>
<p>&lt;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&gt;</p>
<p>&lt;workingDirectory&gt;yourWorkingDirectory&lt;/workingDirectory&gt;</p>
<p>&lt;artifactDirectory&gt;yourArtifactDirectory&lt;/artifactDirectory&gt;</p>
<p>&lt;category&gt;Category 1&lt;/category&gt;</p>
<p>&lt;webURL&gt;http://server1/ccnet/server/local/project/testProject/ViewLatestBuildReport.aspx&lt;/webURL&gt;</p>
<p>&lt;modificationDelaySeconds&gt;2&lt;/modificationDelaySeconds&gt;</p>
<p>&lt;maxSourceControlRetries&gt;5&lt;/maxSourceControlRetries&gt;</p>
<p>&lt;initialState&gt;Stopped&lt;/initialState&gt;</p>
<p>&lt;startupMode&gt;UseInitialState&lt;/startupMode&gt;</p>
<p>&lt;triggers&gt;</p>
<p>&lt;!&#8211;yourFirstTriggerType .. &#8211;&gt;</p>
<p>&lt;!&#8211;yourOtherTriggerType .. &#8211;&gt;</p>
<p>&lt;/triggers&gt;</p>
<p>&lt;!&#8211; state type=&#8221;yourStateManagerType&#8221; .. &#8211;&gt;</p>
<p>&lt;!&#8211; sourcecontrol type=&#8221;yourSourceControlType&#8221; .. &#8211;&gt;</p>
<p>&lt;!&#8211; labeller type=&#8221;yourLabellerType&#8221; .. &#8211;&gt;</p>
<p>&lt;prebuild&gt;</p>
<p>&lt;!&#8211; yourFirstPrebuildTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherPrebuildTask .. &#8211;&gt;</p>
<p>&lt;/prebuild&gt;</p>
<p>&lt;tasks&gt;</p>
<p>&lt;!&#8211; yourFirstTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherTask .. &#8211;&gt;</p>
<p>&lt;/tasks&gt;</p>
<p>&lt;publishers&gt;</p>
<p>&lt;!&#8211; yourFirstPublisherTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherPublisherTask .. &#8211;&gt;</p>
<p>&lt;/publishers&gt;</p>
<p>&lt;externalLinks&gt;</p>
<p>&lt;externalLink name=&#8221;My First Link&#8221; url=&#8221;http://somewhere/&#8221; /&gt;</p>
<p>&lt;externalLink name=&#8221;My Other Link&#8221; url=&#8221;http://somewhere.else/&#8221; /&gt;</p>
<p>&lt;/externalLinks&gt;</p>
<p>&lt;parameters&gt;</p>
<p>&lt;textParameter name=&#8221;Build Name&#8221; default=&#8221;Unknown&#8221; /&gt;</p>
<p>&lt;/parameters&gt;</p>
<p>&lt;linkedSites&gt;</p>
<p>&lt;namedValue name=&#8221;ohloh&#8221; value=&#8221;5623&#8243; /&gt;</p>
<p>&lt;/linkedSites&gt;</p>
<p>&lt;/project&gt;</p>
<p>具体的属性含义可以参考文档：</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block">http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block</a></p>
<p>对于Project块，有几个比较重要的内容值得详细说明：</p>
<p>1. Queue</p>
<p>&lt;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&gt;</p>
<p>这里有一个queue属性，以及对应的queuePriority属性。说明的是该project被放置于哪个queue中，如果不加指明，那么每个project都会被放置到一个独立的queue中。</p>
<p>Queue的作用是什么？</p>
<p>想象一下存在两个项目A和B，并且B依赖A。如果正在构建B的时候，trigger又启动了一个Build开始构建A，但是由于B依赖A，因此在构建B的过程中，需要打开A的目标文件进行读取（例如B使用A的lib文件进行link）。如果A此时恰好要写入该目标文件，则会发生文件访问冲突。进而造成A的构建流程失败。</p>
<p>我们想到或许可以将B依赖的文件拷贝一份出来，从而避免出现使用文件和写入文件冲突的情况，但是很快我们就会意识到这也行不通——因为可能我们在拷贝文件的时候（这里需要打开并读取A的输出文件），遇到了A正要写入文件的状况。</p>
<p>总结一下，对于相互之间存在依赖的项目，我们不应该让他们不受控制的并发构建，而是必须要有一个先后顺序——这里就是Queue的用处所在。</p>
<p>CruiseControl工作的时候，是以queue为单位进行的，当trigger或者人工通过force build触发了一次构建，这次构建会被置放到queue中，并且根据queuePriority决定被插入的位置。</p>
<p>这样，可以保证一系列相互之间存在依赖关系的项目在一个队列中依次进行Build，避免因相互依赖项目同时构建而导致的文件读写冲突等问题。</p>
<p>2. Triggers</p>
<p>触发器是实现我们的各种构建需求的工具。比如，每天夜里0:00产生一次Nightly Build，比如每次Check in之后都进行一次Continuous Integration Build，比如基础项目构建成功了，应当引发所有的依赖该基础项的项目都产生一次构建。等等。</p>
<p>要实现上述的功能，我们可以使用CruiseControl.NET提供的诸多类型的trigger达到目的，或者选择其中若干进行组合使用。</p>
<p>最常用的trigger之一是<a href="http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger">Interval Trigger</a>，我们可以设置Interval的间隔时间，每隔这么长的时间，触发器就会被触发一次。配合SCM工具对源代码的检查，我们可以利用Interval Trigger实现持续集成。（每隔1分钟触发一次，检查源码仓库是否有新checkin的代码，如果有则Build）</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Project+Trigger">Project Trigger</a>可以使得当前项目获知依赖项目构建成功的消息，当制定的Project构建成功之后，Trigger会被触发</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Multiple+Trigger">Multiple Trigger</a>用于将多个Trigger用逻辑关系组合起来使用，例如，当使用And逻辑进行组合时，Multiple Trigger中只要有一个Trigger认为不应当触发构建，就不会被触发构建。</p>
<p>3. Tasks</p>
<p>当Trigger触发之后，就会开始构建流程，从而将Tasks块的所有Task依次执行一遍。</p>
<p>我们需要使用Task来完成主要的构建流程。上篇文章中曾经简单介绍过MSBuild，到了这里，我们就利用MSBuild来实现自动构建了。</p>
<p>&lt;msbuild&gt;</p>
<p>&lt;executable&gt;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe&lt;/executable&gt;</p>
<p>&lt;workingDirectory&gt;C:\dev\ccnet&lt;/workingDirectory&gt;</p>
<p>&lt;projectFile&gt;build.xml&lt;/projectFile&gt;</p>
<p>&lt;targets&gt;Build;Test&lt;/targets&gt;</p>
<p>&lt;timeout&gt;900&lt;/timeout&gt;</p>
<p>&lt;logger&gt;C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll&lt;/logger&gt;</p>
<p>&lt;/msbuild&gt;</p>
<p>最简单的利用MSBuild的方式如上例所示。</p>
<p>projectFile是预先编写好的MSBuild的脚本文件，targets里面填写的是要执行MSBuild脚本中的哪个Target。（关于Target，可以参考此前总结的MSBuild简介里的相关内容，以及MSBuild自身的文档）timeout时间是指此次build如果超过多长时间则认为是超时失败。</p>
<p>4. Publishers</p>
<p>Publisher用于在构建流程走完之后将最终的结果发布出去。Publisher中可以有一系列的Task，与Tasks块中的Task的用法类似，包括调用MSBuild Task，在MSBuild脚本中执行操作等。</p>
<p>我们可以利用Publisher模块，将最新版本的SDK发布到服务器，或是创建提供给用户的安装包等。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>自动构建一：MSBuild基础</title>
		<link>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/</link>
		<comments>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/#comments</comments>
		<pubDate>Sun, 12 Jun 2011 08:24:22 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[自动构建]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/06/12/%e8%87%aa%e5%8a%a8%e6%9e%84%e5%bb%ba%e4%b8%80%ef%bc%9amsbuild%e5%9f%ba%e7%a1%80/</guid>
		<description><![CDATA[MSBuild是微软自Visual Studio 2005开始提供的构建平台，既可以用于VC#项目，也可用于VC++项目。 通常情况下，我们采用IDE本身进行项目的构建，有时，也会采用命令行方式调用IDE程序Build我们的项目。（对于VC++项目来说，就是通过命令行调用devenv进行构建） 然而，当我们希望在持续集成工具当中实现项目的自动构建时，尤其是当依赖情况或者部署情况较为复杂时，我们希望有一个简单、易维护的工具实现构建——MSBuild就是我们所需要的工具。 采用MSBuild进行构建，需要我们编写MSBuild脚本——一种规定格式的XML文件。 MSBuild基本组成部分包括：Target（目标），ItemGroup（项），PropertyGroup（属性），Task（任务）。 其中：Target目标是MSBuild执行的基本单元。 我们在命令行中调用MSBuild /t:TargetName指定需要执行的目标。 每个Target中可以包含若干Task。 Task是基本的功能单元：例如拷贝文件，删除文件，创建/删除目录，使用Exec调用外部程序，调用VCBuild构建.vcproj项目，调用Csc编译.cs文件，等等。此外，你还可以通过实现ITask接口，创建自定义的Task。 PropertyGroup作为属性，起着类似于变量一样的作用，可以定义一个属性，并在后续的位置使用该属性，例如： &#60;PropertyGroup&#62; &#60;ProjFile&#62;C:\Test\test.vcproj&#60;/ProjFile&#62; &#60;Rebuild&#62;true&#60;/Rebuild&#62; &#60;/PropertyGroup&#62; &#60;Target Name=”Build”&#62; &#60;VCBuild     Projects=”$(ProjFile)”     Rebuild=”$(Rebuild)”    /&#62; &#60;/Target&#62; ItemGroup往往作为输入，例如： &#60;ItemGroup&#62; &#60;IncFiles Include=”C:\Test\*.h” /&#62; &#60;/ItemGroup&#62; &#60;Target Name=”Build”&#62; &#60;Copy    SourceFiles=”@(IncFiles)”    DestinationFolder=”c:\output\include” /&#62; &#60;/Target&#62; &#160; 实例： 假设我们有一个项目A，依赖一个动态连接库B，我们需要先构建B，将B的头文件和lib文件拷贝到A的包含头文件路径和Lib路径下，再构建A，然后将A发布到指定的路径下。 B的项目结构如下： C:\B\B.vcproj A的项目结构如下： C:\A \DepInclude \DepLib \Src\A.vcproj \Output 对应的MSBuild脚本如下： &#60;ItemGroup&#62; &#60;BIncFiles Include=”C:\B\*.h” /&#62; &#60;BLibFiles Include=”C:\B\Release\*.lib” [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://msdn.microsoft.com/zh-cn/library/ms171451(v=vs.80).aspx">MSBuild</a>是微软自Visual Studio 2005开始提供的构建平台，既可以用于VC#项目，也可用于VC++项目。</p>
<p>通常情况下，我们采用IDE本身进行项目的构建，有时，也会采用命令行方式调用IDE程序Build我们的项目。（对于VC++项目来说，就是通过命令行调用devenv进行构建）</p>
<p>然而，当我们希望在持续集成工具当中实现项目的自动构建时，尤其是当依赖情况或者部署情况较为复杂时，我们希望有一个简单、易维护的工具实现构建——MSBuild就是我们所需要的工具。</p>
<p><span id="more-271"></span></p>
<p>采用MSBuild进行构建，需要我们编写MSBuild脚本——一种规定格式的XML文件。</p>
<p>MSBuild基本组成部分包括：Target（目标），ItemGroup（项），PropertyGroup（属性），Task（任务）。</p>
<p>其中：Target目标是MSBuild执行的基本单元。</p>
<p>我们在命令行中调用MSBuild /t:TargetName指定需要执行的目标。</p>
<p>每个Target中可以包含若干Task。</p>
<p>Task是基本的功能单元：例如拷贝文件，删除文件，创建/删除目录，使用Exec调用外部程序，调用VCBuild构建.vcproj项目，调用Csc编译.cs文件，等等。此外，你还可以通过实现ITask接口，创建自定义的Task。</p>
<p>PropertyGroup作为属性，起着类似于变量一样的作用，可以定义一个属性，并在后续的位置使用该属性，例如：</p>
<div class="codearea">
<pre>&lt;PropertyGroup&gt;
   &lt;ProjFile&gt;C:\Test\test.vcproj&lt;/ProjFile&gt;
   &lt;Rebuild&gt;true&lt;/Rebuild&gt;
&lt;/PropertyGroup&gt;

&lt;Target Name=”Build”&gt;
   &lt;VCBuild     Projects=”$(ProjFile)”     Rebuild=”$(Rebuild)”    /&gt;
&lt;/Target&gt;</pre>
</div>
<p>ItemGroup往往作为输入，例如：</p>
<div class="codearea">
<pre>&lt;ItemGroup&gt;
   &lt;IncFiles Include=”C:\Test\*.h” /&gt;
&lt;/ItemGroup&gt;

&lt;Target Name=”Build”&gt;
   &lt;Copy    SourceFiles=”@(IncFiles)”    DestinationFolder=”c:\output\include”
/&gt;
&lt;/Target&gt;</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->&nbsp;</p>
<p>实例：</p>
<p>假设我们有一个项目A，依赖一个动态连接库B，我们需要先构建B，将B的头文件和lib文件拷贝到A的包含头文件路径和Lib路径下，再构建A，然后将A发布到指定的路径下。</p>
<p>B的项目结构如下：</p>
<p>C:\B\B.vcproj</p>
<p>A的项目结构如下：</p>
<p>C:\A</p>
<p>\DepInclude</p>
<p>\DepLib</p>
<p>\Src\A.vcproj</p>
<p>\Output</p>
<p>对应的MSBuild脚本如下：</p>
<div class="codearea">
<pre>&lt;ItemGroup&gt;
   &lt;BIncFiles Include=”C:\B\*.h” /&gt;
   &lt;BLibFiles Include=”C:\B\Release\*.lib” /&gt;
   &lt;BDllFiles Include=”C:\B\Release\*.dll” /&gt;
&lt;/ItemGroup&gt;

&lt;TargetName=”Build_B”&gt;
    &lt;VCBuild
      Projects=”C:\B\B.vcproj”
      Configuration=”Release”
      Rebuild=”true”     /&gt;
&lt;Copy   SourceFiles=”@(BIncFiles)”
  DestinationFolder=”C:\A\DepInclude”
/&gt;
&lt;Copy   SourceFiles=”@(BLibFiles)”
  DestinationFolder=”C:\A\DepLib”
/&gt;
&lt;/Target&gt;

&lt;Target Name=“Build_A” DependOnTargets=“Build_B”&gt;
    &lt;VCBuild
      Projects=”C:\A\Src\A.vcproj“
      Configuration=“Release”
      Rebuild=”true”
    /&gt;
&lt;/Target&gt;</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->&nbsp;</p>
<p>Build_A的Target使用了一个DependOnTargets属性，该属性的含义是当前Target要依赖前续的某些Targets，那些依赖的Targets必须先执行，然后才能执行当前的Target。</p>
<p>在本例中Build_B会rebuild整个B工程，并将结果的头文件和lib文件拷贝到A项目的依赖路径当中。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从C++到C#——穿行于Native与.Net 托管代码之间</title>
		<link>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/</link>
		<comments>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/#comments</comments>
		<pubDate>Sun, 19 Dec 2010 09:52:55 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[C++ & Design]]></category>
		<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[托管代码]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=245</guid>
		<description><![CDATA[微软在.Net平台中提供了大量丰富的语言，库，并为之提供了非常强大的开发工具。借用.Net平台，我们可以非常容易的开发基于Windows的应用，与采用C++相比，使用C#语言开发相同功能的软件可以极大的提升开发效率，缩短开发周期，减少Bug。 然而，往往现存有大量的积累代码是基于原生语言开发的，当我们决定采用C#时，必须考虑到与现存的软件组件交互的能力，具体而言——如何在一个以C++语言为主的项目中引入.NET组件？如何在.NET组件中使用C++代码库提供的功能？ 在游戏项目中，比较理想的情况是底层的3D引擎采用C++编写，而上层编辑器则采用C#编写，这样可以兼顾引擎的运行效率和编辑器的开发效率。一个值得一提的案例是UDK（Unreal引擎的开发包），其中就采用了C++语言与.NET组件相结合的模式，其界面上相当一部分新功能是采用.NET开发的。 下面介绍下我总结的C++与.NET互操作的方法： 1. C++ –&#62; .NET在C++语言中调用.NET 由于.NET组件被设计成COM兼容格式，我们可以像使用COM组件一样使用.NET组件。也就是说，我们可以创建.NET组件中的COM对象，并获取接口来使用它。 值得注意的是使用.NET编译出的dll模块，与采用Native语言构建的ActiveX模块或者其他COM DLL是有差别的，你无法使用regsvr32对其进行注册（没有导出DllRegisterServer, DllUnregisterServer）。 &#160; 如果希望在C++中调用.NET代码，那么在实现.NET组件时，需要设置ComVisible属性，具体而言，就是在Properties\AssemblyInfo.cs 中，将[assembly: ComVisible(false)]（默认）属性，更改为： [assembly: ComVisible(true)]，这样才能使用COM的方式访问到.NET组件中的接口。 将需要暴露给Native代码使用的功能抽象成接口，在.NET中实现该接口，并为之提供Guid（可以采用VC2005中的工具Tools/Create Guid），这样才能在Native代码中使用该接口，并用Guid创建这个接口的实例。例如下面这样： namespace XDll { [Guid("1DE25FAA-4AA1-4819-B1FF-0D507567685E ")] public interface IXSystem { } [Guid("603F977B-34CF-40ca-BB30-B6BC25D2A370")] public class ImplXSystem : IXSystem { } } 在.NET组件构建完成后，需要使用regasm工具，将其中的COM接口导出成tlb文件，以供给C++代码使用。Regasm工具通常安装在.NET Framework的目录下，例如： &#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; 命令行如下： &#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; X.dll /tlb:X.tlb 这里注意一点，安装.NET framework并不会自动将上述路径加入到系统路径中，因此运行regasm时需要键入完整的路径。 在Native代码中调用.NET代码时（VC2005开发环境），需要做以下几件事： #import &#60;mscorlib.tlb&#62; raw_interfaces_only #import “X.tlb” no_namespace [...]]]></description>
			<content:encoded><![CDATA[<p>微软在.Net平台中提供了大量丰富的语言，库，并为之提供了非常强大的开发工具。借用.Net平台，我们可以非常容易的开发基于Windows的应用，与采用C++相比，使用C#语言开发相同功能的软件可以极大的提升开发效率，缩短开发周期，减少Bug。</p>
<p>然而，往往现存有大量的积累代码是基于原生语言开发的，当我们决定采用C#时，必须考虑到与现存的软件组件交互的能力，具体而言——如何在一个以C++语言为主的项目中引入.NET组件？如何在.NET组件中使用C++代码库提供的功能？</p>
<p>在游戏项目中，比较理想的情况是底层的3D引擎采用C++编写，而上层编辑器则采用C#编写，这样可以兼顾引擎的运行效率和编辑器的开发效率。一个值得一提的案例是<a href="http://www.udk.com/">UDK</a>（Unreal引擎的开发包），其中就采用了C++语言与.NET组件相结合的模式，其界面上相当一部分新功能是采用.NET开发的。</p>
<p><span id="more-245"></span></p>
<p>下面介绍下我总结的C++与.NET互操作的方法：</p>
<p><strong>1. C++ –&gt; .NET在C++语言中调用.NET</strong></p>
<p>由于.NET组件被设计成COM兼容格式，我们可以像使用COM组件一样使用.NET组件。也就是说，我们可以创建.NET组件中的COM对象，并获取接口来使用它。</p>
<p>值得注意的是使用.NET编译出的dll模块，与采用Native语言构建的ActiveX模块或者其他COM DLL是有差别的，你无法使用regsvr32对其进行注册（没有导出DllRegisterServer, DllUnregisterServer）。</p>
<p>&nbsp;</p>
<p>如果希望在C++中调用.NET代码，那么在实现.NET组件时，需要设置ComVisible属性，具体而言，就是在Properties\AssemblyInfo.cs 中，将[assembly: ComVisible(false)]（默认）属性，更改为：</p>
<p>[assembly: ComVisible(true)]，这样才能使用COM的方式访问到.NET组件中的接口。</p>
<p>将需要暴露给Native代码使用的功能抽象成接口，在.NET中实现该接口，并为之提供Guid（可以采用VC2005中的工具Tools/Create Guid），这样才能在Native代码中使用该接口，并用Guid创建这个接口的实例。例如下面这样：</p>
<div class="codearea">
<pre><span style="color: blue;">namespace </span><span style="color: #010001;">XDll
</span>{
    [<span style="color: #010001;">Guid</span>(<span style="color: #a31515;">"1DE25FAA-4AA1-4819-B1FF-0D507567685E "</span>)]
    <span style="color: blue;">public interface </span><span style="color: #010001;">IXSystem
    </span>{

    }

    [<span style="color: #010001;">Guid</span>(<span style="color: #a31515;">"603F977B-34CF-40ca-BB30-B6BC25D2A370"</span>)]
    <span style="color: blue;">public class </span><span style="color: #010001;">ImplXSystem </span>: <span style="color: #010001;">IXSystem
    </span>{ 

    }
}</pre>
</div>
<p>在.NET组件构建完成后，需要使用<a href="http://msdn.microsoft.com/zh-cn/library/tzat5yw6(v=vs.80).aspx">regasm</a>工具，将其中的COM接口导出成tlb文件，以供给C++代码使用。Regasm工具通常安装在.NET Framework的目录下，例如：</p>
<p>&#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221;</p>
<p>命令行如下：</p>
<p>&#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; X.dll /tlb:X.tlb</p>
<p>这里注意一点，安装.NET framework并不会自动将上述路径加入到系统路径中，因此运行regasm时需要键入完整的路径。</p>
<p>在Native代码中调用.NET代码时（VC2005开发环境），需要做以下几件事：</p>
<p>#import &lt;mscorlib.tlb&gt; raw_interfaces_only</p>
<p>#import “X.tlb” no_namespace named_guids</p>
<p>这两行将一些必要的接口导入，X.tlb是前述步骤中使用regasm工具导出的文件，其中包含了使用C++语言描述的.NET组件中的接口。</p>
<p>通常情况下，Visual Studio会在$CURRENTUSER$\local settings\temp\目录下生成一个后缀名为.tlh的文件，其中包含了.NET库导出的诸多CLSID_X以及IID_X等GUID，以及各COM接口，C++代码可以直接使用该文件中的GUID与接口声明。</p>
<p>接下来使用.NET组件接口与使用COM接口基本相同：</p>
<div class="codearea">
<pre><span style="color: #010001;">CoInitialize</span>(<span style="color: #010001;">NULL</span>); <span style="color: green;">// 初?始?化?COM相?关?

</span><span style="color: #010001;">IX</span>* <span style="color: #010001;">pIX</span>;
<span style="color: #010001;">CoCreateInstance</span>(<span style="color: #010001;">CLSID_X
                 </span>, <span style="color: #010001;">NULL
                 </span>, <span style="color: #010001;">CLSCTX_INPROC_SERVER
                 </span>, <span style="color: #010001;">IID_IX
                 </span>, <span style="color: blue;">reinterpret_cast</span>&lt;<span style="color: blue;">void</span>**&gt;(&amp;<span style="color: #010001;">pIX</span>));</pre>
</div>
<p>&nbsp;</p>
<p>注意这里的CLSID前缀的Guid与IID前缀的Guid的区别，前者是实现类的Guid，后者是接口的Guid，我们这里调用CoCreateInstance的意义在于，创建一个Guid为CLSID_X的实现类，并返回给我一个Guid为IID_IX的接口，我将会以这个接口去访问该实现类。</p>
<p>这里的CLSID_X与IID_IX都是在导出tlb文件时自动生成的GUID数据结构，可以在Native代码中直接使用。</p>
<p>当使用了.NET组件的程序被发布时，需要将上述tlb中的各种ID注册到目标机器的注册表的相应键值中。比如，CLSID就需要导入到目标机器的注册表的HKEY_CLASSES_ROOT\CLSID键中。这一步可以通过regasm的/regfile功能实现，该功能可以导出所有需要的信息到注册表中。</p>
<p>最后当使用完毕之后，记得需要调用创建的COM接口的Release()方法。</p>
<p>pIX-&gt;Release();</p>
<p>以及清理：</p>
<p>CoUninitialize();</p>
<p><strong>2. .NET组件调用Native代码(C++)</strong></p>
<p>由于.NET组件是出现于C++之后，微软在设计时就已经基于COM模型设计其二进制接口，因此很容易可以让以前的原生语言来调用，而C++语言则诞生得更早，没有定义二进制标准，要想让.NET组件调用，只能把自己实现成COM组件的模式。关于如何用Native语言实现一个COM组件，可以参考更加专业的参考书：<a href="http://book.douban.com/subject/1231596/">《COM技术内幕》</a>，<a href="http://book.douban.com/subject/1231481/">《COM本质论》</a>等。本文主要提及在实现COM接口时值得考虑的两种思路。</p>
<p>根据实际应用场景的不同，我们可能面临不同的技术选择：</p>
<p>场景1： <strong>.NET组件依赖于C++库，完全基于C++库构建，并且C++模块不会反向依赖.NET组件</strong></p>
<p>这种情形下，由于依赖结构简单，我们可以将整个C++库采用C++CLI封装，有一些开源库采用了这种做法，比如<a href="http://slimdx.org/">SlimDX</a>项目（这是一个为了可以在.NET中使用DirectX的项目，将DirectX采用C++CLI封装，最终提供了一套面向.NET的开发框架。）再如ManagedOgre或者OgreDotNet，都将Ogre引擎封装在C++CLI中从而提供在.NET中操作OGRE的方案。</p>
<p>场景2： <strong>.NET组件依赖于C++模块，C++模块又依赖.NET组件</strong></p>
<p>.NET中需要调用底层的一些服务做一些事情，例如采用底层接口绘制一个三维场景，置放于.NET模块的窗口中。同时可能C++模块又要依赖.NET组件，比如一个.NET控件需要在一个以C++语言为主开发的编辑器中被调用。</p>
<p>形成类似于下面这样的关系，Native Code与Managed Code互相依赖：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/12/image.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;" title="image" src="http://www.windameister.org/blog/wp-content/uploads/2010/12/image_thumb.png" border="0" alt="image" width="547" height="147" /></a></p>
<p>这里，我们可以将.NET对C++模块的依赖提取成一个.NET接口，而后C++代码实现该接口，从而消除.NET对C++代码的直接依赖，将之转变成C++模块对.NET接口的依赖。这是一种在重构中经常使用的手法。</p>
<p>举一个简单的例子来说明这个问题：</p>
<div class="codearea">
<pre><span style="color: blue;">namespace </span><span style="color: #010001;">XDLL
</span>{
    <span style="color: blue;">interface </span><span style="color: #010001;">XInterface
    </span>{
        <span style="color: blue;">void </span><span style="color: #010001;">Foo</span>();
    };

    <span style="color: blue;">public class </span><span style="color: #010001;">XModule
    </span>{
        <span style="color: blue;">private </span><span style="color: #010001;">XInterface ix</span>;

        <span style="color: #010001;">XModule</span>(<span style="color: #010001;">XInterface ix</span>)
        {
            <span style="color: blue;">this</span>.<span style="color: #010001;">ix </span>= <span style="color: #010001;">ix</span>;
        }

        <span style="color: blue;">void </span><span style="color: #010001;">Foo</span>()
        {
            <span style="color: blue;">this</span>.<span style="color: #010001;">ix</span>.<span style="color: #010001;">Foo</span>();
        }
    }
}</pre>
</div>
<p>假设我们的C#模块中有一个类XModule，需要使用C++代码提供的某种功能，我们不直接调用C++代码（这产生一个.NET到C++的直接依赖），而是调用一个自己定义的接口XInterface，这个接口会被暴露给外部，C++代码实现该接口，并在初始化过程中传入.NET组件。</p>
<p>将上述依赖关系变成下面这样：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/12/image1.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;" title="image" src="http://www.windameister.org/blog/wp-content/uploads/2010/12/image_thumb1.png" border="0" alt="image" width="537" height="270" /></a></p>
<p>## 理想的情况当然是引擎用C++实现，而后底层提供一个C++CLI的封装，这样编辑器以及上层控件都采用C#开发，既可以兼顾引擎部分的运行效率与编辑器的开发效率，这样就可以保证依赖关系始终是上层编辑器直接依赖引擎。但是如果遇到某些现实状况，例如编辑器已经由C++开发，后期的部分控件界面期望采用C#开发，这样就需要采用类似于上文中的方案，将依赖关系反过来处理。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>windows的消息队列与消息循环#1</title>
		<link>http://www.windameister.org/blog/2010/03/27/windows-message-queue-and-message-loop/</link>
		<comments>http://www.windameister.org/blog/2010/03/27/windows-message-queue-and-message-loop/#comments</comments>
		<pubDate>Sat, 27 Mar 2010 11:45:55 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[消息循环]]></category>
		<category><![CDATA[消息队列]]></category>
		<category><![CDATA[线程]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2010/03/27/windows%e7%9a%84%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e4%b8%8e%e6%b6%88%e6%81%af%e5%be%aa%e7%8e%af/</guid>
		<description><![CDATA[在Windows操作系统中，窗口是一种User Object，隶属于创建它的线程。如果创建窗口的线程结束，则操作系统会自动删除窗口。建立窗口的线程，必须是为窗口处理所有消息的线程，如果你创建了一个后台线程，希望更新界面，那么只能通过消息的形式去通知窗口，并由拥有窗口的线程在窗口的消息处理函数中做出处理。 1、Windows的消息队列与消息循环 所有创建了窗口的Windows程序，都需要运行一个消息循环，我们在无数的Windows编程书籍中都可以看到这样的经典代码： 1: while (GetMessage(&#38;msg, hWnd, 0, 0) &#62; 0) 2: { 3: TranslateMessage(&#38;msg); 4: DispatchMessage(&#38;msg); 5: } 这里的hWnd就是创建的窗口句柄，上述循环会不断的把该窗口(hWnd)相关的消息取出来，并分发到消息处理函数当中。 GetMessage函数是用来获取当前线程消息队列当中的消息的，其中的第二个参数如果传递一个窗口句柄，那么就会获取该窗口相关的消息，如果传NULL，那么会将线程消息队列中所有的消息都取出来。如果创建了多个窗口，而只对其中一个窗口句柄调用GetMessage形成消息循环，那么别的窗口都会毫无响应。 这里需要补充说明一个概念：消息队列是操作系统为每个需要处理消息的线程创建的，任何线程只要调用过与消息有关的函数（如，GetMessage，PeekMessage），操作系统就会为该线程创建消息队列。可能有人会问，那没有窗口的线程，操作系统也有必要为其创建消息队列么？这是有可能的，因为我们可以通过诸如PostThreadMessage的Api向别的线程或者本线程发送消息，如果目标线程没有消息队列，会导致这个函数返回失败。 做一个验证上述想法实验： 我们撰写类似于如下代码（所有的示例代码我都已经在Windows7系统上编写程序做过实验，这里忽略与要说明内容无关的细节并不影响对原理的理解，就不再将完整代码附上，读者感兴趣的话可以自行实验） 1: HWND hWnd1 = CreateWindow(...); 2: HWND hWnd2 = CreateWindow(...); 3: while (GetMessage(&#38;msg, hWnd1, 0, 0) &#62; 0) 4: { 5: TranslateMessage(&#38;msg); 6: DispatchMessage(&#38;msg); 7: } 上述程序创建了两个窗口，但是消息循环只传递了其中一个窗口的句柄，那么我们看到的两个窗口中，hWnd2所属的窗口会毫无响应——无法移动也无法关闭，就好象死了一样，另一个hWnd1则可以正常拖动，关闭（这是由于我们将hWnd1的消息取出来并分派处理了）。 如果我们将消息循环稍作改动，GetMessage的参数不再传递某一个窗口的句柄，而是传递NULL： 1: while [...]]]></description>
			<content:encoded><![CDATA[<p>在Windows操作系统中，窗口是一种<a href="http://msdn.microsoft.com/en-us/library/ms725486(v=vs.85).aspx">User Object</a>，隶属于创建它的线程。如果创建窗口的线程结束，则操作系统会自动删除窗口。建立窗口的线程，必须是为窗口处理所有消息的线程，如果你创建了一个后台线程，希望更新界面，那么只能通过消息的形式去通知窗口，并由拥有窗口的线程在窗口的消息处理函数中做出处理。</p>
<p>1、Windows的消息队列与消息循环</p>
<p>所有创建了窗口的Windows程序，都需要运行一个消息循环，我们在无数的Windows编程书籍中都可以看到这样的经典代码：</p>
<div class="codearea linewrap">
<pre class="alt"><span class="lnum">1:  </span><span style="color: blue;">while </span>(<span style="color: #010001;">GetMessage</span>(&amp;<span style="color: #010001;">msg</span>, <span style="color: #010001;">hWnd</span>, 0, 0) &gt; 0)</pre>
<pre><span class="lnum">2:  </span>{</pre>
<pre class="alt"><span class="lnum">3:  </span>    <span style="color: #010001;">TranslateMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre><span class="lnum">4:  </span>    <span style="color: #010001;">DispatchMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre class="alt"><span class="lnum">5:  </span>}</pre>
</div>
<p>这里的hWnd就是创建的窗口句柄，上述循环会不断的把该窗口(hWnd)相关的消息取出来，并分发到消息处理函数当中。</p>
<p>GetMessage函数是用来获取<strong>当前线程消息队列</strong>当中的消息的，其中的第二个参数如果传递一个窗口句柄，那么就会获取该窗口相关的消息，如果传NULL，那么会将线程消息队列中所有的消息都取出来。如果创建了多个窗口，而只对其中一个窗口句柄调用GetMessage形成消息循环，那么别的窗口都会毫无响应。</p>
<p>这里需要补充说明一个概念：<strong>消息队列是操作系统为每个需要处理消息的线程创建的</strong>，任何线程只要调用过与消息有关的函数（如，GetMessage，PeekMessage），操作系统就会为该线程创建消息队列。可能有人会问，那没有窗口的线程，操作系统也有必要为其创建消息队列么？这是有可能的，因为我们可以通过诸如PostThreadMessage的Api向别的线程或者本线程发送消息，如果目标线程没有消息队列，会导致这个函数返回失败。<span id="more-185"></span></p>
<p>做一个验证上述想法实验：</p>
<p>我们撰写类似于如下代码（所有的示例代码我都已经在Windows7系统上编写程序做过实验，这里忽略与要说明内容无关的细节并不影响对原理的理解，就不再将完整代码附上，读者感兴趣的话可以自行实验）</p>
<div class="codearea linewrap">
<pre class="alt"><span class="lnum">1:  </span><span style="color: #010001;">HWND hWnd1 </span>= <span style="color: #010001;">CreateWindow</span>(...);</pre>
<pre><span class="lnum">2:  </span><span style="color: #010001;">HWND hWnd2 </span>= <span style="color: #010001;">CreateWindow</span>(...);</pre>
<pre class="alt"><span class="lnum">3:  </span><span style="color: blue;">while </span>(<span style="color: #010001;">GetMessage</span>(&amp;<span style="color: #010001;">msg</span>, <span style="color: #010001;">hWnd1</span>, 0, 0) &gt; 0)</pre>
<pre><span class="lnum">4:  </span>{</pre>
<pre class="alt"><span class="lnum">5:  </span>    <span style="color: #010001;">TranslateMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre><span class="lnum">6:  </span>    <span style="color: #010001;">DispatchMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre class="alt"><span class="lnum">7:  </span>}</pre>
</div>
<p>上述程序创建了两个窗口，但是消息循环只传递了其中一个窗口的句柄，那么我们看到的两个窗口中，hWnd2所属的窗口会毫无响应——无法移动也无法关闭，就好象死了一样，另一个hWnd1则可以正常拖动，关闭（这是由于我们将hWnd1的消息取出来并分派处理了）。</p>
<p>如果我们将消息循环稍作改动，GetMessage的参数不再传递某一个窗口的句柄，而是传递NULL：</p>
<div class="codearea linewrap">
<pre class="alt"><span class="lnum">1:  </span><span style="color: blue;">while </span>(<span style="color: #010001;">GetMessage</span>(&amp;<span style="color: #010001;">msg</span>, <span style="color: #010001;">NULL</span>, 0, 0) &gt; 0)</pre>
<pre><span class="lnum">2:  </span>{</pre>
<pre class="alt"><span class="lnum">3:  </span>    <span style="color: #010001;">TranslateMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre><span class="lnum">4:  </span>    <span style="color: #010001;">DispatchMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre class="alt"><span class="lnum">5:  </span>}</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->则两个窗口都可以正常响应消息了。</p>
<p>更进一步，我们是否可以GetMessage，而不调用DispatchMessage将其分发到窗口处理函数，而是直接把Get出来的Message自行处理呢？答案是可行，但需要区分使用的场合：</p>
<div class="codearea linewrap">
<pre class="alt"><span class="lnum">1:  </span><span style="color: blue;">while </span>(<span style="color: #010001;">GetMessage</span>(&amp;<span style="color: #010001;">msg</span>, <span style="color: #010001;">NULL</span>, 0, 0) &gt; 0)</pre>
<pre><span class="lnum">2:  </span>{</pre>
<pre class="alt"><span class="lnum">3:  </span>    <span style="color: #010001;">TranslateMessage</span>(&amp;<span style="color: #010001;">msg</span>);</pre>
<pre><span class="lnum">4:  </span>    <span style="color: #010001;">WNDPROC fWndProc </span>= (<span style="color: #010001;">WNDPROC</span>)<span style="color: #010001;">GetWindowLong</span>(<span style="color: #010001;">msg</span>.<span style="color: #010001;">hwnd</span>, <span style="color: #010001;">GWL_WNDPROC</span>);</pre>
<pre class="alt"><span class="lnum">5:  </span>    <span style="color: #010001;">fWndProc</span>(<span style="color: #010001;">msg</span>.<span style="color: #010001;">hwnd</span>, <span style="color: #010001;">msg</span>.<span style="color: #010001;">message</span>, <span style="color: #010001;">msg</span>.<span style="color: #010001;">wParam</span>, <span style="color: #010001;">msg</span>.<span style="color: #010001;">lParam</span>);</pre>
<pre><span class="lnum">6:  </span>}</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->上述代码（源自<a href="http://winprog.org/tutorial/message_loop.html">winprog.org</a>）是可以工作的。但是对于WM_TIMER类型的消息，需要回调到Timer的回调函数中，就需要另作处理了。当你创建的窗口很多的时候，或者有Timer类型消息的时候，这种做法就可能带来麻烦，但是通过尝试，可以弄明白实际上是DispatchMessage替我们回调了窗口处理函数，并没有什么神秘之处，我们也完全可以绕开DispatchMessage。</p>
<p>另外在某些特殊场合，这种写法则有特别的用处：比如想在一个已存在的控件（例如系统的TreeCtrl）中添加框选一系列Item的功能，则可以在收到控件中鼠标点下消息的时候，开启一个局部的GetMessage循环，在自己的局部消息循环中等候鼠标抬起的消息，并在鼠标抬起消息中检查选框的范围，并选中相应元素。</p>
<p>延伸：模态对话框与非模态对话框的本质区别在哪？为什么前者会将界面控制权完全接管，而后者则可以让用户继续操作其他窗口？</p>
<p>在MFC中调用CDialog::DoModal() 或者 在.Net中调用继承自CommonDialog的ShowDialog() 都会在该函数当中创建一个局部的消息循环，从而将上一级消息循环阻断。直到该函数返回，我们才得以继续处理上一级消息。这便是模态对话框。</p>
<p>而调用CWnd::Create创建出来的对话框实际上则共用了同一级的消息循环，成功创建窗口之后GetMessage(&amp;msg, NULL, 0, 0)也会得到针对新窗口的消息，从而让新创建的窗口与原来的窗口保持并行。</p>
<p>对比两者，我们可以发现<strong>模态与非模态对话框的本质区别实际上就在于是否存在一个局部的消息循环</strong>，以及该消息循环是否阻断了上一级消息循环的运行。</p>
<p>2、线程与消息</p>
<p>网上经常可以搜到<a href="http://www.google.com.hk/search?hl=zh-CN&amp;newwindow=1&amp;safe=strict&amp;q=UI%E7%BA%BF%E7%A8%8B+%E5%B7%A5%E4%BD%9C%E7%BA%BF%E7%A8%8B&amp;meta=&amp;aq=f&amp;aqi=g1&amp;aql=&amp;oq=&amp;gs_rfai=">UI线程（User Interface Thread）和工作线程(Working Thread)</a>的说法，认为UI线程就是创建了窗体的线程，从而操作系统会提供消息队列，而工作线程则无消息队列——我认为这种说法有欠妥当，为了查找UI线程这种说法的起源，我在<a href="http://msdn.microsoft.com/en-us/library/b807sta6(VS.80).aspx">MSDN当中搜索了User Interface Thread，发现这个UI线程的概念是MFC给出来的</a>，默认情况下派生自CWinApp的类都会默认工作在User Interface Thread中，我并没有发现Windows自身提供UI线程这个概念的证据，而且也没有什么理由让我相信操作系统会针对线程是否创建了窗体而对当前线程提供什么特别对待。我倾向于认为是这个来自于MFC的概念被多数人误解了。</p>
<p><strong>操作系统可能为任何线程创建消息队列，只要该线程调用了消息获取函数，甚至都不需要该线程创建任何窗口</strong>。</p>
<p>为了验证上述想法，我们可以做下面这样一个实验(代码出自<a href="http://www.cppblog.com/sandy/archive/2009/02/14/2320.html">使用PostThreadMessage在Win32线程之间传递消息</a>一文)：</p>
<p>其中ThreadProc是程序运行期间创建的线程，该线程默认是没有消息队列的，因此如果主线程直接以它的线程id调用PostThreadMessage，会返回FALSE，并在GetLastError中得到1444号错误——MSDN中的解释是ERROR_INVALID_THREAD_ID 1444 Invalid thread identifier。而经过调用了PeekMessage之后，ThreadProc运行的Thread已经拥有了消息队列了，之后主线程或其他线程再调用PostThreadMessage就可以正常运行了。(这里的HANDLE hStartEvent是用来保证上述顺序的同步事件。)</p>
<p>此外，该程序是Console版的程序，从头至尾没有创建过window，与MFC更无瓜葛，可以证实我们的想法——任何线程都可以有消息队列，Windows并没有提供什么特殊的UI线程之一说。</p>
<div class="codearea linewrap">
<pre class="alt"><span class="lnum"> 1:  </span><span style="color: blue;">#include </span><span style="color: #a31515;">"stdafx.h"</span></pre>
<pre><span class="lnum"> 2:  </span><span style="color: #a31515;"> </span><span style="color: blue;">#include </span><span style="color: #a31515;">&lt;Windows.h&gt;</span></pre>
<pre class="alt"><span class="lnum"> 3:  </span><span style="color: blue;">#include </span><span style="color: #a31515;">&lt;process.h&gt;</span></pre>
<pre><span class="lnum"> 4:  </span><span style="color: blue;">#include </span><span style="color: #a31515;">&lt;stdio.h&gt;</span></pre>
<pre class="alt"><span class="lnum"> 5:  </span></pre>
<pre><span class="lnum"> 6:  </span><span style="color: blue;">#define </span><span style="color: #010001;">MSG_SEND_OVERTHREAD WM_USER </span>+ 100</pre>
<pre class="alt"><span class="lnum"> 7:  </span><span style="color: #010001;">HANDLE hStartEvent</span>; <span style="color: green;">// thread start event </span></pre>
<pre><span class="lnum"> 8:  </span></pre>
<pre class="alt"><span class="lnum"> 9:  </span><span style="color: blue;">unsigned _stdcall </span><span style="color: #010001;">ThreadProc</span>(<span style="color: #010001;">PVOID param</span>)</pre>
<pre><span class="lnum">10:  </span>{</pre>
<pre class="alt"><span class="lnum">11:  </span>    <span style="color: #010001;">MSG msg</span>;</pre>
<pre><span class="lnum">12:  </span>    <span style="color: #010001;">PeekMessage</span>(&amp;<span style="color: #010001;">msg</span>, <span style="color: #010001;">NULL</span>, <span style="color: #010001;">WM_USER</span>, <span style="color: #010001;">WM_USER</span>, <span style="color: #010001;">PM_NOREMOVE</span>);</pre>
<pre class="alt"><span class="lnum">13:  </span>    <span style="color: blue;">if </span>(!<span style="color: #010001;">SetEvent</span>(<span style="color: #010001;">hStartEvent</span>)) <span style="color: green;">//set thread start event </span></pre>
<pre><span class="lnum">14:  </span><span style="color: green;">        </span><span style="color: blue;">return </span>1;</pre>
<pre class="alt"><span class="lnum">15:  </span></pre>
<pre><span class="lnum">16:  </span>    <span style="color: blue;">while</span>(<span style="color: blue;">true</span>)</pre>
<pre class="alt"><span class="lnum">17:  </span>    {</pre>
<pre><span class="lnum">18:  </span>        <span style="color: blue;">if </span>(<span style="color: #010001;">GetMessage</span>(&amp;<span style="color: #010001;">msg</span>,0,0,0)) <span style="color: green;">//get msg from message queue</span></pre>
<pre class="alt"><span class="lnum">19:  </span><span style="color: green;">        </span>{</pre>
<pre><span class="lnum">20:  </span>            <span style="color: blue;">switch</span>(<span style="color: #010001;">msg</span>.<span style="color: #010001;">message</span>)</pre>
<pre class="alt"><span class="lnum">21:  </span>            {</pre>
<pre><span class="lnum">22:  </span>            <span style="color: blue;">case </span><span style="color: #010001;">MSG_SEND_OVERTHREAD</span>:</pre>
<pre class="alt"><span class="lnum">23:  </span>                <span style="color: blue;">char </span>* <span style="color: #010001;">pInfo </span>= (<span style="color: blue;">char </span>*)<span style="color: #010001;">msg</span>.<span style="color: #010001;">wParam</span>;</pre>
<pre><span class="lnum">24:  </span>                <span style="color: #010001;">printf</span>(<span style="color: #a31515;">"%s\n"</span>, <span style="color: #010001;">pInfo</span>);</pre>
<pre class="alt"><span class="lnum">25:  </span>                <span style="color: blue;">delete </span><span style="color: #010001;">pInfo</span>;</pre>
<pre><span class="lnum">26:  </span>                <span style="color: blue;">break</span>;</pre>
<pre class="alt"><span class="lnum">27:  </span>            }</pre>
<pre><span class="lnum">28:  </span>        }</pre>
<pre class="alt"><span class="lnum">29:  </span>    }</pre>
<pre><span class="lnum">30:  </span>    <span style="color: blue;">return </span>1;</pre>
<pre class="alt"><span class="lnum">31:  </span>}</pre>
<pre><span class="lnum">32:  </span></pre>
<pre class="alt"><span class="lnum">33:  </span><span style="color: blue;">int </span><span style="color: #010001;">main</span>()</pre>
<pre><span class="lnum">34:  </span>{</pre>
<pre class="alt"><span class="lnum">35:  </span>    <span style="color: #010001;">HANDLE hThread</span>;</pre>
<pre><span class="lnum">36:  </span>    <span style="color: blue;">unsigned </span><span style="color: #010001;">nThreadID</span>;</pre>
<pre class="alt"><span class="lnum">37:  </span>    <span style="color: blue;">char </span><span style="color: #010001;">szBuf</span>[1024];</pre>
<pre><span class="lnum">38:  </span>    <span style="color: green;">//create thread start event</span></pre>
<pre class="alt"><span class="lnum">39:  </span><span style="color: green;">    </span><span style="color: #010001;">hStartEvent </span>= ::<span style="color: #010001;">CreateEvent</span>(0,<span style="color: #010001;">FALSE</span>,<span style="color: #010001;">FALSE</span>,0);</pre>
<pre><span class="lnum">40:  </span>    <span style="color: blue;">if</span>(<span style="color: #010001;">hStartEvent </span>== 0)</pre>
<pre class="alt"><span class="lnum">41:  </span>    {</pre>
<pre><span class="lnum">42:  </span>        <span style="color: #010001;">printf</span>(<span style="color: #a31515;">"create start event failed,errno:%d\n"</span>,<span style="color: #010001;">GetLastError</span>());</pre>
<pre class="alt"><span class="lnum">43:  </span>        <span style="color: blue;">return </span>1;</pre>
<pre><span class="lnum">44:  </span>    }</pre>
<pre class="alt"><span class="lnum">45:  </span>    <span style="color: green;">//start thread</span></pre>
<pre><span class="lnum">46:  </span><span style="color: green;">    </span><span style="color: #010001;">hThread </span>= (<span style="color: #010001;">HANDLE</span>)<span style="color: #010001;">_beginthreadex</span>( <span style="color: #010001;">NULL</span></pre>
<pre class="alt"><span class="lnum">47:  </span><span style="color: #010001;">        </span>, 0</pre>
<pre><span class="lnum">48:  </span>        , &amp;<span style="color: #010001;">ThreadProc</span></pre>
<pre class="alt"><span class="lnum">49:  </span><span style="color: #010001;">        </span>, <span style="color: #010001;">NULL</span></pre>
<pre><span class="lnum">50:  </span><span style="color: #010001;">        </span>, 0</pre>
<pre class="alt"><span class="lnum">51:  </span>        , &amp;<span style="color: #010001;">nThreadID </span>);</pre>
<pre><span class="lnum">52:  </span></pre>
<pre class="alt"><span class="lnum">53:  </span>    <span style="color: blue;">if</span>(<span style="color: #010001;">hThread </span>== 0)</pre>
<pre><span class="lnum">54:  </span>    {</pre>
<pre class="alt"><span class="lnum">55:  </span>        <span style="color: #010001;">printf</span>(<span style="color: #a31515;">"start thread failed,errno:%d\n"</span>,<span style="color: #010001;">GetLastError</span>());</pre>
<pre><span class="lnum">56:  </span>        <span style="color: #010001;">CloseHandle</span>(<span style="color: #010001;">hStartEvent</span>);</pre>
<pre class="alt"><span class="lnum">57:  </span>        <span style="color: blue;">return </span>1;</pre>
<pre><span class="lnum">58:  </span>    }</pre>
<pre class="alt"><span class="lnum">59:  </span></pre>
<pre><span class="lnum">60:  </span>    <span style="color: green;">//wait thread start event to avoid PostThreadMessage return errno:1444</span></pre>
<pre class="alt"><span class="lnum">61:  </span><span style="color: green;">    </span>::<span style="color: #010001;">WaitForSingleObject</span>(<span style="color: #010001;">hStartEvent</span>,<span style="color: #010001;">INFINITE</span>);</pre>
<pre><span class="lnum">62:  </span>    <span style="color: #010001;">CloseHandle</span>(<span style="color: #010001;">hStartEvent</span>);</pre>
<pre class="alt"><span class="lnum">63:  </span>    <span style="color: blue;">int </span><span style="color: #010001;">count </span>= 0;</pre>
<pre><span class="lnum">64:  </span>    <span style="color: blue;">while</span>(<span style="color: blue;">true</span>)</pre>
<pre class="alt"><span class="lnum">65:  </span>    {</pre>
<pre><span class="lnum">66:  </span>        <span style="color: blue;">char</span>* <span style="color: #010001;">pInfo </span>= <span style="color: blue;">new char</span>[100];</pre>
<pre class="alt"><span class="lnum">67:  </span>        <span style="color: green;">//create dynamic msg</span></pre>
<pre><span class="lnum">68:  </span><span style="color: green;">        </span><span style="color: #010001;">sprintf</span>(<span style="color: #010001;">pInfo</span>,<span style="color: #a31515;">"msg_%d"</span>,++<span style="color: #010001;">count</span>);</pre>
<pre class="alt"><span class="lnum">69:  </span>        <span style="color: green;">//post thread msg</span></pre>
<pre><span class="lnum">70:  </span><span style="color: green;">        </span><span style="color: blue;">if</span>( !<span style="color: #010001;">PostThreadMessage</span>(<span style="color: #010001;">nThreadID</span></pre>
<pre class="alt"><span class="lnum">71:  </span><span style="color: #010001;">            </span>, <span style="color: #010001;">MSG_SEND_OVERTHREAD</span></pre>
<pre><span class="lnum">72:  </span><span style="color: #010001;">            </span>, (<span style="color: #010001;">WPARAM</span>)<span style="color: #010001;">pInfo</span></pre>
<pre class="alt"><span class="lnum">73:  </span><span style="color: #010001;">            </span>, 0))</pre>
<pre><span class="lnum">74:  </span>        {</pre>
<pre class="alt"><span class="lnum">75:  </span>            <span style="color: #010001;">printf</span>(<span style="color: #a31515;">"post message failed,errno:%d\n"</span>,<span style="color: #010001;">GetLastError</span>());</pre>
<pre><span class="lnum">76:  </span>            <span style="color: blue;">delete</span>[] <span style="color: #010001;">pInfo</span>;</pre>
<pre class="alt"><span class="lnum">77:  </span>        }</pre>
<pre><span class="lnum">78:  </span>        ::<span style="color: #010001;">Sleep</span>(1000);</pre>
<pre class="alt"><span class="lnum">79:  </span>    }</pre>
<pre><span class="lnum">80:  </span>    <span style="color: #010001;">CloseHandle</span>(<span style="color: #010001;">hThread</span>);</pre>
<pre class="alt"><span class="lnum">81:  </span>    <span style="color: blue;">return </span>1;</pre>
<pre><span class="lnum">82:  </span>}</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->&nbsp;</p>
<p>3、对上述的诸多概念做一番小节如下：<strong>线程第一次被建立时，系统假定线程不会被用于任何与用户相关的任务，这样可以减少线程对系统资源的要求。但是一旦线程调用了一个与图形用户界面相关的函数，例如检查消息队列或窗口创建，操作系统就会为线程创建一些额外的资源，以便它能执行和用户界面有关的任务</strong>。特别的，系统会为该线程分配一个THREADINFO结构（参考下图，来源于《Windows核心编程》第四版 26-1），并将该数据结构与线程联系起来，此后该线程就拥有了自己的消息队列集合（意味着我们可以向该线程发送消息PostThreadMessage等）。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/01/THREADINFO.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="THREADINFO" src="http://www.windameister.org/blog/wp-content/uploads/2011/01/THREADINFO_thumb.jpg" border="0" alt="THREADINFO" width="503" height="734" /></a></p>
<p>向一个线程传递消息可以使用<a href="http://msdn.microsoft.com/en-us/library/ms644946(v=vs.85).aspx">PostThreadMessage</a>，这个函数的接口很明确，要求传递一个线程Id，但是还有<a href="http://msdn.microsoft.com/en-us/library/ms644944(v=vs.85).aspx">PostMessage</a>和<a href="http://msdn.microsoft.com/en-us/library/ms644950(v=vs.85).aspx">SendMessage</a>这样的API，要求传递的是窗口句柄，这可能就是我们此前会迷惑于消息队列是跟窗口相关还是跟线程相关的原因。事实上，在调用PostMessage或SendMessage的时候，系统要根据传入的窗口句柄，确认是哪一个线程创建了该窗口（我们也可以通过<a href="http://msdn.microsoft.com/en-us/library/ms633522(v=vs.85).aspx">GetWindowsThreadProcessId</a>自行获取是哪个线程创建了一个窗口），然后系统分配一块内存，将消息参数存储在这块内存中，并将这块内存增加到相应线程的登记消息队列中。</p>
<p>SendMessage和PostMessage有一个值得注意的区别：</p>
<p>PostMessage是简单的将消息登记到目标线程（创建窗口的线程）的消息队列中。</p>
<p>而SendMessage是一个阻塞调用，调用者必须要等待消息处理后，函数才会返回。（注意，<strong>这并不意味着调用SendMessage的线程就是处理消息线程</strong>，前文提到过，处理窗口消息的线程必须是创建窗口的线程，如果一个线程调用SendMessage向不是由它创建的窗口发送消息，则会挂起调用线程，直到另外的线程处理完消息，才会唤醒该线程。）</p>
<p>线程中可以有消息循环，消息循环将线程中的消息取出来并且进行处理——可以自行根据消息的类型进行处理，也可以交给DispatchMessage处理，该API会回调窗口类中的窗口处理函数（依据该窗口所属窗口类别WNDCLASS的不同分别回调不同的消息处理函数）。如果线程创建了窗口，那么窗口的各种响应事件全部是由消息循环以及相关处理完成的，一个消息循环可以处理很多个窗口的消息。</p>
<p>消息循环可以有多个，可以在上一级消息循环的某个消息的处理过程中，局部创建一个消息循环，模态对话框就是采用这种机制创建出来的。</p>
<p>#1 确认了消息队列是隶属于线程而非窗口，补充了部分说明，本文参考了<a href="http://book.douban.com/subject/3235659/">《Windows核心编程》</a>第四版 第26章 窗口消息的部分内容，感谢<a href="http://hi.baidu.com/zwptools">redcoder</a>同学热心指出</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/03/27/windows-message-queue-and-message-loop/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Win32平台下编译SVN源码全过程</title>
		<link>http://www.windameister.org/blog/2009/10/25/win32-compile-svn-source/</link>
		<comments>http://www.windameister.org/blog/2009/10/25/win32-compile-svn-source/#comments</comments>
		<pubDate>Sat, 24 Oct 2009 16:47:20 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[svn]]></category>
		<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[svn-dev]]></category>
		<category><![CDATA[版本控制]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=156</guid>
		<description><![CDATA[前段时间曾经总结过一些在win32平台下基于SVN开发的一些注意事项，主要是在利用svn官方发布的二进制库进行开发过程中使用的方法和一些值得注意的问题。   由于svn官方发布的win32平台下的二进制文件是基于vc6编译的，在使用vc2005进行开发时，会遇到因CRT冲突而引起的link错误。因此，如果是使用vc2005（我推测使用VC2003也会遇到同样的问题，尚未验证）附带的CRT库与svn官方发布的binary进行link，那么无论如何都会出现crash的问题。最为彻底的解决方案，还是自行编译svn源码。   在win32下编译svn源码说明：  首先需要从官方下载一份SVN源码，版本可以根据需要选取，比如最新的Release 1.6.3(目前已经更新到1.6.6)可以在这个地址下载到： http://subversion.tigris.org/downloads/subversion-1.6.3.zip 以及svn所需的依赖包： http://subversion.tigris.org/downloads/subversion-deps-1.6.3.zip 如果需要支持ssl的话，还需要下载openssl（根据实际需要选择相应版本）： http://www.openssl.org/source/ 如果需要BerkeleyDB的话，需要下载WindowsBDB(BDB是可选的，如果不使用BDB，则默认使用FSFS)： http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688&#38;expandFolder=688&#38;folderID=2627 此外还有Windows libintl： http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627&#38;expandFolder=2627&#38;folderID=8100     解压svn源码包，可以在subversion-1.6.3目录中找到这么一个官方发布的说明文件INSTALL。该文件详述了安装通过源码编译SVN所需依赖的工具及第三方库，并且给出了详细的步骤。 网上同样有一个INSTALL的说明，可以在这里访问到: http://svn.collab.net/repos/svn/trunk/INSTALL   编译开源项目的话，其附带的INSTALL说明都是最重要也是最全面的参考。网上搜索的其他资料，也会有相应的参考价值，但无论如何，其信息的来源也是INSTALL。因此编译开源项目时，认真阅读INSTALL是最重要且效率最高的。   从Google上进行一下简单的搜索的话，可以找到一篇介绍svn源码编译的文章，在这里：http://rocksun.cn/?p=103   为了后续的步骤方便，我们需要先准备编译所必须的一些东西： 包括Perl：http://www.activestate.com/activeperl/ Python：http://www.python.org/download/     安装Python以及Perl，比如分别安装到D:\Python26以及D:\Perl。安装好之后，将python以及perl的bin目录设为系统目录并重新启动使之生效。   编译svn源码第一步，将下载的svn源码包解压到X:\SVN\svn—trunk下。X可以是任意一个盘符。在我的机器上，我使用了F盘，下文中皆以F盘举例。   在F:\SVN\svn-trunk中解压了刚才下载的svn源码包以及依赖之后，可以在目录 F:\SVN\src-trunk\subversion-1.6.3中看到以下文件及目录： 以及文件： Svn的编译需要依赖libapr以及libapr-util，SQLite，zlib，libintl可选，libneon/libserf二则择一，openssl可选，BerkeleyDB可选，libsasl可选，对Python,Perl,Java,Ruby支持的模块可选，以及KDELibs,GNOME Keyring可选。   我们依次先编译依赖项：首先进入到subversion-1.6.3\apr目录中。可以看到存在apr.dsw以及apr.dsp文件，这是VC6的工程文件。我们如果想在2005下编译的话，需要将其转换成sln及vcproj文件，简单的用vc2005打开该文件并保存即可。该目录下还有Makefile.win文件，是win下的makefile，我们打开makefile.win文件查看一下说明：可以得知如果需要编译.sln文件的话，需要置USESLN=1。   在VC2005的命令行中输入nmake -f makefile.win buildall checkall USESLN=1便可以开始编译apr了。Checkall表示编译完成后会去运行所有的测试用例。   编译完成后，当前目录下会多出2个文件夹，分别是LibR – StaticRelease，Release – [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignright" src="http://www.windameister.org/attachments/svn_compile_log/subversion_logo_hor-468x64.png" alt="" width="468" height="64" /></p>
<p>前段时间曾经总结过一些<a href="http://www.windameister.org/blog/index.php/2009/05/15/win32-svn-dev-some-notes/">在win32平台下基于SVN开发的一些注意事项</a>，主要是在利用svn官方发布的二进制库进行开发过程中使用的方法和一些值得注意的问题。</p>
<p> </p>
<p>由于svn官方发布的win32平台下的二进制文件是基于vc6编译的，在使用vc2005进行开发时，会遇到因CRT冲突而引起的link错误。因此，如果是使用vc2005（我推测使用VC2003也会遇到同样的问题，尚未验证）附带的CRT库与svn官方发布的binary进行link，那么无论如何都会出现crash的问题。最为彻底的解决方案，还是自行编译svn源码。</p>
<p> </p>
<p>在win32下编译svn源码说明：</p>
<p> 首先需要从官方下载一份SVN源码，版本可以根据需要选取，比如最新的Release 1.6.3(<a href="http://subversion.tigris.org/">目前已经更新到1.6.6</a>)可以在这个地址下载到：</p>
<p><a href="http://subversion.tigris.org/downloads/subversion-1.6.3.zip">http://subversion.tigris.org/downloads/subversion-1.6.3.zip</a></p>
<p>以及svn所需的依赖包：</p>
<p><a href="http://subversion.tigris.org/downloads/subversion-deps-1.6.3.zip">http://subversion.tigris.org/downloads/subversion-deps-1.6.3.zip</a></p>
<p>如果需要支持ssl的话，还需要下载openssl（根据实际需要选择相应版本）：</p>
<p><a href="http://www.openssl.org/source/">http://www.openssl.org/source/</a></p>
<p>如果需要BerkeleyDB的话，需要下载WindowsBDB(BDB是可选的，如果不使用BDB，则默认使用FSFS)：</p>
<p><a href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688&amp;expandFolder=688&amp;folderID=2627">http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688&amp;expandFolder=688&amp;folderID=2627</a></p>
<p>此外还有Windows libintl：</p>
<p><a href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627&amp;expandFolder=2627&amp;folderID=8100">http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627&amp;expandFolder=2627&amp;folderID=8100</a></p>
<p> </p>
<p> </p>
<p>解压svn源码包，可以在subversion-1.6.3目录中找到这么一个官方发布的说明文件INSTALL。该文件详述了安装通过源码编译SVN所需依赖的工具及第三方库，并且给出了详细的步骤。</p>
<p>网上同样有一个INSTALL的说明，可以在这里访问到:</p>
<p><a href="http://svn.collab.net/repos/svn/trunk/INSTALL">http://svn.collab.net/repos/svn/trunk/INSTALL</a></p>
<p> </p>
<p>编译开源项目的话，其附带的INSTALL说明都是最重要也是最全面的参考。网上搜索的其他资料，也会有相应的参考价值，但无论如何，其信息的来源也是INSTALL。因此编译开源项目时，认真阅读INSTALL是最重要且效率最高的。</p>
<p> </p>
<p>从Google上进行一下简单的搜索的话，可以找到一篇介绍svn源码编译的文章，在这里：<a href="http://rocksun.cn/?p=103" target="_blank">http://rocksun.cn/?p=103</a></p>
<p> </p>
<p>为了后续的步骤方便，我们需要先准备编译所必须的一些东西：</p>
<p>包括Perl：<a href="http://www.activestate.com/activeperl/">http://www.activestate.com/activeperl/</a></p>
<p>Python：<a href="http://www.python.org/download/">http://www.python.org/download/</a></p>
<p> </p>
<p> </p>
<p>安装Python以及Perl，比如分别安装到D:\Python26以及D:\Perl。安装好之后，将python以及perl的bin目录设为系统目录并重新启动使之生效。</p>
<p> </p>
<p>编译svn源码第一步，将下载的svn源码包解压到X:\SVN\svn—trunk下。X可以是任意一个盘符。在我的机器上，我使用了F盘，下文中皆以F盘举例。</p>
<p> </p>
<p>在F:\SVN\svn-trunk中解压了刚才下载的svn源码包以及依赖之后，可以在目录</p>
<p>F:\SVN\src-trunk\subversion-1.6.3中看到以下文件及目录：</p>
<p><img class="alignnone" src="http://www.windameister.org/attachments/svn_compile_log/svn.compile.log.1.jpg" alt="" width="443" height="296" /></p>
<p>以及文件：</p>
<p><img class="alignnone" src="http://www.windameister.org/attachments/svn_compile_log/svn.compile.log.2.jpg" alt="" width="394" height="296" /></p>
<p>Svn的编译需要依赖libapr以及libapr-util，SQLite，zlib，libintl可选，libneon/libserf二则择一，openssl可选，BerkeleyDB可选，libsasl可选，对Python,Perl,Java,Ruby支持的模块可选，以及KDELibs,GNOME Keyring可选。</p>
<p> </p>
<p>我们依次先编译依赖项：首先进入到subversion-1.6.3\apr目录中。可以看到存在apr.dsw以及apr.dsp文件，这是VC6的工程文件。我们如果想在2005下编译的话，需要将其转换成sln及vcproj文件，简单的用vc2005打开该文件并保存即可。该目录下还有Makefile.win文件，是win下的makefile，我们打开makefile.win文件查看一下说明：可以得知如果需要编译.sln文件的话，需要置USESLN=1。</p>
<p> </p>
<p>在VC2005的命令行中输入nmake -f makefile.win buildall checkall USESLN=1便可以开始编译apr了。Checkall表示编译完成后会去运行所有的测试用例。</p>
<p> </p>
<p>编译完成后，当前目录下会多出2个文件夹，分别是LibR – StaticRelease，Release – DllRelease。如果选择Debug编译，则会生成LibD – StaticDebug， Debug – DllDebug。</p>
<p> </p>
<p>类似的，我们将apr-util以及apr-iconv也编译好。</p>
<p> </p>
<p>编译zlib：</p>
<p>进入zlib目录后，使用以下命令编译zlib库</p>
<p>nmake -f win32/Makefile.msc</p>
<p> </p>
<p>编译openssl：</p>
<p>将此前下载的openssl解压到F:\SVN\openssl</p>
<p>阅读其INSTALL文档（INSTALL.W32）</p>
<p>使用VC编译openssl首先需要运行configure：</p>
<p>perl Configure VC-WIN32</p>
<p>接着运行</p>
<p>ms\do_masm</p>
<p>这里的do_masm是一个bat脚本，该脚本会生成nt.mak以及ntdll.mak分别是Release版本的静态和动态的库的make文件。如果想生成debug版本的make文件，可以通过修改do_masm.bat中的调用mk1mf.pl脚本处的参数实现，具体参数可以参考mk1mf.pl文件自身的说明。</p>
<p>接下来，创建动态链接库版本的ssl库用nmake -f ms\ntdll.mak，以及静态版本使用：nmake -f ms\nt.mak</p>
<p>生成的结果文件位于out32dll文件夹，以及out32文件夹中。</p>
<p> </p>
<p>编译neon</p>
<p>进入F:\SVN\src-trunk\subversion-1.6.3\neon目录</p>
<p>nmake –f neon.mak</p>
<p>默认生成的是release版的libneon.lib (debug版为libneonD.lib)</p>
<p>可以用nmake –f neon.mak DEBUG_BUILD=1生成debug版的lib。</p>
<p> </p>
<p>回到F:\SVN\src-trunk\subversion-1.6.3</p>
<p>运行python gen-make.py –help可以了解如何使用gen-make.py生成我们所需的svn编译文件。</p>
<p>由于在此，我打算选用neon, libintl, openssl(本例中并不打算使用BDB，如果需要BDB则需要增加—with-berkeley-db=DIR参数)进行编译，目前需要关注的几个重要参数如下：</p>
<p>&#8211;with-apr=DIR</p>
<p>&#8211;with-apr-util=DIR</p>
<p>&#8211;with-apr-iconv=DIR</p>
<p>&#8211;with-neon=DIR</p>
<p>&#8211;with-libintl=DIR</p>
<p>&#8211;with-openssl=DIR</p>
<p>&#8211;with-zlib=DIR</p>
<p>&#8211;vsnet-version=VER</p>
<p> </p>
<p>运行</p>
<p>F:\SVN\src-trunk\subversion-1.6.3&gt;python gen-make.py -t vcproj</p>
<p>&#8211;with-apr=apr &#8211;with-apr-iconv=apr-iconv &#8211;with-apr-util=apr-util &#8211;with-libintl=svn-win32-libintl &#8211;with-openssl=..\..\openssl &#8211;with-zlib=zlib &#8211;vsnet-version=2005</p>
<p> </p>
<p>(其中的libintl需要解压到当前文件夹中)</p>
<p>即可生成vc2005的sln(subversion_vcnet.sln)文件了。</p>
<p> </p>
<p>打开vc2005，选择Debug编译选项，对项目ALL进行编译。如果一切顺利，则会生成一个F:\SVN\src-trunk\subversion-1.6.3\Debug目录。内容包括svn的所有的lib及可执行文件。</p>
<p><img class="alignnone" src="http://www.windameister.org/attachments/svn_compile_log/svn.compile.log.3.jpg" alt="" width="558" height="367" /></p>
<p>将svn\svn.exe以及所有目录下的.dll文件拷贝到一个新建的bin目录下。将openssl的dll，apr, apr-util, apr-iconv的dll拷贝到同样的bin目录下，如下图：</p>
<p><img class="alignnone" src="http://www.windameister.org/attachments/svn_compile_log/svn.compile.log.4.jpg" alt="" width="558" height="367" /></p>
<p>运行svn &#8211;version看看结果吧～</p>
<p> </p>
<p>最后，当我们有了自行编译的svn可以做什么？你可以做任何你想做的事——比如自己基于svn的接口进行开发（可以参考开源项目rapidsvn以及TortoiseSVN的源码）</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2009/10/25/win32-compile-svn-source/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>编写一个DLL时应当注意什么#1</title>
		<link>http://www.windameister.org/blog/2009/09/06/what-should-you-think-about-when-writting-a-dl/</link>
		<comments>http://www.windameister.org/blog/2009/09/06/what-should-you-think-about-when-writting-a-dl/#comments</comments>
		<pubDate>Sun, 06 Sep 2009 05:50:04 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[crt]]></category>
		<category><![CDATA[dll]]></category>
		<category><![CDATA[windows程序设计]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=106</guid>
		<description><![CDATA[库与代码重用 1、静态库vs动态库 静态库的优势和劣势 动态库的优势与问题 2、静态C/C++运行库 vs 动态C/C++运行库 &#38; manifest 3、关于动态库接口设计 库与代码重用 对于像C，C++这样的语言来说，库是扩展语言功能的重要手段，对于项目来说，代码库是节约开发时间，缩减成本，划分项目模块以利于多人合作，并通过重用已有代码而避免重复劳动的必要工具。开发一个稍具规模的项目，总免不了设计和划分模块，如何设计和划分，采取什么方案解决问题，总要面临诸多取舍。 最近跟动态库/静态库/动态静态CRT/MFC打了诸多交道。本文是对这段时间遇到的问题与思考的总结。 1. 静态库 vs. 动态库 静态库本质上就是编译好的目标代码(使用vc的话，编译.cpp文件或者.c文件之后会得到目标文件.obj，编译结束后，link程序会按照指定的要求将目标文件链接成最终的输出文件——常见的有.lib/.dll/.exe)的一份合集。 相比于动态库，静态库没有入口函数，不能独立执行，不能动态链接，一旦被链接到目标文件中，静态库的代码段/数据段都会被合并到目标文件当中去。这是静态库与动态库的本质区别。 由于上述差别，那么我们就很容易理解下面的情况：如果你在静态库中定义一个静态或全局对象，最终该对象会被链接到目标文件的数据段中（目标文件可能是某个dll也可能是exe）。 静态库的优势在于，简单易用，你无需考虑内存的申请与释放问题，因为静态库的代码段作为目标模块的一部分（EXE或DLL），在其中所调用的new/delete在链接阶段会被重定位到该目标模块的CRT函数地址。 对应的，如果你使用DLL，由于DLL中会自行初始化一份CRT堆，因此它的内存申请与释放是在有别于在EXE的CRT堆中进行的（两个不同的CRT堆），如果你从DLL中申请一块内存出来使用，最后也必须交还给DLL模块内部去释放。否则会产生Heap Collision，在Debug模式下，你会收到一个系统位于malloc中的断言，并了解到发生了什么。* 此处的叙述存在错误，更正如下： 一个DLL或许会，或许不会拥有一份自行初始化的CRT堆，这取决于DLL是通过动态链接MSVCRT还是静态链接LIBCMT。由于CRT是通过一个全局变量_crtheap维护着一个Heap（Windows环境下），并且会在CRT初始化的时候初始化CRT堆，因此，如果整个应用程序都是通过链接动态的msvcrt，使用同一份crt，则不会有问题。但是如果有任一二进制模块链接了静态crt，则会拥有一份自己的crt堆。严格来说，你应当小心的保证来自于某个CRT的堆内存最后也被交还给该CRT堆，否则会发生错误，在Debug模式下，当你调用free函数释放来自于另一个CRT堆的内存时，会有一个_ASSERTE(_CrtIsValidHeapPointer(pUserData));断言失败，提醒你用错了堆。 如果我们的DLL是链接了一个静态的CRT并且发布出去了，那么我们还是应当保证所有来自于该DLL的内存都由该DLL回收。其原因如上述分析所言，是由于该DLL拥有一份自己的CRT堆。 &#160; 再有，如果你的静态库A依赖于静态库B的头文件，而目标应用程序EXE依赖了静态库A，那么你在链接静态库A的时候，也必须链接静态库B。否则会有静态库A中的某些符号无法被找到，从而导致链接失败。而动态库A如果依赖于一个静态库B，那么你无需在依赖该动态库的EXE中再次包含该静态库，因为该动态库A已经将该静态库B链接到A模块当中了，在DLL A中已经有了一份，如果EXE模块没有直接依赖于静态库B的话，那么就无需在EXE中再次link该静态库了。 上述问题也通常是混用静态库和动态库时可能遇到的一个问题：如果有一个静态库A，被动态库B和EXE C都用到了，而EXE C又需要使用动态库B，那么这份A的代码和全局数据就在动态库B和EXE C中存在了两份。第一，这是一种对空间的浪费；其次，这可能会引发问题，比如，当我在EXE C中new一份A中的某对象X的指针，并将该指针传入到动态库B中使用，表面上看似乎没有问题，但是如果这个对象X引用了A库中的全局/静态数据，就可能会引发问题，因为此时动态库B中存在有一份A库中的全局/静态数据，而EXE C中有另一份，如果这个全局/静态数据被X使用的话，那么就会得到前后不一致的上下文环境。具体可能产生什么问题要看具体的逻辑是如何组织的。在这种情况下，最好能将静态库A重新组织成一个动态库，这样就不存在多份代码以及多套全局数据的问题了；如果不能这么做，你可以根据具体的应用程序组织的形式，采取DLL导出A库接口，EXE使用DLL导出接口去访问A库而不直接链接A库本身的方法来变通，但通常这不是根本的解决方案。 另一方面，在DLL中可以包含自己的资源文件，而LIB中不能。因为DLL是一份完整的PE文件，而LIB只是若干个OBJ打包。这并不是说，使用LIB的时候不能使用资源(RC)，你可以将LIB中使用的RC文件，resource.h都include到目标应用程序当中，只要没有资源ID冲突就可以正常使用，就像是你在目标应用程序的资源当中定义了这些资源一样。而DLL中的资源则隶属于DLL自身，不会与EXE中的资源在ID号上产生冲突，只是在使用的时候还需要针对Resource做一些Handle上的设置（参考AfxSetResourceHandle），才能使得API访问DLL中的资源而非EXE中的。如果要编写一个带有资源的库模块，DLL确实是不二选择。 2. 静态&#38;动态C/C++ runtime 以及 manifest 静态的C-runtime库叫做LIBC.lib，我们通常使用多线程的版本叫LIBCMT.lib，debug下的版本叫LIBCMTD.lib。而动态的C-runtime，在VC的环境下，我们使用的是MSVCRT.lib，以及debug下的MSVCRTD.lib（这两个lib实际是两个导出符号文件，实际的运行库位于msvcrt.dll/msvcrtd.dll中(VC6)，如果是VC2005的版本则是msvcr80.dll/msvcr80d.dll） C++的runtime库，静态版的是LIBCPMT.lib/LIBCPMTD.lib。动态版的是MSVCPRT.lib以及MSVCPRTD.lib。（类似的，实际的运行库位于&#60;VC2005中&#62;msvcp80.dll/msvcp80d.dll） 在第一点的对比中已经提到过，如果同时又DLL模块依赖一个静态库，而又有一个依赖该DLL模块的EXE还依赖这个静态库，那么最终的内存中就会存在两份该静态库的代码以及全局数据。这一点对于C/C++运行库也是一样的。所需要注意的是，只要是基于C语言以及C++语言的程序，几乎所有的都会用到C/C++运行库，因此如果在规划一个项目的时候，发现有多个dll模块，那么最好是使用动态版的运行库，以避免最终的内存中出现多份不必要的运行库代码。 再有一点是关于XP之后的系统中，以及在VC2005之后的编译环境中，微软添加的manifest机制。该机制是为了解决同名但版本错误的DLL加载问题而提出的。比如，我们手上有两份VC编译器，分别是不同的版本，其中携带的MSVCR80.dll也是不同的版本，但是都叫MSVCR80.dll，如果我们在应用程序安装时，简单粗暴的将系统目录下替换成我们应用程序所依赖的MSVCR80.dll，那么可能会造成以往的应用程序无法正确运行的问题，因为他们可能依赖的是其他版本的MSVCR80.dll。 解决这个问题的途径是将一个dll的运行环境，版本，以及校验值等信息编码到一个目录名当中，而在安装应用程序时，将该版本的dll放置到对应名字的目录下，这样尽管dll名称一致，我们也可以找到所需要正确版本的dll。但是应用程序怎么知道需要依赖哪个版本的dll呢？如果应用程序不知道，即使系统中有正确版本的dll，操作系统也不知道应该去加载哪一个dll。 因此manifest文件就把该模块所依赖的模块都标明了，通过名称，校验码，版本等等信息以确保所指明的模块不会有歧义（用记事本打开一个manifest文件，你就可以看到上述信息），当该应用程序EXE或者DLL被操作系统加载时，系统就会根据该信息去查找对应模块，首先会在C:\Windows\WinSxS目录下找（打开这个目录，你就能看到大量不同版本的CRT/CPRT/MFC/ATL等等运行库），如果没有找到那么也会应用程序当前目录下找。 这就是为什么使用VC2005编译一个dll也好，一个exe也好，总会多生成一个manifest的文件的原因。所以如果使用了动态版本的CRT运行库，或者其他任何DLL，那么最好就不要关闭manifest生成的编译选项。 3. 关于动态库接口设计 如果动态库可能经常会更新版本，提供新的功能接口，那么如果使得动态库的升级无需改动依赖于他的可执行模块就是一个值得思考的问题。 具体要达到上述目标，需要保证的有以下几点： a. 动态库中的类的内存布局不暴露给外界，也就是说动态库提供给用户的.h中，不应当有类的成员信息。这是因为，如果类的内部数据修改了，增加了一个变量或者减少了，那么这个类所占用的内存大小也就修改了，倘若有exe使用上一版本的类定义，new了一个类对象，那么一旦发生类定义修改的情况，这部分代码就必须要重新编译链接了。比较标准的做法是dll只向外界提供接口定义，而不提供实现定义，即提供的是一个类，但是类中只包含纯虚函数，而没有数据&#60;这种做法有点类似于COM的思路&#62;；或者是只提供一系列普通函数以及一个实现类的指针，这种做法有一个好处，如果添加新的函数接口，也无需重新更新外部程序，但是如果使用虚函数接口则不然。 b. [...]]]></description>
			<content:encoded><![CDATA[<p class="MsoNormal">库与代码重用</p>
<p class="MsoNormal"><span lang="EN-US">1</span><span>、静态库</span><span lang="EN-US">vs</span><span>动态库</span></p>
<p class="MsoNormal"><span>静态库的优势和劣势</span></p>
<p class="MsoNormal"><span>动态库的优势与问题</span></p>
<p class="MsoNormal"><span lang="EN-US">2</span><span>、静态</span><span lang="EN-US">C/C++</span><span>运行库</span><span lang="EN-US"> vs </span><span>动态</span><span lang="EN-US">C/C++</span><span>运行库</span><span lang="EN-US"> &amp; manifest</span></p>
<p class="MsoNormal"><span>3、关于动态库接口设计</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>库与代码重用</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>对于像</span><span lang="EN-US">C</span><span>，</span><span lang="EN-US">C++</span><span>这样的语言来说，库是扩展语言功能的重要手段，对于项目来说，代码库是节约开发时间，缩减成本，划分项目模块以利于多人合作，并通过重用已有代码而避免重复劳动的必要工具。开发一个稍具规模的项目，总免不了设计和划分模块，如何设计和划分，采取什么方案解决问题，总要面临诸多取舍。</span></p>
<p class="MsoNormal"><span>最近跟动态库</span><span lang="EN-US">/</span><span>静态库</span><span lang="EN-US">/</span><span>动态静态</span><span lang="EN-US">CRT/MFC</span><span>打了诸多交道。本文是对这段时间遇到的问题与思考的总结。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>1.<span> </span></span></span><span>静态库</span><span lang="EN-US"> vs. </span><span>动态库</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>静态库本质上就是编译好的目标代码</span><span lang="EN-US">(</span><span>使用</span><span lang="EN-US">vc</span><span>的话，编译</span><span lang="EN-US">.cpp</span><span>文件或者</span><span lang="EN-US">.c</span><span>文件之后会得到目标文件</span><span lang="EN-US">.obj</span><span>，编译结束后，</span><span lang="EN-US">link</span><span>程序会按照指定的要求将目标文件链接成最终的输出文件——常见的有</span><span lang="EN-US">.lib/.dll/.exe)</span><span>的一份合集。</span></p>
<p class="MsoNormal"><span>相比于动态库，静态库没有入口函数，不能独立执行，不能动态链接，一旦被链接到目标文件中，静态库的代码段</span><span lang="EN-US">/</span><span>数据段都会被合并到目标文件当中去。这是静态库与动态库的本质区别。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>由于上述差别，那么我们就很容易理解下面的情况：如果你在静态库中定义一个静态或全局对象，最终该对象会被链接到目标文件的数据段中（目标文件可能是某个</span><span lang="EN-US">dll</span><span>也可能是</span><span lang="EN-US">exe</span><span>）。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>静态库的优势在于，简单易用，你无需考虑内存的申请与释放问题，因为静态库的代码段作为目标模块的一部分（</span><span lang="EN-US">EXE</span><span>或</span><span lang="EN-US">DLL</span><span>），在其中所调用的</span><span lang="EN-US">new/delete</span><span>在链接阶段会被重定位到该目标模块的</span><span lang="EN-US">CRT</span><span>函数地址。</span></p>
<p class="MsoNormal"><del><em>对应的，如果你使用<span lang="EN-US">DLL</span>，由于<span lang="EN-US">DLL</span>中会自行初始化一份<span lang="EN-US">CRT</span>堆，因此它的内存申请与释放是在有别于在<span lang="EN-US">EXE</span>的<span lang="EN-US">CRT</span>堆中进行的（两个不同的<span lang="EN-US">CRT</span>堆），如果你从<span lang="EN-US">DLL</span>中申请一块内存出来使用，最后也必须交还给<span lang="EN-US">DLL</span>模块内部去释放。否则会产生<span lang="EN-US">Heap Collision</span>，在<span lang="EN-US">Debug</span>模式下，你会收到一个系统位于<span lang="EN-US">malloc</span></em><span><em>中的断言，并了解到发生了什么。</em>*</span></del></p>
<p class="MsoNormal"><span>此处的叙述存在错误，更正如下：</span></p>
<p class="MsoNormal">一个DLL或许会，或许不会拥有一份自行初始化的CRT堆，这取决于DLL是通过动态链接MSVCRT还是静态链接LIBCMT。由于CRT是通过一个全局变量_crtheap维护着一个Heap（Windows环境下），并且会在CRT初始化的时候初始化CRT堆，因此，如果整个应用程序都是通过链接动态的msvcrt，使用同一份crt，则不会有问题。但是如果有任一二进制模块链接了静态crt，则会拥有一份自己的crt堆。严格来说，你应当小心的保证来自于某个CRT的堆内存最后也被交还给该CRT堆，否则会发生错误，在Debug模式下，当你调用free函数释放来自于另一个CRT堆的内存时，会有一个_ASSERTE(_CrtIsValidHeapPointer(pUserData));断言失败，提醒你用错了堆。</p>
<p class="MsoNormal">如果我们的DLL是链接了一个静态的CRT并且发布出去了，那么我们还是应当保证所有来自于该DLL的内存都由该DLL回收。其原因如上述分析所言，是由于该DLL拥有一份自己的CRT堆。</p>
<p class="MsoNormal">&nbsp;</p>
<p class="MsoNormal"><span>再有，如果你的静态库</span><span lang="EN-US">A</span><span>依赖于静态库</span><span lang="EN-US">B</span><span>的头文件，而目标应用程序</span><span lang="EN-US">EXE</span><span>依赖了静态库</span><span lang="EN-US">A</span><span>，那么你在链接静态库</span><span lang="EN-US">A</span><span>的时候，也必须链接静态库</span><span lang="EN-US">B</span><span>。否则会有静态库</span><span lang="EN-US">A</span><span>中的某些符号无法被找到，从而导致链接失败。而动态库</span><span lang="EN-US">A</span><span>如果依赖于一个静态库</span><span lang="EN-US">B</span><span>，那么你无需在依赖该动态库的</span><span lang="EN-US">EXE</span><span>中再次包含该静态库，因为该动态库</span><span lang="EN-US">A</span><span>已经将该静态库</span><span lang="EN-US">B</span><span>链接到</span><span lang="EN-US">A</span><span>模块当中了，在</span><span lang="EN-US">DLL A</span><span>中已经有了一份，如果</span><span lang="EN-US">EXE</span><span>模块没有直接依赖于静态库</span><span lang="EN-US">B</span><span>的话，那么就无需在</span><span lang="EN-US">EXE</span><span>中再次</span><span lang="EN-US">link</span><span>该静态库了。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>上述问题也通常是混用静态库和动态库时可能遇到的一个问题：如果有一个静态库</span><span lang="EN-US">A</span><span>，被动态库</span><span lang="EN-US">B</span><span>和</span><span lang="EN-US">EXE C</span><span>都用到了，而</span><span lang="EN-US">EXE C</span><span>又需要使用动态库</span><span lang="EN-US">B</span><span>，那么这份</span><span lang="EN-US">A</span><span>的代码和全局数据就在动态库</span><span lang="EN-US">B</span><span>和</span><span lang="EN-US">EXE C</span><span>中存在了两份。第一，这是一种对空间的浪费；其次，这可能会引发问题，比如，当我在</span><span lang="EN-US">EXE C</span><span>中</span><span lang="EN-US">new</span><span>一份</span><span lang="EN-US">A</span><span>中的某对象</span><span lang="EN-US">X</span><span>的指针，并将该指针传入到动态库</span><span lang="EN-US">B</span><span>中使用，表面上看似乎没有问题，但是如果这个对象</span><span lang="EN-US">X</span><span>引用了</span><span lang="EN-US">A</span><span>库中的全局</span><span lang="EN-US">/</span><span>静态数据，就可能会引发问题，因为此时动态库</span><span lang="EN-US">B</span><span>中存在有一份</span><span lang="EN-US">A</span><span>库中的全局</span><span lang="EN-US">/</span><span>静态数据，而</span><span lang="EN-US">EXE C</span><span>中有另一份，如果这个全局</span><span lang="EN-US">/</span><span>静态数据被</span><span lang="EN-US">X</span><span>使用的话，那么就会得到前后不一致的上下文环境。具体可能产生什么问题要看具体的逻辑是如何组织的。在这种情况下，最好能将静态库</span><span lang="EN-US">A</span><span>重新组织成一个动态库，这样就不存在多份代码以及多套全局数据的问题了；如果不能这么做，你可以根据具体的应用程序组织的形式，采取</span><span lang="EN-US">DLL</span><span>导出</span><span lang="EN-US">A</span><span>库接口，</span><span lang="EN-US">EXE</span><span>使用</span><span lang="EN-US">DLL</span><span>导出接口去访问</span><span lang="EN-US">A</span><span>库而不直接链接</span><span lang="EN-US">A</span><span>库本身的方法来变通，但通常这不是根本的解决方案。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>另一方面，在</span><span lang="EN-US">DLL</span><span>中可以包含自己的资源文件，而</span><span lang="EN-US">LIB</span><span>中不能。因为</span><span lang="EN-US">DLL</span><span>是一份完整的</span><span lang="EN-US">PE</span><span>文件，而</span><span lang="EN-US">LIB</span><span>只是若干个</span><span lang="EN-US">OBJ</span><span>打包。这并不是说，使用</span><span lang="EN-US">LIB</span><span>的时候不能使用资源</span><span lang="EN-US">(RC)</span><span>，你可以将</span><span lang="EN-US">LIB</span><span>中使用的</span><span lang="EN-US">RC</span><span>文件，</span><span lang="EN-US">resource.h</span><span>都</span><span lang="EN-US">include</span><span>到目标应用程序当中，只要没有资源</span><span lang="EN-US">ID</span><span>冲突就可以正常使用，就像是你在目标应用程序的资源当中定义了这些资源一样。而</span><span lang="EN-US">DLL</span><span>中的资源则隶属于</span><span lang="EN-US">DLL</span><span>自身，不会与</span><span lang="EN-US">EXE</span><span>中的资源在</span><span lang="EN-US">ID</span><span>号上产生冲突，只是在使用的时候还需要针对</span><span lang="EN-US">Resource</span><span>做一些</span><span lang="EN-US">Handle</span><span>上的设置（参考</span><span lang="EN-US">AfxSetResourceHandle</span><span>），才能使得</span><span lang="EN-US">API</span><span>访问</span><span lang="EN-US">DLL</span><span>中的资源而非</span><span lang="EN-US">EXE</span><span>中的。如果要编写一个带有资源的库模块，</span><span lang="EN-US">DLL</span><span>确实是不二选择。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span lang="EN-US"><span> </span></span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>2.<span> </span></span></span><span>静态</span><span lang="EN-US">&amp;</span><span>动态</span><span lang="EN-US">C/C++ runtime </span><span>以及</span><span lang="EN-US"> manifest</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>静态的</span><span lang="EN-US">C-runtime</span><span>库叫做</span><span lang="EN-US">LIBC.lib</span><span>，我们通常使用多线程的版本叫</span><span lang="EN-US">LIBCMT.lib</span><span>，</span><span lang="EN-US">debug</span><span>下的版本叫</span><span lang="EN-US">LIBCMTD.lib</span><span>。而动态的</span><span lang="EN-US">C-runtime</span><span>，在</span><span lang="EN-US">VC</span><span>的环境下，我们使用的是</span><span lang="EN-US">MSVCRT.lib</span><span>，以及</span><span lang="EN-US">debug</span><span>下的</span><span lang="EN-US">MSVCRTD.lib</span><span>（这两个</span><span lang="EN-US">lib</span><span>实际是两个导出符号文件，实际的运行库位于</span><span lang="EN-US">msvcrt.dll/msvcrtd.dll</span><span>中</span><span lang="EN-US">(VC6)</span><span>，如果是</span><span lang="EN-US">VC2005</span><span>的版本则是</span><span lang="EN-US">msvcr80.dll/msvcr80d.dll</span><span>）</span></p>
<p class="MsoNormal"><span lang="EN-US">C++</span><span>的</span><span lang="EN-US">runtime</span><span>库，静态版的是</span><span lang="EN-US">LIBCPMT.lib/LIBCPMTD.lib</span><span>。动态版的是</span><span lang="EN-US">MSVCPRT.lib</span><span>以及</span><span lang="EN-US">MSVCPRTD.lib</span><span>。（类似的，实际的运行库位于</span><span lang="EN-US">&lt;VC2005</span><span>中</span><span lang="EN-US">&gt;msvcp80.dll/msvcp80d.dll</span><span>）</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>在第一点的对比中已经提到过，如果同时又</span><span lang="EN-US">DLL</span><span>模块依赖一个静态库，而又有一个依赖该</span><span lang="EN-US">DLL</span><span>模块的</span><span lang="EN-US">EXE</span><span>还依赖这个静态库，那么最终的内存中就会存在两份该静态库的代码以及全局数据。这一点对于</span><span lang="EN-US">C/C++</span><span>运行库也是一样的。所需要注意的是，只要是基于</span><span lang="EN-US">C</span><span>语言以及</span><span lang="EN-US">C++</span><span>语言的程序，几乎所有的都会用到</span><span lang="EN-US">C/C++</span><span>运行库，因此如果在规划一个项目的时候，发现有多个</span><span lang="EN-US">dll</span><span>模块，那么最好是使用动态版的运行库，以避免最终的内存中出现多份不必要的运行库代码。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>再有一点是关于</span><span lang="EN-US">XP</span><span>之后的系统中，以及在</span><span lang="EN-US">VC2005</span><span>之后的编译环境中，微软添加的</span><span lang="EN-US">manifest</span><span>机制。该机制是为了解决同名但版本错误的</span><span lang="EN-US">DLL</span><span>加载问题而提出的。比如，我们手上有两份</span><span lang="EN-US">VC</span><span>编译器，分别是不同的版本，其中携带的</span><span lang="EN-US">MSVCR80.dll</span><span>也是不同的版本，但是都叫</span><span lang="EN-US">MSVCR80.dll</span><span>，如果我们在应用程序安装时，简单粗暴的将系统目录下替换成我们应用程序所依赖的</span><span lang="EN-US">MSVCR80.dll</span><span>，那么可能会造成以往的应用程序无法正确运行的问题，因为他们可能依赖的是其他版本的</span><span lang="EN-US">MSVCR80.dll</span><span>。</span></p>
<p class="MsoNormal"><span>解决这个问题的途径是将一个</span><span lang="EN-US">dll</span><span>的运行环境，版本，以及校验值等信息编码到一个目录名当中，而在安装应用程序时，将该版本的</span><span lang="EN-US">dll</span><span>放置到对应名字的目录下，这样尽管</span><span lang="EN-US">dll</span><span>名称一致，我们也可以找到所需要正确版本的</span><span lang="EN-US">dll</span><span>。但是应用程序怎么知道需要依赖哪个版本的</span><span lang="EN-US">dll</span><span>呢？如果应用程序不知道，即使系统中有正确版本的</span><span lang="EN-US">dll</span><span>，操作系统也不知道应该去加载哪一个</span><span lang="EN-US">dll</span><span>。</span></p>
<p class="MsoNormal"><span>因此</span><span lang="EN-US">manifest</span><span>文件就把该模块所依赖的模块都标明了，通过名称，校验码，版本等等信息以确保所指明的模块不会有歧义（用记事本打开一个</span><span lang="EN-US">manifest</span><span>文件，你就可以看到上述信息），当该应用程序</span><span lang="EN-US">EXE</span><span>或者</span><span lang="EN-US">DLL</span><span>被操作系统加载时，系统就会根据该信息去查找对应模块，首先会在</span><span lang="EN-US">C:\Windows\WinSxS</span><span>目录下找（打开这个目录，你就能看到大量不同版本的</span><span lang="EN-US">CRT/CPRT/MFC/ATL</span><span>等等运行库），如果没有找到那么也会应用程序当前目录下找。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>这就是为什么使用</span><span lang="EN-US">VC2005</span><span>编译一个</span><span lang="EN-US">dll</span><span>也好，一个</span><span lang="EN-US">exe</span><span>也好，总会多生成一个</span><span lang="EN-US">manifest</span><span>的文件的原因。所以如果使用了动态版本的</span><span lang="EN-US">CRT</span><span>运行库，或者其他任何</span><span lang="EN-US">DLL</span><span>，那么最好就不要关闭</span><span lang="EN-US">manifest</span><span>生成的编译选项。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>3.<span> </span></span></span><span>关于动态库接口设计</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span>如果动态库可能经常会更新版本，提供新的功能接口，那么如果使得动态库的升级无需改动依赖于他的可执行模块就是一个值得思考的问题。</span></p>
<p class="MsoNormal"><span>具体要达到上述目标，需要保证的有以下几点：</span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>a.<span> </span></span></span><span>动态库中的类的内存布局不暴露给外界，也就是说动态库提供给用户的</span><span lang="EN-US">.h</span><span>中，不应当有类的成员信息。这是因为，如果类的内部数据修改了，增加了一个变量或者减少了，那么这个类所占用的内存大小也就修改了，倘若有</span><span lang="EN-US">exe</span><span>使用上一版本的类定义，</span><span lang="EN-US">new</span><span>了一个类对象，那么一旦发生类定义修改的情况，这部分代码就必须要重新编译链接了。比较标准的做法是</span><span lang="EN-US">dll</span><span>只向外界提供接口定义，而不提供实现定义，即提供的是一个类，但是类中只包含纯虚函数，而没有数据</span><span lang="EN-US">&lt;</span><span>这种做法有点类似于</span><span lang="EN-US">COM</span><span>的思路</span><span lang="EN-US">&gt;</span><span>；或者是只提供一系列普通函数以及一个实现类的指针，这种做法有一个好处，如果添加新的函数接口，也无需重新更新外部程序，但是如果使用虚函数接口则不然。</span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>b.<span> </span></span></span><span>动态库中的类应当不给外界提供构造析构的能力，同样是基于上述原因，如果外界可以</span><span lang="EN-US">new/delete</span><span>该类，那么当类的定义修改时就会出现不得不重新编译使用该库的应用程序的情况。倘若只通过</span><span lang="EN-US">dll</span><span>中定义好的借口进行交互，则不存在这个问题。比如外部不能直接</span><span lang="EN-US">new</span><span>一个对象，而是必须从一个</span><span lang="EN-US">dll</span><span>导出函数中获取一个对象的指针，外部同样不能</span><span lang="EN-US">delete</span><span>这个对象，而是必须将之送入另一个导出函数交给</span><span lang="EN-US">dll</span><span>去删除。这样只要</span><span lang="EN-US">dll</span><span>保持着两个接口不变，更新</span><span lang="EN-US">dll</span><span>时是无需更新依赖该</span><span lang="EN-US">dll</span><span>的应用程序的。（一个比较好的做法是将</span><span lang="EN-US">dll</span><span>提供外界的接口类的构造析构函数设置为</span><span lang="EN-US">private</span><span>）</span></p>
<p class="MsoListParagraph"><span lang="EN-US"><span>c.<span> </span></span></span><span lang="EN-US">Dll</span><span>应当尽可能使用动态</span><span lang="EN-US">crt</span><span>，如果用到了</span><span lang="EN-US">mfc</span><span>应当尽可能使用动态</span><span lang="EN-US">mfc</span><span>。这是为了避免</span><span lang="EN-US">dll</span><span>与外部模块中存在多份重复代码的情况。同时如果有</span><span lang="EN-US">dll</span><span>需要依赖的其他静态库，也应当尽可能使用该库的动态版本。</span></p>
<p class="MsoNormal"><span lang="EN-US"> </span></p>
<p class="MsoNormal"><span lang="EN-US">p.s. </span><span>如果想了解更多的关于静态库，动态库，</span><span lang="EN-US">C</span><span>运行库方面的知识，可以参考《</span><span lang="EN-US"><a href="http://www.douban.com/subject/3652388/"><span lang="EN-US"><span lang="EN-US">程序员的自我修养——链接装载与库<span lang="EN-US">》</span></span></span></a></span><span>一书。</span></p>
<p class="MsoNormal"><span>p.s.2 果然如<a href="http://mindhacks.cn">pongba老大</a>所言，将自己认为没有问题的知识写下来是一次很好的学习过程，写下本篇文章的过程中，我将原先认为明白的，但是实际并不见得就真的明白的问题，梳理清楚了很多。写的过程有助于把原先大脑中下意识存在的假设显现出来，在思维中再次加工，将原先不明确的概念搞清楚。</span></p>
<p class="MsoNormal">&nbsp;</p>
<p class="MsoNormal">#1 更正了关于DLL与CRT堆内存的一处错误，感谢coldfall热心指正</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2009/09/06/what-should-you-think-about-when-writting-a-dl/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Win32平台下基于SVN开发的若干问题整理</title>
		<link>http://www.windameister.org/blog/2009/05/15/win32-svn-dev-some-notes/</link>
		<comments>http://www.windameister.org/blog/2009/05/15/win32-svn-dev-some-notes/#comments</comments>
		<pubDate>Thu, 14 May 2009 16:24:00 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[svn]]></category>
		<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[svn-dev]]></category>
		<category><![CDATA[版本控制]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=80</guid>
		<description><![CDATA[  Keywords: svn  svn_cmdline_init crash 1.5.6 win32  SVN（SubVersion）是目前开源的版本管理工具中较为流行的，最新的release是1.6.1。但是遗憾的是基于SVN开发的资料在互联网上异常的少。          最近一段时间，我在工作上需要编写一个使用SVN进行版本管理的编辑器。（需要对编辑器生成的文件做版本管理以利于多人合作开发）起初是通过调用svn自身携带的的客户端程序svn.exe以实现相关功能，然而由于实际中的若干设计问题，最终的速度很不理想。于是产生了借用svn自身提供的api接口实现所需功能的想法。            在实际使用svn提供的api时遇到了一些问题，经过多番查找资料以及尝试最终得以解决。将问题整理在此，以方便日后与我遇到类似问题的朋友。            Svn提供的第三方开发接口sdk可以在这里下载到。目前最新的release是1.6.1。我在项目中实际使用的版本是1.5.6。开发需要的几个压缩包分别如下： Svn发布的2进制可运行文件，包括客户端与服务端，以及所需的dll： http://subversion.tigris.org/files/documents/15/45230/svn-win32-1.5.6.zip Svn开发所需的lib及头文件以及文档： http://subversion.tigris.org/files/documents/15/45236/svn-win32-1.5.6_dev.zip Svn的调试符号文件： http://subversion.tigris.org/files/documents/15/45234/svn-win32-1.5.6_pdb.zip   此外，在编写基于SVN的程序时，在链接时还有一些必要的库需要添加，分别是： Berkeley DB for Windows: http://subversion.tigris.org/files/documents/15/32472/db-4.4.20-win32.zip libintl binaries for Windows: http://subversion.tigris.org/files/documents/15/20739/svn-win32-libintl.zip   有了这几个包，就可以使用SVN提供的api开发基于SVN的第三方软件了。            基于SVN1.5及之后的版本开发的时候，会遇到一个CRT冲突的问题，该问题会导致在调用svn_cmdline_init时，如果在error stream参数中传入非NULL值，则会直接crash。一个针对该问题的描述可以参见这里。          我下载了调试符号以及源代码之后，跟踪调试到svn_cmdline_init函数当中，发现crash发生在一个CRT函数（setvbuf）的调用当中。实际跟踪调试的过程中发现，虽然该函数是crt中的函数，然而vc调试器却无法基于本地的调试符号正确跟踪到其源文件当中。 又该问题在1.4及之前的版本不存在。究其原因，我猜测可能是由于自1.5版本之后，SVN提供的库是基于动态链接的CRT库的。而svn本身的lib在编译时链接的crt与客户的VC6的CRT版本不符导致的。（1.4版本之前则是静态链接的CRT） 对该问题的描述还可以参见这里：   The problem is stderr &#8212; it is a FILE* which [...]]]></description>
			<content:encoded><![CDATA[<p align="center"> </p>
<p align="center">
<div style="text-align: auto;">Keywords: svn  svn_cmdline_init crash 1.5.6 win32</div>
<p><a href="http://subversion.tigris.org/" target="_blank"><img class="size-full wp-image-81 alignleft" title="subversion_logo_hor-468x64" src="http://blog.windameister.info/wp-content/uploads/2009/05/subversion_logo_hor-468x64.png" alt="subversion_logo_hor-468x64" width="468" height="64" /></a></p>
<p> SVN（SubVersion）是目前开源的版本管理工具中较为流行的，最新的release是1.6.1。但是遗憾的是基于SVN开发的资料在互联网上异常的少。</p>
<p><a href="http://blog.windameister.info/wp-content/uploads/2009/05/subversion_logo_hor-468x64.png"></a></p>
<p>         最近一段时间，我在工作上需要编写一个使用SVN进行版本管理的编辑器。（需要对编辑器生成的文件做版本管理以利于多人合作开发）起初是通过调用svn自身携带的的客户端程序svn.exe以实现相关功能，然而由于实际中的若干设计问题，最终的速度很不理想。于是产生了借用svn自身提供的api接口实现所需功能的想法。</p>
<p> </p>
<p>         在实际使用svn提供的api时遇到了一些问题，经过多番查找资料以及尝试最终得以解决。将问题整理在此，以方便日后与我遇到类似问题的朋友。</p>
<p> </p>
<p>         Svn提供的第三方开发接口sdk可以在<a href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100&amp;expandFolder=8100&amp;folderID=74">这里</a>下载到。目前最新的release是1.6.1。我在项目中实际使用的版本是1.5.6。开发需要的几个压缩包分别如下：</p>
<p>Svn发布的2进制可运行文件，包括客户端与服务端，以及所需的dll：</p>
<p><a href="http://subversion.tigris.org/files/documents/15/45230/svn-win32-1.5.6.zip">http://subversion.tigris.org/files/documents/15/45230/svn-win32-1.5.6.zip</a></p>
<p>Svn开发所需的lib及头文件以及文档：</p>
<p><a href="http://subversion.tigris.org/files/documents/15/45236/svn-win32-1.5.6_dev.zip">http://subversion.tigris.org/files/documents/15/45236/svn-win32-1.5.6_dev.zip</a></p>
<p>Svn的调试符号文件：</p>
<p><a href="http://subversion.tigris.org/files/documents/15/45234/svn-win32-1.5.6_pdb.zip">http://subversion.tigris.org/files/documents/15/45234/svn-win32-1.5.6_pdb.zip</a></p>
<p> </p>
<p>此外，在编写基于SVN的程序时，在链接时还有一些必要的库需要添加，分别是：</p>
<p>Berkeley DB for Windows:</p>
<p><a href="http://subversion.tigris.org/files/documents/15/32472/db-4.4.20-win32.zip">http://subversion.tigris.org/files/documents/15/32472/db-4.4.20-win32.zip</a></p>
<p>libintl binaries for Windows:</p>
<p><a href="http://subversion.tigris.org/files/documents/15/20739/svn-win32-libintl.zip">http://subversion.tigris.org/files/documents/15/20739/svn-win32-libintl.zip</a></p>
<p> </p>
<p>有了这几个包，就可以使用SVN提供的api开发基于SVN的第三方软件了。</p>
<p> </p>
<p>         基于SVN1.5及之后的版本开发的时候，会遇到一个CRT冲突的问题，该问题会导致在调用svn_cmdline_init时，如果在error stream参数中传入非NULL值，则会直接crash。一个针对该问题的描述可以参见<a href="http://svn.haxx.se/users/archive-2008-08/1071.shtml">这里</a>。</p>
<p>         我下载了调试符号以及源代码之后，跟踪调试到svn_cmdline_init函数当中，发现crash发生在一个CRT函数（setvbuf）的调用当中。实际跟踪调试的过程中发现，虽然该函数是crt中的函数，然而vc调试器却无法基于本地的调试符号正确跟踪到其源文件当中。</p>
<p>又该问题在1.4及之前的版本不存在。究其原因，我猜测可能是由于自1.5版本之后，SVN提供的库是基于动态链接的CRT库的。而svn本身的lib在编译时链接的crt与客户的VC6的CRT版本不符导致的。（1.4版本之前则是静态链接的CRT）</p>
<p>对该问题的描述还可以参见<a href="http://svn.haxx.se/users/archive-2008-08/1092.shtml">这里</a>：</p>
<p> </p>
<p>The problem is stderr &#8212; it is a FILE* which is a CRT type which means <br />
you have to be using the same CRT as the dll&#8217;s were built with. <br />
Unfortunately, there are a few API&#8217;s where raw CRT types crept in that <br />
aren&#8217;t wrapped by APR. </p>
<p>You can either avoid those API&#8217;s, rebuild Subversion with your <br />
compiler, or link to the static libraries instead of the dynamic <br />
libraries and use the no-default-libs option to make sure that your <br />
CRT is linked in instead of the VC6 one (I believe this is how it <br />
worked prior to 1.5). </p>
<p>DJ </p>
<p> </p>
<p>解决该问题的方法之一是自行编译svn的全部源码，而不使用官方提供的dev包。然而svn的源码编译要求的环境异常复杂（需要配置非常多的环境项：包括cygwin，python等等）。更简单的解决方案是使用静态链接版本的svn库。</p>
<p>svn_cmdline_init位于libsvn_subr-1.lib当中。该版本的lib在1.5及以后的版本开发包中是动态库，实际运行时需要libsvn_subr-1.dll的支持。同在该开发包中发布的还有一个静态版本的该库，名为svn_subr-1.lib。</p>
<p>但是链接静态版本的svn_subr-1.lib时，由于其中静态链接了CRT库，因此VC自带的libcmt.lib/libcmtd.lib会与之冲突。实际链接时会报出一堆同一个符号被多次链接的错误，只需将VC自带的libcmt.lib/libcmtd.lib设置忽略即可。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2009/05/15/win32-svn-dev-some-notes/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
	</channel>
</rss>

