<?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>空雪小屋 &#187; Windows编程</title>
	<atom:link href="http://blog.sorayuki.net/?cat=11&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://blog.sorayuki.net</link>
	<description>现在域名是 blog.sorayuki.net ~</description>
	<lastBuildDate>Tue, 29 Nov 2022 08:27:41 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0</generator>
	<item>
		<title>使用VS2022编译7zip的控制台程序</title>
		<link>http://blog.sorayuki.net/?p=651</link>
		<comments>http://blog.sorayuki.net/?p=651#comments</comments>
		<pubDate>Tue, 29 Nov 2022 07:58:52 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[VC]]></category>
		<category><![CDATA[Windows编程]]></category>
		<category><![CDATA[问题解决]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=651</guid>
		<description><![CDATA[7zip除了官方下载安装的图形界面工具之外，是有独立控制台程序的，在CPP\7zip\Bundles里面。这个 &#8230; <a href="http://blog.sorayuki.net/?p=651" class="more-link">继续阅读<span class="screen-reader-text">使用VS2022编译7zip的控制台程序</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>7zip除了官方下载安装的图形界面工具之外，是有独立控制台程序的，在CPP\7zip\Bundles里面。这个控制台程序还分为全功能版（7zz）的和精简的（7zr），但官方那边能下载到的只有精简的，全功能的那个要自己编译。</p>
<p>在用VS2022编译全功能版的时候我遇到了问题。虽然官方提供了nmake用的编译脚本，但是它跑起来总是报错，而且报错的地方在Windows SDK里面、而不是7z自己的代码。报的错误是C2279，出现在mapi.h头文件里、诸如“这个typedef地方不能写throw标识符”这样的东西。</p>
<p>网上搜索之后能找到的相关的东西不太多，有一个说法是C++标准的问题，现在的Windows SDK需要配合新版本的C++标准（C++17之类）才能正常使用。看了一下7zip源码里nmake脚本加的参数，也确实没有指定标准。</p>
<p>本来想试试换个旧版的Windows SDK行不行，一看要下载那么多东西……烦了，就没试。最后我的做法是，<span style="color: #ff0000;">打开CPP下的Build.mk，找到定义 CFLAGS 的那一行，加个 /std:c++17</span>。文件是只读的，要去掉只读属性才能保存。再编译会变成另外的错误，报异常规范不匹配。最简单的<span style="color: #ff0000;">去掉警告视为错误（搜索，去掉-WX）</span>可以编译通过。</p>
<p>另外我发现它没加链接时代码生成这样的优化，暂时不知道为什么。试着开了一下，用7zz b看跑分，和不开看不出区别。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=651</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Windows动态链接库简介</title>
		<link>http://blog.sorayuki.net/?p=630</link>
		<comments>http://blog.sorayuki.net/?p=630#comments</comments>
		<pubDate>Sun, 04 Mar 2018 07:06:31 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=630</guid>
		<description><![CDATA[要说为什么写这篇，大概只是希望它能“有用”吧。 Windows下面可执行的代码通常会存在两种文件里面，一种是  &#8230; <a href="http://blog.sorayuki.net/?p=630" class="more-link">继续阅读<span class="screen-reader-text">Windows动态链接库简介</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>要说为什么写这篇，大概只是希望它能“有用”吧。<span id="more-630"></span>
<p>Windows下面可执行的代码通常会存在两种文件里面，一种是 EXE 一种是 DLL。其中 EXE 大家都知道，双击就能运行。但是 DLL 双击的话，只会弹一些什么不知道如何打开文件啊、选择一个应用程序用来打开，这种。反正就是不让你开。</p>
<h1>1. 链接</h1>
<p>要说动态链接库的话，我觉得要从链接开始说起才能大概讲得清楚。那么先说链接，比如我有两个 C 语言的代码文件，一个叫 a.c 一个叫 b.c。完了在 a.c 里面只有这么一点内容：</p>
<pre>int add(int a, int b) { return a + b; }</pre>
<p>简单到爆了，就是给两个整数算和。然后在 b.c 里面调用它：</p>
<pre>#include &lt;stdio.h&gt;<br />int add(int a, int b);<br />int main() { printf("%d", add(1, 2)); return 0; }</pre>
<p>这样子的代码，编译链接之后执行，输出的结果是 3。……嗯，差不多是废话了。</p>
<p>现在我们来看看编译链接的过程。首先是编译， a.c 变成 a.obj，然后 b.c 变成 b.obj。也就是说，一个 .c 代码文件变成一个 .obj。这么说来，计算加法的那个代码，虽然在 b.c 里面调用，但是并不在 b.obj 里面，它在 a.obj 里面。<br />大概来研究一下 a.obj 里面到底是什么样的东西：这里要关注的，其实就里面的两个部分：一个部分叫“符号表”，a.c 里面的 add 函数，“add”这个名字就叫“符号”，编译成 obj 之后这个符号还保留着，链接的时候要用到；另一个部分叫“代码区”，add 函数里“把 a 和 b 拿出来加在一起又放到存返回值的地方”这一操作所对应的机器代码，就放在“代码区”里。然后符号表的“add”那边，就写了这个“add”对应的东西在代码区的哪里哪里。</p>
<p>然后再来看一下 b.obj 里面是什么。符号表里有 main，另外还多了个叫“重定位表”的东西，写明了有用到 printf 和 add，在哪里哪里用到，并没有说用到的东西在哪里（知道在哪里了还要链接器干嘛）。</p>
<p>好了现在我们拿到这么些东西，b.obj 里面写了“在这里这里用到 add”， a.obj 里写了“我这里有 add”。链接器看到了这两个，就把 a.obj 里面，代码区的 add 函数所在的位置，往 b.obj 里写了“我要add”的地方一填，就大功告成了。同理，printf 就是在标准库的符号表里面，找到 printf，然后把符号表的 printf 的那一栏里面标明的 printf 在代码区的哪里哪里，往 b.obj 里写了“我要printf”的地方一填，事情就差不多了。还有一步就是那个 main，程序运行起来的时候会先执行 C 语言标准库里的初始化代码，然后进 main 函数。至于 main 函数在哪里，标准库也不知道，于是标准库也就留了个地方，写上“我要main”，完了链接器把 main 在代码区里的位置朝这边一填，程序能运行了。</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic1.png"><img width="635" height="536" title="pic1" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="pic1" src="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic1_thumb.png" border="0"></a></p>
<p>每个大格子里，最上面是文件名、库名；第二格是符号表，声明“我有什么”；第三格是代码区，放编译后的代码的；第四格是重定位表（可有可无），代表“我要什么”。这个是简化过的模型，实际场景中的 obj 文件和 库 文件要比这个复杂很多。</p>
<h1>2. 动态链接</h1>
<p>现在我们来改一改，刚才是 a.c 编译成 a.obj，然后 b.c 编译成 b.obj，最后 a.obj 和 b.obj 和标准库链接在一起，变成能跑的代码。现在改为， a.c 编译成 a.obj 然后链接成 a.dll， b.c 编译成 b.obj 链接成 b.exe，那么这其中会有什么变化……</p>
<p>根据上面的描述，要达到这样的效果，至少要有这么几个东西：一个是“我要什么”，一个是“我有什么”，然后才能搞起来。Windows系统确实是提供了这样的一套方式，但并不是上面 a.obj 的“我要什么”：你见过这么用的，是 DLL 而不是什么 OBJ 对吧。DLL 有 DLL 自己的“我有什么”的表示方式，这个就是“导出表”。导出表里面说白了也是上面说的符号表里的那些东西，但是和符号表不同：符号表的使用时机是链接的时候，这个时候有很多“内部符号”还留着的，这些“内部符号”在链接完成之后其实并没有什么用了。但是导出表就可以不必写这些东西：这个导出表又不是给链接器用的……</p>
<p>导出表存放在 exp 文件中。以刚才的 a.c 为例子，在 a.c 编译成 a.obj 之后，假如我要做的是一个 a.dll 文件，我有个 a.exp 文件里面是说了我要导出 add 函数，那就把 a.obj 和 a.exp 链接一下，生成 a.dll 就好了。画个图容易理解一些：</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic2.png"><img width="738" height="517" title="pic2" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="pic2" src="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic2_thumb.png" border="0"></a></p>
<p>这个生成的 DLL 就包含了“我有什么”的部分了，有人要来找 add 的话，就能看到 a.dll 里有 add，还知道它在代码区的什么地方，要调用能调用了。</p>
<p>接下来还有一个，就是 b.obj 那边。现在 a.obj 变成了 a.dll， a.dll 又没有什么符号表之类的给你用，链接器没办法直接往 a.dll 上面链的：这一步是操作系统干的事情，其实不算链接器要干的事情。那么现在 b.obj 要做成 b.exe，需要有一种“DLL的仪式”，来声明“我要什么”。这个是“导入表”。导入表通常是 lib 文件，这种类型，写程序的人看到的应该比 exp 要多的多。导入表里其实也没什么太多东西，大致就是个“我要 xxx.dll 文件里的 xxx 函数，给我放到 xxx 全局变量里”。这个“全局变量”呢，就是个函数指针。这么讲有点抽象，我再画个图就简单多了：</p>
<p>
<a href="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic3.png"><img width="455" height="467" title="pic3" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="pic3" src="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic3_thumb.png" border="0"></a></p>
<p>那么这样的 lib 文件和 b.obj 链接的时候，就是这么一种景象：</p>
<p>
<a href="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic4.png"><img width="1039" height="933" title="pic4" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="pic4" src="http://blog.sorayuki.net/wp-content/uploads/2018/03/pic4_thumb.png" border="0"></a></p>
<p>从这里面可以看到，它也有 b.obj 中重定位表需要找的“符号表”，里面有个 add。然后它也有代码区，里面确实是 add 的代码，不过是假的 add 的代码：它的功能就是把调用转发给 __imp_add 函数指针指向的函数。这个函数指针是属于导入表的一部分，导入表写明了这个函数指针要指向的是“a.dll 文件里的 add 函数”。这样 Windows 在启动 b.exe 文件的时候，就会看到它导入表里面写了“我要 a.dll 文件里的 add 函数”，Windows 就会加载 a.dll 文件，加载了之后把 a.dll 里导出表里写的 add 函数所在的地址，填入 __imp_add 全局变量。这样在 b.exe 执行到这个函数的时候，就会调用 a.dll 里的 add 了。</p>
<p>根据实际情况，有的时候也会跳过 先调用 add 再调用 __imp_add 函数指针 这样两步的步骤，而是简化为一步、直接去调用 __imp_add 函数指针。比如在声明 int add(int a, int b) 的时候加上 __declspec(dllimport) 这样的声明，生成的代码就会直接调用 __imp_add 函数指针。</p>
<h1>3. 使用方法</h1>
<p>说这么多，那么实际操作的时候应该是什么样的呢……我就以上面为例子，说说最简单的情况。</p>
<p>首先要介绍 def 文件，这种文件定义了导入导出的 DLL 叫什么名字，以及函数名是什么。基本语法非常简单，上面的情况就是：</p>
<pre>LIBRARY a.dll
EXPORTS
add</pre>
<p>第一行是 LIBRARY 后面加上 DLL 的文件名，第二行写 EXPORTS 单独一行，后面就都是函数名了，一个一行。比方说除了 add 还有一个 sub 函数，那就在下面加一行 sub 就行了。</p>
<p>之所以要介绍这种文件，是因为生成上面提到的 exp 文件和生成 lib 文件都要用到它。</p>
<h2>1. 在 MSVC 里使用</h2>
<p>不同编译器生成的方法不同，对于微软的 Visual Studio 来说，在命令行下面生成的方式是</p>
<pre>&gt;link /lib /out:a.lib /def:a.def
Microsoft (R) Library Manager Version 9.00.30729.207
Copyright (C) Microsoft Corporation.  All rights reserved.

LINK : warning LNK4068: /MACHINE not specified; defaulting to X86
   Creating library a.lib and object a.exp</pre>
<p>第一行是命令，后面几行是输出。可以看到它同时生成了 exp 文件和 lib 文件。输出的时候有个提示说没有指定机器类型默认给你选 x86 了。这个地方可以用 /MACHINE 来指定你要做 32 位的 exp/lib 文件还是 64 位的 exp/lib 文件。然后拿着这个 exp 文件就可以生成 DLL 了，同样是命令行：</p>
<pre>&gt;cl /nologo /c a.c
a.c

&gt;link /nologo /dll /out:a.dll a.exp a.obj

&gt;
</pre>
<p>这里把 a.c 编译成了 a.obj 然后再和 exp 文件链接在一起，生成了 a.dll。为了验证确实导出了 add 函数，可以用 CFF Explorer 工具打开这个 DLL，切换到 Export Directory 视图。</p>
<p>然后对于上面说的 b.c，这么编译链接就能运行了：</p>
<pre>&gt;cl /nologo /c b.c
b.c

&gt;link /nologo /out:b.exe b.obj a.lib

&gt;b.exe
3
&gt;</pre>
<h2>2. 在 Visual Studio 里使用</h2>
<p>用图形化开发环境的来做这种东西确实是容易。在 Visual Studio 里面，这个例子在同一个解决方案里给 a.dll 和 b.exe 分别创建工程 a 和工程 b 就行。假设两个创建的时候都选了“空工程”，那么要设置的地方，一个是右键点工程 a 选属性，弹出来的“属性页”里在“配置属性——常规——项目默认值——配置类型”那边，改成“动态库”，然后到“配置属性——链接器——输入——模块定义文件”，输入 def 文件的文件名。def 文件和源代码文件放在同一个目录下，为了方便编辑也可以把它添加到工程中。这样在构建工程 a 的时候，就会同时生成 lib 文件和 exp 文件了。</p>
<p>对于工程 b 来说，因为和工程 a 是同属于同一个解决方案，所以直接在工程 b 的“引用”那一栏添加对工程 a 的引用就行了。如果不是同属于一个解决方案，那么同样是右键点工程 b 选属性，弹出来的“属性页”里在“配置属性——链接器——输入——附加依赖项”那边添加 a.lib 即可。</p>
<h2>3. 在 MinGW-GCC 里使用</h2>
<p>Windows 下开发，如果是做开源项目，那么用到 GCC 的概率还是蛮大的。在 MinGW 里，也有一套工具用来搞这个 DLL 的。也还是这么几个套路：生成 .exp 和生成 .lib，然后用什么命令编译链接。<br />生成 exp 和 lib 文件用的工具是 dlltool，命令行非常直白，看得懂英语的话，就是指定输入 def 文件是啥输出 lib 文件是啥输出 exp 文件是啥。</p>
<pre>&gt; dlltool --input-def=a.def --output-exp=a.exp --output-lib=a.lib</pre>
<p>这货很高冷，啥也不输出（</p>
<p>得到 def 文件和 exp 文件之后，可以编译链接 a.c 和 b.c 了。命令行如下：</p>
<pre>&gt; gcc -c a.c

&gt; gcc -shared -o a.dll a.exp a.o

&gt; gcc -c b.c

&gt; gcc -o b.exe b.o a.lib

&gt; b.exe
3
&gt;</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=630</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Windows下控制子进程的句柄继承</title>
		<link>http://blog.sorayuki.net/?p=625</link>
		<comments>http://blog.sorayuki.net/?p=625#comments</comments>
		<pubDate>Wed, 03 Jan 2018 15:00:20 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=625</guid>
		<description><![CDATA[相信用 Windows API 写过重定向子进程的标准输入输出的人都弄过这东西。大概过程是不难，不过对初学者坑 &#8230; <a href="http://blog.sorayuki.net/?p=625" class="more-link">继续阅读<span class="screen-reader-text">Windows下控制子进程的句柄继承</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>相信用 Windows API 写过重定向子进程的标准输入输出的人都弄过这东西。大概过程是不难，不过对初学者坑很多。几年前我写了个文大概说这个的 <a title="http://blog.sorayuki.net/?p=85" href="http://blog.sorayuki.net/?p=85">http://blog.sorayuki.net/?p=85</a> ，现在要说的也是这个话题，不过是另外的事。</p>
<p>之前的经验是这样的，比如我有一个进程，这个进程要开个子进程、并且发数据给子进程。那么做法是创建一对匿名管道，把管道读的那一端拿去给子进程，写的这一端自己留着。容易出错的地方在这个“自己拿着”和“拿去给子进程”。拿去给子进程可以理解为，这个句柄要设置成可继承的，子进程开启以后就带着这个句柄了，那么自己进程拿着的就可以关掉（CloseHandle）了；自己拿着，那么字面意思就是不继承给子进程了。API函数CreatePipe有个 SECURITY_ATTRIBUTES 参数，这个结构体里是可以设置创建的管道句柄能不能继承的，要么两个都能继承要么两个都不能继承。但是现在要的是写的那个不继承、读的那个继承。做法不止一种，不过思路是这么两个，一个是创建的时候两个都不能继承，然后把其中一个改成可以；另一个是创建的时候两个都能继承，然后把其中一个改成不行。改继承属性可以用 SetHandleInformation 也可以用 DuplicateHandle 复制一个能/不能继承的新句柄出来、随后把原来的关掉。我现在是用 SetHandleInformation 的方法，DuplicateHandle 的方法已经忘记是在哪里看的了，现在也不知道怎么比较好坏和必要性。</p>
<p>复杂一点的场景，是我要起两个进程，第一个进程的输出给第二个进程的输入。麻烦一点的办法可以两个都和自己通信、自己在中间做转发；简单的办法可以直接创建一对管道，一个子进程分一个、自己不留。现在这里要说的是后者，复杂性一个表现在管道是自己创建的，但是自己在起了子进程之后一个都不留；另一个表现在要刚好一个进程继承一个，如果有一个进程继承了两个，比如写的那一端一不小心两个进程都继承了，那么很不幸这一对进程永远跑不完了：写入数据的那个子进程写完了关掉了这个写入端，但是由于读取数据的那个子进程也持有这个写端，自己有自己都不知道，也就是说这个写端没法全部被关闭，那么读的那一端就永远在等待了。</p>
<p>虽然上面那个例子只要搞清楚关系、小心操作的话，也没有那么大问题。不过麻烦的事情在，假设我现在是一个有好几个线程在同时执行的程序，在其中一个线程A里，要调用 CreateProcess 的时候，它不知道其他线程比如B有没有创建什么会被继承的句柄，假设这个时候线程B也在调用 CreateProcess，那么线程B创建的这个子进程会把线程A创建的那些句柄也一起继承走。然后问题呢就会出在刚才说的那个场景上，比如一对管道，读取端永远阻塞因为写入端没有全部被关闭。也许会觉得这样的场景只要在要创建子进程的时候锁个整个进程范围的锁就好了，但如果这里面调用了什么第三方库，或者是工程上好多人同时开发一个项目，有些事情就不一定有那么可控了。而且这种Bug还可能在极偶然的情况下才会遇到那么一两次，调试的时候难以重现十分痛苦……啊别说了，害怕。</p>
<p><span id="more-625"></span>
<p>那么在 CreateProcess 的时候有没有什么更可靠的方法，比如不光是给每个句柄设置什么标志能不能继承，还能在调用 CreateProcess 的时候通过什么参数来显式指定我要这个、这个、这个和那个句柄？其实是有的，我以前没用过，最近看了一个博客之后才知道有这样的玩法。博客在这里：</p>
<p><a title="https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873/" href="https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873/">https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873/</a></p>
<p>其中很关键的一个示例函数的代码是</p>
<pre>BOOL CreateProcessWithExplicitHandles(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation,
    // here is the new stuff
  __in         DWORD cHandlesToInherit,
  __in_ecount(cHandlesToInherit) HANDLE *rgHandlesToInherit)
{
 BOOL fSuccess;
 BOOL fInitialized = FALSE;
 SIZE_T size = 0;
 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;

 fSuccess = cHandlesToInherit &lt; 0xFFFFFFFF / sizeof(HANDLE) &amp;&amp;
            lpStartupInfo-&gt;cb == sizeof(*lpStartupInfo);
 if (!fSuccess) {
  SetLastError(ERROR_INVALID_PARAMETER);
 }
 if (fSuccess) {
  fSuccess = InitializeProcThreadAttributeList(NULL, 1, 0, &amp;size) ||
             GetLastError() == ERROR_INSUFFICIENT_BUFFER;
 }
 if (fSuccess) {
  lpAttributeList = reinterpret_cast&lt;LPPROC_THREAD_ATTRIBUTE_LIST&gt;
                                (HeapAlloc(GetProcessHeap(), 0, size));
  fSuccess = lpAttributeList != NULL;
 }
 if (fSuccess) {
  fSuccess = InitializeProcThreadAttributeList(lpAttributeList,
                    1, 0, &amp;size);
 }
 if (fSuccess) {
  fInitialized = TRUE;
  fSuccess = UpdateProcThreadAttribute(lpAttributeList,
                    0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
                    rgHandlesToInherit,
                    cHandlesToInherit * sizeof(HANDLE), NULL, NULL);
 }
 if (fSuccess) {
  STARTUPINFOEX info;
  <a href="http://blogs.msdn.com/b/oldnewthing/archive/2005/06/28/433341.aspx">ZeroMemory</a>(&amp;info, sizeof(info));
  info.StartupInfo = *lpStartupInfo;
  info.StartupInfo.cb = sizeof(info);
  info.lpAttributeList = lpAttributeList;
  fSuccess = CreateProcess(lpApplicationName,
                           lpCommandLine,
                           lpProcessAttributes,
                           lpThreadAttributes,
                           bInheritHandles,
                           dwCreationFlags | EXTENDED_STARTUPINFO_PRESENT,
                           lpEnvironment,
                           lpCurrentDirectory,
                           &amp;info.StartupInfo,
                           lpProcessInformation);
 }
 
 if (fInitialized) DeleteProcThreadAttributeList(lpAttributeList);
 if (lpAttributeList) HeapFree(GetProcessHeap(), 0, lpAttributeList);
 return fSuccess;
}</pre>
<p></p>
<p>我自己在用这个方法的时候倒是没有直接抄代码，里面有些地方被我换成更简单的代码。不过核心思想是，用 STARTUPINFOEX 来代替 STARTUPINFO，这样在那个带 EX 后缀的结构体里有个 lpAttributeList 可以用，里面可以塞一些比如我要继承那些句柄这样的信息。那么在你给的列表之外的句柄，就不会被子进程继承。</p>
<p>这样回到刚才的例子，我要开两个进程比如A和B，把A的标准输出连到B的标准输入上，就可以创建一对管道，反正都设成继承、也不用一个继承一个不继承了。然后，创建子进程的时候一人塞一个，用这种方式指定只继承其中一个、不继承另外一个，两个进程都开好了就把自己手上的全关了。剩下的就是等待子进程运行完成了。</p>
<p>在实验过程中我还发现有这样一个坑（？），Windows的那几个标准输入输出标准错误的句柄，就是用 GetStdHandles 获得的那几个，虽然可以用 DuplicateHandle 来复制，但是这复制出来的却不能继承。如果尝试在继承句柄列表里塞上这样复制出来的 Stdin、Stdout 或者 Stderr 句柄，CreateProcess 会调用失败，然后给你一个 1450（ERROR_NO_SYSTEM_RESOURCES） 错误。还有就是，虽然指定了要继承的句柄的列表，CreateProcess 的那个 bInheritHandles 函数还是要设置成 TRUE 的，不然你列表里指定的那些句柄它继承不下去。以及在要继承的句柄列表里指定的那些句柄，也必须是可继承的，如果有不可继承的在里面，CreateProcess 会报 87（ERROR_INVALID_PARAMETER） 错误。</p>
<p>我这边就拿一个程序就以用lame重编码MP3文件为例的sample吧。代码不一定就没问题，只演示一下用法</p>
<pre>#include &lt;windows.h&gt;
#include &lt;exception&gt;
#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;functional&gt;

#include &lt;atlbase.h&gt;

class OnExit
{
    std::function&lt;void()&gt; onExit_;
public:
    template&lt;class T&gt; OnExit(const T&amp; lambda) : onExit_(lambda) {}
    ~OnExit() { onExit_(); }
};

class ReEncoderErr : public std::exception 
{
    std::string msg_;
public:
    ReEncoderErr(const char* msg) : msg_(msg) {}
    const char* what() const { return msg_.c_str(); }
};

class ReEncoder
{
    CHandle hProcDec_, hProcEnc_;
    CHandle hPipeRead_, hPipeWrite_;

    void ClosePipePair()
    {
        hPipeRead_.Close();
        hPipeWrite_.Close();
    }

    void CreatePipePair()
    {
        ClosePipePair();

        HANDLE hPipeRead, hPipeWrite;
        SECURITY_ATTRIBUTES sa{};
        sa.bInheritHandle = TRUE;
        if (!CreatePipe(&amp;hPipeRead, &amp;hPipeWrite, &amp;sa, 0))
            throw ReEncoderErr("Fail to create pipe");

        hPipeRead_.Attach(hPipeRead);
        hPipeWrite_.Attach(hPipeWrite);
    }

    void RunLame(bool isEncoder)
    {
        wchar_t decCmdLine[] = L"lame.exe --decode input.mp3 -";
        wchar_t encCmdLine[] = L"lame.exe --preset medium - output.mp3";
        wchar_t* cmdLine;

        STARTUPINFOEXW si{};
        PROCESS_INFORMATION pi{};

        std::vector&lt;HANDLE&gt; handlesToInherit;

        si.StartupInfo.cb = sizeof(si);
        si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;

        HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);

        si.StartupInfo.hStdError = hStdErr;

        if (isEncoder)
        {
            si.StartupInfo.hStdInput = hPipeRead_;
            handlesToInherit.push_back(hPipeRead_);
            cmdLine = encCmdLine;
        }
        else
        {
            si.StartupInfo.hStdOutput = hPipeWrite_;
            handlesToInherit.push_back(hPipeWrite_);
            cmdLine = decCmdLine;
        }

        SIZE_T nProcThreadAttrSize = 0;
        InitializeProcThreadAttributeList(0, 1, 0, &amp;nProcThreadAttrSize);
        if (nProcThreadAttrSize == 0)
            throw ReEncoderErr("Fail to get size of procthreadattr");

        std::vector&lt;char&gt; buf(nProcThreadAttrSize);
        si.lpAttributeList = reinterpret_cast&lt;LPPROC_THREAD_ATTRIBUTE_LIST&gt;(buf.data());
        if (InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &amp;nProcThreadAttrSize) == FALSE)
            throw ReEncoderErr("Fail to init procthreadattr");

        OnExit clean([&amp;]() { DeleteProcThreadAttributeList(si.lpAttributeList); });

        if (UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handlesToInherit.data(), handlesToInherit.size() * sizeof(HANDLE), NULL, NULL) == FALSE)
            throw ReEncoderErr("Fail to update procthreadattr");
        
        if (!CreateProcessW(NULL, cmdLine, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &amp;si.StartupInfo, &amp;pi))
            throw ReEncoderErr("Fail to create process");

        CloseHandle(pi.hThread);

        if (isEncoder)
            hProcEnc_.Attach(pi.hProcess);
        else
            hProcDec_.Attach(pi.hProcess);
    }

public:
    void Run()
    {
        CreatePipePair();
        RunLame(false);
        RunLame(true);
        ClosePipePair();

        HANDLE handles[2] = { hProcEnc_.m_h, hProcDec_.m_h };
        WaitForMultipleObjects(2, handles, TRUE, INFINITE);
    }
};

#include &lt;iostream&gt;

int main(int argc, char* argv[])
{
    try 
    {
        ReEncoder reenc;
        reenc.Run();
        return 0;
    }
    catch (const std::exception&amp; exception)
    {
        std::cerr &lt;&lt; exception.what() &lt;&lt; std::endl;
        return 1;
    }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=625</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>返回值优化没了 / FPU爆栈 / 线程池疯长 / 连接被重置</title>
		<link>http://blog.sorayuki.net/?p=595</link>
		<comments>http://blog.sorayuki.net/?p=595#comments</comments>
		<pubDate>Tue, 28 Feb 2017 13:50:09 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>
		<category><![CDATA[程序设计]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=595</guid>
		<description><![CDATA[标题之所以那么复杂是因为我把本来应该分开写的东西弄在一起了…… 这些事情都是在公司里工作（或者摸鱼？）的时候遇 &#8230; <a href="http://blog.sorayuki.net/?p=595" class="more-link">继续阅读<span class="screen-reader-text">返回值优化没了 / FPU爆栈 / 线程池疯长 / 连接被重置</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>标题之所以那么复杂是因为我把本来应该分开写的东西弄在一起了……<br />
这些事情都是在公司里工作（或者摸鱼？）的时候遇到的。有的记下来了有的没有。记下来的这几个，本来说要写到博客上的，结果丢在桌面的便签上这么久了，现在已经从那家公司离职，又入职新的公司做了一年半，这便签上记录的东西一个都还没写。都是两年前的事了吧</p>
<p><span id="more-595"></span></p>
<p>1、全局优化下消失的返回值</p>
<p>那个时候在做的项目是把最终幻想10移植到PC平台上。有个奇怪的地方是玩家放了“探查”技能，出来的显示敌人信息的界面，在Release下什么键都不按、敌人的模型也会在一直缩放到最小才停下；但是Debug下没有这个问题。</p>
<p>因为能单步跟踪调试，所以事情就好办很多。它里面有个函数会在调用的时候返回用户押了哪些按键。但是在Debug和Release下，这个函数的返回值不太一样。函数内容很简单，就大概长这样（大概意思。具体函数名我已经不记得了）：</p>
<pre>int GetPressedKey()
{
    GetPressedKeyImpl();
}
</pre>
<p>GetPressedKeyImpl 是返回 int 的，而且在那之后什么东西都没有执行。所以如果 GetPressedKeyImpl 在返回的时候把返回值存入了 eax 寄存器，然后 GetPressedKey 又什么事都没干，那么在它返回的时候外面拿到的返回值是 GetPressedKeyImpl 的。</p>
<p>但是在开启了全局优化之后，我猜测它检测到了这个 GetPressedKeyImpl 只有这个地方有调用，然而调用了又不拿返回值，所以生成代码的时候，就跳过了存入返回值这个阶段，所以生成的代码在 GetPressedKeyImpl 里面，也找不到和 return 语句相关的部分（反汇编跟踪看到的）。没开全局优化的时候，谁知道它有没有其他地方用到，所以在单个编译单元里面不敢做这样的优化。</p>
<p>当然说到底，这个问题是因为 GetPressedKey 函数里没写 return 。然后编译的时候警告又是几万个那种、没人去看。PS3 和 PS4 的编译器没有到这种程度（链接时）还能优化，所以在 PS3、PS4 上这个游戏运行没有问题。但是到了 PC 上，用了 VC 编译，这问题就出来了。</p>
<p>&nbsp;</p>
<p>2、FPU爆栈</p>
<p>对于 C 语言来说，寄存器怎么用、栈怎么用是编译器给你决定的，在使用 80x87 协处理器计算浮点数的时候，栈怎么操作也是。但是项目里就遇到了个问题，游戏跑起来画面各种诡异，到处都是一块块黑的，但是又不是整个屏幕完全黑。我知道这个事情的时候，从同事那边听说来的，已经是一个函数里下着断点，看局部变量的话，两个里面数据都正常的浮点数，运算出来的结果……是错的。肉眼可见的错误，算出来的结果是奇怪的值。</p>
<p>这个问题我调查没结果，这边的 Leader 程序员调查也没结果，最后是交给技术总监了。后来技术总监给 Leader 回了邮件，我专门她抄送一份给我，然后知道了这个问题出在哪里。</p>
<p>在 VC 里，如果一个函数的返回值是浮点数，那么这个返回值将会放在 FPU 的栈顶来返回的，由调用者从栈顶取出这个值。但是如果，这个函数没有声明就直接调用，在 C 语言里是默认这个函数返回 int 的。这样一来，函数调用完之后，被调者把返回值放在 FPU 的栈顶，而调用者却没有把这个返回值出栈。这样一来要不了几次，栈就到了极限容量。再往里面压新的数据，FPU 就直接报异常了。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>3、线程池中疯长的线程</p>
<p>也是那个项目。游戏用到的数据，无数多的小文件给文件系统带来负担，读取速度也会受影响，而且很多数据其实信息量不大，浪费了不少空间。所以就打算压缩一下打包成“大文件”。研发打包工具的任务在我和另一个同事身上。我是做打包的，他是做解包的。</p>
<p>最初的版本做出来之后，顺序读取、单线程压缩，没有办法很好地发挥打包专用服务器的性能。所以接下去就是再优化一下。</p>
<p>我那会儿想到了Windows的线程池：源文件数据读出来之后，因为是一个个数据块分块压缩的，所以可以把压缩任务用多个线程并行执行。因为之前看过一本《Windows并发编程指南》的书，里面提到了Vista的线程池和遗留线程池，说可以由系统来管理它们，CPU满的时候就不会再开新线程了，看起来正是我所需要的东西；同时为了兼容性，选用了旧的遗留线程池。</p>
<p>我用了信号量来控制最大同时执行的任务数，其中用到了 RegisterWaitForSingleObject 这个 API 来等待信号量。这个 API 有个参数，可以设定等待的模式， WT_EXECUTEONLYONCE 表示只等待一次。我用了这个 Flags，然后执行的时候发现一下子就卡得不行动不了了。</p>
<p>在 Visual Studio 里用调试方式运行，中断程序之后看线程列表，看到有无数个线程在里面，根本不想书上说的“系统会控制线程的数量”。这么明显和严重的 Bug，怎么可能呢……我觉得，有那么多的服务器软件在 Windows 下，会要用线程池。</p>
<p>因为开头说的时间久远的缘故，具体问题在哪里是怎么发现的已经不太记得了，可能是仔细阅读的时候看到了 MSDN 上这个函数下面的 Remark，也可能是网上找的示例代码，发现了这个 RegisterWaitForSingleObject 函数就算指定了 ONCE 的标志位，它还是需要 UnregisterWait 才行。加上这个 UnregisterWait 的调用，出来一大堆线程的问题消失了。</p>
<p>&nbsp;</p>
<p>4、重置的套接字</p>
<p>有忙的时候也有闲着的时候。闲着的时候想学学看怎么 C++ 写服务器程序，因为 CGI 每次要开进程影响性能，FastCGI 看了一圈发现协议还有点小复杂，正好那会儿在尝试 Fossil 版本控制工具能以 SCGI 形式挂在 Web 服务器上用，然后就顺势找了一下 SCGI 是什么东西，发现它协议的实现特别简单，就决定用它来实验了。</p>
<p>实验过程中发现我这边能收到 NginX 的连接，能收到请求，能收到数据也发送了回复，但是浏览器访问 NginX 却总抓不到我 C++ 写的 SCGI 的输出。在 NginX 的日志里面，只见它说 Connection Reset。这个 Connection Reset 对于经常上外国网站的人来说简直不要太常见，但是这是同一台机器、本地环回网络啊。因为网络编程经验不足，遇到这样的错误也没什么思路。</p>
<p>过程不太记得了，最终结论是我收 SCGI 请求的时候，还收了长度和数据，但是最后的逗号漏掉接收了。也就相当于我关闭 Socket 的时候，它接收缓冲区里面还有数据。此时我把连接关掉，它就 Connection Reset 了……算是一个经验。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=595</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WindowsDDK里用std::wstring的问题</title>
		<link>http://blog.sorayuki.net/?p=515</link>
		<comments>http://blog.sorayuki.net/?p=515#comments</comments>
		<pubDate>Sat, 30 Aug 2014 15:30:37 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[VC]]></category>
		<category><![CDATA[Windows编程]]></category>
		<category><![CDATA[问题解决]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=515</guid>
		<description><![CDATA[今天晚上尝试帮某网友制作一个伪春菜的插件，能够检查两次调用过程中剪贴板里面的东西有没有变化（用GetClipb &#8230; <a href="http://blog.sorayuki.net/?p=515" class="more-link">继续阅读<span class="screen-reader-text">WindowsDDK里用std::wstring的问题</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>今天晚上尝试帮某网友制作一个伪春菜的插件，能够检查两次调用过程中剪贴板里面的东西有没有变化（用GetClipboardSequenceNumber函数）和获取剪贴板里的内容（EnumClipboardFormats和GetClipboardData）。这里都不是重点，重点是我用了 <a href="./?p=9">这一篇博客</a> 里面提到的CSaori库来编写。这个库里面的字符是wchar_t类型，这很好，也用了std::basic_string&lt;wchar_t&gt;（其实就是std::wstring），这也很好。不好的地方在于，我为了减小最终DLL的体积、用Windows Driver Kit里面的编译器来编译程序使得动态链接到运行库的时候，报错了。</p>
<p><span id="more-515"></span>
<p>程序本身是没有问题，因为我用VS2010编译器编译它就正常，而且还能运行，运行起来也没问题。仔细查看DDK编译器的报错信息，大概是</p>
<pre style="height: 221px; width: 696px">error LNK2019: unresolved external symbol "__declspec(dllimport) p
ublic: wchar_t&amp; __thiscall std::basic_string&lt;wchar_t,struct std::char_traits&lt;wch
ar_t&gt;,class std::allocator&lt;wchar_t&gt; &gt;::operator[](unsigned int)" (__imp_??A?$bas
ic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@QAEAA_WI@Z) reference
d in function "public: bool __thiscall CSAORIInput::parseString(class std::basic
_string&lt;wchar_t,struct std::char_traits&lt;wchar_t&gt;,class std::allocator&lt;wchar_t&gt; &gt;
 const &amp;)" (?parseString@CSAORIInput@@QAE_NABV?$basic_string@_WU?$char_traits@_W
@std@@V?$allocator@_W@2@@std@@@Z)</pre>
<p>这个样子的，目测意思是链接不上std::wstring。</p>
<p>感觉不应该啊，它不是支持宽字符字符串对象的吗。然后上网搜搜出了这么一个东西：</p>
<p><a title="http://stackoverflow.com/questions/5988457/is-wchar-t-supported-in-c-wdk-stl-i-get-unresolved-external-symbols" href="http://stackoverflow.com/questions/5988457/is-wchar-t-supported-in-c-wdk-stl-i-get-unresolved-external-symbols">http://stackoverflow.com/questions/5988457/is-wchar-t-supported-in-c-wdk-stl-i-get-unresolved-external-symbols</a></p>
<p>感觉好坑……编译器和库不够匹配的结果吗。库用的是VC6.0里面的吧，但是因为编译器是新的，默认wchar_t是内置类型，而库并不是这样的，最终链不上。</p>
<p>最终加上了 /Zc:wchar_t- 参数，它就好了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=515</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>编辑VC生成的导入库文件</title>
		<link>http://blog.sorayuki.net/?p=383</link>
		<comments>http://blog.sorayuki.net/?p=383#comments</comments>
		<pubDate>Thu, 13 Feb 2014 08:21:33 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[VC]]></category>
		<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=383</guid>
		<description><![CDATA[还是接着之前那篇关于制作导入库的工具的后续。另一种方法，虽然局限性更大，但是可以满足大多数情况了。 灵感很简单 &#8230; <a href="http://blog.sorayuki.net/?p=383" class="more-link">继续阅读<span class="screen-reader-text">编辑VC生成的导入库文件</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>还是接着之前那篇关于制作导入库的工具的后续。另一种方法，虽然局限性更大，但是可以满足大多数情况了。</p>
<p><span id="more-383"></span>
<p>灵感很简单，在查看关于DLL调用方面的文章的时候，突然看到了一篇关于MinGW下制作lib文件之类内容的。因为我以前用过MinGW编译器，同样也搞过这个DLL导入的方面。于是就想起来当时用dlltool工具的时候，确实有看到它能够支持对导入名进行调整的参数。但是那个时候并没有对这方面有多少深入的研究，其实说白了就是没有理解那些参数实际想要表达的到底是什么意思。这下有了前面对VC下隐式调用DLL里函数的经验，一下子知道了那些到底是想表达什么，比如导入名中去掉prefix，或者kill掉后面的@nn，之类之类。</p>
<p>然后呢，前面在研究导入库格式的时候，看微软提供的那个规格说明文档，里面也有提到这方面的东西，比如去掉prefix或者kill掉后面的@nn之类。</p>
<p>说简单点，就是正常情况下比如写了一个这样的def文件</p>
<pre><p>LIBRARY MyDLL
EXPORTS
  function
  dosomething</p></pre>
<p>此时如果用lib.exe工具（其实就是link.exe /lib，因为WDK中没有lib.exe所以提一下）从这个def文件生成lib文件，那么这个lib文件将从DLL导入function和dosomething，同时向链接器提供_function和_dosomething。这样的话对于__cdecl方式导出的函数是没有问题的。但是如果DLL的导出符号仍然是function和dosomething，但是调用方式却是__stdcall，就会遇上了问题：如果function有一个int型参数，那么链接器将去寻找_function@4而不是_function，因为找不到符号，所以链接失败了。</p>
<p>如果DLL中导出的符号名是function@4，此时生成的lib就会从DLL导入function@4并向链接器提供_function@4，但是实际上以__stdcall方式调用，但是最后导出的时候不带@nn的情况是非常多的。最典型的情况就是Windows API。而且实话说吧因为@nn这样的实在不怎么好看，所以我自己写DLL的时候也会通过使用DEF文件的方式或者#pragma comment(linker, "/EXPORT:xxxxxx")这样的预编译语句让导出的函数不带如此的后缀。</p>
<p>于是这样需求就变得简单：让生成的导入库从DLL引入的时候是不带后缀的，但是向链接器提供的符号是带后缀的。</p>
<p>这并非不可能，调用Windows API的时候就是这样：Windows SDK里面那些LIB文件，就是实现这样的功能。对其格式进行研究，根本就是和普通的导入库一样。除了……呃，除了某些细节上的参数。</p>
<p>于是我的目的就是修改这些细节上的参数。<a href="http://msdn.microsoft.com/library/windows/hardware/gg463119.aspx">Microsoft PE and COFF Specification</a>里也提供了这方面的详细资料。</p>
<p>方法的话，遍历LIB文件中的每个member，LIB文件的格式可以从上面提到的资料中找到，不会太复杂。<strong>注意每个member的第一个字节似乎是对其到偶数边界的，没有对齐的用\n来补。</strong>在遍历的过程中分析前四个字节就知道这个member是不是声明了一个导入函数，如果是，根据导入的格式分析其头部信息，然后获得DLL名、函数名等。</p>
<p>在这个导入member的“头部信息”中，有关于导入名应该如何计算的属性。但是LIB.exe工具却没有这方面的参数可以调整，默认就是先前面给你加一个_下划线，然后方式是NoPrefix就是再把这个下划线去掉。也就是说，def文件中如果写的是function，那么它就先加一个下划线，变成_function，作为向链接器提供的符号。然后导入名生成方式是NoPrefix，也就是链接器在链接的时候会再从_function去掉前缀的下划线变回function作为从DLL导入的名。</p>
<p>这个导入名的计算方式有</p>
<p>IMPORT_ORDINAL：通过标号导入，此时不会计算导入名<br />IMPORT_NAME：原原本本的，和向链接器提供的符号保持一致<br />IMPORT_NAME_NOPREFIX：不带前缀的，LIB.EXE生成的就是这种。前缀是指?、@或者可选的下划线_<br />IMPORT_NAME_UNDECORATE：不带前缀并且只取@之前的部分，此时如果向链接器提供的符号名是_function@4，那么导入名就是function，也就是我们需要的。</p>
<p>有了这些资料，思路就非常清楚，比如有以下两个函数</p>
<p>
<pre>__declspec(dllimport) int __stdcall add2(int, int);
__declspec(dllimport) int __stdcall add3(int, int, int);</pre>
</p>
<p>然后DLL里的导出名分别是add2和add3而不是add2@8和add3@12，此时可以写这样一个DEF文件</p>
<pre>LIBRARY doadd
EXPORTS
  add2@8
  add3@12</pre>
<p>此时生成的lib文件中，向链接器提供的符号这一边已经是所需要的_add2@8和_add3@12了。然后这个时候要修正的是导入名计算方式，因为现在是IMPORT_NAME_NOPREFIX，要改成IMPORT_NAME_UNDECORATE才行。这个时候就要用十六进制编辑器来修改，但是手工做这种事情实在太麻烦，这种事情应该给程序去做。所以就写了个工具，来解决这种问题。</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2014/02/image.png"><img title="image" style="display: inline" alt="image" src="http://blog.sorayuki.net/wp-content/uploads/2014/02/image_thumb.png" width="970" height="254"></a></p>
<p>图片为修改一个函数的导入名计算方式后的截图。改完以后保存，就可以实现目的了。</p>
<p>这里给出这个自制工具的下载：（上传时间：2014年2月13日。工具有修改的话，这里可能不会同步更新。）</p>
<div id="scid:fb3a1972-4489-4e52-abe7-25a00bb07fdf:9ded0bf0-87e0-48c7-8574-1d5c6554cdbf" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<p>点击下载 <a href="http://blog.sorayuki.net/wp-content/uploads/2014/02/ImpLibEditor.7z" target="_blank">工具 </a></p>
</div>
<p>运行需要.net framework 2.0环境。修改完成后记得保存。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=383</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>自制导入DLL用的LIB文件</title>
		<link>http://blog.sorayuki.net/?p=372</link>
		<comments>http://blog.sorayuki.net/?p=372#comments</comments>
		<pubDate>Sun, 26 Jan 2014 07:27:08 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=372</guid>
		<description><![CDATA[调用DLL早不是什么新话题了。从LoadLibrary和GetProcAddress配合使用的动态加载，到链接 &#8230; <a href="http://blog.sorayuki.net/?p=372" class="more-link">继续阅读<span class="screen-reader-text">自制导入DLL用的LIB文件</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>调用DLL早不是什么新话题了。从LoadLibrary和GetProcAddress配合使用的动态加载，到链接导入LIB的静态加载，方法很多。LoadLibrary和GetProcAddress的方法虽然麻烦但是很通用，毕竟调用系统API就可以了，DLL名和函数名和都是字符串形式。导入LIB方式的话，生成DLL的时候会带一个LIB给你，拿来用即可。</p>
<p>后一种方法很方便。这里主要也是讲后一种。</p>
<p><span id="more-372"></span>
<p>&nbsp;</p>
<p>LIB链接的方式方便是方便，但是前提是要有LIB。对于EXE/DLL来说，LIB并不是必需的。那么就存在这种情况：手头只有一个DLL没有LIB，这要如何使用？</p>
<p>LoadLibrary/GetProcAddress固然可以，但是麻烦。要想不麻烦，就是导入的方式。从DEF做LIB是一个常规考虑的方式，但是这种方式有一个问题，对于导出名只有函数名的DLL，最好的例子就是Windows API那种，用DEF来做LIB就会造成链接的时候链不上：链接器去找__imp__Sleep@4，而LIB里的却是Sleep。DEF文件在做DLL的时候提供导出表的时候，可以指定别名。但是，在做导入LIB的时候，它不认那个别名了，于是DLL里导出什么符号就必须链接到什么符号否则就链不上。对于__stdcall的函数来说，简直无法使用。</p>
<p>于是我就想到一个方式，自己做一个这样的LIB来导入。</p>
<p>网上能找到现成的、最接近需求的工具是IMPLIB SDK（ <a title="http://implib.sourceforge.net/" href="http://implib.sourceforge.net/">http://implib.sourceforge.net/</a> ），它是用FASM语法写的脚本，配合FASM编译器能直接生成导入用的LIB。我本来也一直在用它，但是到后来我发现这东西有个问题，DLL的文件名长了它就死了。好像只支持8.3的样子，这也太人品了吧……</p>
<p>除了这个，网上我也就再也没找到这个功能的东西了。囧</p>
<p>有自己做一个这样的工具的想法很久了。一直到了今天在真正算是出来。之前有尝试过一两次，不过是失败的……</p>
<p>最关键的参考资料是这个：<a href="http://msdn.microsoft.com/library/windows/hardware/gg463119.aspx">Microsoft PE and COFF Specification</a></p>
<p>最初我是想直接做LIB的，不过遇到了点困难。经过Advance指点，就改为从COFF格式的OBJ下手了。本来想直接做LIB的原因是，这参考资料上提到LIB有一种特殊的import的格式，那么我直接拿来用就好了。但是后来手工修改LIB实验的时候，证实到头来还是需要自己构造导入表什么的。于是就放弃了，改为从OBJ下手了。</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2014/01/1.png"><img title="剪贴板-1" style="display: inline" alt="剪贴板-1" src="http://blog.sorayuki.net/wp-content/uploads/2014/01/1_thumb.png" width="228" height="269"></a></p>
<p>这个图是那个参考资料里原原本本的。可以看出，它的格式是首先一个文件头，然后所有Section的头，然后数据经过研究，COFF Header实际上是winnt.h里的IMAGE_FILE_HEADER，可以直接拿来用。然后Section Header实际上是IMAGE_SECTION_HEADER。直接从winnt.h里面引用就省去了按照那个参考资料里自己写结构体的麻烦。从图上<strong>还有两个地方</strong>没很好表示出来，<strong>一个是符号表，一个是字符串表</strong>。这两个东西合起来表示这个OBJ文件中的符号（包括内部和外部的，内部的就比如指向FirstThunk的（RVA值），链接的时候可以内部消化，外部的就比如__imp__这种给别人引用的）。其中符号的类型是IMAGE_SYMBOL，这个类型的数组就是符号表。符号的数目在IMAGE_FILE_HEADER里有，符号表的文件指针（所谓文件指针就是在文件的第几个字节位置，不是只PE加载器加载之后的，是原始文件）也在这个结构体里。</p>
<p>然后是Section的格式……<strong>所有Section的头在一起，数据在另外的一起。</strong>就是说，它是 头 头 头 数据 数据 数据 这个样子，而不是 头 数据 头 数据 头 数据。</p>
<p>Section的原始数据后面含有一个重定位表。重定位表的数据结构是IMAGE_RELOCATION，因为OBJ文件被链接之后，符号的位置才确定的。所以要先声明好哪些地方要重定位，比如这个语句</p>
<p>extern int add(int, int);<br />printf("%d", add(1,2));</p>
<p>在这个调用中，add的函数地址其实还不知道的，编译出来的OBJ里面，这个CALL语句的参数（四个字节）就先预留着，等到链接的时候链接器根据这个重定位表把数值写进去。</p>
<p>于是结构简明地写，用类似正则表达式的语法，就是这样：</p>
<p><strong>IMAGE_FILE_HEADER → IMAGE_SECTION_HEADER+ → (Raw Data → IMAGE_RELOCATION+)+ → IMAGE_SYMBOL+ → String Table</strong></p>
<p>String Table是用来存放符号表里提到的符号的。符号表里本身没有保存符号（字符串），而是保存了这个符号在字符串表的什么地方这样的信息。所以说这两个要配合起来用。重定位表里保存的关于什么符号要填入哪里的，是指向符号表的某个元素。</p>
<p>所以引用大致是，重定位表引用符号表的符号，符号表引用字符串表里的字符串。</p>
<p>知道了OBJ的格式，然后要弄清为什么可以通过链接LIB的方式来实现导入。</p>
<p>已经知道了EXE中导入表的相关知识的基础上，OBJ是通过一种特定的方式来实现在链接的时候构造导入表的：导入表保存在.idata节中，链接器链接的时候有一种规则，就是对于节的名字带有 $ 符号的，会根据 $ 后面的数字决定链接之后的数据的排列顺序。</p>
<p>用 link /dump /all xxx.lib 把VC生成的LIB文件的信息给DUMP出来，结合它提供的符号，可以大致猜出它以什么顺序组装导入表： </p>
<div id="scid:887EC618-8FBE-49a5-A908-2339AF2EC720:2ef19d0d-059e-42c5-b769-3a621222b2e1" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">         <code>
<pre>.idata$2 : IMAGE_IMPORT_DESCRIPTOR 结构体
.idata$3 : __NULL_IMPORT_DESCRIPTOR
.idata$4 : OriginalFirstThunk
.idata$5 : FirstThunk
.idata$6 : DLL的文件名，0结尾的字符串
 </pre>
<p></code>
      </div>
<p>这样就组成了导入表一开始是IMAGE_IMPORT_DESCRIPTOR结构体数组，然后一个__NULL_IMPORT_DESCRIPTOR，这样的结构。为了保证__NULL_IMPORT_DESCRIPTOR只有一个，应该是一个单独的OBJ里面放这个Section，最后把OBJ丢到LIB里。为了保证它能链接，要让它导出一个什么符号来其他的OBJ以EXTERNAL方式引用……DLL文件名应该也是同理。</p>
<p>我认为.idata$3应该用了类似__declspec(selectany)的方法，否则每个导入库都有这样一坨0的话，链接以后.idata$3就会有一堆的全0导入描述表而不是只有一个。虽然不知道到底是怎么回事，总之<strong>在定义的时候不需要特意加个COMDAT做成selectany</strong>。</p>
<p>至于$4和$5结尾的0，暂时的想法是在所有OBJ中都添加NULL_THUNK的引用，然后单独的文件里放一个这个NULL_THUNK。链接的时候可能就会被放在最后了？前提是链接器在查找符号的时候是广度优先而不是深度优先。广度优先的话，只要这些导入OBJ里没有奇怪的互相引用，那么就能保证NULL_THUNK在最后。<strong>最后实际做的时候，<font color="#ff0000">感觉</font>它好像只是按照lib里object出现顺序来搞的，不确定。总之我是放在最后一个成员里。</strong></p>
<p>按这样的方法，.idata$2就聚集了所有导入库的描述符，然后.idata$3结尾，然后第一个导入库的.idata$4，第二个的，第三个的，……每个导入库的.idata$4都要有结尾的0。.idata$5同理。</p>
<p>于是接下来是LookUp Table，也就是IMAGE_IMPORT_BY_NAME，这下我是根本找不到了……怀疑是那种0x0000 0xffff开头的LIB文件里的特殊OBJ会自己生成。但是放哪里，不知道了。我就先暂时放.idata$7去。</p>
<p>至于 _Sleep@4 : jmp [__imp__Sleep@4] 这种，可以自己生成一个.text段的，反正只有一句jmp，查一下很容易就知道。也可以不生成，反正声明了 __declspec(import) 以后就自动链接到 __imp__xxxx@nn 去了。</p>
<p>关于导入的参考资料，这里 <a title="http://www.microsoft.com/msj/0498/hood0498.aspx" href="http://www.microsoft.com/msj/0498/hood0498.aspx">http://www.microsoft.com/msj/0498/hood0498.aspx</a></p>
<p>对于64位的情况，目前通过winnt.h头文件观察到的，除了thunk的大小变了以外，就是表示cpu architecture的标识变了。其他好像没有变。</p>
<p>&nbsp;</p>
<p>结果一直没有进展，到了1月24日，决定不模仿VC和GCC了，改为模仿IMPLIB SDK，发现了一点玄机。</p>
<p>首先，<strong>NULLTHUNK的名字最开头那个字符，其实是固定的，写作\177可能看不出来但是写作\x7f的话</strong>……有符号char中的最大值。猜测是为了排在最后。</p>
<p>然后，<strong>符号表里section的定义一定要在前，特别是COMDAT的section</strong>。否则生成lib的时候给你出fatal error LNK1143。</p>
<p>尝试了这么多次，搞不清，给implib sdk的作者发了个邮件，不知道对方能不能收到会不会回复……</p>
<p>1月25日的时候，抱着试试看的心理，把implib sdk生成的lib里的obj都手工拆解出来，然后再用lib工具生成lib文件，赫然发现，和我做的lib是一样的效果了……无法正常链接。这下好了，原因大概猜到了，问题出在lib生成那边，不是出在obj生成那边。因为implib sdk生成的lib文件里obj的名字都一样，于是我也弄成一样，好了可以链接了，淦……</p>
<p>于是结论，<strong>导入库中object的名字要保持全部一样</strong>，不然会不正常。这样的话就是最后生成lib的步骤也要自己写了，不好用link工具了。</p>
<p>还有就是，<strong>lib里的object文件，貌似文件开头都是对齐到2的整数倍字节的</strong>。如果不是，那么会加入<strong>0x0a作为pad</strong>，定义为IMAGE_ARCHIVE_PAD。</p>
<p>lib文件中的FirstLinkMember和SecondLinkMember，貌似First不能完全省掉。最后还是耐着性子把FirstLinkMember也填上了。这两个，<strong>前者要求offset是递增序列的，后者offset要递增、字符串也要递增</strong>。排序一下就是了。</p>
<p>然后到了今天下午终于做出来了。累不爱。</p>
<p><a href="http://blog.sorayuki.net/wp-content/uploads/2014/01/3a70f53e37a4.jpg"><img title="捕获" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="捕获" src="http://blog.sorayuki.net/wp-content/uploads/2014/01/thumb1.jpg" width="244" height="144"></a></p>
<p>下载：</p>
<p><div id="scid:fb3a1972-4489-4e52-abe7-25a00bb07fdf:5e887f6a-dde7-4d9d-9bed-37f6ab641179" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<p>点击下载： <a href="http://blog.sorayuki.net/wp-content/uploads/2014/01/MakeImpLib.7z" target="_blank">点我</a></p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=372</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>InvokeRequired大坑爹</title>
		<link>http://blog.sorayuki.net/?p=369</link>
		<comments>http://blog.sorayuki.net/?p=369#comments</comments>
		<pubDate>Wed, 18 Dec 2013 03:15:50 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=369</guid>
		<description><![CDATA[.net下又写界面又写多线程的一定知道InvokeRequired（CheckForIllegalCrossT &#8230; <a href="http://blog.sorayuki.net/?p=369" class="more-link">继续阅读<span class="screen-reader-text">InvokeRequired大坑爹</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>.net下又写界面又写多线程的一定知道InvokeRequired（CheckForIllegalCrossThreadCalls党退散……）。我说个笑话，昨天被这东西坑了一下。……</p>
<p><span id="more-369"></span>
<p>正常情况下，.net创建的窗体、控件等对象，只允许通过创建它的线程进行访问。刚开始写多线程的人一定知道“线程间操作无效”这个异常，然后知道了InvokeRequired和Invoke。InvokeRequired按我理解，就是“当前线程 != 创建该窗体的线程”，所以当它为true的时候，要把对窗体的操作放到创建它的线程上去运行（就是调用Invoke）。</p>
<p>之前百度知道上有人求助关于聊天工具，那个时候我用UDP写了个P2P的简单示例，其中消息来了的时候检查发送来源，然后到一个Dictionary里去查找这个来源有没有对应的窗口，如果没有就new一个这对象出来，往窗口的TextBox里添加内容，然后托盘区闪动，点击的时候显示窗口。</p>
<p>“往TextBox里添加内容”这个我写了个方法，因为套接字上用了异步接收，收到后直接就检查有没有窗体和创建窗体这样的操作，所以创建窗口的线程其实是线程池里的线程。然后显示窗口的操作，因为托盘区图标的点击导致显示操作的执行，所以是主窗口所在线程，往TextBox添加内容的操作，因为是收到消息的时候调用（或者可能也包含发送消息的时候），所以是线程池的线程操作的（也可能是主窗口线程操作的），不过这个方法里我有检查InvokeRequired。</p>
<p>（这里插一句，我是承认在线程池的线程里创建窗口很不好啦……因为窗口的消息循环是跑在创建它的线程上的没错吧？但是当时这么写了，之后也没多注意）</p>
<p>然后可能是因为用了线程池的线程创建窗口和操作TextBox的缘故吧，调用Show的那边时不时会抛出异常（因为线程池的线程对于异常的处理貌似是……呃，那啥那啥那啥啥）。到昨天晚上，我改了代码，把创建这个窗体的动作拿到主窗体线程上去跑了，这样用户界面就都在同一个线程上创建和运行。尽管这样，调用Show其实也是在主线程上，但是Show的地方就是会抛异常，而且异常是说TextBox不能跨线程操作。这个时候我在给TextBox添加内容的函数上下了断点，然后发现，</p>
<p><strong>虽然窗口new出来了，但是在窗口还没Show出来的时候，InvokeRequired返回的是false，尽管当前线程和创建它的线程不是同一个线程……</strong></p>
<p>到搜索引擎搜索 InvokeRequired unreliable 关键字，看到也有其他人分遇到这样的问题了。囧</p>
<p>最后我的解决方法是InvokeRequired总是用主窗口的来判断，Invoke也用主窗口来Invoke。</p>
<p>总结就是，在窗口显示出来之前，InvokeRequired不可靠。</p>
<p>网上有提到Workaround的方法是new出一个窗口以后，想办法调用它的CreateHandle方法（protected的，不过可以在内部调用），使它确实是含有Handle的。InvokeRequired在这里会返回false的原因是窗口还没有Handle，我估计是第一次Show的时候它才真正创建窗口（懒汉式？）。不过这个workaround我没试过。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=369</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>贪食蛇</title>
		<link>http://blog.sorayuki.net/?p=363</link>
		<comments>http://blog.sorayuki.net/?p=363#comments</comments>
		<pubDate>Mon, 16 Dec 2013 15:56:09 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=363</guid>
		<description><![CDATA[我是为了证明“这种东西其实我也可以写得出来的！”吗？ 感想：用WM_TIMER定时实在是不够准，偏差太大。最后 &#8230; <a href="http://blog.sorayuki.net/?p=363" class="more-link">继续阅读<span class="screen-reader-text">贪食蛇</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>我是为了证明“这种东西其实我也可以写得出来的！”吗？</p>
<p>感想：用WM_TIMER定时实在是不够准，偏差太大。最后还是换了WaitableTimer</p>
<div id="scid:fb3a1972-4489-4e52-abe7-25a00bb07fdf:42ab43af-2ab4-494e-85c6-d875151b6c55" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">
<p> <a href="http://blog.sorayuki.net/wp-content/uploads/2013/12/tss6.7z" target="_blank">下载EXE</a></p>
</div>
<p><span id="more-363"></span>
<div id="scid:887EC618-8FBE-49a5-A908-2339AF2EC720:74916c14-2dd8-47a6-b157-1dea829aa832" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px">         <code>
<pre>#define _WIN32_WINNT 0x0400
#include &lt;Windows.h&gt;
#include &lt;commctrl.h&gt;
#include &lt;stdlib.h&gt;
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(linker, "/subsystem:windows")

//可调整的初始数据
enum { BlockLen = 30, HCnt = 15, VCnt = 15, InitPosX = 5, InitPosY = 5, InitLen = 3, InitTimerInterval = 800 };

struct SnakeNode {
    int x;
    int y;
    struct SnakeNode* next;
};

struct Map {
    SnakeNode* snake;
    int eggx; //蛋的位置
    int eggy;
    int snakedx; //蛇的方向
    int snakedy;
    int curLevel; //难度
    int eggCnt; //吃的蛋的个数
} gameMap;

HWND hMain, hPic, hInfo;

void GenEgg() //生成新的蛋的位置
{
    int x, y;
    for(;;) {
        int flag = 0;
        x = rand() % HCnt;
        y = rand() % VCnt;
        int snkCnt = 0;
        struct SnakeNode* s = gameMap.snake;
        while(s != 0) {
            if (s-&gt;x == x &#038;& s-&gt;y == y) {
                flag = 1;
                ++snkCnt;
            }
            s = s-&gt;next;
        }
        if (snkCnt == HCnt * VCnt) break; //如果地图满了
        if (flag == 0) break;
    }
    gameMap.eggx = x;
    gameMap.eggy = y;
}

void ReleaseGameMap()
{
    while(gameMap.snake != 0) {
        void* p = gameMap.snake;
        gameMap.snake = gameMap.snake-&gt;next;
        free(p);
    }
}

void InitGameMap()
{
    struct SnakeNode *ls = 0, *s;
    int i;
    int x = InitPosX, y = InitPosY;
    
    for(i = 0; i &lt; InitLen; ++i) {
        s = (struct SnakeNode*) malloc(sizeof(*s));
        s-&gt;x = x; s-&gt;y = y;
        if (ls == 0) {
            gameMap.snake = ls = s;
        } else {
            ls-&gt;next = s;
            ls = s;
        }
        --x;
    }
    gameMap.snakedx = 0;
    gameMap.snakedy = 0;
    ls-&gt;next = 0;
    gameMap.eggCnt = 0;
    gameMap.curLevel = 0;
    GenEgg();
}

void GenPic() {
    HDC wdc = GetDC(hPic);
    HBITMAP buf = CreateCompatibleBitmap(wdc, HCnt * BlockLen, VCnt * BlockLen);
    HDC mdc = CreateCompatibleDC(wdc);
    SelectObject(mdc, buf);
    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
    HPEN whitePen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
    HBRUSH redBrush = CreateSolidBrush(RGB(255, 0, 0));
    HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
    ReleaseDC(hPic, wdc);

    SelectObject(mdc, whitePen);
    SelectObject(mdc, whiteBrush);
    Rectangle(mdc, 0, 0, HCnt * BlockLen, VCnt * BlockLen);

    //画蛋
    SelectObject(mdc, redBrush);
    SelectObject(mdc, redPen);
    int eggl = gameMap.eggx * BlockLen;
    int eggt = gameMap.eggy * BlockLen;
    Ellipse(mdc, eggl, eggt, eggl + BlockLen, eggt + BlockLen);

    //画蛇
    LOGBRUSH lb;
    lb.lbColor = RGB(0, 255, 0);
    lb.lbStyle = BS_SOLID;
    lb.lbHatch = 0;
    HPEN snakePen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_ROUND, BlockLen - 2, &#038;lb, 0, 0);
    SelectObject(mdc, snakePen);
    int snkCnt = 0;
    struct SnakeNode* sn = gameMap.snake;
    while(sn != 0) {
        ++snkCnt;
        sn = sn-&gt;next;
    }
    POINT* pots = (POINT*) malloc(sizeof(POINT) * snkCnt);
    int i = 0;
    sn = gameMap.snake;
    while(sn != 0) {
        pots[i].x = sn-&gt;x * BlockLen + BlockLen / 2;
        pots[i].y = sn-&gt;y * BlockLen + BlockLen / 2;
        ++i;
        sn = sn-&gt;next;
    }
    Polyline(mdc, pots, snkCnt);
    free(pots);
    
    //点蛇头
    SelectObject(mdc, redBrush);
    SelectObject(mdc, redPen);
    Ellipse(mdc, gameMap.snake-&gt;x * BlockLen, gameMap.snake-&gt;y * BlockLen, gameMap.snake-&gt;x * BlockLen + BlockLen, gameMap.snake-&gt;y * BlockLen + BlockLen);

    DeleteObject(snakePen);
    DeleteObject(redBrush);
    DeleteObject(redPen);
    DeleteObject(whiteBrush);
    DeleteObject(whitePen);
    DeleteDC(mdc);

    HBITMAP oldbm = (HBITMAP)SendMessage(hPic, STM_GETIMAGE, (WPARAM)IMAGE_BITMAP, 0);
    SendMessage(hPic, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)buf);
    if (oldbm != 0)
        DeleteObject(oldbm);
    InvalidateRect(hPic, NULL, FALSE);

    //显示信息
    TCHAR infoBuf[64];
    wsprintf(infoBuf, TEXT("难度：%d 得分：%d"), gameMap.curLevel, gameMap.eggCnt);
    SetWindowText(hInfo, infoBuf);
}

void ChangeWay(int dx, int dy)
{
    struct SnakeNode* sn = gameMap.snake;
    if (sn-&gt;x + dx == sn-&gt;next-&gt;x &#038;& sn-&gt;y + dy == sn-&gt;next-&gt;y)
        ;
    else
        gameMap.snakedx = dx, gameMap.snakedy = dy;
}

bool Forward()
{
    bool ret = true;
    if (gameMap.snakedx == 0 &#038;& gameMap.snakedy == 0)
        return true;

    int x = gameMap.snake-&gt;x;
    int y = gameMap.snake-&gt;y;
    x += gameMap.snakedx;
    y += gameMap.snakedy;
    struct SnakeNode* sn;

    //超出边界
    if (x &lt; 0 || y &lt; 0 || x &gt;= HCnt || y &gt;= VCnt) {
        ret = false;
    } else {
        sn = gameMap.snake;
        while(sn != 0) {
            if (sn-&gt;x == x &#038;& sn-&gt;y == y) {
                ret = false; //撞了自己
                break;
            }
            sn = sn-&gt;next;
        }
    }

    if (x == gameMap.eggx &#038;& y == gameMap.eggy) {
        //吃了蛋
        sn = (struct SnakeNode*)malloc(sizeof(*sn));
        sn-&gt;x = x;
        sn-&gt;y = y;
        sn-&gt;next = gameMap.snake;
        gameMap.snake = sn;
        ++gameMap.eggCnt;
        GenEgg();
    } else {
        sn = (struct SnakeNode*)malloc(sizeof(*sn));
        sn-&gt;x = x;
        sn-&gt;y = y;
        sn-&gt;next = gameMap.snake;
        gameMap.snake = sn;
        while(sn-&gt;next-&gt;next != 0)
            sn = sn-&gt;next;
        free(sn-&gt;next);
        sn-&gt;next = 0;
    }

    return ret;
}

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmd, INT nShow)
{
    srand((unsigned int)GetTickCount());
    InitCommonControls();
    hMain = CreateWindow(TEXT("#32770"), TEXT("贪食蛇 by 空雪梦见 2013-12-16"), WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, hInst, 0);
    hPic = CreateWindow(TEXT("Static"), TEXT(""), WS_CHILD | WS_VISIBLE | SS_BITMAP,
        0, 0, BlockLen * HCnt, BlockLen * VCnt, hMain, 0, hInst, 0);
    hInfo = CreateWindow(TEXT("Static"), TEXT(""), WS_CHILD | WS_VISIBLE,
        20, BlockLen * VCnt, BlockLen * HCnt - 40, 30, hMain, 0, hInst, 0);
    RECT r;
    GetWindowRect(hMain, &#038;r);
    r.right = r.left + HCnt * BlockLen;
    r.bottom = r.top + VCnt * BlockLen + 30;
    AdjustWindowRect(&#038;r, WS_OVERLAPPEDWINDOW, FALSE);
    MoveWindow(hMain, r.left, r.top, r.right - r.left, r.bottom - r.top, FALSE);
    ShowWindow(hMain, nShow);

    InitGameMap();
    GenEgg();
    GenPic();

    GetAsyncKeyState(VK_UP);
    GetAsyncKeyState(VK_DOWN);
    GetAsyncKeyState(VK_LEFT);
    GetAsyncKeyState(VK_RIGHT);

    HANDLE hTimer = CreateWaitableTimer(0, FALSE, 0);
    LARGE_INTEGER liDueTime;
    liDueTime.QuadPart = -InitTimerInterval * 10000;
    SetWaitableTimer(hTimer, &#038;liDueTime, InitTimerInterval, 0, 0, TRUE);
    MSG m;
    for(;;) {
        DWORD r = MsgWaitForMultipleObjects(1, &#038;hTimer, FALSE, INFINITE, QS_ALLINPUT);

        if (GetAsyncKeyState(VK_UP) &#038; 1)
            ChangeWay(0, -1);
        else if (GetAsyncKeyState(VK_DOWN) &#038; 1)
            ChangeWay(0, 1);
        else if (GetAsyncKeyState(VK_LEFT) &#038; 1)
            ChangeWay(-1, 0);
        else if (GetAsyncKeyState(VK_RIGHT) &#038; 1)
            ChangeWay(1, 0);

        if (r == WAIT_OBJECT_0) {
            if (Forward() == false) {
                GenPic();
                MessageBox(hMain, TEXT("你死了"), TEXT("死死死死死"), MB_OK);
                ReleaseGameMap();
                InitGameMap();
                liDueTime.QuadPart = -InitTimerInterval * 10000;
                SetWaitableTimer(hTimer, &#038;liDueTime, InitTimerInterval, 0, 0, TRUE);
            }

            if (gameMap.curLevel &lt;= gameMap.eggCnt / 10) {
                int x = InitTimerInterval;
                ++gameMap.curLevel;
                int i, iend = gameMap.curLevel;
                for (i = 0; i &lt; iend; ++i) {
                    x *= 8;
                    x /= 10;
                }
                liDueTime.QuadPart = -x * 10000;
                SetWaitableTimer(hTimer, &#038;liDueTime, x, 0, 0, TRUE);
            }

            GenPic();
        } else if (r == WAIT_OBJECT_0 + 1) {
            bool quit = false;
            while( PeekMessage(&#038;m, 0, 0, 0, PM_REMOVE) != FALSE) {
                if (m.hwnd == hMain &#038;& m.message == WM_COMMAND &#038;& m.wParam == (WPARAM)MAKEWORD(IDCANCEL, BN_CLICKED)) {
                    quit = true;
                    break;
                }
                TranslateMessage(&#038;m);
                DispatchMessage(&#038;m);
            }
            if(quit)
                break;
        }
    }
    CloseHandle(hTimer);
    return 0;
}

 </pre>
<p></code>
      </div>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=363</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GetProcAddress给函数指针赋值</title>
		<link>http://blog.sorayuki.net/?p=360</link>
		<comments>http://blog.sorayuki.net/?p=360#comments</comments>
		<pubDate>Thu, 12 Dec 2013 08:04:11 +0000</pubDate>
		<dc:creator><![CDATA[空雪梦见]]></dc:creator>
				<category><![CDATA[Windows编程]]></category>

		<guid isPermaLink="false">http://blog.sorayuki.net/?p=360</guid>
		<description><![CDATA[根本不是什么新技术，也不是什么新应用，只是嫌麻烦，所以…… 起因是这样的，用LoadLibrary+GetPr &#8230; <a href="http://blog.sorayuki.net/?p=360" class="more-link">继续阅读<span class="screen-reader-text">GetProcAddress给函数指针赋值</span> <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>根本不是什么新技术，也不是什么新应用，只是嫌麻烦，所以……</p>
<p><span id="more-360"></span>
<p>起因是这样的，用LoadLibrary+GetProcAddress动态调用DLL里的函数已经是很经典的用法了。但是GetProcAddress返回类型是FARPROC，明显没办法适应各种函数的，所以要进行强制转换才行。MSDN上的例子是这样，也是最常见到的写法</p>
<pre class="prettyprint">typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
PGNSI pGNSI;
pGNSI = (PGNSI) GetProcAddress(
   GetModuleHandle(TEXT("kernel32.dll")), 
   "GetNativeSystemInfo");
</pre>
<p>这当然没什么问题，但是那一句typedef挺烦人。为了定义一个函数指针，又多了一种类型出来，对于强迫症的人来说实在有点……</p>
<p>于是呢，typedef其实只是把类型用另一种方式表示而已，所以完全可以不用的。不用的话，代码就变成这样子</p>
<pre class="prettyprint">void (WINAPI *pGNSI)(LPSYSTEM_INFO);
pGNSI = (void (WINAPI *)(LPSYSTEM_INFO)) GetProcAddress(
   GetModuleHandle(TEXT("kernel32.dll")), 
   "GetNativeSystemInfo");
</pre>
<p>那一长串的“类型”…………这转换写得有点累人啊。这一个参数的还好，到了CreateRemoteThread啊CreateFile啊CreateProcess啊，写起来实在体力活</p>
<p>
<pre class="prettyprint">HANDLE (WINAPI *pCRT)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
pCRT = (HANDLE (WINAPI *)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD))
    GetProcAddress(hKernel32, "CreateRemoteThread");
</pre>
<p>…………（捂脸</p>
<p>因为很麻烦，所以我前前后后用了好多种方式来对付。比如说，C的共用体。</p>
<pre class="prettyprint">union {
    HANDLE (WINAPI *pCRT)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
    void* val;
} u;
u.val = GetProcAddress(hKernel32, "CreateRemoteThread");
</pre>
<p>这样就是用起来的时候，前面还得跟着 u. ，挺烦的。虽然转换是不用转了；</p>
<p>再比如说，C++的模板，</p>
<pre class="prettyprint">template&lt;class T1, class T2&gt;
void AssignFuncPtr(T1&amp; a, T2 b) {
    a = (T1) b;
}

...
HANDLE (WINAPI *pCRT)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
AssignFuncPtr(pCRT, GetProcAddress(hKernel32, "CreateRemoteThread"));
</pre>
<p>稍微舒服了一点，但是前面还得写个模板。</p>
<p>前一段时间，尝试在VC6不装GdiPlus SDK的情况下调用GdiPlus，又遇上了这样的事情。于是，</p>
<pre class="prettyprint">LRESULT (WINAPI* GdipCreateBitmapFromFile)(LPCWSTR, LPVOID*);
void* faddr = GetProcAddress(hGdiPlus, "GdipCreateBitmapFromFile");
memcpy(&amp;GdipCreateBitmapFromFile, &amp;faddr, sizeof(faddr));</pre>
<p>已经很舒服了，不需要管类型了？但是一个faddr多出来，而且还要调用memcpy，嗯…………</p>
<p>今天脑子一抽，来了这个</p>
<pre class="prettyprint">LRESULT (WINAPI* GdipCreateBitmapFromFile)(LPCWSTR, "LPVOID*);
*(LPVOID*)&amp;GdipCreateBitmapFromFile" = GetProcAddress(hGdiPlus, "GdipCreateBitmapFromFile");
</pre>
<p>卧槽老子为什么这么屌（锤锤锤）你转过去很麻烦那我转过去不就得了，因为LPVOID（void*）是接近万能的指针啊</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.sorayuki.net/?feed=rss2&#038;p=360</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
