﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-溪流漫话-随笔分类-Windows</title><link>http://www.cppblog.com/Streamlet/category/12149.html</link><description>荒废中，求警醒~</description><language>zh-cn</language><lastBuildDate>Sat, 05 Nov 2022 18:05:22 GMT</lastBuildDate><pubDate>Sat, 05 Nov 2022 18:05:22 GMT</pubDate><ttl>60</ttl><item><title>建立一个简单干净的 gn+ninja 工具链</title><link>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sat, 05 Nov 2022 18:05:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/229483.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/229483.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/229483.html</trackback:ping><description><![CDATA[
<h2>背景</h2>
<p>事情的起因是，想找个跨 Windows 和 Mac 的构建方案。第一考虑自然是 CMake，毕竟基本上是事实标准了。</p>
<p>但是研究了一下 Modern CMake，也就是以 target 为核心的理念。但发现看了好几天文档，也折腾出了可用的东西，但仍然是没梳理清楚什么理念、原理。然后 CMake 本身语法就很复杂，再加上搞 target 一套概念，要给 target 设置各种属性之类的，有点强行 OOP 的感觉……但其实我们只是需要一个 include_dir 和 lib_dir 而已，其他都是浮云~</p>
<p>但如果退回到传统模式，不用 Modern 概念呢，好像可以将就，但第一不去用一个工具的最新模式，好像有点不上进的感觉（python 2 除外）；第二，CMake 的两大痛点——语法特立独行、文档晦涩难懂——还是让人有点不爽。</p>
<p>那跳出来看别的选择呢？目前相对成熟的也只有 Google 的 gn+ninja 方案了。gn 这套东西在 Chromium 里是完全配置好的，用起来还算顺手，但要是独立拿出来呢，就没那么便宜了。关键是它的 toolchain 是要自己定义的。</p>
<p>之前还在公司搞客户端的时候，大家就从 Chromium 里面把 build、build_overrides 等等东西全部拷出来，好家伙，几百 MB 甚至上 G。但是公司里嘛，没人管干不干净，怎么快怎么来。后来又看到 Google 自家的 Crashpad 里面也用了这套构建，但工具链被简化了一下，叫 mini_chromium。这个比 Chromium 里的小多了，是可以拿过来直接用的，缺少一些配置可以自己加。但是呢，像我们这种洁癖，仍然是受不鸟的。所以呢，我们要干干净净的建立一套工具链。</p>
<h2>构建系统安装</h2>
<p>首先，我们明确定位。gn 和 ninja 都是开发机上需要预装的，不是软件提供的。Chromium 的搞法是自己提供，gn 的文档也说让开发者提供工具。但这套思路跟传统的理念是冲突的。同时，自己安装工具成本是比较低的：</p>
<ul>
<li>linux
<ul>
<li>ninja 在主流包管理系统里已经有了，包名可能是 ninja 或 ninja-build，直接安装就可以</li>
<li>gn 在部分包管理系统有，尝试包名 gn 或 gn-build 等，没有的话可以下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/linux-amd64/+/latest">二进制版本</a>，或者从源代码自行编译</li>
</ul>
</li>
<li>mac
<ul>
<li>ninja 在 brew 里包名叫 ninja，在 MacPorts 里包名叫 ninja-build</li>
<li>gn 在 brew 里没有，可以下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/mac-amd64/+/latest">二进制版本</a>；在 MacPorts 里叫 gn-devel</li>
</ul>
</li>
<li>win
<ul>
<li>ninja 可以在 <a href="https://github.com/ninja-build/ninja/releases">GitHub</a> 下载二进制版本，gn 可以在 gn 官网下载<a href="https://chrome-infra-packages.appspot.com/dl/gn/gn/windows-amd64/+/latest">二进制版本</a></li>
</ul>
</li>
</ul>
<p>自己下载的设置到 PATH，测试 <code>gn --version</code> 以及 <code>ninja --version</code>，能运行即可</p>
<h2>目标</h2>
<p>希望做到提供一个 git repo，使用者 clone 到自己项目的 build 目录，然后使用者只要在 .gn 文件里配置</p>
<pre><code class="language-gn">buildconfig = &quot;//build/BUILDCONFIG.gn&quot;
</code></pre>
<p>就可以使用我们提供的工具链，在 PC 三端进行构建。</p>
<p>使用者的唯一负担就是编写自己的 BUILD.gn</p>
<h2>工具链搭建</h2>
<p>首先我们看 gn 的文档，以及它的例程 simple_build 里的工具链配置：</p>
<p>https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn</p>
<p>这个是可以直接用的，只不过只有 linux 端（当然 mac 也能用）。我们再结合 chrome 里的工具链配置，进行一些完善。</p>
<h3>基础概念</h3>
<p>首先我们了解 gn 体系需要的最小配置是什么。</p>
<p>第一，它需要在根目录写一个 .gn 文件，在里面定义 buildconfig，指到另一个文件，一般是</p>
<pre><code class="language-gn">buildconfig = &quot;//build/BUILDCONFIG.gn&quot;
</code></pre>
<p>第二、BUILDCONFIG.gn 里面需要设置默认工具链，也就是写一行</p>
<pre><code class="language-gn">set_default_toolchain(&quot;//build/toolchain:gcc&quot;)
</code></pre>
<p>第三、定义工具链，如上例的 //build/toolchain:gcc。</p>
<p>需要在 build/toolchain 下建立 BUILD.gn 文件，内容是</p>
<pre><code>toolchain(&quot;gcc&quot;) {
	# ...
}
</code></pre>
<p>最后在 toolchain 里定义各种 tool。</p>
<h3>工具链中的工具</h3>
<p>这部分文档在这里：https://gn.googlesource.com/gn/+/main/docs/reference.md#func_tool</p>
<p>简单复述一下，可被定义的工具有：</p>
<ul>
<li>编译工具:<br />
&quot;cc&quot;: C 编译器<br />
&quot;cxx&quot;: C++ 编译器<br />
&quot;cxx_module&quot;: 支持 module 的 C++ 编译器<br />
&quot;objc&quot;: Objective C 编译器<br />
&quot;objcxx&quot;: Objective C++ 编译器<br />
&quot;rc&quot;: Windows 资源脚本编译器<br />
&quot;asm&quot;: 汇编器<br />
&quot;swift&quot;: Swift 编译器</li>
<li>链接工具:<br />
&quot;alink&quot;: 静态库链接器<br />
&quot;solink&quot;: 动态库链接器<br />
&quot;link&quot;: 可执行文件链接器</li>
</ul>
<p>（其他的就先不看了）</p>
<p>我们来看一下 https://gn.googlesource.com/gn/+/HEAD/examples/simple_build/build/toolchain/BUILD.gn 的一些关键配置：</p>
<pre><code class="language-gn">toolchain(&quot;gcc&quot;) {
  tool(&quot;cc&quot;) {
    command = &quot;gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}&quot;
    outputs = [ &quot;{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o&quot; ]
    # ...
  }
  tool(&quot;cxx&quot;) {
    command = &quot;g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}&quot;
    outputs = [ &quot;{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o&quot; ]
    # ...
  }
  tool(&quot;alink&quot;) {
    command = &quot;rm -f {{output}} &amp;&amp; ar rcs {{output}} {{inputs}}&quot;
    outputs = [ &quot;{{target_out_dir}}/{{target_output_name}}{{output_extension}}&quot; ]
    # ...
  }
  tool(&quot;solink&quot;) {
    command = &quot;g++ -shared {{ldflags}} -o $sofile $os_specific_option @$rspfile&quot;
    outputs = [ sofile ]
    # ...
  }
  tool(&quot;link&quot;) {
    command = &quot;g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}&quot;
    outputs = [ outfile ]
    # ...
  }
  tool(&quot;stamp&quot;) {
    command = &quot;touch {{output}}&quot;
  }
  tool(&quot;copy&quot;) {
    command = &quot;cp -af {{source}} {{output}}&quot;
  }
}
</code></pre>
<p>可以看到，cc 和 cxx 执行 command 后，生成 .o 文件，然后这些 .o 文件可以作为 alink、solink、link 的 inputs，被它们 command 继续使用，最后输出静态库、动态库以及可执行文件。</p>
<p>其余属性可以查文档了解含义。</p>
<h3>对比 Chromium 中的配置</h3>
<h4>Linux</h4>
<p>主要配置在这里：https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/gcc_toolchain.gni</p>
<p>也是 gcc 的，与 simple_build 里的大同小异，没有特别的。</p>
<h4>Mac</h4>
<p>主要配置在这里：https://source.chromium.org/chromium/chromium/src/+/main:build/toolchain/apple/toolchain.gni</p>
<p>区别有：</p>
<ul>
<li>用 clang 系列编译工具，而不是 gcc</li>
<li>alink 不是用 ar，而使用 libtool</li>
<li>solink 的默认扩展名改成了 dylib</li>
<li>用了一个 linker_driver.py 来支持生成 dSYM，在里面调用了 dsymutil 和 strip</li>
</ul>
<h4>Win</h4>
<ul>
<li>编译用 cl，静态库链接用 lib，动态库和可执行文件的链接用 link</li>
<li>lib_switch = &quot;&quot;，lib_dir_switch = &quot;/LIBPATH:&quot;；前两者 lib_switch = &quot;-l&quot;，lib_dir_switch = &quot;-L&quot;</li>
</ul>
<h3>建立我们的工具链</h3>
<p>基本上是根据上面分析的要点配置，最终结果在此：https://github.com/Streamlet/gn_toolchain</p>
<p>新增的一些差异有：</p>
<ul>
<li>
<p>增加全局参数 is_debug，可以在 <code>gn gen out --args=&quot;is_debug=true&quot;</code>传入。默认 is_debug 为 false，开启所有优化。</p>
</li>
<li>
<p>mac 下生成 dSYM 不使用 python 脚本，直接是 <code>$ld ... &amp;&amp; dsymutil ... &amp;&amp; strip</code></p>
</li>
<li>
<p>mac 下加了一个 template：app_bundle，用来生成 xxx.app，主要配置来自于 create_bundle 文档里的例子</p>
</li>
<li>
<p>win 下增加了一些配置集</p>
<ul>
<li>
<p>动态/静态链接 CRT：//build/config/win:console_subsystem、//build/config/win:static_runtime</p>
</li>
<li>
<p>控制台程序、Win32 程序：//build/config/win:console_subsystem、//build/config/win:windows_subsystem</p>
<p>这个其实一般情况下用不着，只要入口函数是 main/WinMain，link 默认就是控制台程序/Win32 程序</p>
</li>
<li>
<p>XP 支持：//build/config/win:console_subsystem_xp、//build/config/win:windows_subsystem_xp</p>
<p>具体实现是链接参数加 /SUBSYSTEM:CONSOLE,5.01 或 /SUBSYSTEM:WINDOWS,5.01。关键是后面的版本号 5.01，为了加版本号则必须指定子系统名称，所以分了 console_subsystem_xp 和 windows_subsystem_xp。又，xp 这里提供了两个 subsystem 的配置集，非 xp 也提供两个。</p>
<p>但是我们没有加 _WIN32_WINNT=0x0501、WINVER=0x0501、_USING_V110_SDK71_，也没有指定必须使用 7.0 版本的 SDK，这些都是非必须的，只要不用到 XP 以后添加的 API 即可。使用者可以在自己的 target 里面定义这些宏。</p>
</li>
</ul>
</li>
</ul>
<h2>使用案例</h2>
<p>提供一个使用案例：https://github.com/Streamlet/gn_toolchain_sample</p>
<p>因为它以 git submodule 形式引用了 https://github.com/Streamlet/gn_toolchain，所以 git clone 以后，需要 <code>git submodule update --init</code>一下。</p>
<p>然后在根目录执行：（确保 gn 和 ninja 已经在 PATH 中）</p>
<pre><code class="language-sh">gn gen out
ninja -C out
</code></pre>
<p>即可。</p>
<p>Mac 下会额外生成一个 objc 项目 objc_console_application 以及一个 app_bundle：ns_application.app。</p>
<p>Win 会额外生成一个 Win32 项目 win32_application。</p>
<p>Win 下需要先执行一下 Visual Studio 带的命令行环境，如 VS 2022 Community 的 “x64 Native Tools Command Prompt for VS 2022”，cl 等工具才会可用。</p>
<p>如果要测试 XP（32位），用“x86 Native Tools Command Prompt for VS 2022”进入，CD 到项目目录，执行：</p>
<pre><code class="language-gn">gn gen out --args=&quot;target_cpu=\&quot;x86\&quot;&quot;
ninja -C out
</code></pre>
<h2>总结</h2>
<p>我们只用几十 KB 的大小完成了跨端支持，是很轻量的一个配置。如果您觉得实用并认可这种方式，欢迎一起来维护、完善。</p>
<img src ="http://www.cppblog.com/Streamlet/aggbug/229483.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2022-11-06 02:05 <a href="http://www.cppblog.com/Streamlet/archive/2022/11/06/229483.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>微软拼音长句模式恢复工具支持Win10 1803</title><link>http://www.cppblog.com/Streamlet/archive/2020/09/20/217462.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 20 Sep 2020 05:53:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2020/09/20/217462.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/217462.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2020/09/20/217462.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/217462.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/217462.html</trackback:ping><description><![CDATA[<p>4月份就有人留言旧微软拼音恢复工具不支持Win10 1803了，我自己也遇到了，但因为没时间搞，勉为其难使用了词组模式的微软拼音几个月，终于在八月份抽个空研究了下，解决了。</p> <p>这次是因为傻逼大微软改了 System32\IME\shared 里的东西，导致旧系统拷过来的文件与 System32\IME\shared 的东西不兼容了。解决方式很暴力：从以前的版本复制 System32\IME\shared 过来。</p> <p>下载：<a href="https://www.streamlet.org/software/mspyforever/">https://www.streamlet.org/software/mspyforever/</a></p> <p>（原发于 GitHub Pages，2018-10-13 13:36:04）</p><img src ="http://www.cppblog.com/Streamlet/aggbug/217462.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2020-09-20 13:53 <a href="http://www.cppblog.com/Streamlet/archive/2020/09/20/217462.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>问一个 Windows 窗口的 Capture 问题</title><link>http://www.cppblog.com/Streamlet/archive/2014/12/31/209367.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Wed, 31 Dec 2014 08:03:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2014/12/31/209367.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/209367.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2014/12/31/209367.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/209367.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/209367.html</trackback:ping><description><![CDATA[<p>好久没写了，上来先问一个问题。。。羞射。。。</p> <p>&nbsp;</p> <p>有 A、B 两个窗口，A 是 B 的 Owner，B 不激活不抢焦点。在 B 的 WM_LBUTTONDOWN 的时候，设置 A 窗口为 Capture；在&nbsp; A 的 WM_LBUTTONUP 的时候 ReleaseCapture。</p> <p>操作是，在 B 上按下鼠标，然后一直按住鼠标飘啊飘。在某一时机把 B 干掉，继续动鼠标。此时 A 还在，并且 A 仍然是 Capture 状态，但是 A 收不到 A 可视范围外的 WM_MOUSEMOVE 了！在全过程中 A 也没有收到 WM_CAPTURECHANGED。</p> <p>&nbsp;</p> <p>下面是栗子：</p> <p><a title="http://pan.baidu.com/s/1c06dhss" href="http://pan.baidu.com/s/1c06dhss" target="_blank">http://pan.baidu.com/s/1mgurJIS</a></p> <p>&nbsp;</p> <p>求解释。求解决。谢谢~！</p><img src ="http://www.cppblog.com/Streamlet/aggbug/209367.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2014-12-31 16:03 <a href="http://www.cppblog.com/Streamlet/archive/2014/12/31/209367.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让 Win8.1 微软拼音新体验模式支持 Metro 应用</title><link>http://www.cppblog.com/Streamlet/archive/2014/04/20/206654.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 20 Apr 2014 03:22:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2014/04/20/206654.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/206654.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2014/04/20/206654.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/206654.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/206654.html</trackback:ping><description><![CDATA[<p>内容回顾：</p> <p>[1]<a href="http://www.cppblog.com/Streamlet/archive/2012/08/25/188249.html" target="_blank">十个步骤找回 Win8 中的微软拼音新体验模式</a></p> <p>[2]<a href="http://www.cppblog.com/Streamlet/archive/2014/03/26/206335.html" target="_blank">发布个工具，一键恢复Win8/8.1中的微软拼音长句模式（新体验模式）</a></p> <p>在 [1] 中，我们找回了 Win8 里的微软拼音新体验模式；在 [2] 中，依照 <a href="http://epigchina.wordpress.com/2013/11/06/mspy-in-windows-8-1/" target="_blank">ePig 提供的方法</a>，我们在 Win8.1 中也搬回了微软拼音新体验模式，但留下了一点遗憾，不支持 Metro 应用。</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_2.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_thumb.png" width="900" height="600"></a></p> <p>（其实还有一点，没有显示图标。）</p> <p>原本我觉得无所谓，因为我反正不太用 Metro 应用，Metro 下就用自带的微软拼音简捷模式好了。可是，可是，输入法管理器中，比如保留一个兼容 Metro 的输入法，导致桌面下也比如保留简捷模式了，这样切换输入法经常切错，离所谓“一个输入法”的伟大目标相差甚远。于是乎，不能忍。然后昨天起来看了下这个问题。</p> <p>原先微软拼音输入法的一个不知什么的GUID是 {81d4e9c9-1d3b-41bc-9e6c-4b40bf79e35e}，该 GUID 代表的输入法下有两个输入模式，新体验和简捷。（这个构架和相关内容我不是很懂，具体请去查阅 TSF (Text Service Framework) 框架相关文档。） Win 8 下只是删除了新体验模式的那个注册表项并对注册表项加权限保护达到禁用目的。Win8.1 下的变化是，老的文件被删除了，且 {81d4e9c9-1d3b-41bc-9e6c-4b40bf79e35e} 被用作新的微软拼音的 GUID 了，里面也不再分两个模式了（其实只有原先的简捷模式）。按照 <a href="http://epigchina.wordpress.com/2013/11/06/mspy-in-windows-8-1/" target="_blank">ePig 提供的方法</a>，从 Win8 中复制相关文件，并把文件中的 GUID 改了，重新注册进去。这边我在一键恢复工具所带的文件中是把 GUID 末尾 e 改为了 f，完整的就是 {81D4E9C9-1D3B-41BC-9E6C-4B40BF79E35F}。按道理应该完全兼容才是，起码 Win8 下是支持 Metro 的。微软拼音很早就是 TSF 框架了（而不是 Imm），据我之前的了解，要支持 Metro，输入法使用 TSF 框架就好了。</p> <p>想不通的情况下，就来比较下 Win8.1 下自带的微软拼音（简捷模式）和我们新加的微软拼音新体验模式的注册表项差异：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_4.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_thumb_1.png" width="824" height="724"></a></p> <p>发现自带的多了几项（红框部分）。于是仿照着把这些补上去，结果确实不显示“仅适用于桌面版”了，可是好像也没法输入了……于是每次只加一项，依次试过去（穷举法，鄙视）。最后发现前两个 ，也就是 {13A016DF-560B-46CD-947A-4C3AF1E0E35D}、{25504FB4-7BAB-4BC1-9C69-CF81890F0EF5} 可以要，最后 {74769ee9-4a66-4f9d-90d6-bf8b7c3eb461} 不能要。</p> <p>再看下 Win8 的，果然也是有这两个 Category 的：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_6.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_thumb_2.png" width="412" height="601"></a></p> <p>然后这事情就算成了：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_8.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_thumb_3.png" width="900" height="600"></a></p> <p>一键工具已更新，下载页面：<a title="http://www.streamlet.org/Software/MSPYForever/" href="http://www.streamlet.org/Software/MSPYForever/" target="_blank">http://www.streamlet.org/Software/MSPYForever/</a></p> <p>那么这两个 GUID 到底是什么呢？Google 一下……</p> <p>关于前者，<a title="http://msdn.microsoft.com/zh-cn/library/windows/apps/hh967425.aspx#set_compatibility_flag" href="http://msdn.microsoft.com/zh-cn/library/windows/apps/hh967425.aspx#set_compatibility_flag">http://msdn.microsoft.com/zh-cn/library/windows/apps/hh967425.aspx#set_compatibility_flag</a> 中有说明如下：</p> <p><a></a><a></a><em>声明兼容性</em> <p><em>IME 通过使用 ITfCategoryMgr::RegisterCategory 为其 IME 注册类别 GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT 来声明其兼容 Windows 应用商店应用。</em> <p><em></em> <p>其中 GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT 就是 {13A016DF-560B-46CD-947A-4C3AF1E0E35D}。</p> <p>{25504FB4-7BAB-4BC1-9C69-CF81890F0EF5} 也是个预定义的值，叫做 GUID_TFCAT_TIPCAP_SYSTRAYSUPPORT，按字面理解，系统托盘支持，可是查不到官方说明啊，<a title="http://msdn.microsoft.com/en-us/library/ms629012.aspx" href="http://msdn.microsoft.com/en-us/library/ms629012.aspx" target="_blank">http://msdn.microsoft.com/en-us/library/ms629012.aspx</a> 这一页啥也不说，是不是bug……</p> <p>&nbsp;</p> <p>不管怎么说，问题是解决了。大快人心。</p> <p>&nbsp;</p> <p>再顺便黑下大微软。</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/%E6%97%A0%E6%A0%87%E9%A2%98_2.png"><img title="无标题" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="无标题" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/%E6%97%A0%E6%A0%87%E9%A2%98_thumb.png" width="360" height="78"></a></p> <p>看这张图，我在写代码或写Word或者写别的文字，开着中文语言下的中文输入法（写代码怎么会开中文？不要在意这些细节……），然后来了条 QQ 消息。我去任务栏点企鹅图标。结果刚点下，因为任务栏、桌面是英文语言下的美式键盘，输入法指示器变为 ENG，缩短了，企鹅图标跑右边去了，点空了！！！</p> <p>正因为如此，我以前总是把英文语言删掉，只有中文语言，下面一个输入法，靠 Shift 在输入法内切换中英文。可是 Shift 很容易误操作有木有。最佳的方式还是中文语言下的美式键盘……这个问题很早之前研究过几个小时，但没啥结果，后来想，如果真的没办法了，干脆写一个空的输入法原样输入输出，叫做“美式键盘”得了。为啥中文下就不给键盘布局呢？为什么呢为什么呢为什么呢？</p> <p>昨天又去网上搜了下，结果高人已经给出了方法了，不知道哪篇是原创，就不引用了，有兴趣的自己搜。这里还有个半官方的出处：<a title="http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/windows-8%E7%9A%84%E8%BE%93%E5%85%A5%E6%B3%95/91917117-c5fd-4c61-ac2e-da0dd29d12fc" href="http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/windows-8%E7%9A%84%E8%BE%93%E5%85%A5%E6%B3%95/91917117-c5fd-4c61-ac2e-da0dd29d12fc" target="_blank">http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/windows-8%E7%9A%84%E8%BE%93%E5%85%A5%E6%B3%95/91917117-c5fd-4c61-ac2e-da0dd29d12fc</a>。但其实美式键盘没有图标，会显示“简体”，所以也会有长短变化。最佳的方法还是恢复成语言栏的样子：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_10.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/Win8.1Metro_8E42/image_thumb_4.png" width="369" height="148"></a></p> <p>这下同在中文语言下了，Ctrl+Shift 切输入法，怎一个舒服了得~！至此，Win8、Win8.1 的输入法问题对我个人而言已经全部解决了，可以大规模重装系统了。</p> <p>忍不住再黑下大微软：<a title="http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/%E5%85%B3%E4%BA%8E%E4%B8%AD%E6%96%87%E7%89%88windo/b5c3190f-bb82-4855-97f0-00d6011e3e33" href="http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/%E5%85%B3%E4%BA%8E%E4%B8%AD%E6%96%87%E7%89%88windo/b5c3190f-bb82-4855-97f0-00d6011e3e33" target="_blank">http://answers.microsoft.com/zh-hans/windows/forum/windows_8-ime/%E5%85%B3%E4%BA%8E%E4%B8%AD%E6%96%87%E7%89%88windo/b5c3190f-bb82-4855-97f0-00d6011e3e33</a>，这个答非所问，是不是在搞笑？</p><img src ="http://www.cppblog.com/Streamlet/aggbug/206654.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2014-04-20 11:22 <a href="http://www.cppblog.com/Streamlet/archive/2014/04/20/206654.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>发布个工具，一键恢复Win8/8.1中的微软拼音长句模式（新体验模式）</title><link>http://www.cppblog.com/Streamlet/archive/2014/03/26/206335.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 25 Mar 2014 17:10:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2014/03/26/206335.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/206335.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2014/03/26/206335.html#Feedback</comments><slash:comments>24</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/206335.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/206335.html</trackback:ping><description><![CDATA[<p align="center">（cnBeta：<a title="http://www.cnbeta.com/articles/277936.htm" href="http://www.cnbeta.com/articles/277936.htm" target="_blank">http://www.cnbeta.com/articles/277936.htm</a>）</p> <p>首先贴个图，大家来一起念台词~</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/880134ac1d58_D1E/MSPYForever_2.png"><img title="MSPYForever" 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="MSPYForever" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/880134ac1d58_D1E/MSPYForever_thumb.png" width="600" height="400"></a></p> <p>&nbsp;</p> <p>念完了木有？很激情澎湃义愤填膺有木有？</p> <p>&nbsp;</p> <p>这事情最早追溯到前年 8 月的一篇文章《<a href="http://www.cppblog.com/Streamlet/archive/2014/02/17/188249.html" target="_blank">十个步骤找回 Win8 中的微软拼音新体验模式</a>》，其实就是手工注册一个COM完事，只是傻逼大微软刻意弄了注册表权限来屏蔽，操作起来略微繁琐。到目前为止，Win8重装系统已经不下十次了，每次都是这样手工操作，我已经厌倦了。</p> <p>另外还有Win8.1上的问题，由于傻逼大微软已经完全删除了文件，就没法这么搞了，就算从Win8拷过文件来，也无法简单注册使用。加上我对8.1非常非常不感冒，一直没去研究。前些天看到之前的那篇文章里 Charles Leigh 回复了两篇文章（<a href="http://epigchina.wordpress.com/2013/11/06/mspy-in-windows-8-1/" target="_blank">ePig 那篇</a>是原创吧貌似，感谢），提供了解决方案。于是上个周末到现在就捣鼓个一键恢复工具，方便自己以后重装用，也方便广大微拼党。</p> <p>微拼党（包括我）孜孜不倦的追求微拼长句模式的情怀，让我非常感动。希望傻逼大微软看到我们的心声。别搞什么破词组输入法了，你搞不过本土这么多厂家的，你的下限也没有本土厂家低，唯一的优势可能就是没广告没弹窗了吧。至于输入算法什么的，在词组模式里面根本不足以体现得太多，长句模式才是考验啊。回头吧！</p> <p>下载页面：<a title="http://www.streamlet.org/Software/MSPYForever/" href="http://www.streamlet.org/Software/MSPYForever/" target="_blank">http://www.streamlet.org/Software/MSPYForever/</a></p> <p>CodePlex 项目页面：<a title="https://mspyforever.codeplex.com/" href="https://mspyforever.codeplex.com/" target="_blank">https://mspyforever.codeplex.com/</a></p> <p>&nbsp;</p> <p>请微拼党们多传播。有 Bug 及时反馈。</p><img src ="http://www.cppblog.com/Streamlet/aggbug/206335.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2014-03-26 01:10 <a href="http://www.cppblog.com/Streamlet/archive/2014/03/26/206335.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>将 Timer 对象化</title><link>http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Mon, 24 Jun 2013 16:18:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/201279.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/201279.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/201279.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Timer这玩意儿很常用，却又很烦人。烦人之处有四： 1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果将其设到HWND上，则 a)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 必须手工维护Timer的ID，小心翼翼地保证这些ID不重复，可能有人（比如我）就不怎么喜欢手工维护硬编码的ID。 ...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/201279.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2013-06-25 00:18 <a href="http://www.cppblog.com/Streamlet/archive/2013/06/25/201279.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于&amp;ldquo;UI线程&amp;rdquo;</title><link>http://www.cppblog.com/Streamlet/archive/2013/05/05/199999.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 05 May 2013 14:43:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2013/05/05/199999.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/199999.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2013/05/05/199999.html#Feedback</comments><slash:comments>21</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/199999.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/199999.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 缘起 这是一篇找喷的文章。 &nbsp; 由于一些历史原因和人际渊源，周围同事谈论一些技术话题的时候，经常使用“UI线程”一词。虽然我从来没有看到其确切定义，但心里对其含义可能略懂，因此一直装作心知肚明的样子（以免被嘲讽）。 &nbsp; 日前，一同事发了封邮件大谈“UI线程”的概念，分享到大部门。大部门里除了我们一个Windows客户端部门，其他都是做网站的Java开发。因此，在他们面前谈论一些...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2013/05/05/199999.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/199999.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2013-05-05 22:43 <a href="http://www.cppblog.com/Streamlet/archive/2013/05/05/199999.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UpdateLayeredWindow在远程协助下失败的问题</title><link>http://www.cppblog.com/Streamlet/archive/2013/01/18/197382.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Fri, 18 Jan 2013 03:33:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2013/01/18/197382.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/197382.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2013/01/18/197382.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/197382.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/197382.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 最近遇到UpdateLayeredWindow在远程协助下会失败，但是GetLastError返回0。 后来看了http://blog.csdn.net/debehe/article/details/4767472，解决了，记一笔。 原代码：      void Update() { &nbsp;&nbsp;&nbsp; CDC dc = GetDC(m_hWnd); &nbsp;&nbsp;&n...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2013/01/18/197382.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/197382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2013-01-18 11:33 <a href="http://www.cppblog.com/Streamlet/archive/2013/01/18/197382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ASP.NET MVC 框架入门&amp;mdash;&amp;mdash;写一个搜索引擎</title><link>http://www.cppblog.com/Streamlet/archive/2013/01/08/197092.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Mon, 07 Jan 2013 17:54:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2013/01/08/197092.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/197092.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2013/01/08/197092.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/197092.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/197092.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: ASP.NET MVC 框架入门——写一个搜索引擎 &nbsp; 动态网页的历史非常悠久，可以追溯到上个世纪。就技术类型而言，主要有ASP、PHP、JSP三大派。笔者接触过ASP、PHP，遗憾的是几乎从未接触过JSP。偶就天生不是JAVA语系的。 &nbsp; 后来，笔者稍微远离了一下Web开发，Web发生了翻天覆地的变化，css成了布局主流，ASP.NET冒出来了。这使得笔者不得不在2008、2...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2013/01/08/197092.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/197092.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2013-01-08 01:54 <a href="http://www.cppblog.com/Streamlet/archive/2013/01/08/197092.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>裸写一个进程外 COM 组件</title><link>http://www.cppblog.com/Streamlet/archive/2012/12/02/195900.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 02 Dec 2012 11:56:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/12/02/195900.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/195900.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/12/02/195900.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/195900.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/195900.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 引言 前面九月份的八篇关于COM的文章，说的都是进程内COM。那时，我们从一个含内嵌IE控件的窗口说起，根据COM协议手工书写了进程内COM组件，并由此积累了一些类似ATL的框架性代码。 &nbsp; 今天开始，我们把脚步迈向进程外组件。同样是从最基础的开始，本篇我们将根据进程外COM组件的加载规范手工编写一个EXE，然后用标准的COM调用方法来使用它。之前积累的框架性代码不属于第三方库，所以这里...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/12/02/195900.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/195900.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-12-02 19:56 <a href="http://www.cppblog.com/Streamlet/archive/2012/12/02/195900.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EXE导出函数</title><link>http://www.cppblog.com/Streamlet/archive/2012/12/01/195858.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sat, 01 Dec 2012 03:41:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/12/01/195858.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/195858.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/12/01/195858.html#Feedback</comments><slash:comments>15</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/195858.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/195858.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 突然想到个问题，EXE可否像DLL一样导出函数呢？于是就起来做试验—— &nbsp; 静态链接调用 嗯，先建立一个EXE，内容很简单： &nbsp;     #include "stdafx.h" #define EXE_LIBRARY #include "ExeLibrary.h" &nbsp; EXE_LIBRARY_API int Sum(int a, int b) { &nbsp;&nbs...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/12/01/195858.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/195858.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-12-01 11:41 <a href="http://www.cppblog.com/Streamlet/archive/2012/12/01/195858.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GDI+ DrawImage拉伸时右边缘和下边缘被渐变透明的问题</title><link>http://www.cppblog.com/Streamlet/archive/2012/11/13/195112.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 13 Nov 2012 06:49:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/11/13/195112.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/195112.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/11/13/195112.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/195112.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/195112.html</trackback:ping><description><![CDATA[<p>如题，1*1的图片拉伸会被弄成：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_2.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_thumb.png" width="185" height="177"></a></p> <p>&nbsp;</p> <p>2*2的会被弄成：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_4.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_thumb_1.png" width="172" height="163"></a></p> <p>&nbsp;</p> <p>更大的图片，表现为右边缘和下边缘渐变：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_6.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_thumb_2.png" width="199" height="141"></a></p> <p>&nbsp;</p> <p>GDI+自作聪明了……</p> <p>&nbsp;</p> <p>解决方法：</p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-autospace: ; mso-layout-grid-align: none" align="left"><font face="Consolas"><span lang="EN-US" style="font-family: ; background-image: none; background-repeat: repeat; background-attachment: scroll; background-position: 0% 0%; color: ; mso-highlight: white; mso-font-kerning: 0pt"><font color="#2b91af"><font style="font-size: 9.5pt">Graphics</font></font></span><span lang="EN-US" style="font-family: ; background-image: none; background-repeat: repeat; background-attachment: scroll; background-position: 0% 0%; color: ; mso-highlight: white; mso-font-kerning: 0pt"><font style="font-size: 9.5pt"> g;</font></span></font></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-autospace: ; mso-layout-grid-align: none" align="left"><span lang="EN-US" style="font-family: ; background-image: none; background-repeat: repeat; background-attachment: scroll; background-position: 0% 0%; color: ; mso-highlight: white; mso-font-kerning: 0pt"><font face="Consolas"><font style="font-size: 9.5pt">g.SetInterpolationMode(InterpolationModeNearestNeighbor);</font></font></span></p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-autospace: ; mso-layout-grid-align: none" align="left"><span lang="EN-US" style="font-family: ; background-image: none; background-repeat: repeat; background-attachment: scroll; background-position: 0% 0%; color: ; mso-highlight: white; mso-font-kerning: 0pt"><font face="Consolas"><font style="font-size: 9.5pt">g.SetPixelOffsetMode(PixelOffsetModeHalf);</font></font></span></p>  <p>效果：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_8.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/GDI-DrawImage_CEA2/image_thumb_3.png" width="177" height="132"></a></p> <p>&nbsp;</p> <p>特此记录。</p> <p>参考资料：<a title="http://bbs.csdn.net/topics/310212346" href="http://bbs.csdn.net/topics/310212346" target="_blank">http://bbs.csdn.net/topics/310212346</a></p><img src ="http://www.cppblog.com/Streamlet/aggbug/195112.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-11-13 14:49 <a href="http://www.cppblog.com/Streamlet/archive/2012/11/13/195112.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让 COM 脱离注册表</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/21/191436.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Thu, 20 Sep 2012 16:34:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/21/191436.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/191436.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/21/191436.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/191436.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/191436.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 引言 在上一篇《在 DLL 中加入第二个 COM 类》的“单用户注册”一节中，我们曾提到脱离注册表依赖一事，现在我们来把这事儿给办了。 &nbsp; 注册 我们在之前支持了“regsvr32 /n /i:user COMProvider.dll”这一注册命令。这一注册命令给了我们一定的扩展余地。从ATL默认的代码来看，对于DllInstall，目前已定义的命令行参数似乎只有user，于是我们可以定...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/21/191436.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/191436.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-21 00:34 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/21/191436.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在 DLL 中加入第二个 COM 类</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/12/190331.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 11 Sep 2012 16:23:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/12/190331.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/190331.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/12/190331.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/190331.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/190331.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 引言 在前面几篇文章里，我们已经成功脱离ATL写了一个COM组件，并且实现了自动化。今天，我们来加入第二个类，并且为加入第二个类做一些整理工作。 &nbsp; 为DLL建立一个Module类 在前面，我们为了使得DllCanUnloadNow能正确工作而放了一个全局变量LONG g_nModuleCount，并且在SampleClass的构造函数和析构函数里对它进行自增和自减。另外还有个IType...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/12/190331.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/190331.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-12 00:23 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/12/190331.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让COM组件可被跨语言调用</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/09/190026.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 09 Sep 2012 04:43:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/09/190026.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/190026.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/09/190026.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/190026.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/190026.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 错误修正 首先修正一下上篇（《裸写一个进程内 COM 组件》）中的例子的一个小问题。类厂的CreateInstance里面，上次是这么写的： &nbsp;     STDMETHODIMP ClassFactory::CreateInstance(_In_opt_ IUnknown *pUnkOuter, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/09/190026.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/190026.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-09 12:43 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/09/190026.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>裸写一个进程内 COM 组件</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Thu, 06 Sep 2012 16:23:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/189762.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/189762.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/189762.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 引言 前几天山寨了ATL的COM_INTERFACE，了解了一个COM类的如何进行通用的组织。今天再来学习下COM协议，看看如何实现一个COM组件——当然，也是不能用ATL的，不然就学不到什么了。 &nbsp; COM DLL说简单简单，说复杂也很复杂。说简单呢，其实貌似只要导出下面这五个函数就可以了： DllCanUnloadNow DllGetClassObject DllRegisterSe...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/189762.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-07 00:23 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/07/189762.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>写个含 Windows Media Player 的窗口</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/04/189470.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 04 Sep 2012 14:16:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/04/189470.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/189470.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/04/189470.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/189470.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/189470.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在上一篇中，我们实现了COM_INTERFACE宏，并且重新写了一个含有WebBrowser的窗口。在那里我们留了中间层OleContainer。为了验证OleContainer的可用性，现在来写一个含有Windows Media Player（下文简称“WMP”）控件的窗口。 &nbsp; WMP控件的容器类除了IOleClientSite、IOleInPlaceSite、IOleInPlace...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/04/189470.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/189470.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-04 22:16 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/04/189470.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>山寨一下 ATL 的 COM_INTERFACE</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Mon, 03 Sep 2012 15:17:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/189321.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/189321.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/189321.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp; 上一篇我们简单学习了下ATL 的继承链处理。可是，如果要裸写一个含内嵌IE控件的窗口，还是要写一个很长的QueryInterface，以及AddRef和Release，确保引用计数的正确性。于是我们不得不参考ATL的COM_TNTERFACE的处理技巧，来达到一定程度上的易用性。 &nbsp; 首先，除了IUnknown以外，其余所有涉及到的接口，均按上一篇的形式，弄成相应的IXXX...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/189321.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-03 23:17 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习下 ATL 的继承链处理</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/02/189135.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 02 Sep 2012 05:56:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/02/189135.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/189135.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/02/189135.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/189135.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/189135.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 我们先来看一组接口定义： &nbsp;     struct IX { &nbsp;&nbsp;&nbsp; virtual void MethodX() = 0; }; &nbsp; struct IXA : public IX { &nbsp;&nbsp;&nbsp; virtual void MethodXA() = 0; }; &nbsp; struct IXB : public IX {...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/02/189135.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/189135.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-02 13:56 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/02/189135.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>裸写一个含内嵌IE控件的窗口</title><link>http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Fri, 31 Aug 2012 17:04:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/188962.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html#Feedback</comments><slash:comments>19</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/188962.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/188962.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 引言  &nbsp; 之前也做过一些含内嵌IE控件的东西，只是一直用MFC/ATL等框架，对于里面的原理其实一知半解，只有脱离它们写一遍，才算能真正懂。前不久在写一个SkyDriveClient的时候正好有一个需求，就练习了一下。技术含量没有，在此记录一笔，供后来人入门，供前辈们批评。 &nbsp; 本文中，行文以流水帐、贴代码方式为主，同时为了不带来干扰，代码将尽量以不带或少带封装的方式书写。目...&nbsp;&nbsp;<a href='http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html'>阅读全文</a><img src ="http://www.cppblog.com/Streamlet/aggbug/188962.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-09-01 01:04 <a href="http://www.cppblog.com/Streamlet/archive/2012/09/01/188962.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>十个步骤找回 Win8 中的微软拼音新体验模式</title><link>http://www.cppblog.com/Streamlet/archive/2012/08/25/188249.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sat, 25 Aug 2012 07:31:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/08/25/188249.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/188249.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/08/25/188249.html#Feedback</comments><slash:comments>23</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/188249.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/188249.html</trackback:ping><description><![CDATA[<p style="text-align: center"><em>（cnBeta: <a title="http://www.cnbeta.com/articles/203020.htm" href="http://www.cnbeta.com/articles/203020.htm" target="_blank">http://www.cnbeta.com/articles/203020.htm</a>） </em></p> <p>&nbsp;</p> <p>微软拼音输入法的新体验模式，其实是微软拼音一直以来的主要模式。笔者从1.5版开始接触微软拼音以来就一直是这种模式，主要特点就是长句输入、二次确认，在第一次确认的时候，文字就进入仿佛实际输入区域，但是文字下面有虚线，此时我们可以用光标键左右移动，就像下图显示的一样： </p> <p style="text-align: center"><a href="C:\Users\KJJ\AppData\Local\Temp\WindowsLiveWriter1286139640\supfiles78F9AC\image[25].png"><img border="0" alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win81.png"></a> </p> <p>而在输入拼音的时候，我们其实无需开启候选词窗口，也不用看屏幕： </p> <p style="text-align: center"><a href="C:\Users\KJJ\AppData\Local\Temp\WindowsLiveWriter1286139640\supfiles78F9AC\image[26].png"><img border="0" alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win82.png"></a> </p> <p>输入完毕之后按空格，观察首选是否正确，如果正确的话再一次空格确认，否则按一下右光标键回到句首开始选词。非常方便，也显得很专业。 </p> <p>&nbsp;</p> <p>习惯于词组输入的同学可能喜欢时刻盯着屏幕出现的候选词，一旦有错立刻纠正选择。而长句输入则不必这样，输入整个句子，让输入法在整句的语境中替你选词，命中率会高很多，特别是在打长篇文章的时候，非常有用。 </p> <p>&nbsp;</p> <p>很多时候别人在看我输入的时候，发现屏幕上有错别字，会忍不住提醒我错了，但是当我最后确认的时候，往往又对了，会显得有一点点疑惑，但是通常不说。有趣的是，我在打别人名字的时候，他们发现一开始的错别字，也会忍不住提醒我错了，并且对输入法把某人的名字弄成另外一个字非常感兴趣，有时会以此"嘲笑"那个人。然后当我最后回来选词的时候，他们会"指责"干嘛一开始不选对的字……我已经习惯了。但是整句输入带来的便捷之处，大部分人是没法认识到的。所以我今天在这里花很大的篇幅介绍一下。 </p> <p>&nbsp;</p> <p>遗憾的是，微软拼音1.5、2.0、3.0都没人叫好。到Office2003的时候，因为"新体验"模式的出现，使得一部分人叫好，但那部分人其实还是把它当作词组输入法来用的。到Office2010出来的时候，又有一部分人叫好，实际上很大一部分人是为"简捷"模式叫好。到现在Win8成了简体中文语言下默认输入法，即便有强推的味道，但好多人还是叫好，而新体验模式的消失，却很少有人问津。 </p> <p>&nbsp;</p> <p>下面正式来看标题中提到的问题。Win8安装完毕后简体中文下只有一个微软拼音简捷，新体验模式不见了！！！ </p> <p>&nbsp;</p> <p style="text-align: center"><a href="C:\Users\KJJ\AppData\Local\Temp\WindowsLiveWriter1286139640\supfiles78F9AC\image[5].png"><img border="0" alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win83.png"></a> </p> <p>&nbsp;</p> <p>有需要的同学，请跟随笔者，一步一步找回新体验模式。 </p> <p>&nbsp;</p> <p>1、打开开始屏幕，在英文状态下输入regedit，然后回车，打开注册表编辑器。（遇到UAC提醒，请选择"是"） </p> <p>2、在左侧展开目录树，一直到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\TIP\{81d4e9c9-1d3b-41bc-9e6c-4b40bf79e35e}： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win84.png"> </p> <p>&nbsp;</p> <p>微软对这一项做了特殊的权限保护，它的权限和相邻的那几个不一样，我们要取得写权限。 </p> <p>&nbsp;</p> <p>3、右键单击它、选择权限，打开如下图的窗口： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win85.png"> </p> <p>&nbsp;</p> <p>4、单击"高级"，再打开一个窗口： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win86.png"> </p> <p>&nbsp;</p> <p>5、在所有者的右边点击"更改"，输入"everyone"或者当前登录用户名，按"确定"，并选中"替换子容器和对象的所有者"，然后按应用。（不要关闭这个窗口，等下我们还要改回来。） </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win87.png"> </p> <p>&nbsp;</p> <p>6、然后点击左下方的启用继承（按了后该按钮变为禁用继承），并选中"使用可从此对象继承的权限项目替换所有子对象的权限项目"。最后按"应用"，遇到提示选择"是"。 </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win88.png"> </p> <p>&nbsp;</p> <p>现在，这个窗口变成这个样子了： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win89.png"> </p> <p>&nbsp;</p> <p>保留这个窗口，我们进行下一步。 </p> <p>&nbsp;</p> <p>7、打开开始屏幕，在英文状态下输入"cmd"，左侧出现"命令提示符"，右键点击选中它，再到屏幕下方点击"以管理员身份运行"，遇到UAC提示选择"是"。这样我们打开具有管理员权限的命令提示符窗口，工作路径位于System32目录。 </p> <p>&nbsp;</p> <p>8、输入"regsvr32 ime\imesc\imsctip.dll"，并按回车。 </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win810.png"> </p> <p>&nbsp;</p> <p>直到看到下图的提示，点击确定，关闭命令提示符窗口。 </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win811.png"> </p> <p>&nbsp;</p> <p>9、回到刚才的高级安全设置窗口，点击左下方的"禁用继承"按钮，这时出现下图提示： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win812.png"> </p> <p>&nbsp;</p> <p>选择第二项，"从此对象中删除所有以继承的权限"： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win813.png"> </p> <p>&nbsp;</p> <p>并选中"使用可从此对象继承的权限项目替换所有子对象的权限项目"，按"应用"，遇到提示选择"是"。 </p> <p>&nbsp;</p> <p>10、点击上方所有者右边的"更改"，输入"nt service\trustedinstaller"，按"确定"，并选中"替换子容器和对象的所有者"，然后按"应用"。 </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win814.png"> </p> <p>&nbsp;</p> <p>至此，我们将注册表权限恢复如初了。 </p> <p>&nbsp;</p> <p>打开输入法管理界面： </p> <p>&nbsp;</p> <p style="text-align: center"><img alt="" src="http://www.cppblog.com/images/cppblog_com/Streamlet/082512_0730_Win815.png"> </p> <p>&nbsp;</p> <p>我们可以看到多出来的"Microsoft Pinyin NewExperience"了，它就是我们要找的新体验模式！ </p> <p>&nbsp;</p> <p>悲剧的是，貌似新体验模式在Metro界面下有问题，每次确认输入，光标都会回到最前面<font color="#d16349">（注：确认这是当时新浪微博Win8客户端的Bug，不是输入法的问题，新浪微博貌似已经修正此问题）</font>。也许是因为有这些Bug，微软才隐藏它的吧。不过，桌面模式下使用起来未发现任何问题哦。 </p><img src ="http://www.cppblog.com/Streamlet/aggbug/188249.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-08-25 15:31 <a href="http://www.cppblog.com/Streamlet/archive/2012/08/25/188249.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调用约定总结</title><link>http://www.cppblog.com/Streamlet/archive/2012/05/12/174610.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Fri, 11 May 2012 17:36:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2012/05/12/174610.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/174610.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2012/05/12/174610.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/174610.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/174610.html</trackback:ping><description><![CDATA[<p>以前做 Function 的时候恰好取巧避免掉了，这些天在做 Bind，不得已要把每个调用约定罗列一遍。顺手把这些东西复习一下，总结如下——</p> <p>（所有内容针对 VC 编译平台）</p> <p>&nbsp;</p> <p>一、x86</p> <table cellspacing="0" cellpadding="2" width="855" border="0"> <tbody> <tr> <td valign="top" width="119">名称</td> <td valign="top" width="308">传参方式</td> <td valign="top" width="87">栈清理</td> <td valign="top" width="339">C 语言函数重命名（例：int func(int, double)）</td></tr> <tr> <td valign="top" width="122">__cdecl</td> <td valign="top" width="306">从右至左压栈</td> <td valign="top" width="89">主调函数</td> <td valign="top" width="338">前面加“_”（_func）</td></tr> <tr> <td valign="top" width="124">__stdcall</td> <td valign="top" width="304">从右至左压栈</td> <td valign="top" width="90">被调函数</td> <td valign="top" width="336">前面加“_”，后面加“@”再加参数十进制字节数（_func@12）</td></tr> <tr> <td valign="top" width="125">__fastcall</td> <td valign="top" width="303">前两个不大于DWORD长度的参数从左至右分别存到 ECX、EDX，其余从右至左压栈</td> <td valign="top" width="91">被调函数</td> <td valign="top" width="335">前面加“@”，后面加“@”再加参数十进制字节数（@func@12）</td></tr> <tr> <td valign="top" width="126">__thiscall</td> <td valign="top" width="302">ECX 存 this，其余从右至左压栈</td> <td valign="top" width="92">被调函数</td> <td valign="top" width="336">仅用于 C++</td></tr></tbody></table> <p>&nbsp;</p> <p>二、x64</p> <table cellspacing="0" cellpadding="2" width="855" border="0"> <tbody> <tr> <td valign="top" width="120">名称</td> <td valign="top" width="310">传参方式</td> <td valign="top" width="87">栈清理</td> <td valign="top" width="336">&nbsp;</td></tr> <tr> <td valign="top" width="123">__fastcall</td> <td valign="top" width="308">前四个整数/浮点数放在 RCX/XMM0、RDX/XMM1、R8/XMM2、R9/XMM3，其余压栈。<br>如果前 4 个参数分别为 int、float、long、double，它们将分别被存到 RCX、XMM1、R8、XMM3</td> <td valign="top" width="89">被调函数</td> <td valign="top" width="334">&nbsp;</td></tr></tbody></table> <p>64位编译环境下，可以指定 __cdecl、__stdcall、__fastcall，但是编译器会忽略它们。两个显示指定了不同调用约定的函数不构成重载，而构成重定义错误。</p><img src ="http://www.cppblog.com/Streamlet/aggbug/174610.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2012-05-12 01:36 <a href="http://www.cppblog.com/Streamlet/archive/2012/05/12/174610.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何写 执行耗时任务的、可随时立即退出的函数 呢？</title><link>http://www.cppblog.com/Streamlet/archive/2011/05/26/147133.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Wed, 25 May 2011 16:36:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2011/05/26/147133.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/147133.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2011/05/26/147133.html#Feedback</comments><slash:comments>29</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/147133.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/147133.html</trackback:ping><description><![CDATA[<p>如题。</p> <p>稍微解释下，因为有可能有人会误会：放新线程里面去不就可以了？这没有解决问题。如此的话，你那个线程函数怎么写？或者线程函数里调用的某个任务函数怎么写？总之，多线程虽然总是出现在这些问题的解决方案中，但不是多线程解决了这个问题。嗯……不知道说清楚了没？</p> <p>目前我心里的答案只有这一种模式：</p> <p>bool DoTask(HANDLE hQuitSignal)<br>{<br>&nbsp;&nbsp;&nbsp; while (!QuitCondition)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (WaitForSingleObject(hQuitSignal, 0) == WAIT_OBJECT_0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Do something<br>&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; return true;<br>}<br></p> <p>其中，“// Do something”部分要细化到瞬间执行完成的细度。</p> <p>但是我很困惑的是，如果这些任务很繁重，难道我必须每进行一些操作就 if (WaitForSingleObject(hQuitSignal, 0) == WAIT_OBJECT_0) 检查下吗？这样岂不是这种检测代码充斥在任务中了？</p> <p>不知各位有何经验和体会，求教~</p><img src ="http://www.cppblog.com/Streamlet/aggbug/147133.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2011-05-26 00:36 <a href="http://www.cppblog.com/Streamlet/archive/2011/05/26/147133.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>也谈谈GUI框架</title><link>http://www.cppblog.com/Streamlet/archive/2011/01/16/138609.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 16 Jan 2011 12:05:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2011/01/16/138609.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/138609.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2011/01/16/138609.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/138609.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/138609.html</trackback:ping><description><![CDATA[<p>事情的缘起是，耐不住寂寞，准备开始造GUI的轮子。</p> <p>GUI框架，要做的事情我想大概是这么几步：</p> <ol> <li>实现回调函数的成员化。  <li>实现方便程度可接受的消息映射。  <li>确定上述核心部件的使用方式。  <li>制造大量的控件。</li></ol> <p>前三步要走的比较小心，第四步是体力劳动。</p> <p>第一步，Windows下可参考的是MFC方式、WTL方式，以及利用Window相关属性中的某些空位。前不久刚初步看过WTL的机制，虽然当时没写GUI框架的打算，不过也有点技术准备的意思了。现学现用吧。这里一个可以预见的问题是64位兼容，现在没有测试环境，先不管。</p> <p>接下来看第二步了，所要做的事情就是把 WndProc 下的 一堆 case 有效地组织起来，或者换个写法。之前还真不知道 MFC/WTL 的 BEGIN_MSG_MAP。以为很高深的，想不到就是拼装成一个大的 WndProc。先抄了，做成一个可运行的版本。但是，这方面会直接决定以后的大部分使用方式，单单抄一下意义不大。后来去 <a href="http://www.cppblog.com/OwnWaterloo/" target="_blank">@OwnWaterloo</a> 曾推荐过的 <a href="http://www.cppblog.com/cexer/" target="_blank">@cexer</a> 的博客上逛了几圈，第一圈看了一些描述性文字，第二圈大概看了下技术，第三圈是挖坟，那个传说中的 cppblog 第一高楼啊。。其中有一个使用方式很新颖，嗯……是那个不需要手动写映射代码，直接实现消息处理函数的方式。不过我后来觉得还是不要这种样子了，凭我个人的直觉，如果我写下这样的处理函数，我大概会因为不知道何时注册了这个函数而找不到调用来源而感到郁闷。在Windows回调机制的影响下，我可能会很抱有偏见地认为，只有直接来自WndProc的调用，才算是来源明确的，不需要继续追踪的——当然，这是建立在我不熟悉这个框架的基础上的。框架必然需要隐藏调用来源，以及其他一些细节，但是在这一步，我觉得稍微有点早。</p> <p>刚才说到的都是静态绑定。现在我有点倾向于动态绑定。从使用方便程度上来看，动态绑定更具灵活性。从性能上，动态绑定下，消息到处理函数的查找过程可以更快，静态绑定只能遍历。当然，未必将“添加处理函数”这样的接口提供给最终用户，但是这个操作对于整个控件体系的形成应该蛮有帮助的吧。比如MFC下一个控件类使用Message Map做了一些事情，继承类就无法直接继承这个动作，于是可能需要做两套处理函数调用机制，一套是给内部继承用的，一套是给用户的。如果在最开始的基类保存一个消息映射，每个消息对应一族处理函数，每个继承类都可以添加处理函数，但不删除父类已添加的函数，这样就可以在一套Message Map机制下获得父类的行为。以上，不知道考虑得对不对，欢迎讨论。</p> <p>其中，父类保存子类给出的可调用体并正确执行是个问题。折腾了一些时间，都没有成功。我比较纠结，想知道除了用function之类的玩意儿外还有没有其他简单可行的办法。后来去<a href="http://www.cppblog.com/zblc" target="_blank">@zblc</a>的群上问，<a href="mailto:zblc@vczh" target="_blank">@vczh</a>也说需要一套function机制。看来是逃不开这个问题了。嗯……想起来大约两个月前一个同事从codeproject找来了一个GUI框架看，看到几行整整齐齐的 AddMsgHandler(WM_CREATE, XXX(this, &amp;MyWindow::OnCreate));，叹不已。我当时打趣说，这很简单的，无非是搞了个 function 而已，哥哥两天就能搞定。于是他们叫我两天搞定。我鼓捣了10分钟，搞不定，只好丢一句，真的很简单的，类似boost::function，你去看一下就知道了，哥哥要干活了。</p> <p>既然现在还是绕不开这个问题，那还是搞一下了，搞好以后就权且当做给他们交作业吧。我会另写一篇文章说说function的事情，这里先略过。现在开始假设这个设施已经造好了。那么，窗口类中大概可以这么定义相关类型：</p> <p><font face="Consolas">typedef Function&lt;bool (WPARAM, LPARAM)&gt; MsgHandler;<br>typedef List&lt;MsgHandler&gt; MsgHandlerList;<br>typedef Map&lt;UINT, MsgHandlerList&gt; MsgMap;</font></p> <p>然后再定义一个变量：</p> <p><font face="Consolas">MsgMap&nbsp; m_MsgMap;</font></p> <p>它用于保存消息映射。最终的回调函数可以写成：</p> <p><font face="Consolas">LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; bool bHandled = false;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; MsgMap::Iterator itMsgMap = m_MsgMap.Find(uMsg);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; if (itMsgMap != m_MsgMap.End())<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (MsgHandlerList::Iterator it = itMsgMap-&gt;Value.Begin();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; !bHandled &amp;&amp; it != itMsgMap-&gt;Value.End(); ++it)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bHandled = (*it)(wParam, lParam);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return bHandled ? TRUE : DefWindowProc(m_hWnd, uMsg, wParam, lParam);<br>}</font></p> <p>最后给个添加消息映射的接口：</p> <p><font face="Consolas">void AppendMsgHandler(UINT uMsg, MsgHandler pMsgHandler)<br>{<br>&nbsp;&nbsp;&nbsp; m_MsgMap[uMsg].PushBack(pMsgHandler);<br>}</font></p> <p>到目前为止，我们的窗口类大致上可以写成这样：</p> <p><font face="Consolas">#include &lt;Windows.h&gt;<br>#include &lt;tchar.h&gt;<br>#include "../GUIFramework/xlWindowBase.h"</font></p> <p><font face="Consolas">class Window : public xl::WindowBase<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; Window()<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &amp;Window::OnEraseBackground));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppendMsgHandler(WM_PAINT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MsgHandler(this, &amp;Window::OnPaint));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppendMsgHandler(WM_LBUTTONUP,&nbsp; MsgHandler(this, &amp;Window::OnLButtonUp));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppendMsgHandler(WM_RBUTTONUP,&nbsp; MsgHandler(this, &amp;Window::OnRButtonUp));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AppendMsgHandler(WM_DESTROY,&nbsp;&nbsp;&nbsp; MsgHandler(this, &amp;Window::OnDestroy));<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; bool OnEraseBackground(WPARAM wParam, LPARAM lParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; bool OnPaint(WPARAM wParam, LPARAM lParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PAINTSTRUCT ps = {};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BeginPaint(m_hWnd, &amp;ps);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RECT rect = { 200, 200, 400, 400 };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DrawText(ps.hdc, _T("Hello, world!"), -1, &amp;rect, DT_CENTER | DT_VCENTER);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EndPaint(m_hWnd, &amp;ps);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; bool OnLButtonUp(WPARAM wParam, LPARAM lParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MessageBox(m_hWnd, _T("LButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; bool OnRButtonUp(WPARAM wParam, LPARAM lParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MessageBox(m_hWnd, _T("RButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; bool OnDestroy(WPARAM wParam, LPARAM lParam)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostQuitMessage(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp;&nbsp; }<br>};</font></p> <p>在最基础的 WindowBase 里，搞成这样大概差不是很多了。暂时先看第三步。到目前为止，我所听说过的 GUI 框架都是真正的框架，似乎没有“GUI 库”。为什么一定要以继承某个基类的方式来使用呢？如果像下面这样使用呢？</p> <p><font face="Consolas">class Window<br>{<br>private:<br>&nbsp;&nbsp;&nbsp; xl::WindowBase m_WindowBase;</font></p> <p><font face="Consolas">public:<br>&nbsp;&nbsp;&nbsp; Window()<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_WindowBase.AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &amp;Window::OnEraseBackground));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_WindowBase.AppendMsgHandler(WM_PAINT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MsgHandler(this, &amp;Window::OnPaint));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_WindowBase.AppendMsgHandler(WM_LBUTTONUP,&nbsp; MsgHandler(this, &amp;Window::OnLButtonUp));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_WindowBase.AppendMsgHandler(WM_RBUTTONUP,&nbsp; MsgHandler(this, &amp;Window::OnRButtonUp));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_WindowBase.AppendMsgHandler(WM_DESTROY,&nbsp;&nbsp;&nbsp; MsgHandler(this, &amp;Window::OnDestroy));<br>&nbsp;&nbsp;&nbsp; }<br>};</font></p> <p>这个问题，不知道各位有没有什么思考？</p> <p>还有一个问题是，接下去要不要将 WPARAM 和 LPARAM 的含义彻底解析掉，搞成一系列 PaintParam、EraseBackgroundParam、LButtonUpParam、RButtonUpParam，DestroyParam，让使用的时候与原始消息参数彻底隔离呢？</p> <p>最后一步，虽说是体力活，但这跟最终的应用场合密切相关，需要提供怎么样的功能是一件需要考量的事。</p> <p>目前走在第二步，所以下面的两个问题思考得不多。求经验，求意见。</p><img src ="http://www.cppblog.com/Streamlet/aggbug/138609.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2011-01-16 20:05 <a href="http://www.cppblog.com/Streamlet/archive/2011/01/16/138609.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>想讨论个话题，关于DLL的使用方式</title><link>http://www.cppblog.com/Streamlet/archive/2010/12/18/136862.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sat, 18 Dec 2010 14:35:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2010/12/18/136862.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/136862.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2010/12/18/136862.html#Feedback</comments><slash:comments>15</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/136862.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/136862.html</trackback:ping><description><![CDATA[<p>前言：<br>DLL 是个很久远的文件格式，以至于它只支持导出函数（请忽略 .net 的 DLL）。至于导出 class，也是由于编译系统的支持才勉勉强强能进行，只能静态加载，实际上对于DLL文件来说它导出的还是函数。——以上，个人的一点浅显理解。</p> <p>问题：<br>有没有存在一种好的方式，让DLL能够被动态加载，并且能够方便地得到里面的 C++ class 信息？<br>备选：<br>1、别想了，老老实实地用吧，还是导出纯C函数= =<br>2、大胆的导出 class 吧，如果动态加载，自己去拼那些编译后名字吧。。<br>3、COM 形式？可是，要注册到系统中去，凭空多了系统注册表依赖<br>4、还有吗？<br>5、甚至可以抛开DLL，有没有类似的一种方式，可用于二进制代码的模块划分以及闭源的代码重用？</p> <p>（至于跨平台啥的先不考虑吧，暂定Windows平台下吧）</p> <p>请不吝指教~</p><img src ="http://www.cppblog.com/Streamlet/aggbug/136862.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2010-12-18 22:35 <a href="http://www.cppblog.com/Streamlet/archive/2010/12/18/136862.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习下 WTL 的 thunk</title><link>http://www.cppblog.com/Streamlet/archive/2010/10/24/131064.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Sun, 24 Oct 2010 08:44:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2010/10/24/131064.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/131064.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2010/10/24/131064.html#Feedback</comments><slash:comments>39</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/131064.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/131064.html</trackback:ping><description><![CDATA[<p>由于 C++ 成员函数的调用机制问题，对C语言回调函数的 C++ 封装是件比较棘手的事。为了保持C++对象的独立性，理想情况是将回调函数设置到成员函数，而一般的回调函数格式通常是普通的C函数，尤其是 Windows API 中的。好在有些回调函数中留出了一个额外参数，这样便可以由这个通道将 this 指针传入。比如线程函数的定义为：</p> <p><font face="Consolas">typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(<br>&nbsp;&nbsp;&nbsp; LPVOID lpThreadParameter<br>&nbsp;&nbsp;&nbsp; );<br>typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;<br></font></p> <p>这样，当我们实现线程类的时候，就可以：</p> <p><font face="Consolas">class Thread<br>{<br>private:<br>&nbsp;&nbsp;&nbsp; HANDLE m_hThread;</font></p> <p><font face="Consolas">public:<br>&nbsp;&nbsp;&nbsp; BOOL Create()<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_hThread = CreateThread(NULL, 0, StaticThreadProc, (LPVOID)this, 0, NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return m_hThread != NULL;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">private:<br>&nbsp;&nbsp;&nbsp; DWORD WINAPI ThreadProc()<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // TODO<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">private:<br>&nbsp;&nbsp;&nbsp; static DWORD WINAPI StaticThreadProc(LPVOID lpThreadParameter)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((Thread *)lpThreadParameter)-&gt;ThreadProc();<br>&nbsp;&nbsp;&nbsp; }<br>};</font></p> <p>不过，这样，成员函数 ThreadProc() 便丧失了一个参数，这通常无伤大雅，任何原本需要从参数传入的信息都可以作为成员变量让 ThreadProc 来读写。如果一定有些什么是非从参数传入不可的，那也可以，一种做法，创建线程的时候传入一个包含 this 指针信息的结构。第二种做法，对该 class 作单例限制——如果现实情况允许的话。</p> <p>所以，有额外参数的回调函数都好处理。不幸的是，Windows 的窗口回调函数没有这样一个额外参数：</p> <p>typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);</p> <p>这使得对窗口的 C++ 封装变得困难。为了解决这个问题，一个很自然的想法是，维护一份全局的窗口句柄到窗口类的对应关系，如：</p> <p><font face="Consolas">#include &lt;map&gt;</font></p> <p><font face="Consolas">class Window<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; Window();<br>&nbsp;&nbsp;&nbsp; ~Window();<br>&nbsp;&nbsp;&nbsp; <br>public:<br>&nbsp;&nbsp;&nbsp; BOOL Create();</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; HWND m_hWnd;</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br>&nbsp;&nbsp;&nbsp; static std::map&lt;HWND, Window *&gt; m_sWindows;<br>};</font></p> <p>在 Create 的时候，指定 StaticWndProc 为窗口回调函数，并将 hWnd 与 this 存入 m_sWindows：</p> <p><font face="Consolas">BOOL Window::Create()<br>{<br>&nbsp;&nbsp;&nbsp; LPCTSTR lpszClassName = _T("ClassName");<br>&nbsp;&nbsp;&nbsp; HINSTANCE hInstance = GetModuleHandle(NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; WNDCLASSEX wcex&nbsp;&nbsp;&nbsp; = { sizeof(WNDCLASSEX) };<br>&nbsp;&nbsp;&nbsp; wcex.lpfnWndProc&nbsp;&nbsp; = <strong>StaticWndProc</strong>;<br>&nbsp;&nbsp;&nbsp; wcex.hInstance&nbsp;&nbsp;&nbsp;&nbsp; = hInstance;<br>&nbsp;&nbsp;&nbsp; wcex.lpszClassName = lpszClassName;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; RegisterClassEx(&amp;wcex);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; if (m_hWnd == NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return FALSE;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; <strong>m_sWindows.insert(std::make_pair(m_hWnd, this));</strong></font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; ShowWindow(m_hWnd, SW_SHOW);<br>&nbsp;&nbsp;&nbsp; UpdateWindow(m_hWnd);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return TRUE;<br>}</font></p> <p>在 StaticWindowProc 中，由 hWnd 找到 this，然后转发给成员函数：</p> <p><font face="Consolas">LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; std::map&lt;HWND, Window *&gt;::iterator it = m_sWindows.find(hWnd);<br>&nbsp;&nbsp;&nbsp; assert(it != m_sWindows.end() &amp;&amp; it-&gt;second != NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return it-&gt;second-&gt;WndProc(message, wParam, lParam);<br></font><font face="Consolas">}</font></p> <p>（m_sWindows 的多线程保护略过，下同）</p> <p>据说 MFC 采用的就是类似的做法。缺点是，每次 StaticWndProc 都要从 m_sWindows 中去找 this。由于窗口类一般会保存窗口句柄，回调函数里的 hWnd 就没多大作用了，如果这个 hWnd 能够被用来存 this 指针就好了，那么就能写成这样：</p> <p><font face="Consolas">LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; return ((Window *)hWnd)-&gt;WndProc(message, wParam, lParam);<br>}</font></p> <p>这样看上去就爽多了。传说中 WTL 所采取的 thunk 技术就是这么干的。之前，只是听过这遥远的传说，今天，终于有机会走进这个传说去看一看。参考资料是一篇不知原始出处的文章《深入剖析WTL—WTL框架窗口分析》，以及部分 WTL 8.0 代码，还有其他乱七八糟的文章。</p> <p>WTL 的思路是，每次在系统调用 WndProc 的时候，让它鬼使神差地先走到我们的另一处代码，让我们有机会修改堆栈中的 hWnd。这处代码可能是类似这样的：</p> <p><font face="Consolas">__asm<br>{<br>&nbsp;&nbsp;&nbsp; mov dword ptr [esp+4], pThis&nbsp; ;调用 WndProc 时，堆栈结构为：RetAddr, hWnd, message, wParam, lParam, ... 故 [esp+4]<br>&nbsp;&nbsp;&nbsp; jmp WndProc<br>}</font></p> <p>由于 pThis 和 WndProc 需要被事先修改（但又无法在编译前定好），所以我们需要运行的时候去修改这部分代码。先弄一个小程序探测下这两行语句的机器码：</p> <p><font face="Consolas">LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</font></p> <p><font face="Consolas">int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)<br>{<br>&nbsp;&nbsp;&nbsp; MessageBox(NULL, NULL, NULL, MB_OK);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; __asm<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov dword ptr [esp+4], 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jmp WndProc<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return 0;<br>}</font></p> <p>最前面的 MessageBox 是为了等下调试的时候容易找到进入点。</p> <p>然后使用 OllyDbg，在 MessageBoxW 上设置断点，执行到该函数返回：</p> <p><a href="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-WTL--thunk_BC05/image_4.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: ; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/Streamlet/Windows-Live-Writer/-WTL--thunk_BC05/image_thumb_1.png" width="863" height="358"></a></p> <p>这里我们看到，mov dword ptr [esp+4] 的机器码为 C7 44 24 04，后面紧接着的一个 DWORD 是 mov 的第二个操作数。jmp 的机器码是 e9，后面紧接着的一个 DWORD 是跳转的相对地址。其中 00061000h - 0006102Bh = FFFFFFD5h。</p> <p>于是定义这样一个结构：</p> <p><font face="Consolas">#pragma pack(push,1)<br>typedef struct _StdCallThunk<br>{<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_mov;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // = 0x042444C7<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_this;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // = this<br>&nbsp;&nbsp;&nbsp; BYTE&nbsp;&nbsp;&nbsp; m_jmp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // = 0xe9<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_relproc;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // = relative distance<br>} StdCallThunk;<br>#pragma pack(pop)</font></p> <p>这个结构可以作为窗口类的成员变量存在。我们的窗口类现在变成了这样子：</p> <p><font face="Consolas">class Window<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; Window();<br>&nbsp;&nbsp;&nbsp; ~Window();</font></p> <p><font face="Consolas">public:<br>&nbsp;&nbsp;&nbsp; BOOL Create();</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; HWND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_hWnd;<br>&nbsp;&nbsp;&nbsp; StdCallThunk m_thunk;</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br>};</font></p> <p>似乎少了点什么……创建窗口的时候，我们是不能直接把回调函数设到 StaticWndPorc 中去的，因为这个函数是希望被写成这样子的：</p> <p><font face="Consolas">LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; return ((Window *)hWnd)-&gt;WndProc(message, wParam, lParam);<br>}<br></font></p> <p>那么至少需要一个临时的回调函数，在这个函数里去设置新的回调函数（设到 m_thunk 上），再由 m_thunk 来调用 StaticWndProc，StaticWndProc 再去调用 WndProc，这样整个过程就通了。</p> <p>但是，临时回调函数还是需要知道从 hWnd 到 this 的对应关系。可是现在我们不能照搬用刚才的 m_sWindows 了。因为窗口在创建过程中就会调用到回调函数，需要使用到 m_sWindows 里的 this，而窗口被成功创建之前，我们没法提前拿到 HWND 存入 m_sWindows。现在，换个方法，存当前线程 ID 与 this 的对应关系。这样，这个类变成了：</p> <p><font face="Consolas">#include &lt;map&gt;</font></p> <p><font face="Consolas">class Window<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; Window();<br>&nbsp;&nbsp;&nbsp; ~Window();</font></p> <p><font face="Consolas">public:<br>&nbsp;&nbsp;&nbsp; BOOL Create();</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; HWND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_hWnd;<br>&nbsp;&nbsp;&nbsp; <strong>StdCallThunk m_thunk;</strong></font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; <strong>static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br></strong></font></p><font face="Consolas">&nbsp;&nbsp;&nbsp; static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br>&nbsp;&nbsp;&nbsp; <strong>static std::map&lt;DWORD, Window *&gt; m_sWindows;<br></strong>};</font>  <p>然后实现 Create 和 TempWndProc：</p> <p><font face="Consolas">BOOL Window::Create()<br>{<br>&nbsp;&nbsp;&nbsp; LPCTSTR lpszClassName = _T("ClassName");<br>&nbsp;&nbsp;&nbsp; HINSTANCE hInstance = GetModuleHandle(NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; WNDCLASSEX wcex&nbsp;&nbsp;&nbsp; = { sizeof(WNDCLASSEX) };<br>&nbsp;&nbsp;&nbsp; wcex.lpfnWndProc&nbsp;&nbsp; = <strong>TempWndProc</strong>;<br>&nbsp;&nbsp;&nbsp; wcex.hInstance&nbsp;&nbsp;&nbsp;&nbsp; = hInstance;<br>&nbsp;&nbsp;&nbsp; wcex.lpszClassName = lpszClassName;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; RegisterClassEx(&amp;wcex);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; DWORD dwThreadId = GetCurrentThreadId();<br>&nbsp;&nbsp;&nbsp; <strong>m_sWindows.insert(std::make_pair(dwThreadId, this));</strong></font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; <strong>m_thunk.m_mov = 0x042444c7;<br></strong>&nbsp;&nbsp;&nbsp; <strong>m_thunk.m_jmp = 0xe9;</strong></font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; if (m_hWnd == NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return FALSE;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; ShowWindow(m_hWnd, SW_SHOW);<br>&nbsp;&nbsp;&nbsp; UpdateWindow(m_hWnd);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return TRUE;<br>}<br></font></p> <p><font face="Consolas">LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; std::map&lt;DWORD, Window *&gt;::iterator it = m_sWindows.find(GetCurrentThreadId());<br>&nbsp;&nbsp;&nbsp; assert(it != m_sWindows.end() &amp;&amp; it-&gt;second != NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; Window *pThis = it-&gt;second;<br>&nbsp;&nbsp;&nbsp; m_sWindows.erase(it);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; WNDPROC pWndProc = (WNDPROC)&amp;pThis-&gt;m_thunk;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; <strong>pThis-&gt;m_thunk.m_this = (DWORD)pThis;<br></strong>&nbsp;&nbsp;&nbsp; <strong>pThis-&gt;m_thunk.m_relproc = (DWORD)&amp;Window::StaticWndProc - ((DWORD)&amp;pThis-&gt;m_thunk + sizeof(StdCallThunk));</strong></font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; m_hWnd = hWnd;<br>&nbsp;&nbsp;&nbsp; <strong>SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);</strong></font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return pWndProc(hWnd, message, wParam, lParam);<br>}</font></p> <p>差不多可以了，调试一下。结果，在 thunk 的第一行出错了。我原以为地址算错了神马的，尝试把 thunk.m_mov 改为 0x90909090，再运行，还是出错。于是傻掉了……过了好一会儿才意识到，可能是因为 thunk 在数据段，无法被执行。可是，很久很久以前偶滴一个敬爱的老师在 TC 中鼓捣程序运行时改变自身代码时，貌似无此问题啊。。。然后查呀查，原来是 Windows 在的数据执行保护搞的鬼。于是，需要用 VirtualAlloc 来申请一段有执行权限的内存。WTL 里面也是这么做的，不过它似乎维护了一块较大的可执行内存区作为 thunk 内存池，我们这里从简。最后，整个流程终于跑通了。最终代码清单如下：</p><font face="Consolas">#include &lt;Windows.h&gt;<br>#include &lt;assert.h&gt;<br>#include &lt;map&gt; <br>#include &lt;tchar.h&gt;<br></font> <p><font face="Consolas">#pragma pack(push,1)<br>typedef struct _StdCallThunk<br>{<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_mov;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_this;<br>&nbsp;&nbsp;&nbsp; BYTE&nbsp;&nbsp;&nbsp; m_jmp;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; m_relproc;</font></p> <p><font face="Consolas">} StdCallThunk;<br>#pragma pack(pop)</font></p> <p><font face="Consolas">class Window<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; Window();<br>&nbsp;&nbsp;&nbsp; ~Window();</font></p> <p><font face="Consolas">public:<br>&nbsp;&nbsp;&nbsp; BOOL Create();</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; HWND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_hWnd;<br>&nbsp;&nbsp;&nbsp; StdCallThunk *m_pThunk;</font></p> <p><font face="Consolas">protected:<br>&nbsp;&nbsp;&nbsp; static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br>&nbsp;&nbsp;&nbsp; static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);<br>&nbsp;&nbsp;&nbsp; static std::map&lt;DWORD, Window *&gt; m_sWindows;<br>};</font></p> <p><font face="Consolas">std::map&lt;DWORD, Window *&gt; Window::m_sWindows;</font></p> <p><font face="Consolas">Window::Window()<br>{</font></p> <p><font face="Consolas">}</font></p> <p><font face="Consolas">Window::~Window()<br>{<br>&nbsp;&nbsp;&nbsp; VirtualFree(m_pThunk, sizeof(StdCallThunk), MEM_RELEASE);<br>}</font></p> <p><font face="Consolas">BOOL Window::Create()<br>{<br>&nbsp;&nbsp;&nbsp; LPCTSTR lpszClassName = _T("ClassName");<br>&nbsp;&nbsp;&nbsp; HINSTANCE hInstance = GetModuleHandle(NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; WNDCLASSEX wcex&nbsp;&nbsp;&nbsp; = { sizeof(WNDCLASSEX) };<br>&nbsp;&nbsp;&nbsp; wcex.lpfnWndProc&nbsp;&nbsp; = TempWndProc;<br>&nbsp;&nbsp;&nbsp; wcex.hInstance&nbsp;&nbsp;&nbsp;&nbsp; = hInstance;<br>&nbsp;&nbsp;&nbsp; wcex.lpszClassName = lpszClassName;<br>&nbsp;&nbsp;&nbsp; wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; RegisterClassEx(&amp;wcex);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; DWORD dwThreadId = GetCurrentThreadId();<br>&nbsp;&nbsp;&nbsp; m_sWindows.insert(std::make_pair(dwThreadId, this));</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; m_pThunk = (StdCallThunk *)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);<br>&nbsp;&nbsp;&nbsp; m_pThunk-&gt;m_mov = 0x042444c7;<br>&nbsp;&nbsp;&nbsp; m_pThunk-&gt;m_jmp = 0xe9;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; if (m_hWnd == NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return FALSE;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; ShowWindow(m_hWnd, SW_SHOW);<br>&nbsp;&nbsp;&nbsp; UpdateWindow(m_hWnd);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return TRUE;<br>}</font></p> <p><font face="Consolas">LRESULT Window::WndProc(UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; switch (message)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; case WM_LBUTTONUP:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MessageBox(m_hWnd, _T("LButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WM_RBUTTONUP:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MessageBox(m_hWnd, _T("RButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; case WM_DESTROY:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostQuitMessage(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; default:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return DefWindowProc(m_hWnd, message, wParam, lParam);<br>}</font></p> <p><font face="Consolas">LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; std::map&lt;DWORD, Window *&gt;::iterator it = m_sWindows.find(GetCurrentThreadId());<br>&nbsp;&nbsp;&nbsp; assert(it != m_sWindows.end() &amp;&amp; it-&gt;second != NULL);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; Window *pThis = it-&gt;second;<br>&nbsp;&nbsp;&nbsp; m_sWindows.erase(it);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; WNDPROC pWndProc = (WNDPROC)pThis-&gt;m_pThunk;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; pThis-&gt;m_pThunk-&gt;m_this = (DWORD)pThis;<br>&nbsp;&nbsp;&nbsp; pThis-&gt;m_pThunk-&gt;m_relproc = (DWORD)&amp;Window::StaticWndProc - ((DWORD)pThis-&gt;m_pThunk + sizeof(StdCallThunk));</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; pThis-&gt;m_hWnd = hWnd;<br>&nbsp;&nbsp;&nbsp; SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return pWndProc(hWnd, message, wParam, lParam);<br>}</font></p> <p><font face="Consolas">LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp;&nbsp;&nbsp; return ((Window *)hWnd)-&gt;WndProc(message, wParam, lParam);<br>}</font></p> <p><font face="Consolas">int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)<br>{<br>&nbsp;&nbsp;&nbsp; Window wnd;<br>&nbsp;&nbsp;&nbsp; wnd.Create();</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; MSG msg;</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; while (GetMessage(&amp;msg, NULL, 0, 0))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TranslateMessage(&amp;msg);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DispatchMessage(&amp;msg);<br>&nbsp;&nbsp;&nbsp; }</font></p> <p><font face="Consolas">&nbsp;&nbsp;&nbsp; return (int)msg.wParam;<br>}<br></font></p> <p>刚才有一处，存 this 指针的时候，我很武断地把它与当前线程 ID 关联起来了，其实这正是 WTL 本身的做法。它用 CAtlWinModule::AddCreateWndData 存的 this，最终会把当前线程 ID 和 this 作关联。我是这么理解的吧，同一线程不可能同时有两处在调用 CreateWindow，所以这样取回来的 this 是可靠的。</p> <p>好了，到此为止，边试验边记录的，不知道理解是否正确。欢迎指出不当之处，也欢迎提出相关的问题来考我，欢迎介绍有关此问题的新方法、新思路，等等，总之，请各位看官多指教哈。</p><img src ="http://www.cppblog.com/Streamlet/aggbug/131064.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2010-10-24 16:44 <a href="http://www.cppblog.com/Streamlet/archive/2010/10/24/131064.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>InternetOpenURL 内部 crash 的问题</title><link>http://www.cppblog.com/Streamlet/archive/2010/08/26/124787.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Thu, 26 Aug 2010 03:19:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2010/08/26/124787.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/124787.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2010/08/26/124787.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/124787.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/124787.html</trackback:ping><description><![CDATA[<p>近来遇上一个很诡异的 bug：InternetOpenURL 内部发生 crash。虽说发生问题的时刻总是处于这个 API 内部，可也一直不敢确定不是其他原因引起的，就这么一直拖着。</p> <p>前两天终于有可以随时操作的且重现几率非常高的机器了，测试了一下，发现一个规律：只要在调用 InternetOpenURL 之前调用过 SHGetFolderPath，此问题的重现几率就非常高；如果没有调用过 SHGetFolderPath，则基本不出现。</p> <p>目前网上找到的一个几乎唯一的帖子是 <a title="http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/2982efc6-8403-4577-9dba-ad5cfdf01753" href="http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/2982efc6-8403-4577-9dba-ad5cfdf01753" target="_blank">http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/2982efc6-8403-4577-9dba-ad5cfdf01753</a>，现象几乎一模一样。只可惜没有有价值的回复。该文章的作者指出的 VPN 等网络原因好像不是关键，在我这里是很普通的局域网，一样能出现。</p> <p>测试代码如下：</p> <p>#include &lt;Windows.h&gt;<br>#include &lt;tchar.h&gt;<br>#include &lt;ShlObj.h&gt;  <p>#include &lt;WinInet.h&gt;<br>#pragma comment(lib, "wininet.lib")  <p>#define URL _T("http://www.baidu.com/") <p>int main()<br>{<br>&nbsp;&nbsp;&nbsp; TCHAR szCommonAppData[MAX_PATH];<br>&nbsp;&nbsp;&nbsp; SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, szCommonAppData);  <p>&nbsp;&nbsp;&nbsp; HINTERNET hInternet = InternetOpen(_T("WCU"), INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);  <p>&nbsp;&nbsp;&nbsp; if (hInternet == NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp; }  <p>&nbsp;&nbsp;&nbsp; HINTERNET hInternetFile = InternetOpenUrl(hInternet, URL, NULL, 0, INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD, 0);  <p>&nbsp;&nbsp;&nbsp; if (hInternetFile == NULL)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InternetCloseHandle(hInternet);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp; }  <p>&nbsp;&nbsp;&nbsp; InternetCloseHandle(hInternetFile);<br>&nbsp;&nbsp;&nbsp; InternetCloseHandle(hInternet);  <p>&nbsp;&nbsp;&nbsp; return 0;<br>} <p>在能够出现此问题的机器上，Ctrl + F5 直接运行，几乎每次必现；如果 F5 调试运行，则几率小一点，但是跑个七八次左右基本上能出现。目前 XP 32/64 上都有发现这个问题，Vista/Win7 上暂时没有发生此现象。（如果 InternetOpenURL 换成 InternetConnect、HttpOpenRequest、HttpSendrequest，则会 crash 在 HttpSendRequest 内。） <p>附件是一个测试工程，附带上了 Debug、Release 版本的 EXE、PDB 文件以及 Crash 时的 Dump 文件。请有心人帮忙看看。^_^<br><a href="http://www.cppblog.com/Files/Streamlet/InternetOpenURLCrashTest.rar">点击下载</a></p> <p>可是，如果这个问题确实存在，为什么网上查到的相关内容这么少呢？奇怪~</p><img src ="http://www.cppblog.com/Streamlet/aggbug/124787.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2010-08-26 11:19 <a href="http://www.cppblog.com/Streamlet/archive/2010/08/26/124787.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>哥终于悟出了UAC编程的精髓</title><link>http://www.cppblog.com/Streamlet/archive/2010/06/01/116930.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 01 Jun 2010 13:24:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2010/06/01/116930.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/116930.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2010/06/01/116930.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/116930.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/116930.html</trackback:ping><description><![CDATA[<p><font color="#0080ff"><strong>单一用户休想改变全局状态！</strong></font></p> <p>好讨厌，ri啊ri。。。</p> <p>各位有没有突破方法：找到一个任何用户都可以可靠读写的位置？</p><img src ="http://www.cppblog.com/Streamlet/aggbug/116930.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2010-06-01 21:24 <a href="http://www.cppblog.com/Streamlet/archive/2010/06/01/116930.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vista/Win7 句柄表地址</title><link>http://www.cppblog.com/Streamlet/archive/2009/11/17/101227.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Tue, 17 Nov 2009 11:18:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2009/11/17/101227.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/101227.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2009/11/17/101227.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/101227.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/101227.html</trackback:ping><description><![CDATA[<p>网上提得较多的是 2K/XP 的句柄表，以及句柄分配算法。其中 Win2K 的句柄表在 _EPROCESS + 0x128 处，WinXP 在 _EPROCESS + 0x0c4 处。Vista 和 Win7 找遍了 Internet 没找到，于是只好下载符号表，装系统自己找。其实也就 dt _EPROCESS 一下了。Vista 在 _EPROCESS + 0x0dc 处，Win7 在 _EPROCESS + 0x0f4 处。以上均是 32 位系统下的地址。句柄分配算法在 Vista 和 Win7 中都没有变化，和 XP 一样（至少我的测试结果是这样的）。</p> <p>小记一笔。明天继续看 64 位的。</p>
<p>
==================================================<br />
WinXP x64: 0x158<br />
Vista x64: 0x160<br />
Win7 x64: 0x200<br />
</p><img src ="http://www.cppblog.com/Streamlet/aggbug/101227.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2009-11-17 19:18 <a href="http://www.cppblog.com/Streamlet/archive/2009/11/17/101227.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win7 UAC 的一些奇怪现象</title><link>http://www.cppblog.com/Streamlet/archive/2009/10/22/99212.html</link><dc:creator>溪流</dc:creator><author>溪流</author><pubDate>Thu, 22 Oct 2009 11:50:00 GMT</pubDate><guid>http://www.cppblog.com/Streamlet/archive/2009/10/22/99212.html</guid><wfw:comment>http://www.cppblog.com/Streamlet/comments/99212.html</wfw:comment><comments>http://www.cppblog.com/Streamlet/archive/2009/10/22/99212.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Streamlet/comments/commentRss/99212.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Streamlet/services/trackbacks/99212.html</trackback:ping><description><![CDATA[<p>测试程序是一个 InstallShield 做的 Windows Installer 安装程序，发布方式为单一 EXE，Menifest 中指定 requireAdministrator，在 Win7 标准用户下双击执行，在弹出的 UAC 对话框中选择一个管理员用户。这种情况下，对于这个程序来说，HKEY_CURRENT_USER 为管理员用户的数据，SH… 系列 API 的执行环境也是管理员用户，如 SHGetFolderPath 取 AppData 目录取到的是管理员用户的 AppData 目录。</p> <p>但如果从控制面板=&gt;添加删除程序（程序和功能）里执行（相当于执行 msiexec /…），在过一会儿弹出的 UAC 对话框中选管理员用户，此时 HKEY_CURRENT_USER 为管理员用户的数据，但是 SH… 系列 API 的执行环境却是标准用户。查看两个 MsiExec.exe 进程，其中一个是 System 用户的，也就是 Windows Installer 服务对应的进程；另一个是管理员用户的，但是用 Process Explorer 查看，它的所有环境路径全是标准用户的。</p> <p>觉得有点奇怪，是以记之。^_^</p><img src ="http://www.cppblog.com/Streamlet/aggbug/99212.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Streamlet/" target="_blank">溪流</a> 2009-10-22 19:50 <a href="http://www.cppblog.com/Streamlet/archive/2009/10/22/99212.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>