<?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>CSDN &#8211; 张三太爷</title>
	<atom:link href="https://www.somedoc.net/tag/csdn/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.somedoc.net</link>
	<description>看前面，黑洞洞</description>
	<lastBuildDate>Sat, 06 Jul 2013 13:27:12 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.1</generator>

<image>
	<url>https://www.somedoc.net/wp-content/uploads/2016/12/cropped-dandycheung-1-32x32.jpg</url>
	<title>CSDN &#8211; 张三太爷</title>
	<link>https://www.somedoc.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Windows 8 初体验</title>
		<link>https://www.somedoc.net/2012/11/06/windows-8-%e5%88%9d%e4%bd%93%e9%aa%8c/</link>
					<comments>https://www.somedoc.net/2012/11/06/windows-8-%e5%88%9d%e4%bd%93%e9%aa%8c/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Tue, 06 Nov 2012 08:36:38 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2617</guid>

					<description><![CDATA[不管喜欢不喜欢，Windows 8 还是来了。得益于虚拟机技 <a href="https://www.somedoc.net/2012/11/06/windows-8-%e5%88%9d%e4%bd%93%e9%aa%8c/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>不管喜欢不喜欢，Windows 8 还是来了。得益于虚拟机技术的强大，老汉在 Fusion 里安装了一份，一是想体验一下 Windows 8，二是想体验一下 Visual Studio 2012 的 Windows Phone 8 手机开发环境。</p>
<p>安装过程是很顺利的，把从微软申请的序列号填入，安装完毕后也就自动激活了。安装时只创建了一个本地账户，而没有绑定 Microsoft 账户。特别要提一下，登录前的全屏界面非常漂亮，令人振奋。之前看过一些文章，不过都印象不深，所以试用过程中跌跌撞撞。逐渐熟悉了鼠标在左上、左下角，和右上、右下角的两种功能，前者会导致开始屏幕的缩略图窗口出现，如果鼠标再沿着屏幕左侧向垂直方向的中间滑动，则会出现正在运行的应用程序的缩略图列表，点击可以切换应用，后者则是会出现包括搜索、设置等按钮在内的一个系统级快捷按钮栏，据说微软是有一个专有名词来称呼它的，我不记得了。鼠标移动到屏幕的上部接近边缘的时候，指针会变成手状，这时点下向下拖拽可以放置当前窗口停靠到屏幕的左半部分还是右半部分。这时占据的并不是完整的半屏，要小很多，剩余的大半部分屏幕可以用于开启另外一个应用。也就是说，允许你的屏幕上左右平铺两个程序，一大一小，双击两者之间的分割栏可以使此大彼小，或者反过来。</p>
<p>切换到传统风格的桌面很简单，主屏上有一个写着“桌面”的磁帖，点击就可以，当然，作为一个资深的 Windows 用户，我还是喜欢 Win+D 快捷键。在传统桌面下显然还是更熟悉一些，看了一下资源管理器，也看了一下任务管理器，虽然都有变化，但好在没有换的底儿掉，熟悉起来很快。</p>
<p>在新风格的桌面上工作，我遇到两个会让人觉得无助的时刻。由于在这个桌面上所有的应用都是全屏展示，也就是微软所号称的“沉浸式”，所以在应用商店这个程序长达几分钟的启动时间里，我一度不知道自己该干些什么，似乎只能在那儿干坐着傻等。后来才发现在这个环境下，传统的 Alt-Tab 组合键是可以工作的，这略微减少了一些我的焦虑。另一个问题就更诡异了：我在主屏上找不到 Internet Explorer 的磁帖！我在网上看过无数 Windows 8 的主屏截图，都有那个蓝色的 e 的磁帖，偏偏我的就没有。后来我用了个变通的方案，打开 bing 的应用，搜索了一个 URL，然后再打开。到任务管理器里查看，果然出现了 iexplore.exe 的进程，命令行里有一个 -ServerName:DefaultBrowserServer 的参数。这个 IE 进程的窗口是新的 Windows 风格，而我在传统桌面里运行 IE 则自动会以老的界面呈现，甚至按照前面的那个参数运行也不会呈现为新风格。我仔细观察过，它们的映像都指向同一个可执行文件，所以现在我还不清楚其机理。这个情况给我带来的问题在于，即使我在资源管理器里通过关联菜单把 iexplore.exe 贴到主屏上，点击时运行起来的也是老风格的窗口，这让我很沮丧。顺便说一句，切换到传统桌面后，运行于新风格下的 IE 进程在任务管理器里的状态会变为“暂停”，似乎是要节省资源占用，这点似乎有些意思。</p>
<p>我在微软的网站上下载了 WP8 的 SDK 光盘映像。之前有消息宣称 Windows 8 将可以直接支持挂载 ISO 文件为驱动器，但我的各种尝试均告失败，双击时系统提示“无法装载文件：无法通过映像文件或未使用 NTFS 文件系统格式化的可移动介质装载虚拟硬盘文件。”，不知所云。</p>
<p>最后不得不提一下的是输入法，这个东西的改进是显著的。如果焦点在一个输入框内，那么，你只需单击 Shift 键就可以在英文和中文之间进行切换了，而不再需要老系统里面的使用 Ctrl-Space 或者 Ctrl-Shift 组合键先把中文输入法启动起来。在传统桌面下，中英文的输入模式指示仍然在任务栏的右下角，而如果在新风格的桌面下的话，则会在输入框得到焦点时，在其左下角有一个浮动提示出现，而后缓缓消失。</p>
<p>经过一番折腾，我的感觉是，如果大家还没有准备好一块可以触摸的屏幕，就不必理会 Windows 8，继续使用 Windows 7 就好。如果你是编程的苦娃子，恰巧还要写 Windows Phone 8 的程序，那没办法，微软会胁迫你安装 Windows 8，而且还必须是 64 位的。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/11/06/windows-8-%e5%88%9d%e4%bd%93%e9%aa%8c/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>BlackBerry 10 Jam 大会小结及 Dev Alpha 设备初体验</title>
		<link>https://www.somedoc.net/2012/07/12/blackberry-10-jam-%e5%a4%a7%e4%bc%9a%e5%b0%8f%e7%bb%93%e5%8f%8a-dev-alpha-%e8%ae%be%e5%a4%87%e5%88%9d%e4%bd%93%e9%aa%8c/</link>
					<comments>https://www.somedoc.net/2012/07/12/blackberry-10-jam-%e5%a4%a7%e4%bc%9a%e5%b0%8f%e7%bb%93%e5%8f%8a-dev-alpha-%e8%ae%be%e5%a4%87%e5%88%9d%e4%bd%93%e9%aa%8c/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Thu, 12 Jul 2012 10:17:38 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2615</guid>

					<description><![CDATA[7 月 6 日，RIM 在北京千禧大酒店举行了一次开发者大会 <a href="https://www.somedoc.net/2012/07/12/blackberry-10-jam-%e5%a4%a7%e4%bc%9a%e5%b0%8f%e7%bb%93%e5%8f%8a-dev-alpha-%e8%ae%be%e5%a4%87%e5%88%9d%e4%bd%93%e9%aa%8c/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>7 月 6 日，RIM 在北京千禧大酒店举行了一次开发者大会。到场的大概有三四百人，其中部分来自于其他城市，好像还有从国外赶来的。</p>
<p>会上，RIM 列举了 BlackBerry 10 的三大理念：Flow、Connect、Extend。在播放的几段视频中对这些理念也作了相关的展示。令我印象深刻的是其崭新的时间轴拍照模式，这一亮点用最通俗的语言来形容，那就是很有“苹果范”儿，非常值得期待。视频中的另一个重点是输入法，但由于并非中文输入法，所以我并不感兴趣。</p>
<p>从技术的角度来讲，BB10 的新意不多。能够体现 Flow 这一理念的，其实和 Nokia 的 N9 的设计如出一辙。RIM 宣称，和 PlayBook 一样，BB 10 的设备上屏幕周围的边框是可感知手指滑动的，所以能够实现一些与众不同的手势。事实上（我之前使用 PlayBook 和会后使用 Dev Alpha 后的结论），我觉得 N9 尽管没有配备这样的边框，对这些手势的支持也并不差。值得一提的是，在骑上如果用 C/C++ 开发界面，使用的技术就是 Qt w/ QML，也正是 N9 或者 Meltemi 平台的技术。感觉 RIM 抓过了 Nokia 的接力棒。<br />
下午六点左右，开始排队等待发放 BB10 Dev Alpha 设备。人不少，所以等了不短的时间，在回程的地铁上打开了盒子。首先，这些设备都是已经拆封的，并非全新，不止如此，而且是有人用过的。我猜测 RIM 是把自己的测试设备拿出来发放了。其次，其中的 ROM 自带的应用很少，只有一个浏览器。即使是这个浏览器，也经常会导致机器各种真死机/假死机。从关于中看，和 PlayBook 就是一个系统，有很强的延续性，只是版本号一下从 PlayBook 的 2 升级到了 10。与 PlayBook 的差异我基本没有发现，只记得开始激活的时候，有一个界面错位，导致显示设备的 Wifi MAC 地址不全，我都没办法加入到我的无线路由器的白名单里。屏幕分辩率很高，可能与 PlayBook 一样，所以浏览时候，文字很小，比较毁眼睛，好在清晰度和细腻度都还不错，我的像素眼勉强可以接受。最后，电池续航实在太差，放着不动，一天就耗光了。</p>
<p>据工作人员介绍，如果成功开发一款 BB10 的应用并上架到 AppWorld，则明年可以用手中的 Dev Alpha 设备换取一台上市的 BB10 手机。这个还是不错的。</p>
<p>如此而已。不知道为什么北京的会议 CSDN 并没有什么报道，反倒是在报新加坡 10 号的会议。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/07/12/blackberry-10-jam-%e5%a4%a7%e4%bc%9a%e5%b0%8f%e7%bb%93%e5%8f%8a-dev-alpha-%e8%ae%be%e5%a4%87%e5%88%9d%e4%bd%93%e9%aa%8c/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>有趣的 realloc 函数</title>
		<link>https://www.somedoc.net/2012/05/30/%e6%9c%89%e8%b6%a3%e7%9a%84-realloc-%e5%87%bd%e6%95%b0/</link>
					<comments>https://www.somedoc.net/2012/05/30/%e6%9c%89%e8%b6%a3%e7%9a%84-realloc-%e5%87%bd%e6%95%b0/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Wed, 30 May 2012 02:17:29 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2613</guid>

					<description><![CDATA[在 C 语言中，我们最熟悉的函数排行榜上，[crayon-6 <a href="https://www.somedoc.net/2012/05/30/%e6%9c%89%e8%b6%a3%e7%9a%84-realloc-%e5%87%bd%e6%95%b0/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>在 C 语言中，我们最熟悉的函数排行榜上，<pre class="crayon-plain-tag">malloc</pre> 和 <pre class="crayon-plain-tag">free</pre> 想必名列前茅，而 <pre class="crayon-plain-tag">realloc</pre> 则可能就要靠后了。不过你知道吗，有了 <pre class="crayon-plain-tag">realloc</pre> 函数，你甚至可以不使用 <pre class="crayon-plain-tag">malloc</pre> 或者 <pre class="crayon-plain-tag">free</pre>，而是由它一力承担。</p>
<p>该函数的原型如下：</p><pre class="crayon-plain-tag">void* realloc(void* ptr, size_t size);</pre><p>其本职工作，乃是在 <pre class="crayon-plain-tag">ptr</pre> 参数所指向的内存块出现容量不足时进行重新分配，并保证其中内容不变。</p>
<p>不过，函数的规范指出，<pre class="crayon-plain-tag">ptr</pre> 参数可以为 <pre class="crayon-plain-tag">NULL</pre>。在这种情况下，<pre class="crayon-plain-tag">realloc</pre> 的作用其实就相当于 <pre class="crayon-plain-tag">malloc</pre>；另外，在 <pre class="crayon-plain-tag">ptr</pre> 参数不为 <pre class="crayon-plain-tag">NULL</pre>，但 <pre class="crayon-plain-tag">size</pre> 参数为 <pre class="crayon-plain-tag"></pre> 的情况下，其作用就相当于 <pre class="crayon-plain-tag">free</pre>。</p>
<p>如果你在做类似于 64KB 程序竞赛这样的工作时，为节省每一个字节而绞尽脑汁，不希望映像的 IAT 中无谓地多增加导入函数，这个做法就可以略尽绵薄之力。怎么样，是不是很好玩？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/05/30/%e6%9c%89%e8%b6%a3%e7%9a%84-realloc-%e5%87%bd%e6%95%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>fedora 下 Java 的中文显示</title>
		<link>https://www.somedoc.net/2012/03/26/fedora-%e4%b8%8b-java-%e7%9a%84%e4%b8%ad%e6%96%87%e6%98%be%e7%a4%ba/</link>
					<comments>https://www.somedoc.net/2012/03/26/fedora-%e4%b8%8b-java-%e7%9a%84%e4%b8%ad%e6%96%87%e6%98%be%e7%a4%ba/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Mon, 26 Mar 2012 13:26:31 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2611</guid>

					<description><![CDATA[昨天在家无聊的时候，给 fedora 里安装了个 Oracl <a href="https://www.somedoc.net/2012/03/26/fedora-%e4%b8%8b-java-%e7%9a%84%e4%b8%ad%e6%96%87%e6%98%be%e7%a4%ba/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>昨天在家无聊的时候，给 fedora 里安装了个 Oracle Solaris Studio。安装时他说要先安装 JRE，便也由得它，听它的装上了。结果启动 Studio 的时候发现所有的中文都显示为方框，猜测大概是 Java 的什么配置不对，结果打开 Java 的控制面板，里面的中文也是方框。网上搜来的解决方案备忘如下（在终端中操作）。</p>
<p>1、进入到 java 的安装目录（我的是 /usr/java/jre1.6.0_31），进入其下的 lib/fonts 子目录；<br />
2、创建并进入 fallback 子目录中（需要 sudo 操作）；<br />
3、链一个中文字体过来：sudo ln -s /usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc<br />
4、sudo mkfontdir<br />
5、sudo mkfontscale</p>
<p>如此即可。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/03/26/fedora-%e4%b8%8b-java-%e7%9a%84%e4%b8%ad%e6%96%87%e6%98%be%e7%a4%ba/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>64 位整数格式化的小知识</title>
		<link>https://www.somedoc.net/2012/03/21/64-%e4%bd%8d%e6%95%b4%e6%95%b0%e6%a0%bc%e5%bc%8f%e5%8c%96%e7%9a%84%e5%b0%8f%e7%9f%a5%e8%af%86/</link>
					<comments>https://www.somedoc.net/2012/03/21/64-%e4%bd%8d%e6%95%b4%e6%95%b0%e6%a0%bc%e5%bc%8f%e5%8c%96%e7%9a%84%e5%b0%8f%e7%9f%a5%e8%af%86/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Wed, 21 Mar 2012 01:50:43 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2609</guid>

					<description><![CDATA[在代码里需要用到对 64 位整数的格式化输出，结果应该是以十 <a href="https://www.somedoc.net/2012/03/21/64-%e4%bd%8d%e6%95%b4%e6%95%b0%e6%a0%bc%e5%bc%8f%e5%8c%96%e7%9a%84%e5%b0%8f%e7%9f%a5%e8%af%86/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>在代码里需要用到对 64 位整数的格式化输出，结果应该是以十六进制的形式输出的长度为十六的字符串，不足十六个字母的，左端以零补齐。</p>
<p>按照惯性思维，我的代码之前是这样写的：</p><pre class="crayon-plain-tag">sprintf(szBuffer, "0x%016llX%s", ullValue, pszValue);</pre><p>在 Linux 下，无论是 Qt Creator 的工程里，还是 CodeLite 的 g++ 工程里，输出没有任何问题。</p>
<p>把代码拿到 Windows 下的 Qt Creator 中编译，编译器给出了编译警告，说有个 <pre class="crayon-plain-tag">l</pre> 不能识别（由此导致这个格式化位对应的参数被编译器认为是多余的），而且更糟糕的是，输出的字符串里，跟在这个格式化位之后的内容会被覆盖。如上例，如果 <pre class="crayon-plain-tag">ullValue</pre> 值为 <pre class="crayon-plain-tag">1</pre>，<pre class="crayon-plain-tag">pszValue</pre> 指向 &#8220;xyz&#8221;，我们预期的输出应该是 “0000000000000001xyz”，而得到的输出将会是 &#8220;0000000000000001(null)&#8221;。</p>
<p>经过在网上不断淘宝，找到一个解决方案。首先，需要包含 inttypes.h 这个头文件，然后把格式化串中的 <pre class="crayon-plain-tag">llx</pre> 改成一个预定义的宏 <pre class="crayon-plain-tag">PRIX64</pre>。正确的写法见下：</p><pre class="crayon-plain-tag">sprintf(szBuffer, "0x%016" PRIX64 "%s", ullValue, pszValue);</pre><p>此记。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/03/21/64-%e4%bd%8d%e6%95%b4%e6%95%b0%e6%a0%bc%e5%bc%8f%e5%8c%96%e7%9a%84%e5%b0%8f%e7%9f%a5%e8%af%86/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Windows 消息机制浅析</title>
		<link>https://www.somedoc.net/2012/02/28/windows-%e6%b6%88%e6%81%af%e6%9c%ba%e5%88%b6%e6%b5%85%e6%9e%90/</link>
					<comments>https://www.somedoc.net/2012/02/28/windows-%e6%b6%88%e6%81%af%e6%9c%ba%e5%88%b6%e6%b5%85%e6%9e%90/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Tue, 28 Feb 2012 15:16:20 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2607</guid>

					<description><![CDATA[1. Windows 的历史 中国人喜欢以史为鉴，而事实也确 <a href="https://www.somedoc.net/2012/02/28/windows-%e6%b6%88%e6%81%af%e6%9c%ba%e5%88%b6%e6%b5%85%e6%9e%90/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>1. Windows 的历史</p>
<p>中国人喜欢以史为鉴，而事实也确实是，如果你能知道一件事情的来龙去脉，往往可以更容易地理解事物为什么会表现为当前这样的现状。所以，我的介绍性开场白通常会以一段历史开始。不过，我不会以精确到年月日的那种方式详细讲述，而是选取几个对我们的编程生涯有重要影响的关键点。</p>
<p>Windows 是真正的图形化界面操作系统的普及者，无论任何人，争夺什么第一个实现的GUI、第一个商业化的GUI之类的虚名，都替代不了 Windows 的历史功绩，让最普通的用户能够容易地操纵PC。</p>
<p>第一个声名大噪的版本是 Windows 3.0（也有人认为应该是它的更加健康强壮的弟弟 Windows 3.1），从那个时候开始，我们就和本文中以下的几个关键角色有了不尽的情缘：</p><pre class="crayon-plain-tag">while(GetMessage(&msg, NULL,0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}</pre><p>上面代码中的这三个相关函数，会在后文中提到。</p>
<p>第二个大红大紫的版本则非 Windows 95 莫属。这个版本的主要变化在于，无论如何，它是一个大众化的所谓 32 位系统了。之所以要加上“所谓的”三个字，是因为这个系统是个混血儿，在 3 位代码中混杂有大量的从之前的 Windows3.x 上移植过来的 16 位代码。</p>
<p>此时间稍后，另一支潜力股的关键进化过程结束，Windows NT 4.0 隆重登场，这个分支的操作系统是全 32 位的，成为了 Windows 95 系列的掘墓者，也是我们现在所使用几乎所有的 Windows 桌面系统（Windows2000/XP/2003/Vista/2008）的前辈。但是，这个版本由于对系统硬件的要求甚高（在当时），所以没有引起普通用户的广泛关注。</p>
<p>下一个里程碑就是 Windows 2000 了，微软实现了 Windows9x/Me 分支和 Windows NT 分支的合并。紧接着，Windows XP 现身。从有关消息方面来考察，Windows2000 做了微小的改进，在此之前，我们在很多情况下需要创建一个正常的、隐藏的、完整的窗口来处理消息，而 Windows 2000 引入了一种特殊类型的窗口用于此类需求。道理上来讲，应该会减少一些资源占用。</p>
<p>此后经过五六年的时间，Windows Vista 诞生。事实上，从 Windows2000 开始，Windows 家族的编程模型，尤其是对原生态代码（native code）而言，已经基本没有太大的变化。通常只是增加了新的API或者用户控件，或者现有控件增加了新的功能或者风格。尽管 Windows Vista 中有很多的变化，但是对于我们今天要讲到的主题，影响不大。最主要的一个影响，是消息的发送方和接收方之间有了等级限制，不像之前可以随意互相进行消息传递，这是出于安全性的考虑。</p>
<p>2. Windows 的宏观构造</p>
<p>从最原始的版本开始，有三个比较大的功能块占据了 Windows 系统的绝大部分，这三个块，就是赫赫有名的 Kernel、GDI、User。从 Windows 95 起，另两个在先前不太起眼的部分也迅速崛起，那就是大名鼎鼎的 Registry 和 Shell。</p>
<p>这几个大块的分工是这样的：Kernel，望文生义，负责内核部分，这是任何一个可以称之为操作系统的东西的基石，主要职责有：内存管理、任务调度、外设管理等；GDI，则是对可以进行图形化操纵的设备的操作接口，对外提供的主要功能是在设备上：提供坐标系统，绘制点、线、形状，进行填充，文本绘制，管理画笔、画刷、字体等绘图对象；User，则是前两者的粘合剂，使系统能够通过图形化操作方式和使用者（也就是 User）进行交互，把零散的 GDI 对象有机地组织起来，抽象为窗口，用以接受用户的输入，进行相应的运算（广义上的，并不是局限于算数运算），并最终将结果呈现给用户。当然，User 部分通常是指可以实现上述的功能的基础构造，真正的实现部分需要大量的额外工作，这也是 Shell 部分的主要工作。而 Registry，则是提供给用户一种与物理存储无关的统一的数据访问方式。</p>
<p>很容易就可以看出，消息功能，这种被我们一直以窗口间通讯最为自然的方式所使用的机制，应该隶属于 User 部分。</p>
<p>对于 Windows Mobile 系统来说，底层的实现上与桌面系统大相径庭，例如，它本身并没有 kernel32.dll、gdi32.dll、user32.dll 这几个众所周知的系统库，而是有一个多合一的 coredll.dll，而且内核被实现为一个更接近于正常进程的 nk.exe 进程，而不是桌面系统下的那个抽象的执行体。尽管如此，但是在逻辑上，我们依然可以将之与桌面系统同等看待。</p>
<p>3. Windows 的消息概念</p>
<p>在我们的通常认识上，消息事实就是一个数值。我们检查一下消息相关的各个回调函数的原型就会发现，表示消息的那个参数的数据类型是 <pre class="crayon-plain-tag">UINT</pre>，也就是无符号的整数类型。不过，我们通常也会发现，消息往往还附带有两个其他类型的数据，一个是 <pre class="crayon-plain-tag">WPARAM</pre> 类型的，一个是 <pre class="crayon-plain-tag">LPARAM</pre> 类型的，如果算上消息的目标窗口的句柄，那么，一个消息以及相关信息才能够说是比较完整。为什么说是比较呢？看一下 <pre class="crayon-plain-tag">MSG</pre> 这个结构的定义就会发现，其实还有另外两个我们不太经常使用的数据，是与一条消息有关系的。<pre class="crayon-plain-tag">MSG</pre> 的完整声明如下：</p><pre class="crayon-plain-tag">typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;</pre><p>前四项正是我们已经提及过的，而后两项，一个表示消息发生时的时间，一个表示此消息发生时的按屏幕坐标表示的鼠标光标的位置。</p>
<p>从这个结构也可以看出，我们经常所说的消息，更多是指代表了一个确定的消息的数值。</p>
<p>我们可能还会听到有这样的称呼：命令消息、通知消息、反射消息等等。首先需要声明的一点是，这并不是对 Windows 系统中的消息的科学分类，而是在某些特定场景下的通俗称谓。命令消息，一般特指 <pre class="crayon-plain-tag">WM_COMMAND</pre> 消息，此消息通常由控件或者菜单发出，表示用户执行/发出了一个命令。通知消息，一般特指<pre class="crayon-plain-tag">WM_NOTIFY</pre> 消息，此消息通常由公用控件（Common Controls）发出，表示一些事件发生了，需要处理。反射消息，一般用于对 Windows API 的封装类或者类库中。这是一类消息的总称，它们的处理需要经过一种被称为“反射”的机制。这一机制的具体方式下一节中会有描述。</p>
<p>Windows 的消息分类不好分（如果非要划分的话，可以分为系统定义的消息和应用程序定义的消息），不过有一个区段划分。从 <pre class="crayon-plain-tag">0x0000</pre> 到 <pre class="crayon-plain-tag">0x03FF</pre>，为系统定义的消息，常见的 <pre class="crayon-plain-tag">WM_PAINT</pre>、<pre class="crayon-plain-tag">WM_CREATE</pre> 等均在其中；从 <pre class="crayon-plain-tag">0x0400</pre> 到 <pre class="crayon-plain-tag">0x7FFF</pre>，专用于用户自定义的消息，可以使用 <pre class="crayon-plain-tag">WM_USER + x</pre> 的形式自行定义，其中 <pre class="crayon-plain-tag">WM_USER</pre> 的值就是 0x0400，x 取一个整数；从 <pre class="crayon-plain-tag">0x8000</pre> 到 <pre class="crayon-plain-tag">0xBFFF</pre>，从 Windows 95 开始，也用作用户自定义的消息范围，可以使用 <pre class="crayon-plain-tag">WM_APP + x</pre> 的形式自行定义。根据微软的建议，<pre class="crayon-plain-tag">WM_APP</pre> 类消息用于程序之间的消息通信，而 <pre class="crayon-plain-tag">WM_USER</pre> 类消息则最好用于某个特定的窗口类。微软自己遵循这一惯例，所以，公用控件的消息，如 <pre class="crayon-plain-tag">TVM_DELETEITEM</pre>，基本都是 <pre class="crayon-plain-tag">WM_USER</pre> 类属。从 <pre class="crayon-plain-tag">0xC000</pre> 开始，到 <pre class="crayon-plain-tag">0xFFFF</pre>，这个区段的消息值保留给 <pre class="crayon-plain-tag">RegisterWindowMessage</pre> 这个 API，此 API 可以接受一个字符串，把它变换成一个唯一的消息值。在桌面系统上，最常见的源字符串，可能就是“TaskbarCreated”了，由它对应的消息会发送到所有的顶级窗口，通知任务栏刚刚被创建（可能是由于资源管理崩溃后重新启动导致的）。</p>
<p>由上也可以看出，Windows 的消息值是一个 16 位的数字，这是 16 系统时代留给我们的痕迹。另外的一个痕迹是 <pre class="crayon-plain-tag">WPARAM</pre> 和 <pre class="crayon-plain-tag">LPARAM</pre> 这两个数据类型，在 16 位时代，<pre class="crayon-plain-tag">WPARAM</pre> 是 16 位的，其名字的意思是 word parameter，<pre class="crayon-plain-tag">LPARAM</pre> 是 32 位的，其名字的意思是 long parameter。</p>
<p>4. Windows 的消息机制</p>
<p>4.1. 消息队列</p>
<p>说到消息机制，可能连最初级的 Windows 程序员都会对消息队列（Message Queue）这个名词耳熟（不过不见得能详）。对于这样一个基本概念，Windows 操作系统提供的针对消息队列的 API 却少的可怜（<pre class="crayon-plain-tag">GetQueueStatus</pre>、<pre class="crayon-plain-tag">GetInputState</pre>、<pre class="crayon-plain-tag">GetMessageExtraInfo</pre>、<pre class="crayon-plain-tag">SetMessageExtraInfo</pre>），而且，这些 API 的出镜率也相当的低，甚至有不少经验丰富的程序员也从来没有使用过它们。在 Windows Mobile 上，这些 API 干脆付诸阙如，不过有一个同样极少使用的 <pre class="crayon-plain-tag">GetMessageQueueReadyTimeStamp</pre> 函数在充门面。</p>
<p>这一切，都归功于在 API 层极好的封装性，减少了开始接触这个平台时需要了解的概念。但是，对于我们这样既想知其然，又想知其所以然的群体，还是有必要对消息队列有充分的了解。</p>
<p>4.1.1. 系统消息队列</p>
<p>这是一个系统唯一的队列，输入设备（键盘、鼠标或者其他）的驱动程序会把用户的操作输入转化成消息放置于系统队列中，然后系统会把此消息转到目标窗口所在线程的消息队列中等待处理。</p>
<p>4.1.2. 线程消息队列（应用程序消息队列）</p>
<p>应用程序消息队列这个名称是历史遗留，在 32 位（以及之后的 64 位）系统中，正确的名称应该是线程消息队列。每一个 GUI 线程都会维护这样一个线程消息队列。（这个队列只有在线程调用 User 或者 GDI 函数时才会创建，默认并不创建）。然后线程消息队列中的消息会被本线程的消息循环（有时也被称为消息泵）派送到相应的窗口过程（也叫窗口回调函数）处理。</p>
<p>4.2. 消息的生命期</p>
<p>4.2.1. 消息的产生</p>
<p>消息产生的源头有两个，一个是系统，一个是应用程序。系统产生的消息又可以大致分为两类，一类是由输入设备导致的，例如 <pre class="crayon-plain-tag">WM_MOUSEMOVE</pre>，一类是 User 部分（或者是系统内的其他部分通过 User 部分）为了实现自身的正常行为或者管理功能而主动生成的，如 <pre class="crayon-plain-tag">WM_WINDOWPOSCHANGED</pre>。</p>
<p>产生的方式也有两种，一种称为发送（Send），另一种称为投递（Post，也有译作张贴的），对应于大家极为熟悉的两个 API，<pre class="crayon-plain-tag">SendMessage</pre> 和 <pre class="crayon-plain-tag">PostMessage</pre>。系统产生的消息，虽然我们看不到代码，不过我们还是可以粗略地划拨一下，基本上所有的输入类消息，都是以投递的方式抵达应用的，而其他的消息，则大部分是采取了发送方式。</p>
<p>至于应用程序，可以随意选用适合自己的消息产生方式。</p>
<p>4.2.2. 消息的处理</p>
<p>在绝大部分情况下，消息总是有一个目标窗口的，因此，消息也绝大部分是被某个窗口所处理的。处理消息的地方，就是这个窗口的回调函数。</p>
<p>窗口的回调函数，之所以被称作“回调”，就是因为这个函数一般并不是由用户（程序员）主动调用它的，而是系统认为在恰当的时候对它进行调用。那么，这个“恰当的时候”是什么时候呢？根据消息产生的方式，“恰当的时候”也有两个时机。</p>
<p>第一个时机是，<pre class="crayon-plain-tag">DispatchMessage</pre> 函数被调用时，另一个时机是 <pre class="crayon-plain-tag">SendMessage</pre> 函数被调用时。</p>
<p>我们正常情况下以系统处理对一个顶级窗口的关闭按钮的鼠标左键点击事件为例来说明。</p>
<p>这个点击事件完成的标志性消息是 <pre class="crayon-plain-tag">WM_NCLBUTTONUP</pre>，表示在一个窗口的非客户区的鼠标左键释放动作，另外，这个鼠标消息的其他数据中会表明，发生这个动作的位置是在关闭按钮上（<pre class="crayon-plain-tag">HTCLOSE</pre>）。这是一个鼠标输入事件，从前文可以知道，它会被系统投递到消息队列中。</p>
<p>于是，在消息循环中 <pre class="crayon-plain-tag">GetMessage</pre> 的某次执行结束后，这个消息被取到了 <pre class="crayon-plain-tag">MSG</pre> 结构里。从文章开头的消息循环代码可知，这个消息接下来会被 <pre class="crayon-plain-tag">TranslateMessage</pre> 函数做必要的（事实上是“可能的”）翻译，然后交给 <pre class="crayon-plain-tag">DispatchMessage</pre> 来全权处理。</p>
<p><pre class="crayon-plain-tag">DispatchMessage</pre> 拿到了 <pre class="crayon-plain-tag">MSG</pre> 结构，开始自己的一套办事流程。</p>
<p>首先，检查消息指定的目标窗口句柄。看系统内（实际上是本线程内）是不是确实存在这样一个窗口，如果没有，那说明这个消息已经不会有需要对它负责的人选了，那么这个消息就会被丢弃。</p>
<p>如果有，它就会直接调用目标窗口的回调函数。终于看到，我们写的回调函数出场了，这就是“恰当的时机”之一。当然，为了叙述清晰，此处省略了系统做的一些其他处理。</p>
<p>这样，对于系统来说，一条投递消息就处理完成，转而继续 <pre class="crayon-plain-tag">GetMessage</pre>。</p>
<p>不过对于我们上面的例子，事情还没有完。</p>
<p>我们都清楚，对于 <pre class="crayon-plain-tag">WM_NCLBUTTONUP</pre> 这样一条消息，通常我们是无暇去做额外处理的（正事还忙不过来呢……）。所以，我们一般都会把它扔到那个著名的垃圾堆里，没错，<pre class="crayon-plain-tag">DefWindowProc</pre>。尽管如此，我们还是可以看出，<pre class="crayon-plain-tag">DefWindowProc</pre> 其实已经成了我们的回调函数的一个组成部分，唯一的差别在，这个函数不是我们自己写的而已。</p>
<p><pre class="crayon-plain-tag">DefWindowProc</pre> 对这个消息的处理也是相当轻松，它基本上没有做什么实质性的事情，而是生成了另外一个消息，<pre class="crayon-plain-tag">WM_SYSCOMMAND</pre>，同时在 <pre class="crayon-plain-tag">wParam</pre> 里指定为 <pre class="crayon-plain-tag">SC_CLOSE</pre>。这一次，消息没有被投递到消息队列里，而是直接 Send 出来的。</p>
<p>于是，<pre class="crayon-plain-tag">SendMessage</pre> 的艰难历程开始。</p>
<p>第一步，<pre class="crayon-plain-tag">SendMessage</pre> 的方向和 <pre class="crayon-plain-tag">DispatchMessage</pre> 几乎一模一样，检查句柄。</p>
<p>第二步，事情就来了，它需要检查目标窗口和自己在不在一个线程内。如果在，那就比较好办，按照 <pre class="crayon-plain-tag">DispatchMessage</pre> 趟出来的老路走：调用目标窗口的回调函数。这，就是“恰当的时机”之二。</p>
<p>可是要是不在一个线程内，那就麻烦了。道理很简单，别的线程有自己的运行轨迹，没有办法去让它立即就来处理这个消息。</p>
<p>现在，<pre class="crayon-plain-tag">SendMessage</pre> 该怎么处理手里的这个烫手山芋呢？（作者注：写到此处时，很有写上“欲知后事如何，且听下回分解”的冲动）</p>
<p>微软的架构师做了个非常聪明的选择：不干涉其他线程的内政。我不会生拉硬拽让你来处理我的消息，我会把消息投递给你（这个投递是内部操作，从外面看，这条消息应该一直被认为是发送过去的），然后 —— 我等着。</p>
<p>这下，球踢到了目标线程那边。目标线程一点也不含糊，既然消息来到了我的队列里，那我的 <pre class="crayon-plain-tag">GetMessage</pre> 会按照既定的流程走，不过，和上文<pre class="crayon-plain-tag">WM_NCLBUTTONUP</pre> 的经历有所不同。鉴于这条消息是外来客，而且是 Send 方式，于是它以优先于线程内部的其他消息进行处理（毕竟友邦在等着啊），处理完毕之后，把结果返回给消息的源线程。可以参见下文中对 <pre class="crayon-plain-tag">GetMessage</pre> 函数的叙述。</p>
<p>在我们的现在讨论的这个例子里，由于 <pre class="crayon-plain-tag">SendMessage(WM_SYSCOMMAND)</pre> 是属于本线程内的，所以就会递归调用回窗口的回调函数里。此后的处理，还是另外的几个消息被衍生出来，如 <pre class="crayon-plain-tag">WM_CLOSE</pre> 和 <pre class="crayon-plain-tag">WM_DESTROY</pre>。这个例子仅仅出于概念性的展示，而不是完全精确可靠的，而且，在 Windows Mobile 上，干脆就没有非客户区的概念。</p>
<p>这就是系统内所有消息的处理方式。</p>
<p>不过稍等，<pre class="crayon-plain-tag">PostThreadMessage</pre> 投递到消息队列里的消息怎么办？答案是：你自己看着办。最好的处理位置，就是在消息循环中的<pre class="crayon-plain-tag">TranslateMessage</pre> 调用之前。</p>
<p>另外一个需要稍做注解的问题是消息的返回值问题，这个问题有些微妙。对于大多数的消息，返回值都没有什么意义。对于另外的一些消息，返回值意义重大。我相信有很多人对 <pre class="crayon-plain-tag">WM_ERASEBKGND</pre> 消息的返回值会有印象，该消息的返回值直接影响到系统是不是要进行缺省的绘制窗口背景操作。所以，处理完一条消息究竟应该返回什么，查一下文档会更稳妥一些。</p>
<p>这才算是功德圆满了。</p>
<p>4.2.3. 消息的优先级</p>
<p>上一节中其实已经暗示了这一点，来自于其他线程的发送的消息优先级会高一点点。</p>
<p>不过还需要注意，还有那么几个优先级比正常的消息低一点点的。它们是：<pre class="crayon-plain-tag">WM_PAINT</pre>、<pre class="crayon-plain-tag">WM_TIMER</pre>、<pre class="crayon-plain-tag">WM_QUIT</pre>。只有在队列中没有其他消息的时候，这几个消息才会被处理，多个 <pre class="crayon-plain-tag">WM_PAINT</pre> 消息还会被合并以提高效率（内幕揭示：<pre class="crayon-plain-tag">WM_PAINT</pre> 其实也是一个标志位，所以看上去是被“合并了”）。</p>
<p>其他所有消息则以先进先出（FIFO）的方式被处理。</p>
<p>4.2.4. 没有处理的消息呢？</p>
<p>有人会问出这个问题的。事实上，这差不多就是一个伪命题，基本不存在没有处理的消息。从 4.2.2 节的叙述也可以看出，消息总会流到某一个处理分支里去。</p>
<p>那么，我本人倾向于提问者在问这样一个问题：如果窗口回调函数没有处理某个消息，那这个消息最终怎么样了？其实这还是取决于回调函数实现者的意志。如果你只是简单地返回，那事实上也是进行了处理，只不过，处理的方式是“什么都没做”而已；如果你把消息传递给 <pre class="crayon-plain-tag">DefWindowProc</pre>，那么它会处理自己感兴趣的若干消息，对于别的消息，它也一概不管，直接返回。</p>
<p>4.3. 消息死锁</p>
<p>假设有线程 A 和 B，现在有以下步骤：</p>
<p>1) 线程 A <pre class="crayon-plain-tag">SendMessage</pre> 给线程 B，A 等待消息在线程 B 中处理后返回<br />
2) 线程 B 收到了线程 A 发来的消息，并进行处理，在处理过程中，B 也向线程 A <pre class="crayon-plain-tag">SendMessage</pre>，然后等待从 A 返回。</p>
<p>此时线程 A 正等待从线程 B 返回，无法处理B发来的消息，从而导致了线程 A 和 B 相互等待，形成死锁。</p>
<p>以此类推，多个线程也可以形成环形死锁。</p>
<p>可以使用 <pre class="crayon-plain-tag">SendNotifyMessage</pre> 或 <pre class="crayon-plain-tag">SendMessageTimeout</pre> 来避免出现此类死锁。</p>
<p>（作者注：对两个线程互相 <pre class="crayon-plain-tag">SendMessage</pre> 曾经专门写程序进行过验证，结果却没有死锁，不知道是不是新一些的 Windows 系统作了特殊的处理。请大家自行验证。）</p>
<p>4.4. 模态（Modal）</p>
<p>这个词汇曾给我带来极大的困惑，我曾经做过不少的努力，想弄清楚为什么当初系统的构建者使用“模态”这个词汇来表达这样一种情景，但是最后失败了。我不得不接受这个词，并运用它。直到数天前，我找到了一个对模态的简要介绍，如果有兴趣，各位可以自己去看：http://www.usabilityfirst.com/glossary/main.cgi?function=display_term&#038;term_id=320。（我曾做过的另外一个努力是想知道为什么 Handle 会被翻译为“句柄”，或者，是谁首先这样翻译的，迄今无解）。Windows 中的模态有好几个场景，比较典型的有：</p>
<p>显示了一个对话框<br />
显示出一个菜单<br />
操作滚动条<br />
移动窗口<br />
改变窗口大小</p>
<p>把我的体会归纳起来，那就是：如果进入了一个模态场景，那么，除了这个模态本身的明确目标，其余操作被一概禁止。概念上可以理解为，模态，是一种独占模式、一种强制模式，一种霸道模式。</p>
<p>在 Windows 里，模态的实现其实很简单，只不过就是包含了自己的消息循环而已，说穿了毫无悬念可言，但是如果不明白这个内幕的话，就会觉得很神秘。那么，根据此结论，我们就可以做一些有趣（或者有意义）的事情了，看一下以下代码，预测一下 <pre class="crayon-plain-tag">TestModal</pre> 的执行结果：</p><pre class="crayon-plain-tag">void CALLBACK RequestQuit(HWNDhwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
void TestModal()
{
    UINT uTimerId =SetTimer(NULL, 66, 1000, RequestQuit);
    MessageBox(NULL, NULL, NULL,MB_OK);
    KillTimer(NULL, uTimerId);
}

void CALLBACK RequestQuit(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    PostMessage(NULL, WM_QUIT,0, 0);
}</pre><p>答案见本大节末尾。</p>
<p>需要提醒的是，模态是用户界面里相当重要而普遍的一个概念，不仅存在于 Windows 环境下，也存在于其他的用户界面系统中，例如 Symbian。</p>
<p>4.5. 与消息处理有关的钩子（Hook）</p>
<p>很多人都或多或少地听说过或者接触过钩子。钩子在处理事务的正常流程之外，额外给予了我们一种监听或者控制的方式（注意：在 Windows Mobile 系统下，钩子并不被正式支持）。</p>
<p>（TODO: 细化，不过由于这个内容针对桌面系统更多，所以暂时可以略过）</p>
<p>4.6. 所谓的反射（Reflection）</p>
<p>上文也已经提到，反射通常会在对 Windows API 的封装类或者类库中出现，这是由于 Windows SDK 的 API 是以 C 的风格暴露给使用者的，与 C++ 语言的主要用类编程的风格有一些需要啮合的地方。</p>
<p>举例来说，一个 Button，在 SDK 中是一个已经定型的控件，基本上实现了自包容，要扩展它的功能的话（例如，绘制不同的外观），系统把接口（广义上的接口，即一种交互上的契约）制定为发给 Button 的属主（通常就是父窗口）的两条消息（<pre class="crayon-plain-tag">WM_MEASUREITEM</pre> 和 <pre class="crayon-plain-tag">WM_DRAWITEM</pre>）。其道理在于，使用 Button 控件的父窗口，往往是用户自己实现的，处理起来更方便，而不需要对 Button 自身做什么手脚。</p>
<p>但是，这种交互方式在 C++ 的世界里是相当忌讳的。C++ 的自包容单位是对象，那么一个 Button 对象的封装类，假定是 CButton，不能自己处理自己的绘制问题，这是不太符合法则的（尽管不是不可以）。</p>
<p>为了消除这一不和谐音，就有人提出了反射机制。其核心就在于，对于本该子控件自己处理的事件所对应的消息（如前面的 <pre class="crayon-plain-tag">WM_DRAWITEM</pre>），父窗口即使收到，也不进行直接处理，而是把这个消息重新发回给子控件本身。</p>
<p>这样带来一个问题，当 Button 收到一个 <pre class="crayon-plain-tag">WM_DRAWITEM</pre> 消息时，弄不清楚究竟是自己的子窗口发来的（虽说往 Button 上建立子窗口不常见，但不是不可以），还是父窗口把原本是自己的消息反射回来了。所以，最后微软给出一个解决办法，就是反射消息的时候，把消息的值上加一个固定的附加值，这个值就是 <pre class="crayon-plain-tag">OCM__BASE</pre>。尽管最初只是微软自己在这样做，这个值也完全可以各取各的，但是后来别的类/类库的编制者几乎都无一例外地和微软保持了一致。</p>
<p>当控件收到消息之后，先把这个附加值减掉，就可以知道是哪一条消息被反射回来了，然后再作相应的处理。</p>
<p>4.4 节小测试的答案：一个消息框显示大概 1 秒钟的时间，然后自动消失。有的人根据这一表现，写出了自己的超时候自动关闭的消息框。如果各位有兴趣，可以自己尝试也实现一下。（提示：需要考虑一下用户先于定时器触发就手动关闭了消息框的情况）</p>
<p>5. Windows 的消息本质</p>
<p>一个特殊的事件同步机制，使用多种常规线程间同步机制实现。</p>
<p>6. Windows 的消息操纵</p>
<p>注意：以下讨论中用浅绿色标注的函数，表示在 Windows Mobile 平台上是没有的。</p>
<p></p><pre class="crayon-plain-tag">SendMessage
PostMessage</pre><p>在使用消息的过程中，这两个函数的使用率是最高的。初学者有时会搞不清楚这两个发送消息的函数的使用场景，容易误用。所以放在这里一起说。其实上面已经对 <pre class="crayon-plain-tag">SendMessage</pre> 做了很多的介绍，所以在这儿的重点会放在 <pre class="crayon-plain-tag">PostMessage</pre> 上。相较 <pre class="crayon-plain-tag">SendMessage</pre> 而言，<pre class="crayon-plain-tag">PostMessage</pre> 的工作要轻松许多，只要找到知道那个的窗口句柄所在的线程，把消息放到该线程的消息队列里就可以了，完全不理会这条消息最终的命运，是不是被正确处理了。</p>
<p>这一点，从 <pre class="crayon-plain-tag">PostMessage</pre> 和 <pre class="crayon-plain-tag">SendMessage</pre> 的返回值的不同也有体现。<pre class="crayon-plain-tag">PostMessage</pre> 函数的返回值是 <pre class="crayon-plain-tag">BOOL</pre> 类型，体现的是投递操作是否成功。投递操作是有可能失败的，尽管我们不愿意同时也确实很少看到。例如，目标线程的消息队列已经满（在 16 位时代出现概率较高），或者更糟糕，目标线程根本就没有消息队列。</p>
<p>当然，<pre class="crayon-plain-tag">PostMessage</pre> 也要检查窗口句柄的合法性，不过和 <pre class="crayon-plain-tag">SendMessage</pre> 不同的一点是，它允许窗口句柄是 <pre class="crayon-plain-tag">NULL</pre>。在此情况下，对它的调用就等价于调用 <pre class="crayon-plain-tag">PostThreadMessage</pre> 向自身所在线程投递一条消息。</p>
<p>从上面的描述可以很容易地看出，<pre class="crayon-plain-tag">PostMessage</pre> 和 <pre class="crayon-plain-tag">SendMessage</pre> 的本质区别在于前者发出的消息是异步处理的，而后者发出的消息是同步处理的。理解这一点非常重要。</p>
<p>从上面的这个结果推演，还可以得到另外一个有时会很有用的推论。在本线程之内，如果你在处理某个窗口消息的时候，希望在处理之后开展另一项以此消息为前提的工作，那么可以向本窗口 Post 一条消息，来作为该后续工作的触发机制。</p>
<p><img fetchpriority="high" decoding="async" src="https://cxnc4a.bn1.livefilestore.com/y2p1gCZZWNDHD_7onrU2zhzOr1AaNvfQlqT7WgV9bdb6xNg1pwRNtwyrVIcr7kawUydEL6vLvo3HRIgMy4KrGzGAlOHYnXIai5RkkrmRV0jgD0/msgq.png" width="609" height="407" class="alignnone" /></p>
<p></p><pre class="crayon-plain-tag">GetMessage</pre><p>检查线程的消息队列，如果有消息就取出该消息到一个传入的 <pre class="crayon-plain-tag">MSG</pre> 结构中并返回，没有消息，就等待。等待时线程处于休眠状态，CPU 被分配给系统内的其他线程使用。</p>
<p>需要注意的是，由其它线程 Send 过来的消息，会在这里就地处理（即调用相应的窗口回调函数），而不会返回给调用者。</p>
<p></p><pre class="crayon-plain-tag">DispatchMessage</pre><p>这个消息的来龙去脉在上文中已经有较为详细的叙述，故此略去。</p>
<p></p><pre class="crayon-plain-tag">TranslateMessage
TranslateAccelerator // Windows Mobile 上不可用</pre><p>这个函数在本质上与消息机制的关系不大，绝大多数的消息循环中都出现它的身影是因为绝大多数的程序员都不知道这个函数真正是干什么的，仅仅是出于惯例或者初学时教科书上给出的范例。这个函数的作用主要和输入有关，它会把 <pre class="crayon-plain-tag">WM_KEYDOWN</pre> 和 <pre class="crayon-plain-tag">WM_KEYUP</pre> 这样的消息恰当地、适时地翻译出新的消息来，如 <pre class="crayon-plain-tag">WM_CHAR</pre>。如果你确信某个线程根本不会有用户输入方面的需求，基本上可以安全地将之从循环中移除。</p>
<p>可以和它相提并论的就是列出的 <pre class="crayon-plain-tag">TranslateAccelerator</pre> 函数，这个函数会把用户输入根据指定的加速键（Accelerator）表翻译为适当的命令消息。</p>
<p></p><pre class="crayon-plain-tag">PeekMessage</pre><p>窥探线程的消息队列。无论队列中有没有消息，这个函数都立即返回。它的参数列表与 <pre class="crayon-plain-tag">GetMessage</pre> 基本一致，只是多了一个标志参数。这个标志参数指定了如果队列中如果有消息的话，<pre class="crayon-plain-tag">PeekMessage</pre> 的行为。如果该标志中含有 <pre class="crayon-plain-tag">PM_REMOVE</pre>，则 <pre class="crayon-plain-tag">PeekMessage</pre> 会把新消息返回到 <pre class="crayon-plain-tag">MSG</pre> 结构中，正如 <pre class="crayon-plain-tag">GetMessage</pre> 的行为那样。如果标志中指定了 <pre class="crayon-plain-tag">PM_NOREMOVE</pre>，则不会取出任何消息。</p>
<p></p><pre class="crayon-plain-tag">WaitMessage // Windows Mobile 上不可用</pre><p>这个函数的作用是等待一条消息的到来。等待期间线程处于休眠状态，一旦有新消息到来，则立即返回。</p>
<p>了解了 <pre class="crayon-plain-tag">PeekMessage</pre> 和 <pre class="crayon-plain-tag">WaitMessage</pre> 之后，理论上，我们可以写出自己的 <pre class="crayon-plain-tag">GetMessage</pre> 了。</p>
<p></p><pre class="crayon-plain-tag">SendNotifyMessage</pre><p>这个函数很有意思，它的行为属于看人下菜碟型。如果目标线程就是自身所处线程，那么它就是 <pre class="crayon-plain-tag">SendMessage</pre>；而一旦发现目标线程是其他线程，那它就类似于 <pre class="crayon-plain-tag">PostMessage</pre>，不等待目标窗口处理完成。不过，仅仅是类似，因为它发出的消息仍然会被目标线程认为是 Send 过来的。</p>
<p></p><pre class="crayon-plain-tag">SendMessageTimeout</pre><p>这个函数可以说是 <pre class="crayon-plain-tag">SendMessage</pre> 函数家族（相对 <pre class="crayon-plain-tag">PostMessage</pre> 而言）之中最强大的函数。它在标准的 <pre class="crayon-plain-tag">SendMessage</pre> 函数的功能前提下，加入了许多额外的控制选项以及一个超时设定。例如，它可以指定，如果发现目标窗口已经失去响应的话，那么就立即返回；也可以指定如果目标窗口的响应时间超过了指定的超时时限的话也返回，而不是无限等待下去。而且我们知道，<pre class="crayon-plain-tag">SendMessage</pre> 是会固执地等待下去的。（内幕揭示：<pre class="crayon-plain-tag">SendMessage</pre> 其实就是对 <pre class="crayon-plain-tag">SendMessageTimeout</pre> 的一个浅封装）</p>
<p></p><pre class="crayon-plain-tag">SendMessageCallback // Windows Mobile 上不可用</pre><p>与 <pre class="crayon-plain-tag">SendMessageTimeout</pre> 不同，这个函数在另外一个方向上对标准的 <pre class="crayon-plain-tag">SendMessage</pre> 进行了扩展。它的行为与<pre class="crayon-plain-tag">SendNotifyMessage</pre> 类似，只不过允许在对方处理完消息之后，指定一个本线程内的后续处理函数。仔细观察可以发现，<pre class="crayon-plain-tag">SendNotifyMessage</pre> 其实是本函数的一个特例。</p>
<p>对这个函数的使用场景较少，实际上，作者几乎从来没有见到必须使用它的情况。网上有一些对此函数的讨论和测试代码，但很少有实用价值。（恐怕这也是 Windows Mobile 没有实现此函数的原因之一。）</p>
<p></p><pre class="crayon-plain-tag">PostQuitMessage</pre><p>这个函数的名字具有迷惑性。事实上，它本身并不会投递任何消息，而是偷偷在系统内部置了一个标志，当调用 <pre class="crayon-plain-tag">GetMessage</pre> 时会检测此标志位。若此标志位被置位，而且队列中已经没有别的符合条件的投递消息，则 <pre class="crayon-plain-tag">GetMessage</pre> 返回 <pre class="crayon-plain-tag">FALSE</pre>，用以终止消息循环。</p>
<p>不过，有人会有这样的疑惑。我们知道，<pre class="crayon-plain-tag">PostMessage</pre> 当窗口句柄为 <pre class="crayon-plain-tag">NULL</pre> 的时候，就相当于 <pre class="crayon-plain-tag">PostThreadMessage(GetCurrentThreadId(), …)</pre>，那么，为什么不用 <pre class="crayon-plain-tag">PostMessage(NULL, WM_QUIT, 0, 0)</pre>，而要引入这么一个单独的 API 呢？有的人给出的原因是，这个 API 出现在 Windows 的 16 位时代，当时还没有线程的概念。这个答案仔细推敲的话，其实似是而非，因为完全可以把进程的执行看作是一个线程。真正的原因，可能从前文能得到一些思考线索，尤其注意“队列中已经没有别的符合条件的投递消息”这个叙述。</p>
<p></p><pre class="crayon-plain-tag">PostThreadMessage</pre><p>跨线程投递消息。我们知道，消息队列是属于线程的，所以，可以不指定目标窗口而只指定目标线程就投递消息。投递到目标线程的消息通常会被 <pre class="crayon-plain-tag">GetMessage</pre> 取出，但是，由于没有指定目标窗口，所以不会被派发到任何一个窗口回调函数中。</p>
<p>请注意上文中的通常二字。这是因为在一般的情况下，我们是按照 <pre class="crayon-plain-tag">GetMessage(&msg, NULL, 0, 0)</pre> 这样的形式对 <pre class="crayon-plain-tag">GetMessage</pre> 进行调用的，但是，第二个参数是一个窗口句柄，如果指定了一个合法的窗口句柄，那么 <pre class="crayon-plain-tag">GetMessage</pre> 就只会取出与该窗口有关的投递消息。如果这样的调用放在线程的主消息循环中，就可能会造成消息积压（这和你在本线程中究竟创建了多少个窗口有关）。所幸的是，迄今我还没有见到过有谁这样使用 <pre class="crayon-plain-tag">GetMessage</pre>。</p>
<p></p><pre class="crayon-plain-tag">BroadcastSystemMessage[Ex] // Windows Mobile 上不可用</pre><p>我们一般所接触到的消息都是发送给窗口的，其实， 消息的接收者可以是多种多样的，它可以是应用程序（application）、可安装驱动程序（installable driver）、网络驱动程序（networkdriver）、系统级设备驱动程序（system-leveldevice driver）等，用 <pre class="crayon-plain-tag">BroadcastSystemMessage</pre> 这个 API 可以对以上系统组件发送消息。</p>
<p></p><pre class="crayon-plain-tag">InSendMessage
InSendMessageEx // Windows Mobile 上不可用</pre><p>这个函数用于在处理某条消息时，检查消息是不是来自于其他线程的发送操作。它的使用场景也极其有限，除非你确实计划限制某些消息的来源和产生方式。</p>
<p></p><pre class="crayon-plain-tag">ReplyMessage // Windows Mobile 上不可用</pre><p>这个函数在 MSDN 中的解释非常简单，只有寥寥数语，几乎到了模糊不清的地步。从示例代码段来推测，其作用大概是：消息的接收线程（目标线程）在处理过程中可以通过调用此函数使得消息的发送线程（源线程）结束等待状态继续执行。</p>
<p>根据微软的文档，其官方建议是：在处理每个有可能来自于其他线程的消息的时候，如果某一步骤的处理会调用到导致线程移交控制的函数（原文如此：any function that causes the thread to yield control），都应该先调用 <pre class="crayon-plain-tag">InSendMessage</pre> 类属的函数进行判断，如果返回 <pre class="crayon-plain-tag">TRUE</pre>，则要立即使用 <pre class="crayon-plain-tag">ReplyMessage</pre> 答复消息的源线程。</p>
<p>“会导致线程移交控制的函数”，MSDN 给出的例子是 <pre class="crayon-plain-tag">DialogBox</pre>，这使得我做出自己的推测，这样的函数，至少包括会导致进入某种模态场景的函数。</p>
<p>至于“有可能来自于其他线程的消息”，在 Windows 世界里的现实状况是，几乎任何一个消息都会来自于其他线程。</p>
<p>我多年以来的观察可以断定，现实中有无数没有进行以上流程判断的代码都在运行，而且也几乎没有暴露出什么严重的不良后果。这使得我有理由猜测，微软也许已经把对此情况的处理隐含到了系统内部。更何况，Windows Mobile 中根本就没有 <pre class="crayon-plain-tag">ReplyMessage</pre> 这个 API。</p>
<p></p><pre class="crayon-plain-tag">GetMessagePos
GetMessageTime // Windows Mobile 上不可用</pre><p>这两个函数用于访问当前处理的消息的另外两个信息，对应于 <pre class="crayon-plain-tag">MSG</pre> 结构里的相应域。它们存在的原因是因为窗口回调/消息处理函数一般都不会传递这两个数据。</p>
<p></p><pre class="crayon-plain-tag">MsgWaitForMultipleObjects
MsgWaitForMultipleObjectsEx</pre><p>这是一个在讲到消息相关的内容时，十有八九会被人遗忘的 API。它属于传统的 ITC、IPC 和 Windows 特有的消息机制的交叉地带。不过，在 Windows 平台上，如果还没有了解并掌握这个函数，那一定不能称其为专家。</p>
<p>这个函数揭示了以下平时不太为人所注意的细节：</p>
<p>1、  消息和内核对象，有千丝万缕的联系<br />
2、  消息和内核对象可以按照相似的方式去处理</p>
<p>如果说，<pre class="crayon-plain-tag">SendMessageTimeout</pre> 是 Windows 平台下最强大的发送消息的机制，那么，<pre class="crayon-plain-tag">MsgWaitForMultipleObjects[Ex]</pre> 就是最强大等待机制，它是 <pre class="crayon-plain-tag">WaitMessage</pre> 和 <pre class="crayon-plain-tag">WaitFor…</pre> 函数族的集大成者。根据我们上面使用 <pre class="crayon-plain-tag">WaitMessage</pre> 和 <pre class="crayon-plain-tag">PeekMessage</pre> 结合使用可以取代 <pre class="crayon-plain-tag">GetMessage</pre> 的论断，我们也可以这样说，<pre class="crayon-plain-tag">MsgWaitForMultipleObjects[Ex]</pre> 是最强大的消息循环发动机。</p>
<p>仔细描述此函数会超出单纯的消息机制范畴，所以把深入学习它的工作遗留给各位自己去实践。</p>
<p>7. Windows 的消息辨析</p>
<p>7.1. SendMessage 和 PostMessage 的区别</p>
<p>请考虑有面试考官问及此问题时你如何组织回答。</p>
<p>7.2. SendMessage 发送的消息不进入消息队列吗</p>
<p>提示：请考虑跨线程的情况。</p>
<p>这个说法不完全正确。当 <pre class="crayon-plain-tag">SendMessage</pre> 发送的消息跨越线程边界时，消息其实被加入到了目标线程的消息队列里。不过，在线程队列里，别的线程Send过来的消息会被优先处理。</p>
<p>7.3. <pre class="crayon-plain-tag">PostMessage(WM_QUIT)</pre> 和 <pre class="crayon-plain-tag">PostQuitMessage()</pre> 的区别，可能会产生怎样的差异化执行效果</p>
<p>提示：请考虑发生以上某个调用时，消息队列里不为空的情况。</p>
<p>7.4. 文章开头的经典消息循环正确么？</p>
<p>提示：请注意 <pre class="crayon-plain-tag">GetMessage</pre> 的返回值。</p>
<p>曾经有很长一段时间，连微软的例子也这样写。但是，这样写其实是不对的。原因很简单，<pre class="crayon-plain-tag">GetMessage</pre> 不仅仅是取道消息返回 <pre class="crayon-plain-tag">TRUE</pre>，取不到（遇到 <pre class="crayon-plain-tag">WM_QUIT</pre> 消息）返回 <pre class="crayon-plain-tag">FALSE</pre> 这么单纯，它还会出错。出错时返回 <pre class="crayon-plain-tag">-1</pre>。这就了能使得经典循环在 <pre class="crayon-plain-tag">GetMessage</pre> 发生错误时变成死循环。微软的建议是，当 <pre class="crayon-plain-tag">GetMessage</pre> 返回 <pre class="crayon-plain-tag">-1</pre> 时，跳出循环，结束程序。</p>
<p>注：本文乃是数年前的培训讲义，文中有某处不完整，迄今未补，读者自察之。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/02/28/windows-%e6%b6%88%e6%81%af%e6%9c%ba%e5%88%b6%e6%b5%85%e6%9e%90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Java 和优雅</title>
		<link>https://www.somedoc.net/2012/02/09/java-%e5%92%8c%e4%bc%98%e9%9b%85/</link>
					<comments>https://www.somedoc.net/2012/02/09/java-%e5%92%8c%e4%bc%98%e9%9b%85/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Thu, 09 Feb 2012 09:15:33 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2604</guid>

					<description><![CDATA[Java 语言的语法是以优雅著称的。至少我接触过的很多书籍、 <a href="https://www.somedoc.net/2012/02/09/java-%e5%92%8c%e4%bc%98%e9%9b%85/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>Java 语言的语法是以优雅著称的。至少我接触过的很多书籍、资料是这么说的，当然，以我数百行的 Java 编程经验来讲，我也在大多数的情况下感受到了这种优雅，直到遇到一个特殊情况。</p>
<p>这个情况是这样的。假设你写了一个数据访问的管理器，其中有一个方法提供给使用者以获取数据的能力，用我熟悉的 C 或者 C++，我会很快写出类似如下的函数定义：</p><pre class="crayon-plain-tag">size_t DataManager_GetData(void* buffer, size_t bufferSize);</pre><p>或者</p><pre class="crayon-plain-tag">class CDataManager
{
    //...
    size_t GetData(void* buffer, size_t bufferSize);
    // ...
};</pre><p>输入的参数为接收数据的缓冲区以及缓冲区的大小，返回值则是返回数据的大小。当然，Java 支持这样的一个小需求毫无压力，示例代码我就不献丑了。接下来，对这个方法的需求略微有了一点增加，除了要返回取到的数据的大小之外，希望顺便还能给出一个指示，表示缓冲区内是否还有更多的数据可供获取。根据此需求，C++ 的方法原型通常会做如下的修订：</p><pre class="crayon-plain-tag">size_t GetData(void* buffer, size_t bufferSize, bool& moreData);</pre><p>那么 Java 呢，应该如何写出提供相同功能的方法原型？作为一个菜鸟，我尝试了传入一个 boolean 的参数（因为有朋友告诉过我 Java 的参数其实是“引用”，当然了，看到本文的朋友千万别这么天真地相信，否则我也不会傻乎乎写这篇文章），很显然地失败了。后来我又自作聪明地想起原生类型和对象类型在 Java 中似乎有所不同，于是把 boolean 改成 Boolean，满怀希望地出现奇迹，也以失败告终。</p>
<p>于是我求助于同事和朋友。答案很多。例如，把数据量和标记映射为一个条目，返回一个 Map 类型的对象。再例如，返回一个对象数组，如此等等。是的，我知道他们都是可以解决这个问题的方法，但是，我们呼应一下标题，我认为都不够优雅。因为我要同时返回的这两个数据，事实上相互之间的关系很弱，本质上是两个/两类信息，硬生生捏合在一起做为一个对象（Object，或者说物体）返回，我觉得很丑陋。可惜到目前我自己还没有能够想出更好的方案。</p>
<p>大家呢？敬请不吝赐教。当然，最好不要质疑需求，说是分成两次调用什么的。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/02/09/java-%e5%92%8c%e4%bc%98%e9%9b%85/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Unix/Linux 桌面环境相关知识备忘</title>
		<link>https://www.somedoc.net/2012/01/27/unixlinux-%e6%a1%8c%e9%9d%a2%e7%8e%af%e5%a2%83%e7%9b%b8%e5%85%b3%e7%9f%a5%e8%af%86%e5%a4%87%e5%bf%98/</link>
					<comments>https://www.somedoc.net/2012/01/27/unixlinux-%e6%a1%8c%e9%9d%a2%e7%8e%af%e5%a2%83%e7%9b%b8%e5%85%b3%e7%9f%a5%e8%af%86%e5%a4%87%e5%bf%98/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Fri, 27 Jan 2012 03:34:27 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2602</guid>

					<description><![CDATA[多年前认识几位 Unix 下的资深开发人员，例如老许，随意兄 <a href="https://www.somedoc.net/2012/01/27/unixlinux-%e6%a1%8c%e9%9d%a2%e7%8e%af%e5%a2%83%e7%9b%b8%e5%85%b3%e7%9f%a5%e8%af%86%e5%a4%87%e5%bf%98/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>多年前认识几位 Unix 下的资深开发人员，例如老许，随意兄等，当时正在对 Windows 下的用户界面相关领域做研究，比如 win32k.sys 里面的东西，DirectUI/NetUI 的实现原理，如何做外挂式的窗口风格切换器（类似于 WindowBlinds 那样的机制在一个应用内或者整个系统内的应用），为了做知识延展，曾经请教过他们 Unix/Linux 下 X 窗口系统的知识。尽管他们专注的领域并不在这方面，但还是解决了我不少疑问，很可惜的是，这么多年过去后，那些曾经知道的东西又淡忘了。</p>
<p>从 2011 年末，又开始接触 Linux，了解了一些 shell 和 ssh 相关的粗浅知识，昨天下午突然又对 X 的体系结构和发展历史产生了疑惑，因此 google 到深夜。以下为简要的记录，以作备忘。因为不是这个领域内的专家，而且有的信息并没有做严格的考证和验证，所以不能保证所有信息均正确无误，欢迎指正和补充。</p>
<p>在 *nix 下，尤其是 Linux 下，桌面环境是个笼统的称呼，通常包括了一个使用了某个图形开发库的窗口管理器，以及若干可供使用的常用软件（例如终端模拟程序、文本编辑器、文件管理器、控制中心、浏览器甚至开发工具等等）。</p>
<p>长期以来，*nix 系统的图形化界面基础是 X 窗口系统（X Window System），这个系统主要由 X 服务器（X Server），X 客户端（X Client）和 X 协议（X Protocol）组成。X 服务器负责在绘图设备（通常是屏幕）上绘图、与输入设备（通常是鼠标和键盘）交互以及处理窗口的创建销毁等。通常用到的 X 协议是 X11R6，意为 X protocol version 11 release 6（写本文时最新的 release 是 X11R7.6）。X 窗口系统里，窗口是个相对抽象的概念，其包含的信息只是窗口的相关属性，并不涉及到具体的视觉呈现方面等的细节。这后一部分的工作，由专门的组件负责，称作窗口管理器（Window Manager）。</p>
<p>根据以上的基本信息，我们来了解一下最常用的两个桌面环境，KDE 和 GNOME。KDE 的全称是 K Desktop Environment，GNOME 的全称是 GNU Network Object Model Environment。KDE 使用到了称作 Qt 的图形开发库（目前为 Nokia 所有），它带的窗口管理器叫做 KWin，文件管理器实用工具是 Dolphin；GNOME 使用的图形开发库则是 GTK+，其自带窗口管理器的名字是 Metacity，文件管理器是 Nautilus。</p>
<p>除了上述的两个桌面环境，还有两个桌面环境也比较常用，分别是 Xfce 和 LXDE。相比较而言，它们要轻量级一些，因为它们在开发之时，选择了一个比较高的出发点，即使用和 GNOME 相同的底层图形开发库 GTK+（其实 Xfce 是后来才从 XForms 转过来的），在此基础上，提供了不同的窗口管理器以及少量应用。GTK+ 图形库（以及 Qt）的底层，是 X11 的开发库 Xlib，直接使用 Xlib 提供的 API 进行开发是一件繁冗的事情。</p>
<p>由于整个 X 体系架构的灵活性很强，因此除了像 KDE、GNOME、Xfce、LXDE 这样的图形化操作全面解决方案（即桌面环境）之外，还存在着大量单独的窗口管理器可供使用。比较常见的如 IceWM 和 FVWM 等等，还有现在风头很劲的 Enlightenment。后者基于的图形库也很有意思，并没有选择 GTK+ 或者 Qt，而是一个叫做 Elementary 的库。以 Enlightenment 为窗口管理器的 E17 桌面环境也正在成长。 与之情形类似的还有 OpenBox 这个窗口管理器。这些窗口管理器往往可以代替 KDE 或者 GNOME 自己的窗口管理器进行工作。我们还经常看到，有的 Linux 发行版的桌面有很多的显示特效，这个是由一个叫做 Compiz 的窗口管理器（它的更精准的分类是组合型窗口管理器，Compositing Window Manager，参见 http://en.wikipedia.org/wiki/Compositing_window_manager）做到的。它的独特之处在于，底层依赖于一个不同于传统的 X 环境：Xgl，更底层的绘图等操作则使用了称为 Glitz 的 OpenGL。</p>
<p>X 体系里还有个东西叫显示管理器（X Display Manager，如 XDM、gdm、wdm），可以对本地的或远程的多个 X Server 进行管理。Linux 系统如果以级别 5 启动，进入的用户登录界面就是显示管理器，用户输入用户名和密码后，它就会启动本地的 X Server，初始化一个 X 会话（X Session），一般还会通过 X 会话启动本地的窗口管理器和桌面环境。如果在配置文件里设置 xdmcp 为 true，显示管理器就可以通过 xdmcp 协议管理远程的 X Server。当在 Windows 机器上使用 X manager 登录 Linux/Unix 机器时，其实是用 xdmcp 协议登录的，用户同样输入用户名/密码登陆，xdm 启动一个 X 会话，不过这次的 X Server 是在远程（相对于 X Client 而言，也即 Windows 上）的机器上。X 显示管理器也有了新的发展，之前通常就是 XDM 或者基于 XDM 的变种，现在，一个叫做 LightDM 的跨桌面显示管理器正在试图成为 X 服务器的标准显示管理器。 </p>
<p>出于各种目的，有的人希望所使用的 Linux 的外观和 Windows 或者 Mac OS X 保持一致。这样做的初始目的可能是为了好玩，做到最后则往往是因为追求尽善尽美的信念。如果你希望桌面看起来像是 Windows，有一个叫做 Win2-7 Pack 的软件可以做到（应用于 GNOME），如果是想类似于 Mac OS X，则 Macbuntu 可以应用 Ubuntu 上。</p>
<p>很有意思的是，最近 *nix 上的变革有爆发的趋势，可能是科技发展到一定程度时的必然现象吧。连 X 窗口系统的权威地位，目前也受到了后来者的挑战和威胁。Wayland（http://wayland.freedesktop.org）就是这样的一个后来者。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2012/01/27/unixlinux-%e6%a1%8c%e9%9d%a2%e7%8e%af%e5%a2%83%e7%9b%b8%e5%85%b3%e7%9f%a5%e8%af%86%e5%a4%87%e5%bf%98/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>闲谈字符和字符集以及编码（下）</title>
		<link>https://www.somedoc.net/2011/12/08/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8b%ef%bc%89/</link>
					<comments>https://www.somedoc.net/2011/12/08/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8b%ef%bc%89/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Thu, 08 Dec 2011 09:56:04 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2633</guid>

					<description><![CDATA[在本文的上篇中，大致介绍了一下字符集是个什么东西，以及与汉字 <a href="https://www.somedoc.net/2011/12/08/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8b%ef%bc%89/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>在本文的上篇中，大致介绍了一下字符集是个什么东西，以及与汉字相关的几个字符集的发展历程，在接下来的这个部分里，我计划谈一谈上篇文末所提到的那两个东西，一个是 UCS，一个是 UTF。</p>
<p>需要提前说明的是，有关 Unicode 的信息在网上的散布很零散，有的信息被广泛传播但是已经过时，甚至有的文中都没有表明该文撰写的时间点，比较权威的信息源，例如 Wikipedia，对于某些词条的解释也是模糊不清甚至互相冲突的。因此，我这本部分中所写的，将会是网络信息与我自己个人知识的杂糅，而且出于题中的“闲谈”二字，某些信息的出处来源我也不会完全标明。</p>
<p>UCS，是 Universal Character Set 的首字母缩写，还有一种说法是，它是 Unicode Character Set 的首字母缩写。可奇怪的是，据说 Unicode 的正式名称是 Universal multiple-octet coded character set，也可以写作 UCS。到这里，笔者已经差不多晕了，敬请方家指正。我个人倾向于前者，因为从各方面的信息来看，UCS 都是一个和 ISO10646 关系紧密的术语，很少有人在 Unicode 相关的上下文中使用，即使有，也可能是一些写作不是那么严谨的文章的误用。</p>
<p>UCS 有两种，UCS-2 和 UCS-4。用两个字节来对 UCS 字符进行编码的方案，称之为 UCS-2，同理，用四个字节对 UCS 字符进行编码的方案，称之为 UCS-4。UCS 的这两种编码都是定长的，也就是说，只要给定字符的个数，那么用以编码这些字符的字节数量就已经确定，无非是乘以二还是四的区别。在 UCS 里，只可能存在没有被指定的代码点（即对某个代码点没有赋予确定的字符），而不存在不能使用的代码点。通过这些可以知道，UCS-2 最多只能表示 65536 个字符。<br />
在我的印象里，最开始在 Windows 下接触 Unicode 就是其号称有 65536 个码位，几乎可以容纳世界上所有语言的字符。这个认识一直深深地烙印在我的大脑里，甚至在后来，明明知道光是汉字的个数就已经超过了 65536 而达到了 7 万多，也没有能导致我去质疑两个字节已经不够对所有的 Unicode 字符进行编码了。不过从我刚才找到的 Unicode 1.1 的相关信息来看，代码点只定义到了 U+FFFD，还没有脱出 65536 的范围，两个字节在当时的确是够用的（从而可知，我的认知错误在于没有能够与时俱进，不清楚后来 Unicode 的发展）。</p>
<p>如果你只对某个字符在 Unicode 字符集中的代码点有兴趣，那显然不需要考虑这个代码点的数值究竟用多少个字节来表示，可当你要落实到计算机程序上，要对之进行存储或者传输时，那么一个代码点到底要占用多大的空间就必须要确定下来了。正好比在 C 语言中，给你一个数字 1，你是计划用一个 char 型的数据呢还是一个 short 的数据，或者是 int 甚至 long？在这个事情上，Unicode 规范中给出了三个官方的方案，分别是 UTF-8、UTF-16 和 UTF-32。UTF 这三个字母后面的数字是各个方案中最小编码单元占用的位数，除以 8 就是字节数了。也就是说，如果是 UTF-8，编码一个 Unicode 字符最少会用 1 个字节，UTF-16 和 UTF-32 则分别是 2 个、4 个。请注意，说的是最少，还没封顶呢。那么最大呢？在本文中限于篇幅，不再给出各个方案的具体编码规则，只说一下结论：UTF-8 编码，目前对一个字符最长是 3 个字节（当然也可能是 2 个），要是采用 UTF-16，则一个 Unicode 字符的编码可能是 2 个字节或者 4 个字节（不可能是 3 个），要是 UTF-32，那是定死的，就是 4 个字节。计算机基础扎实的读者在这儿应该马上就产生一个疑惑，因为会想到超过一个字节的数据在排列时一定会有字节序的问题。这是没错的，也因此，UTF-16 有两种形式，称之为 UTF-16LE 和 UTF-16BE，分别对应小端字节序和大端字节序，UTF-32 同理。如果你对字节序的问题有所困惑，请参阅拙文《字节那些事儿》。对于绝大部分的两个字节可以表示的字符，UTF-16 和 UCS-2 的编码是一样的，这个绝大部分如果用数字来说，是 96.9%。但 UTF-16 的优势在于，对于超出两个字节表示范围的字符，它也有办法来编码，只是增加了编码的长度而已，而 UCS-2 则就无能为力了。<br />
一旦表示一个字符已经达到了要动用 4 个字节的地步，和谐的面貌就开始出现了，UCS-4 和 UTF-32 成了一模一样的东西，没有任何差异，互为别名而已。ISO 组织和 Unicode 组织甚至在将来指定新的字符的代码点上也达成了一致，以保证相互兼容的长治久安局面。</p>
<p>最后顺便要提一下，在历史上还出现过一些其他的编码方案，例如 UTF-1，UTF-7（甚至还有 UTF-9 和 UTF-18，不过千万注意，这两个是被作为愚人节笑话发出来的，别像国内一些媒体一样上了这种当，当了真），这些东西，绝大部分已经不再鲜活了，除非有很大必要，是无需去做深入了解的，当然，如果谁喜欢把自己修炼成计算机历史学家，尽管去精研无妨。</p>
<p>在本文里还有两个和 UTF 有关的术语没有解释，一个是 BOM，全称 Byte Order Mark，即字节序标记，还有一个是 ZWNBSP，全称 Zero Width Non Breaking Space，是为零宽度非换行空白。有兴趣可以自行搜索，它们引发的歧义并不多，而且失之于过于琐细，所以不是本文的介绍对象。<br />
对于 UCS 和 UTF 的大致介绍就到这里了，这后一篇写得不算多，却费了很大的劲，因为斟酌哪些信息要放进来，哪些要舍弃着实不容易。希望能够达到我的小小初衷，能让看到此文的朋友在短时间内了解到这些相关概念、术语的概貌。若果如此，幸甚至哉。</p>
<p>参考资料<br />
1、http://www.elfdata.com/plugin/unicodefaqdata.html<br />
2、http://www.cl.cam.ac.uk/~mgk25/unicode.html<br />
3、http://en.wikipedia.org/wiki/UTF-8<br />
4、http://en.wikipedia.org/wiki/UTF-16<br />
5、http://en.wikipedia.org/wiki/UTF-32</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2011/12/08/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8b%ef%bc%89/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>闲谈字符和字符集以及编码（上）</title>
		<link>https://www.somedoc.net/2011/09/26/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8a%ef%bc%89/</link>
					<comments>https://www.somedoc.net/2011/09/26/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8a%ef%bc%89/#respond</comments>
		
		<dc:creator><![CDATA[张三太爷]]></dc:creator>
		<pubDate>Mon, 26 Sep 2011 02:50:55 +0000</pubDate>
				<category><![CDATA[[未分类]]]></category>
		<category><![CDATA[CSDN]]></category>
		<guid isPermaLink="false">http://www.somedoc.net/?p=2631</guid>

					<description><![CDATA[这几天在工作中接连有同事遇到和字符集、字符编码相关的问题，一 <a href="https://www.somedoc.net/2011/09/26/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8a%ef%bc%89/" class="more-link">[&#8230;]</a>]]></description>
										<content:encoded><![CDATA[<p>这几天在工作中接连有同事遇到和字符集、字符编码相关的问题，一个问题是在黑莓平台上葡萄牙语的资源文件在保存时正确选择编码的问题，一个是要支持汉字转拼音的问题；在解决问题之余，导致我有了写一篇比较详细的文章的打算。提纲写完后才发现不是那么好开展，比较棘手的一个问题是还没有找到一个令我满意的有关字符这一术语的通俗易懂而又精准的定义。我的初中老师对我说，概念不清，寸步难行，于是这个比较正式一些的文档只好稍后再进行。然而这不影响我写现在的这篇闲谈，在本文中我避开了这些术语的精准定义，而采用大家皆可心领神会的方法直接使用之。</p>
<p>在我看来，“字符”有本质和表示两层上的含义。其本质上，应该是人类文化中所在某个特定范围内共同认可具有一定含义或者作用的文字字元或者符号（文字其实也是符号，为了符合惯用法，分别列出）。在表示上，尤其是在计算机领域之内，应该是对具有其本质作用的个体进行编号。根据编号，再使用其他方式来规范统一其呈现（主要是视觉方面，当然也涉及听觉等其他方面，例如盲文还可能关系到触觉）。有 C 语言基础的朋友们对此应该毫不意外，char 其实就是 int，这在一定程度上透露出了，计算机认为字符就是个数字。正因为是这样，几乎所有的字符集在确定了所收录的字符的同时，也就确定了其中的单个元素所对应的数值，这个字符与数值的对应关系，称之为编码。一般来讲，一个字符集都只使用一种编码方案，因此，日常的文档中也会用编码方案的名称来指称对应的字符集。例如，GB2312 是一个以简体汉字为主的字符集的编码方案，同时也经常用来作为该字符集本身的名字。<br />
从发展过程来看，每一个字符集都是着重于处理当时现实中的迫切需求，即使有一定的前瞻性考虑，也并不很强，当然，这种情形也和当时的计算机应用状况密切关联，尤其是地域性，所以很多字符集都仅限于解决当地语言文字的输入输出和显示问题。从感性多一点的理解上，可以把字符集当成是一本字典所收录的字的总目录。由此，字符集有几个和字典类似的特性，1、字符集之间可能是包容关系，例如 GB2312 和 GBK 的关系，就像你的中学生英汉辞典与朗文辞典的关系；2、字符集之间也可能毫无关系，比如 GB2312 和某个只包括阿拉伯语字符的字符集，就像是新华字典和朗文辞典的关系；3、不存在收录了世间所有字符的完全字符集，只有当前收录字符最全的字符集。</p>
<p>与汉字相关的字符集，我们经常听到的有 GB2312、BIG5、GBK、UNICODE 等。GB2312 的制定时间很早，是在 1980 年，由于“历史局限性”，此字符集内收录的字符很少，汉字总共收录了 6763 个，而且全部都是大陆通用的简体汉字。GB，是“国标”二字的汉语拼音首字母，也因此 GB2312 的发音应该是国标 2312。在此标准肆虐横行的同时，我们的港澳台同胞，也制定了一个适用于当地繁体汉字需要的字符集，其对应编码名为 BIG5，也有人称之为大五码。</p>
<p>此处会引入一个比较有趣的问题，大多数搞不清楚字符集之间的区别与联系的人都是因为在这里没弄明白。按道理讲，各个字符集你用你的编码，我用我的编码，井水不犯河水，在哪个字符集的编码范围之内，就显示该字符集内对应的字符，不就完了吗？怎么常常会出现非要用户指定一个字符集才能正确显示内容，使用别的字符集查看就是乱码呢？这是因为，字符集之间的字符尽管可以认为没有交集（不要从字符本身所具有的人类可理解的含义方面考虑。比如一个汉字“阿”，即使没有在简体和繁体间存在字形上的差别，我们也可以认为它在简体中文的字符集和繁体中文的字符集中是不同的字），但其所使用的编码互相是有重叠的。举例来说，简体汉字字符集 GB2312 中的第一个汉字“啊”，其编码为 B0A1，而在 BIG5 中，相同编码的汉字为“陛”。这还是你的运气好，在另外的字符集中，很显然就不一定会是一个你可以识别的字符了。</p>
<p>1993 年，Unicode 1.1 标准发布，其中收录了中国大陆、台湾、日本、韩国所使用的字符（这些字符的集合又被称为 CJK） 20902 个，紧接着，中国将该标准正式采用为国家标准，是为 GB13000.1。同年，随着计算机文字处理需求的发展，又对 GB2312 进行了扩展，收录了更多的字符。这个扩展后的规范（请注意，当时还不是标准）被称为 GBK，K 就是“扩”的拼音首字母。具体的需求示例之一，就是如果不扩展的话，当时的总理朱镕基的姓名中的“镕”字将无法处理。在 GBK 中，收录的汉字字符个数大幅增加，BIG5 中的所有字符更是无一遗漏（当然，字符对应的编码变了）。GB2312 中已经收录的字符其编码则保持不变，因此，GBK 不止在收录的字符数量上是 GB2312 的超集，在字符编码的技术实现方案上也兼容了原来的 GB2312。GBK 被微软公司实现到了 Windows 95 和 Windows NT 3.51 中，对应的代码页为 936。由于微软操作系统的广泛使用，导致 GBK 在当时成为了事实标准。到 1995 年，中国国家信息技术标准化技术委员会才将 GBK 敲定，并确定了版本号 1.0。GBK 1.0 对微软实现的 936 代码页做了轻微的修订，向其中新增了 95 个字符，这些字符在 GB13000.1 中是没有的，于是被临时指定到了 Unicode 中的私用区（PUA）中。特别指出，CJK 中的所有字符，GBK 中都是有的。后来，微软又向 936 代码页中增加了欧元符号，但指定的编码（0x80）在 GBK 中是一个不合法的值（准确术语为代码点，即 code point）。</p>
<p>到了 2000 年，GB18030 国标发布。在保持兼容的前提下对 GBK 进行了扩充。GB18030 的字符数目大大增加，以至于两个字节已经无法满足编码要求，于是将编码一个字符所需的可能字节数上限提到了 4。而其中的由一个字节或者两个字节可以表示的这部分字符，有时也被称做 GBK。当然，这个 GBK 中的字符在 Unicode 中的对应关系与前文提到的 GBK 1.0 略有差异，因为那 95 个字符已经随着 Unicode 本身的发展，部分被收录到了 Unicode 中，而不必再停留在 PUA 中。</p>
<p>不知不觉写了不少了，本来还要写 UCS 和 UTF 相关的内容的，留待下次吧。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.somedoc.net/2011/09/26/%e9%97%b2%e8%b0%88%e5%ad%97%e7%ac%a6%e5%92%8c%e5%ad%97%e7%ac%a6%e9%9b%86%e4%bb%a5%e5%8f%8a%e7%bc%96%e7%a0%81%ef%bc%88%e4%b8%8a%ef%bc%89/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
