<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>九月鹰飞</title><link>http://blog.vckbase.com/iwaswzq/</link><description>慎独自修，忠恕宽容，至诚尽性。</description><managingEditor>九月鹰飞</managingEditor><dc:language>af</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>九月鹰飞</dc:creator><title>求多边形面积的简单方法</title><link>http://blog.vckbase.com/iwaswzq/archive/2007/01/10/23949.html</link><pubDate>Wed, 10 Jan 2007 04:53:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2007/01/10/23949.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/23949.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2007/01/10/23949.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/23949.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/23949.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;不论凸多边形还是凹多边形，都可以采用向量叉乘的方法来求面积。代码如下&lt;BR&gt;#include "stdafx.h"&lt;BR&gt;#include "stdio.h"&lt;BR&gt;typedef struct tagPoint&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;double x;&lt;BR&gt;&amp;nbsp;double y;&lt;BR&gt;}FPoint;&lt;BR&gt;double MianJi(FPoint *pArray,int NumPoint)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(NumPoint&amp;lt;3)return 0;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;double sum = 0;&lt;BR&gt;&amp;nbsp;int m = 1;&lt;BR&gt;&amp;nbsp;for(;;)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(NumPoint &amp;lt; 3)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;FPoint r1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;r1.x = pArray[m].x - pArray[0].x;&lt;BR&gt;&amp;nbsp;&amp;nbsp;r1.y = pArray[m].y - pArray[0].y;&lt;BR&gt;&amp;nbsp;&amp;nbsp;FPoint r2;&lt;BR&gt;&amp;nbsp;&amp;nbsp;r2.x = pArray[m+1].x - pArray[m].x;&lt;BR&gt;&amp;nbsp;&amp;nbsp;r2.y = pArray[m+1].y - pArray[m].y;&lt;BR&gt;&amp;nbsp;&amp;nbsp;sum += (r1.x * r2.y - r2.x * r1.y);&lt;BR&gt;&amp;nbsp;&amp;nbsp;m++;&lt;BR&gt;&amp;nbsp;&amp;nbsp;NumPoint --;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;double result = (sum&amp;gt;0)?(sum/2.):(-sum/2.);&lt;BR&gt;&amp;nbsp;return result;&lt;BR&gt;}&lt;BR&gt;//test&lt;BR&gt;void main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;FPoint pArray1[4]={{1,1},{1,2},{2,2},{2,1}};&lt;BR&gt;&amp;nbsp;FPoint pArray2[5]={{0,0},{0,2},{2,2},{1,1},{2,0}};&lt;BR&gt;&amp;nbsp;FPoint pArray3[9]={{0,0},{2,0},{2,1},{0,1},{0,2},{-2,0},{-1,0},{-1,-1},{0,-1}};&lt;BR&gt;&amp;nbsp;printf("Area1 = %f\n",MianJi(pArray1,4));&lt;BR&gt;&amp;nbsp;printf("Area2 = %f\n",MianJi(pArray2,5));&lt;BR&gt;&amp;nbsp;printf("Area3 = %f\n",MianJi(pArray3,9));&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;iwaswzq 2007/1/10&lt;BR&gt;&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/23949.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>一个数独小游戏。[附送源代码]</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/12/31/23788.html</link><pubDate>Sun, 31 Dec 2006 03:10:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/12/31/23788.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/23788.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/12/31/23788.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/23788.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/23788.html</trackback:ping><description>&amp;lt;PRE&amp;gt;&lt;BR&gt;画面简单，不过已经能玩了。喜欢数独的朋友可以拿来消磨时间。喜欢编程的朋友可以拿去进一步改进。不过记得保留我的名字。有些功能感觉没有必要，不过懒得再折腾这个破玩意了。&lt;BR&gt;&lt;BR&gt;下载地址：&lt;BR&gt;&lt;A href="http://blog.vckbase.com/Files/iwaswzq/ShuDu.rar"&gt;http://blog.vckbase.com/Files/iwaswzq/ShuDu.rar&lt;/A&gt;&lt;BR&gt;&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/23788.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>Windows 语音编程初步</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22382.html</link><pubDate>Tue, 12 Sep 2006 14:49:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22382.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/22382.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22382.html#Feedback</comments><slash:comments>21</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/22382.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/22382.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;Windows 语音编程初步&lt;/P&gt;
&lt;P&gt;一、SAPI简介&lt;/P&gt;
&lt;P&gt;软件中的语音技术包括两方面的内容，一个是语音识别(speech recognition) 和语音合成(speech synthesis)。这两个技术都需要语音引擎的支持。微软推出的应用编程接口API，虽然现在不是业界标准，但是应用比较广泛。&lt;/P&gt;
&lt;P&gt;SAPI全称 The Microsoft Speech API.相关的SR和SS引擎位于Speech SDK开发包中。这个语音引擎支持多种语言的识别和朗读，包括英文、中文、日文等。&lt;/P&gt;
&lt;P&gt;SAPI包括以下组件对象（接口）：&lt;/P&gt;
&lt;P&gt;（1）Voice Commands API。对应用程序进行控制，一般用于语音识别系统中。识别某个命令后，会调用相关接口是应用程序完成对应的功能。如果程序想实现语音控制，必须使用此组对象。&lt;BR&gt;（2）Voice Dictation API。听写输入，即语音识别接口。&lt;BR&gt;（3）Voice Text API。完成从文字到语音的转换，即语音合成。&lt;BR&gt;（4）Voice Telephone API。语音识别和语音合成综合运用到电话系统之上，利用此接口可以建立一个电话应答系统，甚至可以通过电话控制计算机。&lt;BR&gt;（5）Audio Objects API。封装了计算机发音系统。&lt;/P&gt;
&lt;P&gt;SAPI是架构在COM基础上的，微软还提供了ActiveX控件，所以不仅可用于一般的windows程序，还可以用于网页、VBA甚至EXCEL的图表中。如果对COM感到陌生，还可以使用微软的C++ WRAPPERS，它用C++类封装了语音SDK COM对象。&lt;/P&gt;
&lt;P&gt;二、安装SAPI SDK。&lt;/P&gt;
&lt;P&gt;首先从这个站点下载开发包：&lt;A href="http://www.microsoft.com/speech/download/sdk51"&gt;http://www.microsoft.com/speech/download/sdk51&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;Microsoft Speech SDK 5.1添加了Automation支持。所以可以在VB,ECMAScript等支持Automation的语言中使用。&lt;/P&gt;
&lt;P&gt;版本说明：&lt;BR&gt;Version: 5.1 &lt;BR&gt;发布日期: 8/8/2001 &lt;BR&gt;语音: English &lt;BR&gt;下载尺寸: 2.0 MB - 288.8 MB&lt;/P&gt;
&lt;P&gt;这个SDK开发包还包括了可以随便发布的英文和中文的语音合成引擎(TTS)，和英文、中文、日文的语音识别引擎(SR)。&lt;/P&gt;
&lt;P&gt;系统要求98以上版本。编译开发包中的例子程序需要vc6以上环境。&lt;/P&gt;
&lt;P&gt;******下载说明******：&lt;BR&gt;（1）如果要下载例子程序，说明文档，SAPI以及用于开发的美国英语语音引擎，需要下载SpeechSDK51.exe，大约68M。&lt;BR&gt;（2）如果想要使用简体中文和日文的语音引擎，需要下载SpeechSDK51LangPack.exe。大约82M。&lt;BR&gt;（3）如果想要和自己的软件一起发布语音引擎，需要下载SpeechSDK51MSM.exe，大约132M。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; （在这个地址，我未能成功下载）。&lt;BR&gt;（4）如果要获取XP下的 Mike 和 Mary 语音，下载Sp5TTIntXP.exe。大约3.5M。&lt;BR&gt;（5）如果要获取开发包的文档说明，请下载sapi.chm。大约2.3M。这个在sdk51里面已经包含。&lt;/P&gt;
&lt;P&gt;下载完毕后，首先安装SpeechSDK51.exe，然后安装中文语言补丁包SpeechSDK51LangPack，然后展开&lt;BR&gt;msttss22l，自动将所需dll安装到系统目录。&lt;/P&gt;
&lt;P&gt;三、配置vc环境&lt;/P&gt;
&lt;P&gt;在vc6.0的环境下编译语音工程，首先要配置编译环境。假设sdk安装在d:\Microsoft Speech SDK 5.1\路径下，打开工程设置对话框，在c/c++栏中选择Preprocessor分类，然后在"附加包含路径"中输入&lt;BR&gt;d:\Microsoft Speech SDK 5.1\include &lt;BR&gt;告诉vc编译程序所需的SAPI头文件的位置。&lt;BR&gt;然后切换到LINK栏，在Input分类下的附加库路径中输入：&lt;BR&gt;d:\Microsoft Speech SDK 5.1\lib\i386&lt;BR&gt;使vc在链接的时候能够找到sapi.lib。&lt;/P&gt;
&lt;P&gt;四、语音合成的应用。即使用SAPI实现TTS(Text to Speech)。&lt;/P&gt;
&lt;P&gt;1、首先要初始化语音接口，一般有两种方式：&lt;BR&gt;&amp;nbsp;&amp;nbsp; ISpVoice* pVoice;&lt;BR&gt;&amp;nbsp;&amp;nbsp; ::CoInitialize(NULL);&lt;BR&gt;&amp;nbsp;&amp;nbsp; HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (void **)&amp;amp;pVoice);&lt;BR&gt;&amp;nbsp;&amp;nbsp; 然后就可以使用这个指针调用SAPI函数了，例如&lt;BR&gt;&amp;nbsp;&amp;nbsp; pVoice-&amp;gt;SetVolume(50);//设置音量&lt;BR&gt;&amp;nbsp;&amp;nbsp; pVoice-&amp;gt;Speak(str.AllocSysString(),SPF_ASYNC,NULL);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 另外也可以使用如下方式：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpVoice&amp;gt;&amp;nbsp;&amp;nbsp; m_cpVoice;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HRESULT&amp;nbsp; hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );&lt;BR&gt;&amp;nbsp;&amp;nbsp; 在下面的例子中都用这个m_cpVoice变量。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; CLSID_SpVoice的定义位于SPAI.H中。&lt;/P&gt;
&lt;P&gt;2、获取/设置输出频率。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; SAPI朗读文字的时候，可以采用多种频率方式输出声音，比如：&lt;BR&gt;&amp;nbsp;&amp;nbsp; 8kHz 8Bit Mono、8kHz 8Bit Stereo、44kHz 16Bit Mono、44kHz 16Bit Stereo等。在音调上有所差别。具体可以参考sapi.h。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 可以使用如下代码获取当前的配置：&lt;BR&gt;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpStreamFormat&amp;gt; cpStream;&lt;BR&gt;&amp;nbsp;&amp;nbsp; HRESULT hrOutputStream = m_cpVoice-&amp;gt;GetOutputStream(&amp;amp;cpStream);&lt;BR&gt;&amp;nbsp;&amp;nbsp; if (hrOutputStream == S_OK)&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CSpStreamFormat Fmt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; hr = Fmt.AssignFormat(cpStream);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (SUCCEEDED(hr))&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SPSTREAMFORMAT 是一个ENUM类型，定义位于SPAI.H中。每一个值对应了不同的频率设置。例如 SPSF_8kHz8BitStereo&amp;nbsp; = 5&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 通过如下代码设置当前朗读频率：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpAudio&amp;gt;&amp;nbsp;&amp;nbsp; m_cpOutAudio; //声音输出接口&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &amp;amp;m_cpOutAudio ); //创建接口&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SPSTREAMFORMAT eFmt = 21; //SPSF_22kHz 8Bit Stereo&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CSpStreamFormat Fmt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Fmt.AssignFormat(eFmt);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if ( m_cpOutAudio )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;hr = m_cpOutAudio-&amp;gt;SetFormat( Fmt.FormatId(), Fmt.WaveFormatExPtr() );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&amp;nbsp; hr = E_FAIL;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( SUCCEEDED( hr ) )&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetOutput( m_cpOutAudio, FALSE );&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;3、获取/设置播放所用语音。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 引擎中所用的语音数据文件一般保存在SpeechEngines下的spd或者vce文件中。安装sdk后，在注册表中保存了可用的语音，比如英文的男/女，简体中文的男音等。位置是：&lt;BR&gt;&amp;nbsp;&amp;nbsp; HKEY_LOCAL_MACHINE\Software\Microsoft\Speech\Voices\Tokens&lt;BR&gt;如果安装在中文操作系统下，则缺省所用的朗读语音是简体中文。SAPI的缺点是不能支持中英文混读，在朗读中文的时候，遇到英文，只能逐个字母读出。所以需要程序自己进行语音切换。&lt;/P&gt;
&lt;P&gt;(1) 可以采用如下的函数把当前SDK支持的语音填充在一个组合框中：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // SAPI5 helper function in sphelper.h &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HWND hWndCombo = GetDlgItem( hWnd, IDC_COMBO_VOICES ); //组合框句柄&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HRESULT hr = SpInitTokenComboBox( hWndCombo , SPCAT_VOICES );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个函数是通过IEnumSpObjectTokens接口枚举当前可用的语音接口，把接口的说明文字添加到组合框中，并且把接口的指针作为LPARAM&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 保存在组合框中。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一定要记住最后程序退出的时候，释放组合框中保存的接口：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SpDestroyTokenComboBox( hWndCombo );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个函数的原理就是逐个取得combo里面每一项的LPARAM数据，转换成IUnknown接口指针，然后调用Release函数。&lt;BR&gt;(2) 当组合框选择变化的时候，可以用下面的函数获取用户选择的语音：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo );&lt;/P&gt;
&lt;P&gt;(3) 用下面的函数获取当前正在使用的语音：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpObjectToken&amp;gt; pOldToken;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HRESULT hr = m_cpVoice-&amp;gt;GetVoice( &amp;amp;pOldToken );&lt;BR&gt;(4) 当用户选择的语音和当前正在使用的不一致的时候，用下面的函数修改：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (pOldToken != pToken)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 首先结束当前的朗读，这个不是必须的。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; HRESULT hr = m_cpVoice-&amp;gt;Speak( NULL, SPF_PURGEBEFORESPEAK, 0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (SUCCEEDED (hr) )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; hr = m_cpVoice-&amp;gt;SetVoice( pToken );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;(5) 也可以直接使用函数SpGetTokenFromId获取指定voice的Token指针，例如：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WCHAR pszTokenId[] = L"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Speech\\Voices\\Tokens\\MSSimplifiedChineseVoice";&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SpGetTokenFromId(pszTokenID , &amp;amp;pChineseToken);&lt;/P&gt;
&lt;P&gt;4、开始/暂停/恢复/结束当前的朗读&lt;BR&gt;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp; 要朗读的文字必须位于宽字符串中，假设位于szWTextString中，则：&lt;BR&gt;&amp;nbsp;&amp;nbsp; 开始朗读的代码：&lt;BR&gt;&amp;nbsp;&amp;nbsp; hr = m_cpVoice-&amp;gt;Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );&lt;BR&gt;&amp;nbsp;&amp;nbsp; 如果要解读一个XML文本，用：&lt;BR&gt;&amp;nbsp;&amp;nbsp; hr = m_cpVoice-&amp;gt;Speak( szWTextString, SPF_ASYNC | SPF_IS_XML, 0 );&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 暂停的代码：&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;Pause();&lt;BR&gt;&amp;nbsp;&amp;nbsp; 恢复的代码：&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;Resume();&lt;BR&gt;&amp;nbsp;&amp;nbsp; 结束的代码：（上面的例子中已经给出了）&lt;BR&gt;&amp;nbsp;&amp;nbsp; hr = m_cpVoice-&amp;gt;Speak( NULL, SPF_PURGEBEFORESPEAK, 0);&lt;/P&gt;
&lt;P&gt;5、跳过部分朗读的文字&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 在朗读的过程中，可以跳过部分文字继续后面的朗读，代码如下：&lt;BR&gt;&amp;nbsp;&amp;nbsp; ULONG ulGarbage = 0;&lt;BR&gt;&amp;nbsp;&amp;nbsp; WCHAR szGarbage[] = L"Sentence";&lt;BR&gt;&amp;nbsp;&amp;nbsp; hr = m_cpVoice-&amp;gt;Skip( szGarbage, SkipNum, &amp;amp;ulGarbage );&lt;BR&gt;&amp;nbsp;&amp;nbsp; SkipNum是设置要跳过的句子数量，值可以是正/负。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 根据sdk的说明，目前SAPI仅仅支持SENTENCE这个类型。SAPI是通过标点符号来区分句子的。&lt;/P&gt;
&lt;P&gt;6、播放WAV文件。SAPI可以播放WAV文件，这是通过ISpStream接口实现的：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpStream&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cpWavStream;&lt;BR&gt;&amp;nbsp;&amp;nbsp; WCHAR&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; szwWavFileName[NORM_SIZE] = L"";;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; USES_CONVERSION;&lt;BR&gt;&amp;nbsp;&amp;nbsp; wcscpy( szwWavFileName, T2W( szAFileName ) );//从ANSI将WAV文件的名字转换成宽字符串&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; //使用sphelper.h 提供的这个函数打开 wav 文件，并得到一个 IStream 指针&lt;BR&gt;&amp;nbsp;&amp;nbsp; hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &amp;amp;cpWavStream );&lt;BR&gt;&amp;nbsp;&amp;nbsp; if( SUCCEEDED( hr ) )&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SpeakStream( cpWavStream, SPF_ASYNC, NULL );//播放WAV文件&lt;BR&gt;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;7、将朗读的结果保存到wav文件&lt;BR&gt;&amp;nbsp;&amp;nbsp; TCHAR szFileName[256];//假设这里面保存着目标文件的路径&lt;BR&gt;&amp;nbsp;&amp;nbsp; USES_CONVERSION;&lt;BR&gt;&amp;nbsp;&amp;nbsp; WCHAR m_szWFileName[MAX_FILE_PATH];&lt;BR&gt;&amp;nbsp;&amp;nbsp; wcscpy( m_szWFileName, T2W(szFileName) );//转换成宽字符串&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; //创建一个输出流，绑定到wav文件&lt;BR&gt;&amp;nbsp;&amp;nbsp; CSpStreamFormat OriginalFmt;&lt;BR&gt;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpStream&amp;gt;&amp;nbsp; cpWavStream;&lt;BR&gt;&amp;nbsp;&amp;nbsp; CComPtr&amp;lt;ISpStreamFormat&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; cpOldStream;&lt;BR&gt;&amp;nbsp;&amp;nbsp; HRESULT hr = m_cpVoice-&amp;gt;GetOutputStream( &amp;amp;cpOldStream );&lt;BR&gt;&amp;nbsp;&amp;nbsp; if (hr == S_OK) hr = OriginalFmt.AssignFormat(cpOldStream);&lt;BR&gt;&amp;nbsp;&amp;nbsp; else&amp;nbsp; hr = E_FAIL;&lt;BR&gt;&amp;nbsp;&amp;nbsp; // 使用sphelper.h中提供的函数创建 wav 文件&lt;BR&gt;&amp;nbsp;&amp;nbsp; if (SUCCEEDED(hr))&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS, &amp;amp;cpWavStream, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() ); &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; if( SUCCEEDED( hr ) )&lt;BR&gt;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //设置声音的输出到 wav 文件，而不是 speakers&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetOutput(cpWavStream, TRUE);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //开始朗读&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //等待朗读结束&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;WaitUntilDone( INFINITE );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; cpWavStream.Release();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //把输出重新定位到原来的流&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetOutput( cpOldStream, FALSE );&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;BR&gt;8、设置朗读音量和速度&lt;BR&gt;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetVolume((USHORT)hpos); //设置音量，范围是 0 - 100&lt;BR&gt;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetRate(hpos);&amp;nbsp;&amp;nbsp;//设置速度，范围是 -10 - 10&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; hpos的值一般位于&lt;/P&gt;
&lt;P&gt;9、设置SAPI通知消息。SAPI在朗读的过程中，会给指定窗口发送消息，窗口收到消息后，可以主动获取SAPI的事件，&lt;BR&gt;&amp;nbsp;&amp;nbsp; 根据事件的不同，用户可以得到当前SAPI的一些信息，比如正在朗读的单词的位置，当前的朗读口型值（用于显&lt;BR&gt;&amp;nbsp;&amp;nbsp; 示动画口型，中文语音的情况下并不提供这个事件）等等。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 要获取SAPI的通知，首先要注册一个消息：&lt;BR&gt;&amp;nbsp;&amp;nbsp; m_cpVoice-&amp;gt;SetNotifyWindowMessage( hWnd, WM_TTSAPPCUSTOMEVENT, 0, 0 );&lt;BR&gt;&amp;nbsp;&amp;nbsp; 这个代码一般是在主窗口初始化的时候调用，hWnd是主窗口（或者接收消息的窗口）句柄。WM_TTSAPPCUSTOMEVENT&lt;BR&gt;&amp;nbsp;&amp;nbsp; 是用户自定义消息。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 在窗口响应WM_TTSAPPCUSTOMEVENT消息的函数中，通过如下代码获取sapi的通知事件：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CSpEvent&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; event;&amp;nbsp; // 使用这个类，比用 SPEVENT结构更方便&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; while( event.GetFrom(m_cpVoice) == S_OK )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; switch( event.eEventId )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 。。。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; eEventID有很多种，比如SPEI_START_INPUT_STREAM表示开始朗读，SPEI_END_INPUT_STREAM表示朗读结束等。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 可以根据需要进行判断使用。&lt;/P&gt;
&lt;P&gt;四、结束语&lt;BR&gt;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp; SAPI的功能很多，比如语音识别、使用语法分析等，由于条件和精力有限，我未能一一尝试，感兴趣的朋友可以自己安装一个研究一下。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 另外提供一个简单例子程序的下载，位置是：&lt;BR&gt;&amp;nbsp;&amp;nbsp; &lt;A href="ftp://vckbase:vckbase@210.192.111.117/user/iwaswzq/Universe.rar"&gt;ftp://vckbase:vckbase@210.192.111.117/user/iwaswzq/Universe.rar&lt;/A&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 再次感谢砸玻璃。&lt;BR&gt;&amp;nbsp; &lt;BR&gt;============================================&lt;BR&gt;iwaswzq 初稿于 2006/7/26 ， 修改于 2006/9/7。&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/22382.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>谈谈父窗口和所有者窗口</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22380.html</link><pubDate>Tue, 12 Sep 2006 14:43:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22380.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/22380.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/09/12/22380.html#Feedback</comments><slash:comments>14</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/22380.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/22380.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;一、概念和区别&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在windows系统中，每个窗口对象都对应有一个数据结构，形成一个list链表。系统的窗口管理器通过这个list来获取窗口信息和管理每个窗口。这个数据结构中有四个数据用来构建list，即child、sibling、parent、owner四个域。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 所以我们可以看到，窗口之间的关系有两种：owner-owned 关系和 parent-child关系。前者称之为拥有/被拥有关系，后者称之为父/子关系。在这篇文字中，我把owner窗口称之所有者窗口。换句话说，一个窗口在有一个父窗口（parent)的同时，还可能被不同的窗口拥有（owner)，也可以有自己的子窗口(child)。在MFC 的CWnd类中，所有者窗口保存在m_hWndOwner成员变量中，父窗口则保存在m_hParent中，但是这两个值并不一定和窗口对象数据结构中的值相对应。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 窗口之间的关系，决定了窗口的外在表现。比如显示、销毁等。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果一个窗口数据的owner域非NULL，则它和该窗口建立了owner-owned 关系，拥有关系决定了：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （1）被拥有的窗口永远显示在拥有它的那个窗口的前面；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （2）当所有者窗口最小化的时候，它所拥有的窗口都会被隐藏；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （3）当所有者窗口被销毁的时候，它所拥有的窗口都会被销毁。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 需要注意的是，隐藏所有者窗口并不会影响它所拥有的窗口的可见状态。比如：如果窗口 A 拥有窗口B,窗口B拥有窗口C,则当窗口A最小化的时候，窗口B被隐藏，但是窗口 C还是可见。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果一个窗口的parent域非NULL，则它和该窗口之间就建立了parent-child关系。父子决定了：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （1）窗口在屏幕上面的显示位置。父窗口提供了用来定位子窗口的坐标系统，一个子窗口只能显示在它的父窗口的客户区中，之外的部分将被裁减。这个裁减法则决定了如果父窗口不可见，则子窗口肯定不可见。如果父窗口移动到了屏幕之外，子窗口也一样。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （2）当父窗口被隐藏时，它的所有子窗口也被隐藏。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; （3）父窗口被销毁的时候，它所拥有的子窗口都会被销毁。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 注意！最小化父窗口不会影响子窗口的可见状态，子窗口会随着父窗口被最小化，但是它的WS_VISIBLE属性不会变。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Windows系统为什么要使用两种关系呢？这是为了更加灵活的管理窗口。举个例子：组合框（combobox)的下拉列表框（list box）可以超出组合框的父窗口的客户区，这样有利于显示，因此系统创建该list box的时候，是作为控制台窗口（desktop window）的子窗口，它的父窗口hWndParent是NULL，这样，list box的显示区域是限制在整个屏幕内，但是该list box的所有者却是组合框的第一个非子窗口祖先（比如对话框），当它的所有者窗口销毁后，该 list box自动销毁。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 另外，窗口之间消息的传递也和窗口关系有关，通常，一个窗口会把自己的通知消息发送给它的父窗口，但不全是这样，比如，CToolBar发送通知消息给它的所有者窗口而不是父窗口。这样以来，就可以允许工具条作为一个窗口（比如一个 OLE 容器程序窗口）的子窗口的同时，能够给另一个窗口（比如in-place框架窗口）发送消息。至于某类窗口到底是把消息发送给谁，是父窗口还是所有者窗口，microsoft并没有明示。还有，在现场（in-place）编辑的情况下，当一个 server 窗口激活或者失效的时候，框架窗口所拥有的子窗口自动隐藏或者显示，这也是通过直接调用SetOwner函数实现的。&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &lt;BR&gt;二、窗口类型的说明和限制&lt;/P&gt;
&lt;P&gt;（1）控制台窗口（desktop window）。这是系统最早创建的窗口。可以认为它是所有 WS_OVERLAPPED 类型窗口的所有者和父窗口。Kyle Marsh在他的文章&amp;#8220;Win32 Window Hierarchy and Styles&amp;#8221;中指出，当系统初始化的时候，它首先创建控制台窗口，大小覆盖整个屏幕。所有其它窗口都在这个控制台窗口上面显示。窗口管理器所用的窗口list中第一个就是这个控制台。它的下一层窗口叫做顶级窗口（top-level），顶级窗口是指所有非child、没有父窗口，或者父窗口是desktop的窗口，它们没有WS_CHILD属性。&lt;/P&gt;
&lt;P&gt;（2）WS_OVERLAPPED类型的窗口可以显示在屏幕的任何地方。它们的所有者窗口是控制台。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Overlapped 类型的窗口属于顶级窗口，一般作为应用程序的主窗口。不论是否给出了WS_CAPTION、WS_BORDER属性，这类窗口创建后都有标题栏和边框。Overlapped窗口可以拥有其它顶级窗口或者被其它顶级窗口所拥有。所有overlapped窗口都有WS_CLIPSIBLINGS属性。系统可以自动设置 overlapped窗口的大小和初始位置。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 当系统 shuts down的时候，它将销毁所有overlapped类型的窗口。&lt;/P&gt;
&lt;P&gt;（3）WS_POPUP类型的窗口可以显示在屏幕任何地方，它们一般没有父窗口，但是如果明确调用SetParent，这类窗口也可以有父窗口。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WS_POPUP类型的窗口的所有者是在CreateWindow函数中通过设置hWndParent参数给定的，如果hWndParent不是子窗口，则该窗口就成为这个新的弹出式窗口的owner，否则，系统从hWndParent的父窗口向上找，直到找到第一个非子窗口，把它作为该弹出窗口的owner。当owner窗口销毁的时候，系统自动销毁这个弹出窗口。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Pop-up类型的窗口也属于顶级窗口，它和 overlapped 窗口的主要区别是弹出式窗口不需要有标题栏，也不必有边框。弹出式可以拥有其它顶级窗口或者被拥有。所有弹出式窗口也都有 WS_CLIPSIBLINGS属性。&lt;/P&gt;
&lt;P&gt;（4）所有者窗口（owner)只能是 overlapped 或者 pop-up 类型的窗口，子窗口不能是所有者窗口，也就是说子窗口不能拥有其它窗口。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; overlapped 或者 pop-up 类型的窗口在拥有其它窗口的同时，也可以被拥有。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在使用CreateWindowEx创建 WS_OVERLAPPED 或者 WS_POPUP类型的窗口时，可以在 hwndParent 参数中给出它的所有者窗口的句柄。如果 hwndParent 给出的是一个child 类型的窗口句柄，则系统自动将新创建窗口的所有权交给该子窗口的顶级父窗口。在这种情况下，参数hwndParent被保存在新建窗口的parent域中，而它的所有者窗口句柄则保存在owner域中。&lt;/P&gt;
&lt;P&gt;（5）缺省情况下，对话框和消息框属于 owned 窗口，除非在创建它们的时候明确给出了WS_CHILD属性，（比如对话框中嵌入对话框的情形）&lt;BR&gt;否则由系统负责给它们指定owner窗口。需要注意的是，一旦创建了owned类型的窗口，就无法再改变其所有关系，因为WIN32没有没有提供改变窗口所有者的方法。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 而且在Win32中，由于有多线程的存在，所以要注意保证父子窗口或者owner/owned 窗口要同属于一个线程。&lt;/P&gt;
&lt;P&gt;（6）对于 WS_CHILD类型的窗口，它的父窗口就是它的所有者窗口。一个子窗口的父窗口也是在CreateWindow函数中用hWndParent参数指定的。子窗口只能在父窗口的客户区中显示，并随父窗口一起销毁。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 子窗口必须有一个父窗口，这是它和overlapped 以及 pop-up 窗口之间的主要区别。父窗口可以是顶级窗口，也可以是其它子窗口。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;三、几个相关函数的说明&lt;/P&gt;
&lt;P&gt;（1）获取/设置所有者窗口&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; win32 API提供了函数GetWindow函数（GW_OWNER 标志）来获取一个窗口的所有者窗口句柄。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; GetWindow(hWnd, GW_OWNER)永远返回窗口的所有者(owner)。对于子窗口，函数返回 NULL，因为它们的父窗口就相当于所有者（注意，是&amp;#8220;相当于&amp;#8221;）。因为Windows系统没有维护子窗口的所有者信息。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; MFC中则是通过如下函数得到所有者窗口指针：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _AFXWIN_INLINE CWnd* CWnd::GetOwner() const&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { return m_hWndOwner != NULL ? CWnd::FromHandle(m_hWndOwner) : GetParent(); }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从上述代码我们可以看出，它返回的值和GetWindow返回的有所区别，如果当前窗口没有owner，那么将返回它的父窗口指针。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 但是Windows没有提供改变窗口所有者的方法。MFC中则提供了改变所有者的方法：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _AFXWIN_INLINE void CWnd::SetOwner(CWnd* pOwnerWnd)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { m_hWndOwner = pOwnerWnd != NULL ? pOwnerWnd-&amp;gt;m_hWnd : NULL; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 另外，mfc还提供了CWnd::GetSafeOwner( CWnd* pParent, HWND* pWndTop );函数，可以用来得到参数pParent的第一个非child属性的父窗口指针。如果这个参数是NULL，则返回当前线程的主窗口(通过AfxGetMainWnd得到)。框架经常使用这个函数查找对话框或者属性页的所有者窗口。&lt;/P&gt;
&lt;P&gt;（2）获取/设置父窗口&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WIN32 API给出了函数GetParent和SetParent。而mfc也是完全封装了这两个函数：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _AFXWIN_INLINE CWnd* CWnd::SetParent(CWnd* pWndNewParent)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::SetParent(m_hWnd,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;pWndNewParent-&amp;gt;GetSafeHwnd())); }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; _AFXWIN_INLINE CWnd* CWnd::GetParent() const&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 对于SetParent，msdn里面说明了父子窗口必须是同一个进程的。但是由于窗口句柄是系统全局唯一的，不属于同一个进程的情况下，也可以成功调用，但是后果未知。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; GetParent的返回值比较复杂，对于overlapped类型的窗口，它返回0，对于WS_CHILD类型，它返回其父窗口，对于WS_POPUP类型，它返回其所有者窗口，如果想得到创建它时所传递进去的那个hwndParent参数，应该用GetWindowWord(GWW_HWNDPARENT)函数。&lt;/P&gt;
&lt;P&gt;（3）GetWindowWord(hWnd, GWW_HWNDPARENT)返回一个窗口的父窗口，如果没有，则返回其所有者。&lt;/P&gt;
&lt;P&gt;（4）上面谈到，当一个owner窗口被最小化后，系统自动隐藏它所拥有的窗口。当owner窗口被恢复的时候，系统自动显示它所拥有的窗口。在这两种情况下，系统都会发送（send）WM_SHOWWINDOW消息给被拥有的窗口。某些时候，我们可能需要隐藏 owned窗口，但并不想最小化其所有者窗口，这时候，可以通过ShowOwnedPopups函数来实现，该函数设置或者删除当前窗口所拥有的窗口的WS_VISIBLE属性，然后发送WM_SHOWWINDOW消息更新窗口显示。&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;==========================&lt;BR&gt;iwaswzq 初稿于2005/5/19 ，修改于2006/9/12&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/22380.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>从内存中加载动态库(二)</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html</link><pubDate>Fri, 28 Jul 2006 04:57:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/21563.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21563.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/21563.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/21563.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;五、加载类的源代码。（编译环境vc6,win98）&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;typedef&amp;nbsp;&amp;nbsp; BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,&amp;nbsp; LPVOID );&lt;/P&gt;
&lt;P&gt;class CMemLoadDll&lt;BR&gt;{&lt;BR&gt;public:&lt;BR&gt;&amp;nbsp;CMemLoadDll();&lt;BR&gt;&amp;nbsp;~CMemLoadDll();&lt;BR&gt;&amp;nbsp;BOOL&amp;nbsp;&amp;nbsp;&amp;nbsp; MemLoadLibrary( void* lpFileData , int DataLength);&amp;nbsp; // Dll file data buffer&lt;BR&gt;&amp;nbsp;FARPROC MemGetProcAddress(LPCSTR lpProcName);&lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;BOOL isLoadOk;&lt;BR&gt;&amp;nbsp;BOOL CheckDataValide(void* lpFileData, int DataLength);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;void CopyDllDatas(void* pDest, void* pSrc);&lt;BR&gt;&amp;nbsp;BOOL FillRavAddress(void* pBase);&lt;BR&gt;&amp;nbsp;void DoRelocation(void* pNewBase);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; GetAlignedSize(int Origin, int Alignment); &lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;ProcDllMain pDllMain;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;private:&lt;BR&gt;&amp;nbsp;DWORD&amp;nbsp; pImageBase;&lt;BR&gt;&amp;nbsp;PIMAGE_DOS_HEADER pDosHeader;&lt;BR&gt;&amp;nbsp;PIMAGE_NT_HEADERS pNTHeader;&lt;BR&gt;&amp;nbsp;PIMAGE_SECTION_HEADER pSectionHeader;&lt;BR&gt;};&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;CMemLoadDll::CMemLoadDll()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;isLoadOk = FALSE;&lt;BR&gt;&amp;nbsp;pImageBase = NULL;&lt;BR&gt;&amp;nbsp;pDllMain = NULL;&lt;BR&gt;}&lt;BR&gt;CMemLoadDll::~CMemLoadDll()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(isLoadOk)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;ASSERT(pImageBase != NULL);&lt;BR&gt;&amp;nbsp;&amp;nbsp;ASSERT(pDllMain&amp;nbsp;&amp;nbsp; != NULL);&lt;BR&gt;&amp;nbsp;&amp;nbsp;//脱钩，准备卸载dll&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间，缺省位置0x10000000&lt;BR&gt;//返回值： 成功返回TRUE , 失败返回FALSE&lt;BR&gt;//lpFileData: 存放dll文件数据的缓冲区&lt;BR&gt;//DataLength: 缓冲区中数据的总长度&lt;BR&gt;BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(pImageBase != NULL)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&amp;nbsp; //已经加载一个dll，还没有释放，不能加载新的dll&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;//检查数据有效性，并初始化&lt;BR&gt;&amp;nbsp;if(!CheckDataValide(lpFileData, DataLength))return FALSE;&lt;BR&gt;&amp;nbsp;//计算所需的加载空间&lt;BR&gt;&amp;nbsp;int ImageSize = CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;if(ImageSize == 0) return FALSE;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;// 分配虚拟内存&lt;BR&gt;&amp;nbsp;void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); &lt;BR&gt;&amp;nbsp;if(pMemoryAddress == NULL) return FALSE;&lt;BR&gt;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据，并对齐每个段&lt;BR&gt;&amp;nbsp;&amp;nbsp;//重定位信息&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress &amp;gt;0&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;&amp;amp; pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size&amp;gt;0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DoRelocation(pMemoryAddress);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//填充引入地址表&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;VirtualFree(pMemoryAddress,0,MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。&lt;BR&gt;&amp;nbsp;&amp;nbsp;//统一设置成一个属性PAGE_EXECUTE_READWRITE&lt;BR&gt;&amp;nbsp;&amp;nbsp;unsigned long old;&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&amp;amp;old);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;//修正基地址&lt;BR&gt;&amp;nbsp;pNTHeader-&amp;gt;OptionalHeader.ImageBase = (DWORD)pMemoryAddress;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//接下来要调用一下dll的入口函数，做初始化工作。&lt;BR&gt;&amp;nbsp;pDllMain = (ProcDllMain)(pNTHeader-&amp;gt;OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);&lt;BR&gt;&amp;nbsp;BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);&lt;BR&gt;&amp;nbsp;if(!InitResult) //初始化失败&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);&lt;BR&gt;&amp;nbsp;&amp;nbsp;VirtualFree(pMemoryAddress,0,MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp;pDllMain = NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;isLoadOk = TRUE;&lt;BR&gt;&amp;nbsp;pImageBase = (DWORD)pMemoryAddress;&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//MemGetProcAddress函数从dll中获取指定函数的地址&lt;BR&gt;//返回值： 成功返回函数地址 , 失败返回NULL&lt;BR&gt;//lpProcName: 要查找函数的名字或者序号&lt;BR&gt;FARPROC&amp;nbsp; CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||&lt;BR&gt;&amp;nbsp;&amp;nbsp;pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;return NULL;&lt;BR&gt;&amp;nbsp;if(!isLoadOk) return NULL;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;DWORD OffsetStart = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;&lt;BR&gt;&amp;nbsp;DWORD Size = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);&lt;BR&gt;&amp;nbsp;int iBase = pExport-&amp;gt;Base;&lt;BR&gt;&amp;nbsp;int iNumberOfFunctions = pExport-&amp;gt;NumberOfFunctions;&lt;BR&gt;&amp;nbsp;int iNumberOfNames = pExport-&amp;gt;NumberOfNames; //&amp;lt;= iNumberOfFunctions&lt;BR&gt;&amp;nbsp;LPDWORD pAddressOfFunctions = (LPDWORD)(pExport-&amp;gt;AddressOfFunctions + pImageBase);&lt;BR&gt;&amp;nbsp;LPWORD&amp;nbsp; pAddressOfOrdinals = (LPWORD)(pExport-&amp;gt;AddressOfNameOrdinals + pImageBase);&lt;BR&gt;&amp;nbsp;LPDWORD pAddressOfNames&amp;nbsp; = (LPDWORD)(pExport-&amp;gt;AddressOfNames + pImageBase);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;int iOrdinal = -1;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;if(((DWORD)lpProcName &amp;amp; 0xFFFF0000) == 0) //IT IS A ORDINAL!&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;iOrdinal = (DWORD)lpProcName &amp;amp; 0x0000FFFF - iBase;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;else&amp;nbsp; //use name&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;int iFound = -1;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;for(int i=0;i&amp;lt;iNumberOfNames;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;char* pName= (char* )(pAddressOfNames[i] + pImageBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(strcmp(pName, lpProcName) == 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;iFound = i; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(iFound &amp;gt;= 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;iOrdinal = (int)(pAddressOfOrdinals[iFound]);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;if(iOrdinal &amp;lt; 0 || iOrdinal &amp;gt;= iNumberOfFunctions ) return NULL;&lt;BR&gt;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pFunctionOffset &amp;gt; OffsetStart &amp;amp;&amp;amp; pFunctionOffset &amp;lt; (OffsetStart+Size))//maybe Export Forwarding&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;else return (FARPROC)(pFunctionOffset + pImageBase);&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;}&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;// 重定向PE用到的地址&lt;BR&gt;void CMemLoadDll::DoRelocation( void *NewBase)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;/* 重定位表的结构：&lt;BR&gt;&amp;nbsp;// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)&lt;BR&gt;&amp;nbsp;// 例如 1000节需要修正5个重定位数据的话，重定位表的数据是&lt;BR&gt;&amp;nbsp;// 00 10 00 00&amp;nbsp;&amp;nbsp; 14 00 00 00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; xxxx xxxx xxxx xxxx xxxx 0000&lt;BR&gt;&amp;nbsp;// -----------&amp;nbsp;&amp;nbsp; -----------&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ----&lt;BR&gt;&amp;nbsp;// 给出节的偏移&amp;nbsp; 总尺寸=8+6*2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 需要修正的地址&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 用于对齐4字节&lt;BR&gt;&amp;nbsp;// 重定位表是若干个相连，如果address 和 size都是0 表示结束&lt;BR&gt;&amp;nbsp;// 需要修正的地址是12位的，高4位是形态字，intel cpu下是3&lt;BR&gt;&amp;nbsp;*/&lt;BR&gt;&amp;nbsp;//假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000&lt;BR&gt;&amp;nbsp;DWORD Delta = (DWORD)NewBase - pNTHeader-&amp;gt;OptionalHeader.ImageBase;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//注意重定位表的位置可能和硬盘文件中的偏移地址不同，应该使用加载后的地址&lt;BR&gt;&amp;nbsp;PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase &lt;BR&gt;&amp;nbsp;&amp;nbsp;+ pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);&lt;BR&gt;&amp;nbsp;while((pLoc-&amp;gt;VirtualAddress + pLoc-&amp;gt;SizeOfBlock) != 0) //开始扫描重定位表&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));&lt;BR&gt;&amp;nbsp;&amp;nbsp;//计算本节需要修正的重定位项（地址）的数目&lt;BR&gt;&amp;nbsp;&amp;nbsp;int NumberOfReloc = (pLoc-&amp;gt;SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);&lt;BR&gt;&amp;nbsp;&amp;nbsp;for( int i=0 ; i &amp;lt; NumberOfReloc; i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if( (DWORD)(pLocData[i] &amp;amp; 0xF000) == 0x00003000) //这是一个需要修正的地址&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 举例： &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// pLoc-&amp;gt;VirtualAddress = 0x1000; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 因此 pAddress = 基地址 + 0x113E&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 里面的内容是 A1 ( 0c d4 02 10)&amp;nbsp; 汇编代码是： mov eax , [1002d40c]&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 需要修正1002d40c这个地址&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc-&amp;gt;VirtualAddress + (pLocData[i] &amp;amp; 0x0FFF));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*pAddress += Delta;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;//转移到下一个节进行处理&lt;BR&gt;&amp;nbsp;&amp;nbsp;pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc-&amp;gt;SizeOfBlock);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//填充引入地址表&lt;BR&gt;BOOL CMemLoadDll::FillRavAddress(void *pImageBase)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;// 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组，全部是0表示结束&lt;BR&gt;&amp;nbsp;// 数组定义如下：&lt;BR&gt;&amp;nbsp;// &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; OriginalFirstThunk;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 0表示结束，否则指向未绑定的IAT结构数组&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; TimeDateStamp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; ForwarderChain;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // -1 if no forwarders&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; Name;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 给出dll的名字&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // DWORD&amp;nbsp;&amp;nbsp; FirstThunk;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 指向IAT结构数组的地址(绑定后，这些IAT里面就是实际的函数地址)&lt;BR&gt;&amp;nbsp;unsigned long Offset = pNTHeader-&amp;gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;&lt;BR&gt;&amp;nbsp;if(Offset == 0) return TRUE; //No Import Table&lt;BR&gt;&amp;nbsp;PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);&lt;BR&gt;&amp;nbsp;while(pID-&amp;gt;Characteristics != 0 )&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID-&amp;gt;FirstThunk);&lt;BR&gt;&amp;nbsp;&amp;nbsp;PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID-&amp;gt;OriginalFirstThunk);&lt;BR&gt;&amp;nbsp;&amp;nbsp;//获取dll的名字&lt;BR&gt;&amp;nbsp;&amp;nbsp;char buf[256]; //dll name;&lt;BR&gt;&amp;nbsp;&amp;nbsp;BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID-&amp;gt;Name);&lt;BR&gt;&amp;nbsp;&amp;nbsp;for(int i=0;i&amp;lt;256;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pName[i] == 0)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;buf[i] = pName[i];&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(i&amp;gt;=256) return FALSE;&amp;nbsp; // bad dll name&lt;BR&gt;&amp;nbsp;&amp;nbsp;else buf[i] = 0;&lt;BR&gt;&amp;nbsp;&amp;nbsp;HMODULE hDll = GetModuleHandle(buf);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(hDll == NULL)return FALSE; //NOT FOUND DLL&lt;BR&gt;&amp;nbsp;&amp;nbsp;//获取DLL中每个导出函数的地址，填入IAT&lt;BR&gt;&amp;nbsp;&amp;nbsp;//每个IAT结构是 ：&lt;BR&gt;&amp;nbsp;&amp;nbsp;// union {&amp;nbsp;PBYTE&amp;nbsp; ForwarderString;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;PDWORD Function;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD Ordinal;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //&amp;nbsp;&amp;nbsp;&amp;nbsp;PIMAGE_IMPORT_BY_NAME&amp;nbsp; AddressOfData;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// } u1;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 长度是一个DWORD ，正好容纳一个地址。&lt;BR&gt;&amp;nbsp;&amp;nbsp;for(i=0; ;i++)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pOriginalIAT[i].u1.Function == 0)break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FARPROC lpFunction = NULL;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pOriginalIAT[i].u1.Ordinal &amp;amp; IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal &amp;amp; 0x0000FFFF));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else //按照名字导入&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//获取此IAT项所描述的函数名称&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(pByName-&amp;gt;Hint !=0)&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (LPCSTR)pByName-&amp;gt;Hint);&lt;BR&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lpFunction = GetProcAddress(hDll, (char *)pByName-&amp;gt;Name);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(lpFunction != NULL)&amp;nbsp;&amp;nbsp; //找到了！&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pRealIAT[i].u1.Function = (PDWORD) lpFunction;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else return FALSE;&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;//move to next &lt;BR&gt;&amp;nbsp;&amp;nbsp;pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件&lt;BR&gt;//返回值： 是一个可执行的dll则返回TRUE，否则返回FALSE。&lt;BR&gt;//lpFileData: 存放dll数据的内存缓冲区&lt;BR&gt;//DataLength: dll文件的长度&lt;BR&gt;BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;//检查长度&lt;BR&gt;&amp;nbsp;if(DataLength &amp;lt; sizeof(IMAGE_DOS_HEADER)) return FALSE;&lt;BR&gt;&amp;nbsp;pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;&amp;nbsp;&amp;nbsp;// DOS头&lt;BR&gt;&amp;nbsp;//检查dos头的标记&lt;BR&gt;&amp;nbsp;if(pDosHeader-&amp;gt;e_magic != IMAGE_DOS_SIGNATURE) return FALSE;&amp;nbsp;&amp;nbsp;//0x5A4D : MZ&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//检查长度&lt;BR&gt;&amp;nbsp;if((DWORD)DataLength &amp;lt; (pDosHeader-&amp;gt;e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;&lt;BR&gt;&amp;nbsp;//取得pe头&lt;BR&gt;&amp;nbsp;pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader-&amp;gt;e_lfanew); // PE头&lt;BR&gt;&amp;nbsp;//检查pe头的合法性&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;Signature != IMAGE_NT_SIGNATURE) return FALSE;&amp;nbsp;&amp;nbsp;//0x00004550 : PE00&lt;BR&gt;&amp;nbsp;if((pNTHeader-&amp;gt;FileHeader.Characteristics &amp;amp; IMAGE_FILE_DLL) == 0)&amp;nbsp;//0x2000&amp;nbsp; : File is a DLL&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&amp;nbsp; &lt;BR&gt;&amp;nbsp;if((pNTHeader-&amp;gt;FileHeader.Characteristics &amp;amp; IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行&lt;BR&gt;&amp;nbsp;&amp;nbsp;return FALSE;&lt;BR&gt;&amp;nbsp;if(pNTHeader-&amp;gt;FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;//取得节表（段表）&lt;BR&gt;&amp;nbsp;pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));&lt;BR&gt;&amp;nbsp;//验证每个节表的空间&lt;BR&gt;&amp;nbsp;for(int i=0; i&amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; i++)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) &amp;gt; (DWORD)DataLength)return FALSE;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;//计算对齐边界&lt;BR&gt;int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;return (Origin + Alignment - 1) / Alignment * Alignment;&lt;BR&gt;}&lt;BR&gt;//计算整个dll映像文件的尺寸&lt;BR&gt;int CMemLoadDll::CalcTotalImageSize()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int Size;&lt;BR&gt;&amp;nbsp;if(pNTHeader == NULL)return 0;&lt;BR&gt;&amp;nbsp;int nAlign = pNTHeader-&amp;gt;OptionalHeader.SectionAlignment; //段对齐字节数&lt;/P&gt;
&lt;P&gt;&amp;nbsp;// 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小&lt;BR&gt;&amp;nbsp;Size = GetAlignedSize(pNTHeader-&amp;gt;OptionalHeader.SizeOfHeaders, nAlign);&lt;BR&gt;&amp;nbsp;// 计算所有节的大小&lt;BR&gt;&amp;nbsp;for(int i=0; i &amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; ++i)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;//得到该节的大小&lt;BR&gt;&amp;nbsp;&amp;nbsp;int CodeSize = pSectionHeader[i].Misc.VirtualSize ;&lt;BR&gt;&amp;nbsp;&amp;nbsp;int LoadSize = pSectionHeader[i].SizeOfRawData;&lt;BR&gt;&amp;nbsp;&amp;nbsp;int MaxSize = (LoadSize &amp;gt; CodeSize)?(LoadSize):(CodeSize);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(Size &amp;lt; SectionSize) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Size = SectionSize;&amp;nbsp; //Use the Max;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return Size;&lt;BR&gt;}&lt;BR&gt;//CopyDllDatas函数将dll数据复制到指定内存区域，并对齐所有节&lt;BR&gt;//pSrc: 存放dll数据的原始缓冲区&lt;BR&gt;//pDest:目标内存地址&lt;BR&gt;void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;// 计算需要复制的PE头+段表字节数&lt;BR&gt;&amp;nbsp;int&amp;nbsp; HeaderSize = pNTHeader-&amp;gt;OptionalHeader.SizeOfHeaders;&lt;BR&gt;&amp;nbsp;int&amp;nbsp; SectionSize = pNTHeader-&amp;gt;FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);&lt;BR&gt;&amp;nbsp;int&amp;nbsp; MoveSize = HeaderSize + SectionSize;&lt;BR&gt;&amp;nbsp;//复制头和段信息&lt;BR&gt;&amp;nbsp;memmove(pDest, pSrc, MoveSize);&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//复制每个节&lt;BR&gt;&amp;nbsp;for(int i=0; i &amp;lt; pNTHeader-&amp;gt;FileHeader.NumberOfSections; ++i)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 定位该节在内存中的位置&lt;BR&gt;&amp;nbsp;&amp;nbsp;void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);&lt;BR&gt;&amp;nbsp;&amp;nbsp;// 复制段数据到虚拟内存&lt;BR&gt;&amp;nbsp;&amp;nbsp;memmove((void *)pSectionAddress,&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pSectionHeader[i].SizeOfRawData);&lt;BR&gt;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;//修正指针，指向新分配的内存&lt;BR&gt;&amp;nbsp;//新的dos头&lt;BR&gt;&amp;nbsp;pDosHeader = (PIMAGE_DOS_HEADER)pDest;&lt;BR&gt;&amp;nbsp;//新的pe头地址&lt;BR&gt;&amp;nbsp;pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader-&amp;gt;e_lfanew));&lt;BR&gt;&amp;nbsp;//新的节表地址&lt;BR&gt;&amp;nbsp;pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));&lt;BR&gt;&amp;nbsp;return ;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;谨依此文祝贺我在VCKBASE里升级。&lt;BR&gt;iwaswzq 2006/7/27&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/21563.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>从内存中加载动态库(一)</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html</link><pubDate>Fri, 28 Jul 2006 04:55:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/21562.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/07/28/21562.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/21562.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/21562.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 程序使用动态库DLL一般分为隐式加载和显式加载两种，分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道，要显式加载一个DLL，并取得其中导出的函数地址一般是通过如下步骤：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (1) 用LoadLibrary加载dll文件，获得该dll的模块句柄；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (2) 定义一个函数指针类型，并声明一个变量；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (3) 用GetProcAddress取得该dll中目标函数的地址，赋值给函数指针变量；&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (4) 调用函数指针变量。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个方法要求dll文件位于硬盘上面。现在假设我们的dll已经位于内存中，比如通过脱壳、解密或者解压缩得到，能不能不把它写入硬盘文件，而直接从内存加载呢？答案是肯定的。经过多天的研究，非法操作了N次，修改了M个BUG，死亡了若干脑细胞后，终于有了初步的结果，下面做个总结与大家共享。&lt;/P&gt;
&lt;P&gt;一、加载的步骤&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 由于没有相关的资料说明，只能凭借感觉来写。首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中，我们要实现的也是这个。所以先要弄清楚dll的文件结构。好在这个比较简单，它和exe一样也是PE文件结构，关于PE文件的资料很多，阅读一番后，基本上知道了必须做的几个工作：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (1)判断内存数据是否是一个有效的DLL。这个功能通过函数CheckDataValide完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (2)计算加载该DLL所需的虚拟内存大小。这个功能通过函数CalcTotalImageSize完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int CMemLoadDll::CalcTotalImageSize();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (3)将DLL数据复制到所分配的虚拟内存块中。该功能通过函数CopyDllDatas完成。要注意段对齐。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (4)修正基地重定位数据。这个功能通过函数DoRelocation完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; void CMemLoadDll::DoRelocation( void *NewBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (5)填充该DLL的引入地址表。这个功能由函数FillRavAddress完成。原型是：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BOOL CMemLoadDll::FillRavAddress(void *pImageBase);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (6)根据DLL每个节的属性设置其对应内存页的读写属性。我这里做了简化，所有内存区域都设置成一样的读写属性。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (7)调用入口函数DllMain，完成初始化工作。这一步我一开始忽略了，所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较，够晕的)。只是最近猜想到还需要这一步。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (8)保存dll的基地址（即分配的内存块起始地址）,用于查找dll的导出函数。从现在开始这个dll已经完全映射到了进程的虚拟地址空间，可以使用它了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (9)不需要dll的时候，释放所分配的虚拟内存。&lt;/P&gt;
&lt;P&gt;二、要说明的几个问题&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; (1)目前CMemLoadDll仅仅针对win32 动态库，没有考虑mfc常规和扩展dll。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (2)只考虑使用dll中的函数，对于导出类的dll，由于通常都是隐式链接，所以也没有考虑。导出变量的dll虽然也是隐式链接，但是通过查找函数的方法也可以找到该变量，不过在取值的时候一定要符合dll中对变量的定义，比如dll中导出的是一个int变量，则得到该变量在dll中的地址后，需要强制转换成int*指针，然后取值。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (3)查找函数的功能通过函数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FARPROC&amp;nbsp; CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName);&lt;BR&gt;实现，参数是dll导出的函数（或者变量）的名字。这里必须注意函数名修饰，通常不加extern"C"的函数，编译以后在dll中导出的都是修饰名，比如：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在dll头文件中: extern __declspec(dllexport) int nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在.dll中的导出符号变成 &lt;A href="mailto:?nTestDll@@3HA"&gt;?nTestDll@@3HA&lt;/A&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 所以，为了能够找到我们需要的函数，必须在.h中添加extern "C"修饰。最好是给dll加一个def文件，里面明确给出每个函数的导出名字。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (4)PE中的内容比较多，有些细节没有考虑。比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (5)PE文件中的节有很多种。可以从节表（或者叫做区块表）中一一找到。而且每个节的属性都不同。例如：.text, .data, .rsrc, .crt等等。由于这个代码基于手头已有的pe文件资料，对于不熟悉的节，在映射dll数据的时候没有考虑是否需要处理。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (6)一开始把dll映射到进程的地址空间以后，我试图直接使用GetProcAddress查找函数。最初我认为LoadLibrary返回的HINSTANCE值是0x10000000，把它传递给GetProcAddress可以找到目标函数，而我也把dll映射到0x10000000这个地址，但是当我把这个值传递给GetProcAddress的时候，发现无法找到函数，用GetLastError得到错误码一看是无效句柄的错误，这才明白原来LoadLibrary在加载dll的时候，同时创建了一个句柄放入进程的句柄表，而我们要做这个工作是比较麻烦的，所以只能自己写一个查找函数。&lt;BR&gt;&amp;nbsp;&amp;nbsp; (7)释放dll所占据的虚拟内存，原来我使用&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; VirtualFree((LPVOID)pImageBase, 0,MEM_FREE);&lt;BR&gt;后来发现有问题，应该使用 VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);&lt;BR&gt;&amp;nbsp;&amp;nbsp; (8)MemGetProcAddress不仅支持通过函数名查找，还支持通过导出序号查找函数。例如下面的用法：&lt;BR&gt;&amp;nbsp;DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);&lt;/P&gt;
&lt;P&gt;三、创建测试用的DLL，工程的名字取"TestDll"&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 用VC向导创建一个WIN32 DLL工程，里面选择&amp;#8220;导出一些符号&amp;#8221;，为了测试需要，对源代码进行如下修改：&lt;BR&gt;&amp;nbsp;（1）头文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // This class is exported from the TestDll.dll&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; class TESTDLL_API CTestDll {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public:&lt;BR&gt;&amp;nbsp;CTestDll(void);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; };&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; extern TESTDLL_API int nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //要修改的地方，添加了extern "C" 和 char *参数：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; extern "C"&amp;nbsp; TESTDLL_API int fnTestDll(char *);&lt;BR&gt;&amp;nbsp; （2）cpp文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; a. 添加 #include "stdlib.h"&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b. DllMain中&lt;BR&gt;&amp;nbsp;&amp;nbsp;case DLL_PROCESS_DETACH:&lt;BR&gt;&lt;FONT color=#0000ff&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;nTestDll = 12345;&lt;BR&gt;&lt;/FONT&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; c. 初始化变量&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TESTDLL_API int nTestDll=654321;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; d. 修改函数&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TESTDLL_API int fnTestDll(char *p)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;if(p == NULL)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return nTestDll;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return atoi(p);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;四、创建测试工程。使用一个dlg工程，测试代码如下：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 假设 DllNameBuffer里面保存有dll文件的路径&lt;BR&gt;&amp;nbsp;CFile f;&lt;BR&gt;&amp;nbsp;if(f.Open(DllNameBuffer,CFile::modeRead))&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;int FileLength = f.GetLength();&lt;BR&gt;&amp;nbsp;&amp;nbsp;void *lpBuf = new char[FileLength];&lt;BR&gt;&amp;nbsp;&amp;nbsp;f.Read(lpBuf, FileLength);&lt;BR&gt;&amp;nbsp;&amp;nbsp;f.Close();&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;CMemLoadDll a;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(a.MemLoadLibrary(lpBuf, FileLength)) //加载dll到当前进程的地址空间&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;typedef&amp;nbsp; int (*DLLFUNCTION)(char *);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress("fnTestDll");&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(fDll != NULL)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox("找到函数！！");&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str.Format("Result is: %d &amp;amp; %d",fDll(NULL), fDll("100"));&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox(str);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DWORD err = GetLastError();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str.Format("Error: %d",err);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox(str);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;delete[] lpBuf;&lt;BR&gt;&amp;nbsp;} &lt;/P&gt;
&lt;P&gt;五、加载类源代码。（在后续贴子里面给出）&lt;/P&gt;
&lt;P&gt;iwaswzq 2006/7/27&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/21562.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>虚拟列表控件</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html</link><pubDate>Fri, 07 Jul 2006 03:00:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/21113.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html#Feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/21113.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/21113.html</trackback:ping><description>&lt;P&gt;&amp;lt;PRE&amp;gt;&lt;BR&gt;一、什么是虚拟列表控件&lt;/P&gt;
&lt;P&gt;虚拟列表控件是指带有LVS_OWNERDATA风格的列表控件。。&lt;/P&gt;
&lt;P&gt;二、为什么使用虚拟列表控件&lt;/P&gt;
&lt;P&gt;我们知道，通常使用列表控件CListCtrl，需要调用InsertItem把要显示的数据插入列表中，之后我们就不必关心数据在哪里了，这是因为控件自己开辟了内存空间来保存这些数据。现在假设我们要显示一个数据库，里面的信息量很大，有几十万条记录。通常有两种方法解决这个问题：1是仅仅在ListCtrl中插入少量的数据，比如100个，然后通过[上一页][下一页]两个按钮进行控制，某一时刻显示的只是从xxx到xxx+100之间的记录。2是把所有数据全部插入到ListCtrl中，然后让用户通过滚动来查看数据。无疑，很多用户喜欢采用第二种方式，特别是对于已经排序的数据，用户只需用键盘输入某行的开头字符，就可以快速定位到某一行。但是，如果这样做，InsertItem插入数据的过程将是很漫长的，而且用户会看到ListCtrl刷新速度也很慢，而且所有数据都位于内存中消耗了大量的内存，当数据多达上万以后几乎是不能忍受的。&lt;/P&gt;
&lt;P&gt;为此，mfc特别提供了虚拟列表的支持。一个虚拟列表看起来和普通的ListCtrl一样，但是不用通过InsertItem来插入数据，它仅仅知道自己应该显示多少数据。但是它如何知道要显示什么数据呢？秘密就在于当列表控件需要显示某个数据的时候，它向父窗口要。假设这个列表控件包含100个元素，第10到20个元素（行）是可见的。当列表控件重画的时候 ，它首先请求父窗口给它第10个元素的数据，父窗口收到请求以后，把数据信息填充到列表提供的一个结构中，列表就可以用来显示了，显示第10个数据后，列表会继续请求下一个数据。&lt;/P&gt;
&lt;P&gt;在虚拟的样式下，ListCtrl可以支持多达DWORD个数据项。(缺省的listctrl控件最多支持int个数据项)。但是，虚拟列表的最大优点不在于此，而是它仅仅需要在内存中保持极少量的数据，从而加快了显示的速度。所以，在使用列表控件显示一个很大的数据库的情况下，采用虚拟列表最好不过了。&lt;/P&gt;
&lt;P&gt;不仅CListCtrl提供虚拟列表的功能， MFC的CListView类也有同样的功能。&lt;/P&gt;
&lt;P&gt;三、虚拟列表控件的消息&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;虚拟列表总共发送三个消息给父窗口：当它需要数据的时候，它发送LVN_GETDISPINFO消息。这是最重要的消息。当用户试图查找某个元素的时候，它发送LVN_ODFINDITEM消息；还有一个消息是LVN_ODCACHEHINT，用来缓冲数据，基本上很少用到这个消息。&lt;/P&gt;
&lt;P&gt;虚拟列表控件使用起来非常简单。它总共只有三个相关的消息，如果你直接使用CListCtrl，应该在对话框中响应这三个消息。如果你使用CListCtrl派生类，可以在派生类中响应这三个消息的反射消息。这三个消息分别是：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; （1）LVN_GETDISPINFO 控件请求某个数据&lt;BR&gt;&amp;nbsp; （2）LVN_ODFINDITEM&amp;nbsp; 查找某个数据&lt;BR&gt;&amp;nbsp; （3）LVN_ODCACHEHINT 缓冲某一部分数据&lt;/P&gt;
&lt;P&gt;我们必须响应的消息是（1），多数情况下要响应(2)，极少数的情况下需要响应（3）&lt;/P&gt;
&lt;P&gt;四、如何使用虚拟列表控件&lt;/P&gt;
&lt;P&gt;1、首先要创建控件，创建一个虚拟列表和创建一个正常的 CListCtrl差不多。先在资源编辑器里面添加一个list control资源。然后选中"Owner data"属性，然后给它捆绑一个CListCtrl变量。添加列，添加imagelist等都和使用正常的listctrl一样。&lt;/P&gt;
&lt;P&gt;2、给虚拟列表添加元素。假设 m_list 是这个列表的控制变量。通常的情况下这样添加数据：&lt;/P&gt;
&lt;P&gt;m_list.InsertItem(0, _T("Hello world"));&lt;/P&gt;
&lt;P&gt;但是对于虚拟列表，不能这么做。只需告诉列表有多少个数据:&lt;/P&gt;
&lt;P&gt;//总共显示100行&lt;BR&gt;m_list.SetItemCount(100);&lt;/P&gt;
&lt;P&gt;3、处理它的通知消息。&lt;/P&gt;
&lt;P&gt;五、如何响应虚拟列表的消息&lt;/P&gt;
&lt;P&gt;1、处理 LVN_GETDISPINFO 通知消息&lt;/P&gt;
&lt;P&gt;当虚拟列表控件需要某个数据的时候，它给父窗口发送一个 LVN_GETDISPINFO通知消息，表示请求某个数据。因此列表的所有者窗口（或者它自己）必须处理这个消息。例如派生类的情况 (CMyListCtrl是一个虚拟列表类对象):&lt;/P&gt;
&lt;P&gt;//这里处理的是反射消息&lt;BR&gt;BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)&lt;BR&gt;&amp;nbsp;&amp;nbsp; //{{AFX_MSG_MAP(CMyListCtrl)&lt;BR&gt;&amp;nbsp;&amp;nbsp; ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)&lt;BR&gt;&amp;nbsp;&amp;nbsp; //}}AFX_MSG_MAP&lt;BR&gt;END_MESSAGE_MAP()&lt;/P&gt;
&lt;P&gt;在LVN_GETDISPINFO的处理函数中，必须首先检查列表请求的是什么数据，可能的值包括: &lt;/P&gt;
&lt;P&gt;（1）LVIF_TEXT&amp;nbsp;&amp;nbsp; 必须填充 pszText&lt;BR&gt;（2）LVIF_IMAGE&amp;nbsp; 必须填充 iImage &lt;BR&gt;（3）LVIF_INDENT 必须填充 iIndent&lt;BR&gt;（4）LVIF_PARAM&amp;nbsp; 必须填充 lParam &lt;BR&gt;（5）LVIF_STATE&amp;nbsp; 必须填充 state &lt;/P&gt;
&lt;P&gt;根据它的请求，填充所需的数据即可。&lt;/P&gt;
&lt;P&gt;//================= 例子代码=====================================&lt;/P&gt;
&lt;P&gt;下面的给出一个例子，填充的是列表所需的某个数据项的文字以及图像信息：&lt;/P&gt;
&lt;P&gt;LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;&lt;BR&gt;LV_ITEM* pItem= &amp;amp;(pDispInfo)-&amp;gt;item;&lt;/P&gt;
&lt;P&gt;int iItemIndx= pItem-&amp;gt;iItem;&lt;/P&gt;
&lt;P&gt;if (pItem-&amp;gt;mask &amp;amp; LVIF_TEXT) //字符串缓冲区有效&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; switch(pItem-&amp;gt;iSubItem){&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case 0: //填充数据项的名字&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lstrcpy(pItem-&amp;gt;pszText,m_Items[iItemIndx].m_strItemText);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case 1: //填充子项1&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lstrcpy(pItem-&amp;gt;pszText,m_Items[iItemIndx].m_strSubItem1Text);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case 2: //填充子项2&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lstrcpy(pItem-&amp;gt;pszText,m_Items[iItemIndx].m_strSubItem2Text);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;}&lt;BR&gt;/*注意，多数情况下要使用lstrcpyn ，因为最多复制字符的个数由pItem-&amp;gt;cchTextMax给出：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lstrcpyn(pItem-&amp;gt;pszText, text, pItem-&amp;gt;cchTextMax);&lt;BR&gt;*/&lt;/P&gt;
&lt;P&gt;if (pItem-&amp;gt;mask &amp;amp; LVIF_IMAGE) //是否请求图像&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pItem-&amp;gt;iImage= m_Items[iItemIndx].m_iImageIndex;&lt;/P&gt;
&lt;P&gt;甚至连某行数据是否选中（当有checkbox的情况下）的信息也需要由用户自己来维护，例如：&lt;BR&gt;//是否显示该行的选择信息?&lt;BR&gt;if(IsCheckBoxesVisible()) //自定义函数&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; pItem-&amp;gt;mask |= LVIF_STATE;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; pItem-&amp;gt;stateMask = LVIS_STATEIMAGEMASK;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(m_database[itemid].m_checked)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pItem-&amp;gt;state = INDEXTOSTATEIMAGEMASK(2);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pItem-&amp;gt;state = INDEXTOSTATEIMAGEMASK(1);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;2、处理 LVN_ODFINDITEM 消息&lt;/P&gt;
&lt;P&gt;在资源管理器里面，定位到某个文件夹，会显示很多文件，如果按下键盘的&amp;#8216;A&amp;#8217;，则资源管理器会自动找到名字以 'A'打头的文件夹或者文件, 并选择该文件。继续按 A，如果还有其它名字以'A'打头的文件，则下一个文件被选中。如果输入 "AB"，则 'AB'打头的文件被选中。这就是列表控件的自动查找功能。&lt;/P&gt;
&lt;P&gt;当虚拟列表收到一个LVM_FINDITEM消息，它也会发送这个消息通知父窗口查找目标元素。要搜索的信息通过 LVFINDINFO 结构给出。它是 NMLVFINDITEM 结构的一个成员。当找到要搜索的数据后，应该把该数据的索引（行号）返回，如果没有找到，则返回-1。&lt;/P&gt;
&lt;P&gt;以对话框为例，响应函数大致如下:&lt;/P&gt;
&lt;P&gt;//================= 例子代码=====================================&lt;BR&gt;void CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult) &lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // pNMHDR 里面是要查找的元素的信息&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 要选中的目标元素的行号最后要保存在 pResult 中， 这是关键！&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; /* pFindInfo-&amp;gt;iStart 是查找的起始位置，一直到最后，然后从头开始，如果没有找到合适的，最终停留在iStart*/&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *pResult = -1;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //是否按照文字查找？&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( (pFindInfo-&amp;gt;lvfi.flags &amp;amp; LVFI_STRING) == 0 )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //这是我们要找的字符串&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; CString searchstr = pFindInfo-&amp;gt;lvfi.psz;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int startPos = pFindInfo-&amp;gt;iStart;//保存起始位置&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //判断是否最后一行&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(startPos &amp;gt;= m_list.GetItemCount())&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; startPos = 0;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int currentPos=startPos;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; //开始查找&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; do&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( _tcsnicmp(m_database[currentPos].m_name, &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; searchstr, searchstr.GetLength()) == 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //选中这个元素，停止查找&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; *pResult = currentPos;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; currentPos++;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //从头开始&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(currentPos &amp;gt;= m_list.GetItemCount())&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; currentPos = 0;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }while(currentPos != startPos);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;显然，如果数据很多，必须实现一个快速查找的方法。&lt;/P&gt;
&lt;P&gt;关于pFindInfo-&amp;gt;lvfi里面的信息的详细说明可以参考 MSDN。&lt;/P&gt;
&lt;P&gt;3、处理 LVN_ODCACHEHINT 消息。&lt;/P&gt;
&lt;P&gt;假如我们从数据库或者其它地方读取数据的速度比较慢，则可以利用这个消息，批量读取一些数据，然后根据请求，逐个提供给虚拟列表。LVN_ODCACHEHINT消息的用途就是给程序一个缓冲数据的机会。以提高程序的性能。&lt;/P&gt;
&lt;P&gt;//================= 例子代码=====================================&lt;BR&gt;使用 ClassWizard 重载 OnChildNotify 函数，检查是否 LVN_ODCACHEHINT 消息，然后准备缓冲数据：&lt;/P&gt;
&lt;P&gt;NMLVCACHEHINT* pcachehint=NULL;&lt;/P&gt;
&lt;P&gt;NMHDR* phdr = (NMHDR*)lParam;&lt;/P&gt;
&lt;P&gt;if(phdr-&amp;gt;code == LVN_ODCACHEHINT)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pcachehint= (NMLVCACHEHINT*) phdr;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //自定义函数，准备指定范围的数据到缓冲区&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PrepCache(pcachehint-&amp;gt;iFrom, pcachehint-&amp;gt;iTo);&lt;BR&gt;}&lt;BR&gt;else ...&lt;/P&gt;
&lt;P&gt;注意，如果消息不是 LVN_ODCACHEHINT，则要传递给基类进行处理。&lt;/P&gt;
&lt;P&gt;五、如何修改ListCtrl显示的数据。&lt;/P&gt;
&lt;P&gt;由于是程序自己维护数据，所以只需修改数据库中的数据，然后调用CListCtrl::RedrawItems函数进行重画即可。&lt;/P&gt;
&lt;P&gt;六、数据的选择状态和选择框&lt;/P&gt;
&lt;P&gt;CListCtrl可以显示checkbox选择框。有些情况下是很有用的。对于正常的listctrl，用户可以用鼠标来修改某个元素的选择状态，但是对于虚拟列表就不行了。必须自己处理一些消息，然后自己保存数据的选中状态:&lt;/P&gt;
&lt;P&gt;void CVirtualListDlg::ToggleCheckBox(int item)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_database[item].m_checked = !m_database[item].m_checked;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; m_list.RedrawItems(item, item);&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;处理 LVN_KEYDOWN消息，添加对空格键 的响应，用于切换选择状态：&lt;/P&gt;
&lt;P&gt;void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult) &lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( pLVKeyDown-&amp;gt;wVKey == VK_SPACE )&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int item = m_list.GetSelectionMark();&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(item != -1)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ToggleCheckBox(item);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *pResult = 0;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;然后处理 NM_CLICK 消息:&lt;/P&gt;
&lt;P&gt;void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult) &lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; LVHITTESTINFO hitinfo;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; hitinfo.pt = pNMListView-&amp;gt;ptAction;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; int item = m_list.HitTest(&amp;amp;hitinfo); &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if(item != -1)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //看看鼠标是否单击在 check box上面了?&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if( (hitinfo.flags &amp;amp; LVHT_ONITEMSTATEICON) != 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ToggleCheckBox(item);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *pResult = 0;&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;七、备注：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1、虚拟列表无法进行排序。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2、虚表的一个优点是容易保持和数据库的同步。修改数据库中的数据，然后重画list十分容易而且高效。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号。&lt;/P&gt;
&lt;P&gt;iwaswzq 2006/6/21 0:16&lt;BR&gt;--------------------------------------------------------------------------------&lt;BR&gt;&amp;lt;/PRE&amp;gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/21113.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>奇怪的SetCheck</title><link>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html</link><pubDate>Sun, 19 Feb 2006 08:01:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/17836.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2006/02/19/17836.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/17836.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/17836.html</trackback:ping><description>&lt;P&gt;一、问题的提出&lt;/P&gt;
&lt;P&gt;CTreeCtrl有个属性TVS_HASBUTTONS，如果创建控件的时候加上了这个属性，则在每个节点的左侧&lt;BR&gt;都有一个按钮，用来表示节点的选择状态。通过两个函数SetCheck / GetCheck来设置和获取指定&lt;BR&gt;节点的选择状态。&lt;/P&gt;
&lt;P&gt;但是奇怪的是，在对话框中按照常规的方法使用了SetCheck，最后CTreeCtrl并没有显示节点被选&lt;BR&gt;中，下面是测试例子：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 1、用wizard创建一个对话框工程，并且在上面放置一个CTreeCtrl控件。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 2、设置CTreeCtrl的属性，"More Styles"里面选中"Check Boxes"，给它加上复选框。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3、对话框初始化的时候，给CTreeCtrl控件添加节点，并设置选中。代码如下：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#0000ff&gt;BOOL CTestCheckDlg::OnInitDialog()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;CDialog::OnInitDialog();&lt;BR&gt;。。。&lt;BR&gt;&amp;nbsp;// TODO: Add extra initialization here&lt;BR&gt;&amp;nbsp;HTREEITEM hRoot = m_Tree.InsertItem("Root");&lt;BR&gt;&amp;nbsp;m_Tree.SetCheck(hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.InsertItem("Child1", hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.InsertItem("Child2", hRoot);&lt;BR&gt;&amp;nbsp;m_Tree.Expand(hRoot, TVE_EXPAND );&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#0000ff&gt;&amp;nbsp;return TRUE;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;但是，对话框运行以后，树控件的根节点并没有显示被选中。用GetCheck测试，返回FALSE。但是&lt;BR&gt;如果在OnInitDialog里面SetCheck以后，立刻用GetCheck测试，发现返回的却是TRUE。&lt;/P&gt;
&lt;P&gt;进一步测试可以发现，对话框显示以后，任何时候再次使用SetCheck都没有问题了。&lt;/P&gt;
&lt;P&gt;二、问题分析&lt;/P&gt;
&lt;P&gt;由于无法直接监视树控件根节点状态的变化，为了弄清楚树控件究竟是什么时候修改了我们设置的&lt;BR&gt;check状态，我在其他消息响应函数中进一步用GetCheck检查，发现对话框在第一次OnPaint的时候&lt;BR&gt;，树控件根节点的check状态还是选中的，但紧接着下个消息来到后，它的状态就发生了改变。我&lt;BR&gt;思来想去肯定是树控件本身的问题。可能是它第一次画自己的时候，修改了每个节点的选择状态。&lt;/P&gt;
&lt;P&gt;但是它为何这么做？我开始猜测和它的图表列表有关系。为了验证这个想法，加入下面的代码：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;CImageList* pStateIcon = m_Tree.GetImageList(TVSIL_STATE);&lt;BR&gt;&amp;nbsp;int nItem = (pStateIcon == NULL)?(0):(pStateIcon-&amp;gt;GetImageCount());&lt;BR&gt;&amp;nbsp;CString str;&lt;BR&gt;&amp;nbsp;str.Format("Count of State Icon: %d\n", nItem);&lt;BR&gt;&amp;nbsp;TRACE(str);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;果然不出所料，测试结果发现，在OnInitDialog里面，树控件虽然添加了数据，但是它的State Icon&lt;BR&gt;并没有加载，ImageList是空的。一旦它显示自己后，ImageList就不是NULL了，里面的图标数目是3。&lt;BR&gt;也就是说它使用了3个图标。&lt;/P&gt;
&lt;P&gt;显然是树控件在创建自己的时候，并没有加载所需的图标列表，而是在显示的时候，发现需要后，它才&lt;BR&gt;加载，并且重新复位了每个节点的选择状态。它之所以这么做，我想可能是出于效率方面的考虑。也就&lt;BR&gt;是说，如果用户没有添加TVS_HASBUTTONS，那么它就不需要这个图标列表了。&lt;/P&gt;
&lt;P&gt;三、解决问题&lt;/P&gt;
&lt;P&gt;既然知道了问题的所在，那么鉴于在对话框初始化的时候，设置树控件的数据和选择状态是一个常规的&lt;BR&gt;做法，我就不打算修改这些代码的位置，而采用提前给它加载图标列表的方法。方法如下：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 1、在资源中添加一个位图资源IDB_BITMAP1，尺寸拉伸成48*16。（长48，高16，共三个图标）&lt;BR&gt;&amp;nbsp;&amp;nbsp; 2、在位图上面画三个图标，每个大小都是16*16，第二个是X，第三个是选择（对勾）。至于第一个&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 的用途，还不清楚。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3、在对话框的头文件中，添加一个CImageList对象：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CImageList m_ImageList;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 4、在对话框初始化函数中，添加如下代码：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;m_ImageList.Create(IDB_BITMAP1, 16,3,RGB(255,255,255));&lt;BR&gt;&amp;nbsp;m_Tree.SetImageList(&amp;amp;m_ImageList, TVSIL_STATE);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;运行以后，发现问题已经解决。只不过树控件的状态图标已经换成了自己的，可以弄得更好看一些。&lt;/P&gt;
&lt;P&gt;以上代码的测试环境[win98 / vc6.0]&lt;/P&gt;
&lt;P&gt;2006/2/17 iwaswzq&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/17836.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>关于f(n)=n的我的算法</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/12/03/15587.html</link><pubDate>Sat, 03 Dec 2005 09:02:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/12/03/15587.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/15587.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/12/03/15587.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/15587.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/15587.html</trackback:ping><description>&lt;P&gt;&lt;BR&gt;上次看到星星贴的一道面试题，觉得有点意思，自己也作了一下。&lt;/P&gt;
&lt;P&gt;题目：&lt;BR&gt;有一个整数n,写一个函数f(n),返回0到n之间出现的"1"的个数。比如f(13)=6,现在f(1)=1,问下一个最大的f(n)=n的n是什么？&lt;/P&gt;
&lt;P&gt;算法1。大家自然想到一个最简单的算法如下：&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#000080&gt;#include "stdafx.h"&lt;BR&gt;#include "stdio.h"&lt;BR&gt;#include &amp;lt;windows.h&amp;gt; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#000080&gt;//统计一个数字中1的个数&lt;BR&gt;int NumberofOne(int num)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int result = 0;&lt;BR&gt;&amp;nbsp;while(num&amp;gt;0)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;int r = num%10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(r == 1) result++;&lt;BR&gt;&amp;nbsp;&amp;nbsp;num /= 10;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return result;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #a9a9a9" color=#000080&gt;int main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;int sum = 0;&lt;BR&gt;&amp;nbsp;for(int i=0;i&amp;lt;4000000000;i++)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;sum += NumberofOne(i);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(sum == i)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("f(%d) = %d\n",i,sum);&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return 0;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;算法虽然很简单，但是效率太差，应该考虑优化算法，提高计算结果的速度。后来一个网友发布了另一个代码。&lt;/P&gt;
&lt;P&gt;算法二：来自&lt;A href="http://blog.csdn.net/psyl/archive/2005/11/29/539103.aspx"&gt;http://blog.csdn.net/psyl/archive/2005/11/29/539103.aspx&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;他用的是剪枝算法，速度还可以，本机测试运算到40亿的话，用了32ms&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;#include "stdafx.h"&lt;BR&gt;#include "stdio.h"&lt;BR&gt;#include &amp;lt;windows.h&amp;gt; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int gTable[10]; &lt;BR&gt;const unsigned int gMAX = 4000000000L; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int f(int n) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;int ret = 0; &lt;BR&gt;&amp;nbsp;int ntemp=n; &lt;BR&gt;&amp;nbsp;int ntemp2=1; &lt;BR&gt;&amp;nbsp;int i=1; &lt;BR&gt;&amp;nbsp;while(ntemp) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;ret += (((ntemp-1)/10)+1) * i; &lt;BR&gt;&amp;nbsp;&amp;nbsp;if( (ntemp%10) == 1 ) &lt;BR&gt;&amp;nbsp;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ret -= i; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ret += ntemp2; &lt;BR&gt;&amp;nbsp;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;&amp;nbsp;ntemp = ntemp/10; &lt;BR&gt;&amp;nbsp;&amp;nbsp;i*=10; &lt;BR&gt;&amp;nbsp;&amp;nbsp;ntemp2 = n%i+1; &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;return ret; &lt;BR&gt;} &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int count1(int n) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;int count = 0; &lt;BR&gt;&amp;nbsp;while(n) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;if( (n%10) == 1) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;++count; &lt;BR&gt;&amp;nbsp;&amp;nbsp;n /= 10; &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;return count; &lt;BR&gt;} &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int cal(unsigned int number,int nwei,int count1,unsigned int ncount) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;int i,n=1; &lt;BR&gt;&amp;nbsp;unsigned int maxcount; &lt;BR&gt;&amp;nbsp;if(nwei==0) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;ncount += count1; &lt;BR&gt;&amp;nbsp;&amp;nbsp;if(number == ncount) &lt;BR&gt;&amp;nbsp;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("f(%d) = %d \n",number,number); &lt;BR&gt;&amp;nbsp;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;&amp;nbsp;return ncount; &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;for(i=0;i&amp;lt;nwei;++i) &lt;BR&gt;&amp;nbsp;&amp;nbsp;n *= 10; &lt;BR&gt;&amp;nbsp;maxcount = ncount + gTable[nwei-1]; &lt;BR&gt;&amp;nbsp;maxcount += count1*n; &lt;BR&gt;&amp;nbsp;if(ncount &amp;gt; (number + (n-1)) ) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;return maxcount; &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;if(maxcount &amp;lt; number) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;return maxcount; &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;n /= 10; &lt;BR&gt;&amp;nbsp;for(i=0;i&amp;lt;10;++i) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;if(i==1) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ncount = cal(number+i*n,nwei-1,count1+1,ncount); &lt;BR&gt;&amp;nbsp;&amp;nbsp;else &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ncount = cal(number+i*n,nwei-1,count1,ncount); &lt;BR&gt;&amp;nbsp;} &lt;BR&gt;&amp;nbsp;return ncount; &lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int main(int argc, char* argv[]) &lt;BR&gt;{ &lt;BR&gt;&amp;nbsp;int i; &lt;BR&gt;&amp;nbsp;unsigned int n=1; &lt;BR&gt;&amp;nbsp;unsigned int ncount = 0; &lt;BR&gt;&amp;nbsp;int nwei = 0; &lt;BR&gt;&amp;nbsp;int ncount1; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;int beginTime=GetTickCount(); &lt;BR&gt;&amp;nbsp;//init gTable &lt;BR&gt;&amp;nbsp;for(i=0;i&amp;lt;10;++i) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;n *= 10; &lt;BR&gt;&amp;nbsp;&amp;nbsp;gTable[i] = f(n-1); &lt;BR&gt;&amp;nbsp;} &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;n=0; &lt;BR&gt;&amp;nbsp;nwei = 0; &lt;BR&gt;&amp;nbsp;ncount1 = 0; &lt;BR&gt;&amp;nbsp;while(n&amp;lt;gMAX) &lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;unsigned int temp; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;&amp;nbsp;temp = 1; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;&amp;nbsp;ncount =cal(n,nwei,ncount1,ncount); &lt;BR&gt;&amp;nbsp;&amp;nbsp;for(i=0;i&amp;lt;nwei;++i) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;temp *= 10; &lt;BR&gt;&amp;nbsp;&amp;nbsp;n += temp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;if( (n/temp)/10 == 1) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;++nwei; &lt;BR&gt;&amp;nbsp;&amp;nbsp;ncount1 = count1(n); &lt;BR&gt;&amp;nbsp;} &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;int endTime=GetTickCount(); &lt;BR&gt;&amp;nbsp;endTime-=beginTime; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;printf("time: %d ms\n",endTime); &lt;BR&gt;&amp;nbsp;return 0; &lt;BR&gt;} &lt;BR&gt;&lt;/FONT&gt;因为没有说明，没有仔细研究这个算法。&lt;/P&gt;
&lt;P&gt;算法三：这是我的算法。加速变量和优化一起做。基于以下考虑：&lt;/P&gt;
&lt;P&gt;1、两个函数i和f(i)都是单增函数，故有：&lt;/P&gt;
&lt;P&gt;1、计算n位最大数的f值，例如f(9) = 1; f(99) = 20; f(999) = 300, 自然有：&lt;BR&gt;&amp;nbsp;&amp;nbsp; 如果i是n位数字，而且i&amp;gt;f(999..9) ,则可以跳过后面的计算，例如当i&amp;gt;300的时候，可以忽略f(301) 至f(999)。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp; 我称以上优化为后段优化&lt;/P&gt;
&lt;P&gt;2、计算i和f(i)之间的差值。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 假设：diff = i - f(i); &lt;BR&gt;&amp;nbsp;&amp;nbsp; （1）如果diff&amp;gt;0，说明f(i) &amp;lt; i。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 考虑f(i)的增长速度：显然此时f(i)的增加速度&amp;lt;=i的位数。例如i是二位数，f(20) = 11; 差值diff = 20-11 = 9;至少i要增加9/2=4次，&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; f(i)才有可能追上i的增长。因此可以直接计算f(20+4);&lt;BR&gt;&amp;nbsp;&amp;nbsp; （2）如果diff&amp;lt;0, 说明f(i) &amp;gt; i。显然i至少要增长 -diff才有可能追上f(i)。例如f(1622581) = 1642401; &lt;BR&gt;差值diff =&amp;nbsp;1622581 -&amp;nbsp;1642401 = -19820;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 可以直接考虑计算f(1622581 + 19820),也就是计算f(1642401)即可。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 我称以上优化为前段优化&lt;/P&gt;
&lt;P&gt;综合以上两点，写出如下算法：&lt;BR&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;#include "stdafx.h"&lt;BR&gt;#include "stdio.h"&lt;BR&gt;#include &amp;lt;windows.h&amp;gt; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;//计算从1到num的1的个数&lt;BR&gt;UINT NumberOneBelow(UINT num)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;UINT mod = 1;&lt;BR&gt;&amp;nbsp;UINT add = 0;&lt;BR&gt;&amp;nbsp;UINT result = 0;&lt;BR&gt;&amp;nbsp;while(num&amp;gt;0)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;UINT chu = num/10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;UINT yu&amp;nbsp; = num%10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;result += chu * mod;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(yu &amp;gt; 1) result += mod;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(yu ==1) result += (add+1);&lt;BR&gt;&amp;nbsp;&amp;nbsp;add += yu * mod;&lt;BR&gt;&amp;nbsp;&amp;nbsp;mod *= 10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;num /= 10;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return result;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;//计算n位数之内1的总数统计值，这里的num只考虑它的位数&lt;BR&gt;UINT Total(UINT num)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;UINT key = num;&lt;BR&gt;&amp;nbsp;UINT add = 1;&lt;BR&gt;&amp;nbsp;while(key&amp;gt;9)&lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;add*=10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;key/=10;&lt;BR&gt;&amp;nbsp;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;if(num&amp;lt;10)return 1;&lt;BR&gt;&amp;nbsp;else return 10*Total(num/10)+add;&lt;BR&gt;}&lt;BR&gt;//计算num相关的最大值和位数。例如num是二位数，则max = 99, bit = 2&lt;BR&gt;void MaxAndBit(UINT num, UINT &amp;amp;max, UINT &amp;amp;bit)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;max = 9;&lt;BR&gt;&amp;nbsp;bit = 1;&lt;BR&gt;&amp;nbsp;while(num&amp;gt;9)&lt;BR&gt;&amp;nbsp;{ &lt;BR&gt;&amp;nbsp;&amp;nbsp;num/=10;&lt;BR&gt;&amp;nbsp;&amp;nbsp;max*=10; max+=9;&lt;BR&gt;&amp;nbsp;&amp;nbsp;bit++;&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;return ;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;int main()&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;UINT max, bit;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;UINT TotalOnes = Total(1);&lt;BR&gt;&amp;nbsp;MaxAndBit(1,max,bit);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;int beginTime=GetTickCount(); &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;for(UINT i=1;i&amp;lt;4000000000;i++)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;//如果i超过n位之内所有1的个数，则跳过后面的计算&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(i&amp;gt;TotalOnes)&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;i = max; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;TotalOnes = Total(i+1);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;MaxAndBit(i+1,max,bit);&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;//超过最大整数表示范围，用固定数替换&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(TotalOnes&amp;gt;1000000000)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TotalOnes = 4000000000;&amp;nbsp; //实际上，应该是100亿&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;UINT one = NumberOneBelow(i);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(one == i)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("f(%d)=%d\n",i,one);&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//计算差值&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int&amp;nbsp; diff = i-one;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(diff &amp;gt; 0)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i+= diff/bit;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i = one-1;//下个循环i自动加1后，自动从one开始计算&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;&amp;nbsp;}&lt;BR&gt;&amp;nbsp;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;int endTime=GetTickCount(); &lt;BR&gt;&amp;nbsp;endTime-=beginTime; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT style="BACKGROUND-COLOR: #d3d3d3" color=#0000ff&gt;&amp;nbsp;printf("time: %d ms\n",endTime); &lt;BR&gt;&amp;nbsp;return 0;&lt;BR&gt;}&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;计算到最后一个结果f(1111111110) = 1111111110; 所用时间在16 - 47毫秒之间。&lt;/P&gt;
&lt;P&gt;iwaswzq 2005/12/2&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/15587.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>九月鹰飞</dc:creator><title>scanf中有意思的过滤</title><link>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html</link><pubDate>Thu, 20 Oct 2005 10:46:00 GMT</pubDate><guid>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html</guid><wfw:comment>http://blog.vckbase.com/iwaswzq/comments/13646.html</wfw:comment><comments>http://blog.vckbase.com/iwaswzq/archive/2005/10/20/13646.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/iwaswzq/comments/commentRss/13646.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/iwaswzq/services/trackbacks/13646.html</trackback:ping><description>1、scanf输入数据的时候，它的参数表达式有过滤功能。比如：&lt;BR&gt;int i,j,k;&lt;BR&gt;scanf("%d空格%d空格%d空格",&amp;amp;i,&amp;amp;j,&amp;amp;k);&lt;BR&gt;你输入 10空格20空格30回车， scanf开始处理，它把10 读入到i中，看到后面的空格，会自动删除之。虽然&lt;BR&gt;最后30后面没有空格也没有关系。但是如果10后面或者20后面没有空格，或者是其它字符，比如：&lt;BR&gt;10,20空格30回车，输入就会出错。当然多个空格也没有关系，例如：10空格空格20空格30回车。因为扫描&lt;BR&gt;整数的时候会自动删除多余的空格。&lt;BR&gt;如果用scanf("%d空格,%d,空格%d",&amp;amp;i,&amp;amp;j,&amp;amp;k); //注意空格和逗号的顺序，则输入和结果的比较是：&lt;BR&gt;10空格,20,空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //和表达式正好匹配，没有问题&lt;BR&gt;10,20,30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //ok&lt;BR&gt;10,空格20,空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //ok&lt;BR&gt;10,空格20空格,30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //出错&lt;BR&gt;10空格20空格30&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //更错&lt;BR&gt;2、过滤具有多重功能。例如下面输入字符的例子：&lt;BR&gt;&amp;nbsp;char c;&lt;BR&gt;&amp;nbsp;for(;;)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%cabc",&amp;amp;c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c=='q')break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;printf("c=%c\n",c);&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;则在输入的字符串里面遇到a, ab, abc, 会自动删除。例如输入：&lt;BR&gt;1a2ab3abc4bc5ba6aa7abca8&lt;BR&gt;输出结果是：&lt;BR&gt;c=1&lt;BR&gt;c=2&lt;BR&gt;c=3&lt;BR&gt;c=4&lt;BR&gt;c=b&lt;BR&gt;c=c&lt;BR&gt;c=5&lt;BR&gt;c=b&lt;BR&gt;c=6&lt;BR&gt;c=a&lt;BR&gt;c=7&lt;BR&gt;c=a&lt;BR&gt;c=8&lt;BR&gt;&lt;BR&gt;可以看到实际的过滤情况，但是有意思的一点是scanf不会过滤bc，而且一旦成功过滤一组输入，&lt;BR&gt;接下来的数据就会直接读入，例如遇到aa的情况，过滤掉a以后，下一个a被成功读入。&lt;BR&gt;&lt;BR&gt;而且上面的例子，如果输入一些数据后，再输入q退出，则还会输出一个c=， 表明读入了一个回车符，&lt;BR&gt;真的很奇怪。可以如下测试：&lt;BR&gt;&amp;nbsp;char c;&lt;BR&gt;&amp;nbsp;for(;;)&lt;BR&gt;&amp;nbsp;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%cabc",&amp;amp;c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c=='q')break;&lt;BR&gt;&amp;nbsp;&amp;nbsp;if(c!='\n')&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("c=%c\n",c);&lt;BR&gt;&amp;nbsp;&amp;nbsp;else &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;printf("haha!\n");&lt;BR&gt;&amp;nbsp;}&lt;BR&gt;3、再做进一步的测试，会发现更诡异的事情，把上面的代码改成：&lt;BR&gt;&amp;nbsp;&amp;nbsp;scanf("%c abc",&amp;amp;c); //注意abc前面的空格&lt;BR&gt;你会发现输入q以后，程序并没有退出，还需要再输入一个字符。改成scanf("%c",&amp;amp;c);就没有问题了。&lt;BR&gt;&lt;BR&gt;上述现象应该不是什么语法问题，是scanf这个函数本身的代码产生的问题。诸如下面这样的代码：&lt;BR&gt;char a[20];&lt;BR&gt;&amp;nbsp;scanf("%[A-Za-z ]s", a); &lt;BR&gt;因为很少用到它，我不想再去研究了，谁感兴趣谁去弄吧，呵呵。&lt;BR&gt;&lt;img src ="http://blog.vckbase.com/iwaswzq/aggbug/13646.html" width = "1" height = "1" /&gt;</description></item></channel></rss>