﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-twzheng's cppblog-文章分类-C/C++</title><link>http://www.cppblog.com/twzheng/category/3939.html</link><description>『站在风口浪尖紧握住鼠标旋转！』</description><language>zh-cn</language><lastBuildDate>Fri, 15 Aug 2008 12:19:15 GMT</lastBuildDate><pubDate>Fri, 15 Aug 2008 12:19:15 GMT</pubDate><ttl>60</ttl><item><title>Visual C++线程同步技术剖析</title><link>http://www.cppblog.com/twzheng/articles/40548.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Sun, 06 Jan 2008 06:41:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/40548.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/40548.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/40548.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/40548.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/40548.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Visual C++线程同步技术剖析摘自：天极网作者：中国电波传播研究所 郎锐　&nbsp;&nbsp; 摘要： 多线程同步技术是计算机软件开发的重要技术，本文对多线程的各种同步技术的原理和实现进行了初步探讨。　　关键词： VC++6.0； 线程同步；临界区；事件；互斥；信号量； 　　阅读目录： 　　使线程同步 　　临界区　　管理事件内核对象 　　信号量内核对象　　互斥内核对象 　　小结 　　...&nbsp;&nbsp;<a href='http://www.cppblog.com/twzheng/articles/40548.html'>阅读全文</a><img src ="http://www.cppblog.com/twzheng/aggbug/40548.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2008-01-06 14:41 <a href="http://www.cppblog.com/twzheng/articles/40548.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>句柄的本质</title><link>http://www.cppblog.com/twzheng/articles/40545.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Sun, 06 Jan 2008 06:12:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/40545.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/40545.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/40545.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/40545.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/40545.html</trackback:ping><description><![CDATA[<strong style="FONT-SIZE: 18pt">句柄的本质</strong><br><span style="FONT-SIZE: 10pt">摘自 好易教程网</span><br><br>一、书上定义：<br><br>&lt;&lt;Microsoft Windows 3 Developer''s Workshop&gt;&gt;(Microsoft Press,by Richard Wilton)<br>&nbsp;&nbsp;&nbsp;&nbsp;在Windows环境中，句柄是用来标识项目的，这些项目包括：模块(module)、任务(task)、实例 (instance)、文件(file)、内存块(block of memory)、菜单(menu)、控制(control)、字体(font)、资源(resource)，包括图标(icon)，光标 (cursor)，字符串(string)等、GDI对象(GDI object)，包括位图(bitmap)，画刷(brush)，元文件（metafile）,调色板(palette)，画笔(pen)，区域 (region)，以及设备描述表(device context)。 <br><br>&lt;&lt;WINDOWS编程短平快&gt;&gt;(南京大学出版社)：<br>&nbsp;&nbsp;&nbsp;&nbsp;句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数，WINDOWS使用各种各样的句柄标识诸如应用程序实例，窗口，控制，位图，GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。 <br><br>二、MFC源代码：<br><br>#ifdef STRICT<br>typedef void *HANDLE;<br>#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name<br>#else<br>typedef PVOID HANDLE;<br>#define DECLARE_HANDLE(name) typedef HANDLE name<br>#endif<br><br>DECLARE_HANDLE(HMODULE); <br>DECLARE_HANDLE(HINSTANCE); <br>DECLARE_HANDLE(HLOCAL); <br>DECLARE_HANDLE(HGLOBAL); <br>DECLARE_HANDLE(HDC); <br>DECLARE_HANDLE(HRGN); <br>DECLARE_HANDLE(HWND); <br>DECLARE_HANDLE(HMENU); <br>DECLARE_HANDLE(HACCEL); <br>DECLARE_HANDLE(HTASK); <br><br><br>三、理解：<br>&nbsp;&nbsp;&nbsp;&nbsp;HANDLE就是PVOID，也就是无类型指针，<br>&nbsp;&nbsp;&nbsp;&nbsp;上面这些资源的句柄Handles都不过是指向struct的指针，至于这个struct的用处，连M$都说unused了，现在解释下M$这么做的意义，这就是所谓数据封装，你可以在你的程序中把M$的内部结构指针传来传去，可是你却不知道它到底指向的内容是什么。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;句柄与指针确实是完全不同的两个概念。句柄仅仅是一个32位整数，WIN32中用于标记某个系统或进程的对象，可以理解为对象索引（由于M$未完全公开相关技术，在一定程度上只能如此理解），这个索引更像是一种映射关系（从句柄到对象指针的映射），而不是纯粹意义上的&#8220;数组下标&#8221;。 <br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;句柄可以理解为用于指向或标识内存的一块&#8220;资源&#8221;，这些资源如：文件(file)、内存块(block of memory)、菜单(menu)等等。操作系统通过句柄来定位核心对象和系统资源。<br>&nbsp;&nbsp;&nbsp;&nbsp;指针即为指向内存的&#8220;数据或指令&#8221;某一单元。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;说的确切一点，句柄实际上是一种指向某种资源的指针，但与指针又有所不同：指针对应着一个数据在内存中的地址，得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构，因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针，但不要直接操作它)，平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<br>四、引喻：<br>&nbsp;&nbsp;&nbsp;牧童遥指杏花村<br>&nbsp;&nbsp;&nbsp;牧童的手为指针，杏花村的牌子为句柄，杏花村酒店为对象的实例. <br><br><br>附注：获得窗口句柄三种方法<br><br>1.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName) <br><br>&nbsp;&nbsp;&nbsp;HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName) <br><br>2.HWND WindowFromPoint(POINT&amp; Point)//获得当前鼠标光标位置的窗口HWND<br><br>3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)<br><br>&nbsp;&nbsp;&nbsp;BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)<br><br>&nbsp;&nbsp;&nbsp;BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)<br><br>&nbsp;&nbsp;&nbsp;BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
<img src ="http://www.cppblog.com/twzheng/aggbug/40545.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2008-01-06 14:12 <a href="http://www.cppblog.com/twzheng/articles/40545.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于strcpy()与memcpy()比较</title><link>http://www.cppblog.com/twzheng/articles/26275.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Wed, 13 Jun 2007 12:57:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/26275.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/26275.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/26275.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/26275.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/26275.html</trackback:ping><description><![CDATA[<p><strong style="FONT-SIZE: 18pt">关于strcpy()与memcpy()比较<br></strong><br>举例：</p>
<p>{</p>
<p>byte a[4];// 每一个字节赋数值类型<br><br>byte c1[4];<br><br>byte c2[4];</p>
<p>memcpy(c1, a, sizeof(byte)*4); //正确使用<br><br>strcpy((char*)(byte*)c2, (char*)(byte*)a);//错误调用</p>
<p>}</p>
<p>函数原型<br><br>&nbsp;&nbsp; strcpy<br><br>&nbsp;&nbsp; extern char *strcpy(char *dest,char *src);<br><br>&nbsp;&nbsp; #include &lt;string.h&gt;<br><br>&nbsp;&nbsp; 功能：把src所指由NULL结束的字符串复制到dest所指的数组中<br><br>&nbsp;&nbsp; 说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。<br><br>&nbsp;&nbsp; 返回指向dest的指针<br><br><br><br>&nbsp; memcpy<br><br>&nbsp; extern void *memcpy(void *dest,void *src,unsigned int count);<br><br>&nbsp; #include &lt;string.h&gt;<br><br>&nbsp;&nbsp; 功能：由src所指内存区域复制count个字符串到dest所指内存区域.<br><br>&nbsp;&nbsp; 说明：src和dest所指内存区域不能重叠，函数返回指向dest的指针.函数原型<br><br>&nbsp;</p>
<p><span>关于memcpy的用法</span></p>
<p><span>作用：在dest处拷贝src处的字节，并以count来计算需要拷贝的字节数量，进行内存的拷贝。</span></p>
<p><span>参数： dest:新的存贮区的开始部位&nbsp;&nbsp; src：需要拷贝的开始部位&nbsp;&nbsp;&nbsp; count：需要拷贝的字节数备注：dest,src，它们都是从各自的地址处进行写入，如果是p而不是&amp;p,那么奖会取得p的值（地址）,在该值的地址处进行读出或写入。</span></p>
<p><span>例：&nbsp;&nbsp;</span></p>
<p><span>int* intPoint = new int(3333);&nbsp;&nbsp;</span></p>
<p><span>int* intPoint1;&nbsp;&nbsp;</span></p>
<p><span>//在intPoint1的地址处写入intPoint地址处的值，也就是intPoint指针值。&nbsp;</span></p>
<p><span>memcpy( &amp;intPoint1, &amp;intPoint, 4 );</span></p>
<p><span>//使intPoint1指向了intPoint. 或 &nbsp; int* intPoint = new int(3333);</span></p>
<p><span>cout &lt;&lt; *intPoint1 &lt;&lt; endl;</span></p>
<p>可以看出　strcpy()　是处理的字符串(遇零结束)，memcpy()　是处理一个缓冲区（void*类型的），而我们的内容中有数字0，而数字0又是字符串的结尾字符 ' \0' 的数字表现，字符串拷贝是遇到0就结束，所以，如果要拷贝的缓冲区如果是非字符串那么就尽量用memcpy()，这样可以避免出错的可能。</p>
<img src ="http://www.cppblog.com/twzheng/aggbug/26275.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-06-13 20:57 <a href="http://www.cppblog.com/twzheng/articles/26275.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++中回调函数初探</title><link>http://www.cppblog.com/twzheng/articles/CALLBACK.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 21 May 2007 08:51:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/CALLBACK.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/24543.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/CALLBACK.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/24543.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/24543.html</trackback:ping><description><![CDATA[<h1>C/C++中回调函数初探</h1>
<span style="FONT-SIZE: 10pt">[摘自] 天极开发网（http://dev.yesky.com/）</span><br><br><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;简介<br><br></strong>　　对于很多初学者来说，往往觉得回调函数很神秘，很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题，在开始之前，假设你已经熟知了函数指针。 <br><br>　　<strong>什么是回调函数？</strong><br><br>　　简而言之，回调函数就是一个通过函数指针调用的函数。如果你把函数的指针（地址）作为参数传递给另一个函数，当这个指针被用为调用它所指向的函数时，我们就说这是回调函数。<br><br>　　<strong>为什么要使用回调函数？</strong><br><br>　　因为可以把调用者与被调用者分开。调用者不关心谁是被调用者，所有它需知道的，只是存在一个具有某种特定原型、某些限制条件（如返回值为int）的被调用函数。<br><br>　　如果想知道回调函数在实际中有什么作用，先假设有这样一种情况，我们要编写一个库，它提供了某些排序算法的实现，如冒泡排序、快速排序、shell排序、shake排序等等，但为使库更加通用，不想在函数中嵌入排序逻辑，而让使用者来实现相应的逻辑；或者，想让库可用于多种数据类型（int、float、string），此时，该怎么办呢？可以使用函数指针，并进行回调。<br><br>　　回调可用于通知机制，例如，有时要在程序中设置一个计时器，每到一定时间，程序会得到相应的通知，但通知机制的实现者对我们的程序一无所知。而此时，就需有一个特定原型的函数指针，用这个指针来进行回调，来通知我们的程序事件已经发生。实际上，SetTimer() API使用了一个回调函数来通知计时器，而且，万一没有提供回调函数，它还会把一个消息发往程序的消息队列。<br><br>　　另一个使用回调机制的API函数是EnumWindow()，它枚举屏幕上所有的顶层窗口，为每个窗口调用一个程序提供的函数，并传递窗口的处理程序。如果被调用者返回一个值，就继续进行迭代，否则，退出。EnumWindow()并不关心被调用者在何处，也不关心被调用者用它传递的处理程序做了什么，它只关心返回值，因为基于返回值，它将继续执行或退出。<br><br>　　不管怎么说，回调函数是继续自C语言的，因而，在C++中，应只在与C代码建立接口，或与已有的回调接口打交道时，才使用回调函数。除了上述情况，在C++中应使用虚拟方法或函数符（functor），而不是回调函数。<br><br>　　<strong>一个简单的回调函数实现</strong><br><br>　　下面创建了一个sort.dll的动态链接库，它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*)，它就是回调函数的类型。另外，它也导出了两个方法：Bubblesort()和Quicksort()，这两个方法原型相同，但实现了不同的排序算法。<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);<br><br>void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc);</td>
        </tr>
    </tbody>
</table>
<br>　　这两个函数接受以下参数：<br><br>　　&#183;byte * array：指向元素数组的指针（任意类型）。<br><br>　　&#183;int size：数组中元素的个数。<br><br>　　&#183;int elem_size：数组中一个元素的大小，以字节为单位。<br><br>　　&#183;CompareFunction cmpFunc：带有上述原型的指向回调函数的指针。<br><br>　　这两个函数的会对数组进行某种排序，但每次都需决定两个元素哪个排在前面，而函数中有一个回调函数，其地址是作为一个参数传递进来的。对编写者来说，不必介意函数在何处实现，或它怎样被实现的，所需在意的只是两个用于比较的元素的地址，并返回以下的某个值（库的编写者和使用者都必须遵守这个约定）：<br><br>　　&#183;-1：如果第一个元素较小，那它在已排序好的数组中，应该排在第二个元素前面。<br><br>　　&#183;0：如果两个元素相等，那么它们的相对位置并不重要，在已排序好的数组中，谁在前面都无所谓。 <br><br>　　&#183;1：如果第一个元素较大，那在已排序好的数组中，它应该排第二个元素后面。<br><br>　　基于以上约定，函数Bubblesort()的实现如下，Quicksort()就稍微复杂一点：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc)<br>{<br>　for(int i=0; i &lt; size; i++)<br>　{<br>　　for(int j=0; j &lt; size-1; j++)<br>　　{<br>　　　//回调比较函数<br>　　　if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))<br>　　　{<br>　　　　//两个相比较的元素相交换<br>　　　　byte* temp = new byte[elem_size];<br>　　　　memcpy(temp, array+j*elem_size, elem_size);<br>　　　　memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size);<br>　　　　memcpy(array+(j+1)*elem_size, temp, elem_size);<br>　　　　delete [] temp;<br>　　　}<br>　　}<br>　}<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　注意：因为实现中使用了memcpy()，所以函数在使用的数据类型方面，会有所局限。<br><br>　　对使用者来说，必须有一个回调函数，其地址要传递给Bubblesort()函数。下面有二个简单的示例，一个比较两个整数，而另一个比较两个字符串：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>int __stdcall CompareInts(const byte* velem1, const byte* velem2)<br>{<br>　int elem1 = *(int*)velem1;<br>　int elem2 = *(int*)velem2;<br><br>　if(elem1 &lt; elem2)<br>　　return -1;<br>　if(elem1 &gt; elem2)<br>　　return 1;<br><br>　return 0;<br>}<br><br>int __stdcall CompareStrings(const byte* velem1, const byte* velem2)<br>{<br>　const char* elem1 = (char*)velem1;<br>　const char* elem2 = (char*)velem2;<br>　return strcmp(elem1, elem2);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　下面另有一个程序，用于测试以上所有的代码，它传递了一个有5个元素的数组给Bubblesort()和Quicksort()，同时还传递了一个指向回调函数的指针。<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>int main(int argc, char* argv[])<br>{<br>　int i;<br>　int array[] = {5432, 4321, 3210, 2109, 1098};<br><br>　cout &lt;&lt; "Before sorting ints with Bubblesort\n";<br>　for(i=0; i &lt; 5; i++)<br>　　cout &lt;&lt; array[i] &lt;&lt; '\n';<br><br>　Bubblesort((byte*)array, 5, sizeof(array[0]), &amp;CompareInts);<br><br>　cout &lt;&lt; "After the sorting\n";<br>　for(i=0; i &lt; 5; i++)<br>　　cout &lt;&lt; array[i] &lt;&lt; '\n';<br><br>　const char str[5][10] = {"estella","danielle","crissy","bo","angie"};<br><br>　cout &lt;&lt; "Before sorting strings with Quicksort\n";<br>　for(i=0; i &lt; 5; i++)<br>　　cout &lt;&lt; str[i] &lt;&lt; '\n';<br><br>　Quicksort((byte*)str, 5, 10, &amp;CompareStrings);<br><br>　cout &lt;&lt; "After the sorting\n";<br>　for(i=0; i &lt; 5; i++)<br>　　cout &lt;&lt; str[i] &lt;&lt; '\n';<br><br>　return 0;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　如果想进行降序排序（大元素在先），就只需修改回调函数的代码，或使用另一个回调函数，这样编程起来灵活性就比较大了。<br><strong><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;调用约定<br><br></strong>　　上面的代码中，可在函数原型中找到__stdcall，因为它以双下划线打头，所以它是一个特定于编译器的扩展，说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定，为什么叫标准约定呢，因为所有的Win32 API（除了个别接受可变参数的除外）都使用它。标准调用约定的函数在它们返回到调用者之前，都会从堆栈中移除掉参数，这也是Pascal的标准约定。但在C/C++中，调用约定是调用者负责清理堆栈，而不是被调用函数；为强制函数使用C/C++调用约定，可使用__cdecl。另外，可变参数函数也使用C/C++调用约定。<br><br>　　Windows操作系统采用了标准调用约定（Pascal约定），因为其可减小代码的体积。这点对早期的Windows来说非常重要，因为那时它运行在只有640KB内存的电脑上。<br><br>　　如果你不喜欢__stdcall，还可以使用CALLBACK宏，它定义在windef.h中：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>#define CALLBACK __stdcallor<br><br>#define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall</td>
        </tr>
    </tbody>
</table>
<br>　　<strong>作为回调函数的C++方法</strong><br><br>　　因为平时很可能会使用到C++编写代码，也许会想到把回调函数写成类中的一个方法，但先来看看以下的代码：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>class CCallbackTester<br>{<br>　public:<br>　int CALLBACK CompareInts(const byte* velem1, const byte* velem2);<br>};<br><br>Bubblesort((byte*)array, 5, sizeof(array[0]),<br>&amp;CCallbackTester::CompareInts);</td>
        </tr>
    </tbody>
</table>
<br>　　如果使用微软的编译器，将会得到下面这个编译错误：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible</td>
        </tr>
    </tbody>
</table>
<br>　　这是因为非静态成员函数有一个额外的参数：this指针，这将迫使你在成员函数前面加上static。当然，还有几种方法可以解决这个问题，但限于篇幅，就不再论述了。<br><br>
<img src ="http://www.cppblog.com/twzheng/aggbug/24543.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-05-21 16:51 <a href="http://www.cppblog.com/twzheng/articles/CALLBACK.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows数据类型</title><link>http://www.cppblog.com/twzheng/articles/24535.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 21 May 2007 07:55:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/24535.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/24535.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/24535.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/24535.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/24535.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Platform&nbsp;SDK:&nbsp;Windows API            Windows Data Types[摘自] MSDNThe data types supported by Microsoft&#174; Windows&#174; are used to define function return values, function an...&nbsp;&nbsp;<a href='http://www.cppblog.com/twzheng/articles/24535.html'>阅读全文</a><img src ="http://www.cppblog.com/twzheng/aggbug/24535.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-05-21 15:55 <a href="http://www.cppblog.com/twzheng/articles/24535.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>最通俗浅显的“IO模式”解析</title><link>http://www.cppblog.com/twzheng/articles/24508.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Sun, 20 May 2007 17:46:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/24508.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/24508.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/24508.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/24508.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/24508.html</trackback:ping><description><![CDATA[<table style="TABLE-LAYOUT: fixed">
    <tbody>
        <tr>
            <td>
            <div class=cnt>
            <div class=articleTitle><strong>[转] 最通俗浅显的&#8220;IO模式&#8221;解析<br></strong>[源] ：http://hi.baidu.com/firebird/blog/item/f592b3193a02814542a9adeb.html<br><br>&nbsp;<font size=2>&nbsp;&nbsp;&nbsp; 一：select模型<br>&nbsp;&nbsp;&nbsp; 二：WSAAsyncSelect模型<br>&nbsp;&nbsp;&nbsp; 三：WSAEventSelect模型<br>&nbsp;&nbsp;&nbsp; 四：Overlapped I/O 事件通知模型<br>&nbsp;&nbsp;&nbsp; 五：Overlapped I/O 完成例程模型<br>&nbsp;&nbsp;&nbsp; 六：IOCP模型</font><br><br>原文名：《基于Delphi的Socket&nbsp;I/O模型全接触&nbsp;》 <br>老陈有一个在外地工作的女儿，不能经常回来，老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。&nbsp; <br><br>这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket&nbsp;I/O模型。&nbsp; <br><br>一：select模型&nbsp; <br><br>老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱，看是否有女儿的信，在这种情况下，&#8220;下楼检查信箱&#8221;然后回到楼上耽误了老陈太多的时间，以至于老陈无法做其他工作。&nbsp; <br><br>select模型和老陈的这种情况非常相似：周而复始地去检查......如果有数据......接收/发送.......&nbsp; <br><br>使用线程来select应该是通用的做法：&nbsp; <br><br>procedure&nbsp;TListenThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;fd_read&nbsp;:&nbsp;TFDSet;&nbsp; <br>　&nbsp;timeout&nbsp;:&nbsp;TTimeVal;&nbsp; <br>　&nbsp;ASock,&nbsp; <br>　&nbsp;MainSock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;len,&nbsp;i&nbsp;:&nbsp;Integer;&nbsp; <br>begin&nbsp; <br>　&nbsp;MainSock&nbsp;:=&nbsp;socket(&nbsp;AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP&nbsp;);&nbsp; <br>　&nbsp;addr.sin_family&nbsp;:=&nbsp;AF_INET;&nbsp; <br>　&nbsp;addr.sin_port&nbsp;:=&nbsp;htons(5678);&nbsp; <br>　&nbsp;addr.sin_addr.S_addr&nbsp;:=&nbsp;htonl(INADDR_ANY);&nbsp; <br>　&nbsp;bind(&nbsp;MainSock,&nbsp;@addr,&nbsp;sizeof(addr)&nbsp;);&nbsp; <br>　&nbsp;listen(&nbsp;MainSock,&nbsp;5&nbsp;);&nbsp; <br><br>　&nbsp;while&nbsp;(not&nbsp;Terminated)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;FD_ZERO(&nbsp;fd_read&nbsp;);&nbsp; <br>　　&nbsp;FD_SET(&nbsp;MainSock,&nbsp;fd_read&nbsp;);&nbsp; <br>　　&nbsp;timeout.tv_sec&nbsp;:=&nbsp;0;&nbsp; <br>　　&nbsp;timeout.tv_usec&nbsp;:=&nbsp;500;&nbsp; <br>　　&nbsp;if&nbsp;select(&nbsp;0,&nbsp;@fd_read,&nbsp;nil,&nbsp;nil,&nbsp;@timeout&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp;//至少有1个等待Accept的connection&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;FD_ISSET(&nbsp;MainSock,&nbsp;fd_read&nbsp;)&nbsp;then&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　&nbsp;for&nbsp;i:=0&nbsp;to&nbsp;fd_read.fd_count-1&nbsp;do&nbsp;//注意，fd_count&nbsp;&lt;=&nbsp;64， <br>&nbsp;&nbsp;&nbsp;也就是说select只能同时管理最多64个连接&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　　&nbsp;len&nbsp;:=&nbsp;sizeof(addr);&nbsp; <br>　　　　&nbsp;ASock&nbsp;:=&nbsp;accept(&nbsp;MainSock,&nbsp;addr,&nbsp;len&nbsp;);&nbsp; <br>　　　　&nbsp;if&nbsp;ASock&nbsp;&lt;&gt;&nbsp;INVALID_SOCKET&nbsp;then&nbsp; <br>　　　　　&nbsp;....//为ASock创建一个新的线程，在新的线程中再不停地select&nbsp; <br>　　　　&nbsp;end;&nbsp; <br>　　　&nbsp;end;&nbsp;　　&nbsp; <br>　　&nbsp;end;&nbsp; <br>　&nbsp;end;&nbsp;//while&nbsp;(not&nbsp;self.Terminated)&nbsp; <br><br>　&nbsp;shutdown(&nbsp;MainSock,&nbsp;SD_BOTH&nbsp;);&nbsp; <br>　&nbsp;closesocket(&nbsp;MainSock&nbsp;);&nbsp; <br>end; <br>&nbsp; <br><br><br>二：WSAAsyncSelect模型&nbsp; <br><br>后来，老陈使用了微软公司的新式信箱。这种信箱非常先进，一旦信箱里有新的信件，盖茨就会给老陈打电话：喂，大爷，你有新的信件了！从此，老陈再也不必频繁上下楼检查信箱了，牙也不疼了，你瞅准了，蓝天......不是，微软......&nbsp; <br><br>微软提供的WSAAsyncSelect模型就是这个意思。&nbsp; <br><br>WSAAsyncSelect模型是Windows下最简单易用的一种Socket&nbsp;I/O模型。使用这种模型时，Windows会把网络事件以消息的形势通知应用程序。&nbsp; <br><br>首先定义一个消息标示常量：&nbsp; <br><br>const&nbsp;WM_SOCKET&nbsp;=&nbsp;WM_USER&nbsp;+&nbsp;55; <br>&nbsp; <br><br><br>再在主Form的private域添加一个处理此消息的函数声明：&nbsp; <br><br>private&nbsp; <br>procedure&nbsp;WMSocket(var&nbsp;Msg:&nbsp;TMessage);&nbsp;message&nbsp;WM_SOCKET; <br>&nbsp; <br>　&nbsp; <br><br>然后就可以使用WSAAsyncSelect了：&nbsp; <br><br>var&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddr;&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br><br>　&nbsp;sock&nbsp;:=&nbsp;socket(&nbsp;AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP&nbsp;);&nbsp; <br>　&nbsp;addr.sin_family&nbsp;:=&nbsp;AF_INET;&nbsp; <br>　&nbsp;addr.sin_port&nbsp;:=&nbsp;htons(5678);&nbsp; <br>　&nbsp;addr.sin_addr.S_addr&nbsp;:=&nbsp;htonl(INADDR_ANY);&nbsp; <br>　&nbsp;bind(&nbsp;m_sock,&nbsp;@addr,&nbsp;sizeof(SOCKADDR)&nbsp;);&nbsp; <br><br>　&nbsp;WSAAsyncSelect(&nbsp;m_sock,&nbsp;Handle,&nbsp;WM_SOCKET,&nbsp;FD_ACCEPT&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br><br>　&nbsp;listen(&nbsp;m_sock,&nbsp;5&nbsp;);&nbsp; <br>　&nbsp;.... <br>&nbsp; <br><br><br>应用程序可以对收到WM_SOCKET消息进行分析，判断是哪一个socket产生了网络事件以及事件类型：&nbsp; <br><br>procedure&nbsp;TfmMain.WMSocket(var&nbsp;Msg:&nbsp;TMessage);&nbsp; <br>var&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;addr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;addrlen&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;buf&nbsp;:&nbsp;Array&nbsp;[0..4095]&nbsp;of&nbsp;Char;&nbsp; <br>begin&nbsp; <br>　&nbsp;//Msg的WParam是产生了网络事件的socket句柄，LParam则包含了事件类型&nbsp; <br>　&nbsp;case&nbsp;WSAGetSelectEvent(&nbsp;Msg.LParam&nbsp;)&nbsp;of&nbsp; <br>　&nbsp;FD_ACCEPT&nbsp;:&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;addrlen&nbsp;:=&nbsp;sizeof(addr);&nbsp; <br>　　　&nbsp;sock&nbsp;:=&nbsp;accept(&nbsp;Msg.WParam,&nbsp;addr,&nbsp;addrlen&nbsp;);&nbsp; <br>　　　&nbsp;if&nbsp;sock&nbsp;&lt;&gt;&nbsp;INVALID_SOCKET&nbsp;then&nbsp; <br>　　　　&nbsp;WSAAsyncSelect(&nbsp;sock,&nbsp;Handle,&nbsp;WM_SOCKET,&nbsp;FD_READ&nbsp;or&nbsp;FD_WRITE&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br><br>　　&nbsp;FD_CLOSE&nbsp;:&nbsp;closesocket(&nbsp;Msg.WParam&nbsp;);&nbsp; <br>　　&nbsp;FD_READ&nbsp;:&nbsp;recv(&nbsp;Msg.WParam,&nbsp;buf[0],&nbsp;4096,&nbsp;0&nbsp;);&nbsp; <br>　　&nbsp;FD_WRITE&nbsp;:&nbsp;;&nbsp; <br>　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>三：WSAEventSelect模型&nbsp; <br><br>后来，微软的信箱非常畅销，购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话，累得腰酸背痛，喝蚁力神都不好使。微软改进了他们的信箱：在客户的家中添加一个附加装置，这个装置会监视客户的信箱，每当新的信件来临，此装置会发出&#8220;新信件到达&#8221;声，提醒老陈去收信。盖茨终于可以睡觉了。&nbsp; <br><br>同样要使用线程：&nbsp; <br><br>procedure&nbsp;TListenThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;hEvent&nbsp;:&nbsp;WSAEvent;&nbsp; <br>　&nbsp;ret&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;ne&nbsp;:&nbsp;TWSANetworkEvents;&nbsp; <br>　&nbsp;sock&nbsp;:&nbsp;TSocket;&nbsp; <br>　&nbsp;adr&nbsp;:&nbsp;TSockAddrIn;&nbsp; <br>　&nbsp;sMsg&nbsp;:&nbsp;String;&nbsp; <br>　&nbsp;Index,&nbsp; <br>　&nbsp;EventTotal&nbsp;:&nbsp;DWORD;&nbsp; <br>　&nbsp;EventArray&nbsp;:&nbsp;Array&nbsp;[0..WSA_MAXIMUM_WAIT_EVENTS-1]&nbsp;of&nbsp;WSAEVENT;&nbsp; <br>begin&nbsp; <br>　&nbsp;...socket...bind...&nbsp; <br>　&nbsp;hEvent&nbsp;:=&nbsp;WSACreateEvent();&nbsp; <br>　&nbsp;WSAEventSelect(&nbsp;ListenSock,&nbsp;hEvent,&nbsp;FD_ACCEPT&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　&nbsp;...listen...&nbsp; <br><br>　&nbsp;while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;Index&nbsp;:=&nbsp;WSAWaitForMultipleEvents(&nbsp;EventTotal,&nbsp;@EventArray[0],&nbsp;FALSE,&nbsp; <br>&nbsp;&nbsp;WSA_INFINITE,&nbsp;FALSE&nbsp;);&nbsp; <br>　　&nbsp;FillChar(&nbsp;ne,&nbsp;sizeof(ne),&nbsp;0&nbsp;);&nbsp; <br>　　&nbsp;WSAEnumNetworkEvents(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;EventArray <br>&nbsp;&nbsp;[Index-WSA_WAIT_EVENT_0],&nbsp;@ne&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;(&nbsp;ne.lNetworkEvents&nbsp;and&nbsp;FD_ACCEPT&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;ne.iErrorCode[FD_ACCEPT_BIT]&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br><br>　　　&nbsp;ret&nbsp;:=&nbsp;sizeof(adr);&nbsp; <br>　　　&nbsp;sock&nbsp;:=&nbsp;accept(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp;adr,&nbsp;ret&nbsp;);&nbsp; <br>　　　&nbsp;if&nbsp;EventTotal&nbsp;&gt;&nbsp;WSA_MAXIMUM_WAIT_EVENTS-1&nbsp;then <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//这里WSA_MAXIMUM_WAIT_EVENTS同样是64&nbsp; <br>　　　&nbsp;begin&nbsp; <br>　　　　&nbsp;closesocket(&nbsp;sock&nbsp;);&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br>　　　&nbsp;end;&nbsp; <br><br>　　　&nbsp;hEvent&nbsp;:=&nbsp;WSACreateEvent();&nbsp; <br>　　　&nbsp;WSAEventSelect(&nbsp;sock,&nbsp;hEvent,&nbsp;FD_READ&nbsp;or&nbsp;FD_WRITE&nbsp;or&nbsp;FD_CLOSE&nbsp;);&nbsp; <br>　　　&nbsp;SockArray[EventTotal]&nbsp;:=&nbsp;sock;&nbsp; <br>　　　&nbsp;EventArray[EventTotal]&nbsp;:=&nbsp;hEvent;&nbsp; <br>　　　&nbsp;Inc(&nbsp;EventTotal&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br><br>　　&nbsp;if&nbsp;(&nbsp;ne.lNetworkEvents&nbsp;and&nbsp;FD_READ&nbsp;)&nbsp;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;if&nbsp;ne.iErrorCode[FD_READ_BIT]&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　　&nbsp;continue;&nbsp; <br>　　　　&nbsp;FillChar(&nbsp;RecvBuf[0],&nbsp;PACK_SIZE_RECEIVE,&nbsp;0&nbsp;);&nbsp; <br>　　　　&nbsp;ret&nbsp;:=&nbsp;recv(&nbsp;SockArray[Index-WSA_WAIT_EVENT_0],&nbsp;RecvBuf[0],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;PACK_SIZE_RECEIVE,&nbsp;0&nbsp;);&nbsp; <br>　　　　&nbsp;......&nbsp; <br>　　　&nbsp;end;&nbsp; <br>　　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br><br>四：Overlapped&nbsp;I/O&nbsp;事件通知模型&nbsp; <br><br>后来，微软通过调查发现，老陈不喜欢上下楼收发信件，因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术，只要用户告诉微软自己的家在几楼几号，新式信箱会把信件直接传送到用户的家中，然后告诉用户，你的信件已经放到你的家中了！老陈很高兴，因为他不必再亲自收发信件了！&nbsp; <br><br>Overlapped&nbsp;I/O&nbsp;事件通知模型和WSAEventSelect模型在实现上非常相似，主要区别在"Overlapped&#8221;，Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED)，一次投递一个或多个Winsock&nbsp;I/O请求。这些提交的请求完成后，应用程序会收到通知。什么意思呢？就是说，如果你想从socket上接收数据，只需要告诉系统，由系统为你接收数据，而你需要做的只是为系统提供一个缓冲区~~~~~&nbsp; <br><br>Listen线程和WSAEventSelect模型一模一样，Recv/Send线程则完全不同：&nbsp; <br><br>procedure&nbsp;TOverlapThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;dwTemp&nbsp;:&nbsp;DWORD;&nbsp; <br>　&nbsp;ret&nbsp;:&nbsp;Integer;&nbsp; <br>　&nbsp;Index&nbsp;:&nbsp;DWORD;&nbsp; <br>begin&nbsp; <br>　&nbsp;......&nbsp; <br><br>　&nbsp;while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;Index&nbsp;:=&nbsp;WSAWaitForMultipleEvents <br>&nbsp;&nbsp;&nbsp;(&nbsp;FLinks.Count,&nbsp;@FLinks.Events[0],&nbsp;FALSE,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;RECV_TIME_OUT,&nbsp;FALSE&nbsp;);&nbsp; <br>　　&nbsp;Dec(&nbsp;Index,&nbsp;WSA_WAIT_EVENT_0&nbsp;);&nbsp; <br>　　&nbsp;if&nbsp;Index&nbsp;&gt;&nbsp;WSA_MAXIMUM_WAIT_EVENTS-1&nbsp;then&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;//超时或者其他错误&nbsp; <br>　　　&nbsp;continue;&nbsp; <br><br>　　&nbsp;WSAResetEvent <br>&nbsp;&nbsp;&nbsp;(&nbsp;FLinks.Events[Index]&nbsp;);&nbsp; <br>　　&nbsp;WSAGetOverlappedResult(&nbsp;FLinks.Sockets[Index],&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pOverlaps[Index],&nbsp;@dwTemp,&nbsp;FALSE,FLinks. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pdwFlags[Index]^&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;dwTemp&nbsp;=&nbsp;0&nbsp;then&nbsp;//连接已经关闭&nbsp; <br>　　&nbsp;begin&nbsp; <br>　　　&nbsp;......&nbsp; <br>　　　&nbsp;continue;&nbsp; <br>　　&nbsp;end&nbsp;else&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;fmMain.ListBox1.Items.Add(&nbsp;FLinks.pBufs[Index]^.buf&nbsp;);&nbsp; <br>　&nbsp;end;&nbsp; <br><br>　&nbsp;//初始化缓冲区&nbsp; <br>　&nbsp;FLinks.pdwFlags[Index]^&nbsp;:=&nbsp;0;&nbsp; <br>　&nbsp;FillChar(&nbsp;FLinks.pOverlaps[Index]^,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;sizeof(WSAOVERLAPPED),&nbsp;0&nbsp;);&nbsp; <br>　&nbsp;FLinks.pOverlaps[Index]^. <br>&nbsp;&nbsp;&nbsp;hEvent&nbsp;:=&nbsp;FLinks.Events[Index];&nbsp; <br>　&nbsp;FillChar(&nbsp;FLinks.pBufs[Index]^.buf^,&nbsp; <br>&nbsp;&nbsp;&nbsp;BUFFER_SIZE,&nbsp;0&nbsp;);&nbsp; <br><br>　&nbsp;//递一个接收数据请求&nbsp; <br>　&nbsp;WSARecv(&nbsp;FLinks.Sockets[Index],&nbsp;FLinks.pBufs[Index],&nbsp;1, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pdwRecvd[Index]^,&nbsp;FLinks.pdwFlags[Index]^,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FLinks.pOverlaps[Index],&nbsp;nil&nbsp;);&nbsp; <br>end;&nbsp; <br>end; <br>&nbsp; <br><br><br>五：Overlapped&nbsp;I/O&nbsp;完成例程模型&nbsp; <br><br>老陈接收到新的信件后，一般的程序是：打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担，微软又开发了一种新的技术：用户只要告诉微软对信件的操作步骤，微软信箱将按照这些步骤去处理信件，不再需要用户亲自拆信/阅读/回复了！老陈终于过上了小资生活！&nbsp; <br><br>Overlapped&nbsp;I/O&nbsp;完成例程要求用户提供一个回调函数，发生新的网络事件的时候系统将执行这个函数：&nbsp; <br><br>procedure&nbsp;WorkerRoutine(&nbsp;const&nbsp;dwError,&nbsp;cbTransferred&nbsp;:&nbsp;DWORD;&nbsp; <br>const&nbsp; <br>lpOverlapped&nbsp;:&nbsp;LPWSAOVERLAPPED;&nbsp;const&nbsp;dwFlags&nbsp;:&nbsp;DWORD&nbsp;);&nbsp;stdcall; <br>&nbsp; <br><br><br>然后告诉系统用WorkerRoutine函数处理接收到的数据：&nbsp; <br><br>WSARecv(&nbsp;m_socket,&nbsp;@FBuf,&nbsp;1,&nbsp;dwTemp,&nbsp;dwFlag,&nbsp;@m_overlap,&nbsp;WorkerRoutine&nbsp;);&nbsp; <br>　　&nbsp;然后......没有什么然后了，系统什么都给你做了！微软真实体贴！&nbsp; <br><br>while&nbsp;(&nbsp;not&nbsp;Terminated&nbsp;)&nbsp;do//这就是一个Recv/Send线程要做的事情......什么都不用做啊！！！&nbsp; <br>begin&nbsp; <br>　&nbsp;if&nbsp;SleepEx(&nbsp;RECV_TIME_OUT,&nbsp;True&nbsp;)&nbsp;=&nbsp;WAIT_IO_COMPLETION&nbsp;then&nbsp;//&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;;&nbsp; <br>　&nbsp;end&nbsp;else&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;continue;&nbsp; <br>　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>六：IOCP模型&nbsp; <br><br>微软信箱似乎很完美，老陈也很满意。但是在一些大公司情况却完全不同！这些大公司有数以万计的信箱，每秒钟都有数以百计的信件需要处理，以至于微软信箱经常因超负荷运转而崩溃！需要重新启动！微软不得不使出杀手锏......&nbsp; <br><br>微软给每个大公司派了一名名叫&#8220;Completion&nbsp;Port&#8221;的超级机器人，让这个机器人去处理那些信件！&nbsp; <br><br>&#8220;Windows&nbsp;NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢?&#8221;-----摘自nonocast的《理解I/O&nbsp;Completion&nbsp;Port》&nbsp; <br><br>先看一下IOCP模型的实现：&nbsp; <br><br>//创建一个完成端口&nbsp; <br>FCompletPort&nbsp;:=&nbsp;CreateIoCompletionPort(&nbsp;INVALID_HANDLE_VALUE,&nbsp;0,0,0&nbsp;);&nbsp; <br><br>//接受远程连接，并把这个连接的socket句柄绑定到刚才创建的IOCP上&nbsp; <br>AConnect&nbsp;:=&nbsp;accept(&nbsp;FListenSock,&nbsp;addr,&nbsp;len);&nbsp; <br>CreateIoCompletionPort(&nbsp;AConnect,&nbsp;FCompletPort,&nbsp;nil,&nbsp;0&nbsp;);&nbsp; <br><br>//创建CPU数*2&nbsp;+&nbsp;2个线程&nbsp; <br>for&nbsp;i:=1&nbsp;to&nbsp;si.dwNumberOfProcessors*2+2&nbsp;do&nbsp; <br>begin&nbsp; <br>　&nbsp;AThread&nbsp;:=&nbsp;TRecvSendThread.Create(&nbsp;false&nbsp;);&nbsp; <br>　&nbsp;AThread.CompletPort&nbsp;:=&nbsp;FCompletPort;//告诉这个线程，你要去这个IOCP去访问数据&nbsp; <br>end; <br>&nbsp; <br><br><br>就这么简单，我们要做的就是建立一个IOCP，把远程连接的socket句柄绑定到刚才创建的IOCP上，最后创建n个线程，并告诉这n个线程到这个IOCP上去访问数据就可以了。&nbsp; <br><br>再看一下TRecvSendThread线程都干些什么：&nbsp; <br><br>procedure&nbsp;TRecvSendThread.Execute;&nbsp; <br>var&nbsp; <br>　&nbsp;......&nbsp; <br>begin&nbsp; <br>　&nbsp;while&nbsp;(not&nbsp;self.Terminated)&nbsp;do&nbsp; <br>　&nbsp;begin&nbsp; <br>　　&nbsp;//查询IOCP状态（数据读写操作是否完成）&nbsp; <br>　　&nbsp;GetQueuedCompletionStatus(&nbsp;CompletPort,&nbsp;BytesTransd,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;CompletKey,&nbsp;POVERLAPPED(pPerIoDat),&nbsp;TIME_OUT&nbsp;);&nbsp; <br><br>　　&nbsp;if&nbsp;BytesTransd&nbsp;&lt;&gt;&nbsp;0&nbsp;then&nbsp; <br>　　　&nbsp;....;//数据读写操作完成&nbsp; <br>　　&nbsp; <br>　　　&nbsp;//再投递一个读数据请求&nbsp; <br>　　　&nbsp;WSARecv(&nbsp;CompletKey,&nbsp;@(pPerIoDat^.BufData),&nbsp;1,&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;BytesRecv,&nbsp;Flags,&nbsp;@(pPerIoDat^.Overlap),&nbsp;nil&nbsp;);&nbsp; <br>　　&nbsp;end;&nbsp; <br>end; <br>&nbsp; <br><br><br>读写线程只是简单地检查IOCP是否完成了我们投递的读写操作，如果完成了则再投递一个新的读写请求。&nbsp; <br><br>应该注意到，我们创建的所有TRecvSendThread都在访问同一个IOCP（因为我们只创建了一个IOCP），并且我们没有使用临界区！难道不会产生冲突吗？不用考虑同步问题吗？&nbsp; <br><br>这正是IOCP的奥妙所在。IOCP不是一个普通的对象，不需要考虑线程安全问题。它会自动调配访问它的线程：如果某个socket上有一个线程A正在访问，那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的，我们无需过问。</div>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/twzheng/aggbug/24508.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-05-21 01:46 <a href="http://www.cppblog.com/twzheng/articles/24508.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>typename 的两个含义</title><link>http://www.cppblog.com/twzheng/articles/23693.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Wed, 09 May 2007 03:47:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/23693.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/23693.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/23693.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/23693.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/23693.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt"><span style="FONT-SIZE: 10pt; COLOR: #993366"><font size=3><font color=#800080>[转]<strong>&nbsp;typename 的两个含义</strong><br></font></font>【摘自】armman的专栏<br><br><strong>template&lt;class T&gt; class Widget; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // uses "class"<br>template&lt;typename T&gt; class Widget; &nbsp;&nbsp;&nbsp; // uses "typename"</strong></span><span style="FONT-SIZE: 10pt; COLOR: #993366"><br>　　答案：没什么不同。在声明一个 template type parameter（模板类型参数）时，class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 class，因为它更容易输入。其他人更喜欢 typename，因为它暗示着这个参数不必要是一个 class type（类类型）。少数开发者在任何类型都被允许的时候使用 typename，而把 class 保留给仅接受 user-defined types（用户定义类型）的场合。但是从 C++ 的观点看，class 和 typename 在声明一个 template parameter（模板参数）时意味着完全相同的东西。<br><br>　　然而，C++ 并不总是把 class 和 typename 视为等同的东西。有时必须使用 typename。为了理解这一点，我们不得不讨论在一个 template（模板）中涉及到的两种名字。<br><br>　　假设我们有一个函数的模板，它能取得一个 STL-compatible container（STL 兼容容器）中持有的能赋值给 ints 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数，而且就像下面写的，它甚至不能编译，但是请将这些事先放在一边：<br><br>template&lt;typename C&gt; // print 2nd element in<br>void print2nd(const C&amp; container) // container;<br>{ <br>　// this is not valid C++!<br>　if (container.size() &gt;= 2) {<br>　　C::const_iterator iter(container.begin()); // get iterator to 1st element<br>　　++iter; // move iter to 2nd element<br>　　int value = *iter; // copy that element to an int<br>　　std::cout &lt;&lt; value; // print the int<br>　}<br>}<br>　　这个函数中的两个 local variables（局部变量），iter 和 value。iter 的类型是 C::const_iterator，一个依赖于 template parameter（模板参数）C 的类型。一个 template（模板）中的依赖于一个 template parameter（模板参数）的名字被称为 dependent names（依赖名字）。当一个 dependent names（依赖名字）嵌套在一个 class（类）的内部时，称它为 nested dependent name（嵌套依赖名字）。C::const_iterator 是一个 nested dependent name（嵌套依赖名字）。实际上，它是一个 nested dependent type name（嵌套依赖类型名），也就是说，一个涉及到一个 type（类型）的 nested dependent name（嵌套依赖名字）。<br><br>　　print2nd 中的另一个 local variable（局部变量）value 具有 int 类型。int 是一个不依赖于任何 template parameter（模板参数）的名字。这样的名字以 non-dependent names（非依赖名字）闻名。<br><br>　　nested dependent name（嵌套依赖名字）会导致解析困难。例如，假设我们更加愚蠢地以这种方法开始 print2nd：<br>template&lt;typename C&gt;<br>void print2nd(const C&amp; container)<br>{<br>　C::const_iterator * x;<br>　...<br>}<br>　　这看上去好像是我们将 x 声明为一个指向 C::const_iterator 的 local variable（局部变量）。但是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type（类型）。但是如果 C::const_iterator 不是一个 type（类型）呢？如果 C 有一个 static data member（静态数据成员）碰巧就叫做 const_iterator 呢？再如果 x 碰巧是一个 global variable（全局变量）的名字呢？在这种情况下，上面的代码就不是声明一个 local variable（局部变量），而是成为 C::const_iterator 乘以 x！当然，这听起来有些愚蠢，但它是可能的，而编写 C++ 解析器的人必须考虑所有可能的输入，甚至是愚蠢的。<br><br>　　直到 C 成为已知之前，没有任何办法知道 C::const_iterator 到底是不是一个 type（类型），而当 template（模板）print2nd 被解析的时候，C 还不是已知的。C++ 有一条规则解决这个歧义：如果解析器在一个 template（模板）中遇到一个 nested dependent name（嵌套依赖名字），它假定那个名字不是一个 type（类型），除非用其它方式告诉它。缺省情况下，nested dependent name（嵌套依赖名字）不是 types（类型）。（对于这条规则有一个例外）<br><br>　　记住这个，再看看 print2nd 的开头：<br>template&lt;typename C&gt;void print2nd(const C&amp; container)<br>{<br>　if (container.size() &gt;= 2) {<br>　　C::const_iterator iter(container.begin()); // this name is assumed to<br>　　... // not be a type<br>　　这为什么不是合法的 C++ 现在应该很清楚了。iter 的 declaration（声明）仅仅在 C::const_iterator 是一个 type（类型）时才有意义，但是我们没有告诉 C++ 它是，而 C++ 就假定它不是。要想转变这个形势，我们必须告诉 C++ C::const_iterator 是一个 type（类型）。我们将 typename 放在紧挨着它的前面来做到这一点：<br><br>template&lt;typename C&gt; // this is valid C++<br>void print2nd(const C&amp; container)<br>{<br>if (container.size() &gt;= 2) {<br>typename C::const_iterator iter(container.begin());<br>...<br>}<br>}<br>　　通用的规则很简单：在涉及到一个在 template（模板）中的 nested dependent type name（嵌套依赖类型名）的任何时候，必须把单词 typename 放在紧挨着它的前面。（重申一下，我待会儿要描述一个例外。）<br><br>　　typename 应该仅仅被用于标识 nested dependent type name（嵌套依赖类型名）；其它名字不应该用它。例如，这是一个取得一个 container（容器）和这个 container（容器）中的一个 iterator（迭代器）的 function template（函数模板）：<br><br>template&lt;typename C&gt; // typename allowed (as is "class")<br>void f(const C&amp; container, // typename not allowed<br>typename C::iterator iter); // typename required<br>　　C 不是一个 nested dependent type name（嵌套依赖类型名）（它不是嵌套在依赖于一个 template parameter（模板参数）的什么东西内部的），所以在声明 container 时它不必被 typename 前置，但是 C::iterator 是一个 nested dependent type name（嵌套依赖类型名），所以它必需被 typename 前置。<br><br>　　"typename must precede nested dependent type names"（</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#8220;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">typename 必须前置于嵌套依赖类型名</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#8221;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">）规则的例外是 typename 不必前置于在一个 list of base classes（基类列表）中的或者在一个 member initialization list（成员初始化列表）中作为一个 base classes identifier（基类标识符）的 nested dependent type name（嵌套依赖类型名）。例如：<br><br>template&lt;typename T&gt;<br>class Derived: public Base&lt;T&gt;::Nested { // base class list: typename not<br>　public: // allowed<br>　　explicit Derived(int x)<br>　　: Base&lt;T&gt;::Nested(x) // base class identifier in mem<br>　　{ <br>　　　// init. list: typename not allowed<br>　　　　typename Base&lt;T&gt;::Nested temp; // use of nested dependent type<br>　　　... // name not in a base class list or<br>　　} // as a base class identifier in a<br>　... // mem. init. list: typename required<br>};<br>　　这样的矛盾很令人讨厌，但是一旦在经历中获得一点经验，几乎不会在意它。<br><br>　　让我们来看最后一个 typename 的例子，因为它在看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator（迭代器）的 function template（函数模板），而且我们要做一个 iterator（迭代器）指向的 object（对象）的局部拷贝 temp，我们可以这样做：<br>template&lt;typename IterT&gt;<br>void workWithIterator(IterT iter)<br>{<br>　typename std::iterator_traits&lt;IterT&gt;::value_type temp(*iter);<br>　...<br>}<br>　　不要让 std::iterator_traits&lt;IterT&gt;::value_type 吓倒你。那仅仅是一个 standard traits class（标准特性类）的使用，用 C++ 的说法就是 "the type of thing pointed to by objects of type IterT"（</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#8220;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">被类型为 IterT 的对象所指向的东西的类型</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#8221;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">）。这个语句声明了一个与 IterT objects 所指向的东西类型相同的 local variable（局部变量）(temp)，而且用 iter 所指向的 object（对象）对 temp 进行了初始化。如果 IterT 是 vector&lt;int&gt;::iterator，temp 就是 int 类型。如果 IterT 是 list&lt;string&gt;::iterator，temp 就是 string 类型。因为 std::iterator_traits&lt;IterT&gt;::value_type 是一个 nested dependent type name（嵌套依赖类型名）（value_type 嵌套在 iterator_traits&lt;IterT&gt; 内部，而且 IterT 是一个 template parameter（模板参数）），我们必须让它被 typename 前置。<br><br>　　如果觉得读 std::iterator_traits&lt;IterT&gt;::value_type 令人讨厌，就想象那个与它相同的东西来代表它。如果像大多数程序员，对多次输入它感到恐惧，那么就需要创建一个 typedef。对于像 value_type 这样的 traits member names（特性成员名），一个通用的惯例是 typedef name 与 traits member name 相同，所以这样的一个 local typedef 通常定义成这样：<br><br>template&lt;typename IterT&gt; void workWithIterator(IterT iter)<br>{<br>　typedef typename std::iterator_traits&lt;IterT&gt;::value_type value_type;<br><br>　value_type temp(*iter);<br>　...<br>}<br>　　很多程序员最初发现 "typedef typename" 并列不太和谐，但它是涉及 nested dependent type names（嵌套依赖类型名）规则的一个合理的附带结果。<br><br>　　一些编译器接受必需 typename 时它却缺失的代码；一些编译器接受不许 typename 时它却存在的代码；还有少数的（通常是老旧的）会拒绝 typename 出现在它必需出现的地方。这就意味着 typename 和 nested dependent type names（嵌套依赖类型名）的交互作用会导致一些轻微的可移植性问题。<br><br><strong>Things to Remember　　</strong>&nbsp;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#183;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">在声明 template parameters（模板参数）时，class 和 typename 是可互换的。<br>　　</span><span style="FONT-SIZE: 10pt; COLOR: #993366">&#183;</span><span style="FONT-SIZE: 10pt; COLOR: #993366">用 typename 去标识 nested dependent type names（嵌套依赖类型名），在 base class lists（基类列表）中或在一个 member initialization list（成员初始化列表）中作为一个 base class identifier（基类标识符）时除外。</span></p>
<img src ="http://www.cppblog.com/twzheng/aggbug/23693.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-05-09 11:47 <a href="http://www.cppblog.com/twzheng/articles/23693.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++/CLR泛型与C++模板之间的对比</title><link>http://www.cppblog.com/twzheng/articles/23591.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 07 May 2007 16:04:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/23591.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/23591.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/23591.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/23591.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/23591.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 24pt">&nbsp;C++/CLR泛型与C++模板之间的对比<span style="FONT-SIZE: 24pt">[摘自编程乐园]</span><br><br></span>
<p>Visual Studio 2005把泛型编程的类型参数模型引入了微软.NET框架组件。C++/CLI支持两种类型参数机制--通用语言运行时（CLR）泛型和C++模板。本文将介绍两者之间的一些区别--特别是参数列表和类型约束模型之间的区别。 <br><br>　　参数列表又回来了<br><br>　　参数列表与函数的信号（signature）类似：它标明了参数的数量和每个参数的类型，并把给每个参数关联一个唯一的标识符，这样在模板定义的内部，每个参数就可以被唯一地引用。<br><br>　　参数在模板或泛型的定义中起占位符（placeholder）的作用。用户通过提供绑定到参数的实际值来建立对象实例。参数化类型的实例化并非简单的文本替代（宏扩展机制就是使用文本替代的）。相反地，它把实际的用户值绑定到定义中的相关的形式参数上。<br><br>　　在泛型中，每个参数都表现为Object类型或衍生自Object的类型。在本文后面你可以看到，这约束了你可能执行的操作类型或通过类型参数声明的对象。你可以通过提供更加明确的约束来调整这些约束关系。这些明确的约束引用那些衍生出实际类型参数的基类或接口集合。<br><br>　　模板除了支持类型参数之外，还支持表达式和模板参数。此外，模板还支持默认的参数值。这些都是按照位置而不是名称来分解的。在两种机制之下，类型参数都是与类或类型名称关键字一起引入的。<br><br>　　参数列表的额外的模板功能<br><br>　　模板作为类型参数的补充，允许两种类型的参数：非类型（non-type）参数和模板参数。我们将分别简短地介绍一下。<br><br>　　非类型参数受常数表达式的约束。我们应该立即想到它是数值型或字符串常量。例如，如果选择提供固定大小的堆栈，你就可能同时指定一个非类型的大小参数和元素类型参数，这样就可以同时按照元素类别和大小来划分堆栈实例的类别。例如，你可以在代码1中看到带有非类型参数的固定大小的堆栈。<br><br>　　代码1：带有非类型固定大小的堆栈<br><br>template &lt;class elemType, int size&gt;<br>public ref class tStack<br>{<br>　array&lt;elemType&gt; ^m_stack;<br>　int top;<br><br>　public:<br>　　tStack() : top( 0 ) <br>　　{ m_stack = gcnew array&lt;elemType&gt;( size ); }<br>};<br>　　此外，如果模板类设计者可以为每个参数指定默认值，使用起来就可能方便多了。例如，把缓冲区的默认大小设置为1KB就是很好的。在模板机制下，可以给参数提供默认值，如下所示：<br><br>// 带有默认值的模板声明<br>template &lt;class elemType, int size = 1024&gt;<br>public ref class FixedSizeStack {};<br>　　用户可以通过提供明确的第二个值来重载默认大小值：<br><br>// 最多128个字符串实例的堆栈<br>FixedSizeState&lt;String^, 128&gt; ^tbs = gcnew FixedSizeStack&lt;String^, 128&gt;;<br>　　否则，由于没有提供第二个参数，它使用了相关的默认值，如下所示：<br><br>// 最多1024个字符串实例的堆栈<br>FixedSizeStack&lt;String^&gt; ^tbs = gcnew FixedSizeStack&lt;String^&gt;;<br>　　使用默认的参数值是标准模板库（STL）的一个基本的设计特征。例如，下面的声明就来自ISO-C++标准：<br><br>// ISO-C++名字空间std中的默认类型参数值示例 <br>{<br>　template &lt;class T, class Container = deque&lt;T&gt; &gt; <br>　class queue;<br><br>　template &lt;class T, class Allocator = allocator&lt;T&gt; &gt; <br>　class vector;<br>　// ...<br>}<br>　　你可以提供默认的元素类型，如下所示：<br><br>// 带有默认的元素类型的模板声明 <br>template &lt;class elemType=String^, int size=1024&gt;<br>public ref class tStack {}; </p>
<p><br>&nbsp;</p>
<p>　　从设计的角度来说很难证明它的正确性，因为一般来说容器不会集中在在单个默认类型上。<br><br>　　指针也可以作为非类型参数，因为对象或函数的地址在编译时就已知了，因此是一个常量表达式。例如，你可能希望为堆栈类提供第三个参数，这个参数指明遇到特定条件的时候使用的回调处理程序。明智地使用typedef可以大幅度简化那些表面上看起来很复杂的声明，如下所示：</p>
<p>typedef void (*handler)( ... array&lt;Object^&gt;^ );<br>template &lt;class elemType, int size, handler cback &gt;<br>public ref class tStack {};<br>　　当然，你可以为处理程序提供默认值--在这个例子中，是一个已有的方法的地址。例如，下面的缓冲区声明就提供了大小和处理程序：<br><br>void defaultHandler( ... array&lt;Object^&gt;^ ){ ... }<br><br>template &lt; class elemType, <br>int size = 1024, <br>handler cback = &amp;defaultHandler &gt;<br>public ref class tStack {};<br>　　由于默认值的位置次序优先于命名次序，因此如果不提供明确的大小值（即使这个大小与默认值是重复的），就无法提供重载的处理程序的。下面就是可能用到的修改堆栈的方法：<br><br>void demonstration() <br>{<br>　// 默认的大小和处理程序<br>　tStack&lt;String^&gt; ^ts1 = nullptr; <br>　// 默认的处理程序<br>　tStack&lt;String^, 128&gt; ^ts2 = gcnew tStack&lt;String^, 128&gt;;<br>　// 重载所有的三个参数<br>　tStack&lt;String^, 512, &amp;yourHandler&gt; ^ts3;<br>}<br><br>　　模板支持的第二种额外的参数就是template模板参数--也就是这个模板参数本身表现为一个模板。例如：<br><br>// template模板参数<br>template &lt;template &lt;class T&gt; class arena, class arenaType&gt;<br>class Editor { <br>arena&lt;arenaType&gt; m_arena; <br>// ...<br>};<br>　　Editor模板类列出了两个模板参数arena和arenaType。ArenaType是一个模板类型参数；你可以传递整型、字符串型、自定义类型等等。Arena是一个template模板参数。带有单个模板类型参数的任何模板类都可以绑定到arena。m_arena是一个绑定到arenaType模板类型参数的模板类实例。例如：<br><br>// 模板缓冲区类<br>template &lt;class elemType&gt;<br>public ref class tBuffer {};<br><br>void f()<br>{<br>　Editor&lt;tBuffer,String^&gt; ^textEditor;<br>　Editor&lt;tBuffer,char&gt; ^blitEditor;<br>　// ...<br>} </p>
<p>&nbsp;</p>
<p>　　类型参数约束<br><br>　　如果你把参数化类型简单地作为存储和检索元素的容器，那么你可以略过这一部分了。当你需要调用某个类型参数（例如在比较两个对象，查看它们相等或者其中一个小于另一个的时候，或者通过类型参数调用方法名称或嵌套类型的时候）上的操作的时候，才会考虑约束的问题。例如：</p>
<p>template &lt;class T&gt;<br>ref class Demonstration {<br>　int method() {<br>　　typename T::A *aObj; <br>　　// ...<br>　}<br>};</p>
<p><br>　　这段代码成功地声明了aObj，它同时还约束了能够成功地绑定到你的类模板的类型参数。例如，如果你编写下面的代码，aObj的声明就是非法的（在这种特定的情况下），编译器会报错误信息： </p>
<p>int demoMethod()<br>{<br>　Demonstration&lt;int&gt; ^demi = gcnew Demonstration&lt;int&gt;( 1024 );<br>　return dm-&gt;method();<br>}</p>
<p><br>　　当然，其特定的约束是，这个类型参数必须包含一个叫做A的类型的嵌套声明。如果它的名字叫做B、C或Z都没有关系。更普通的约束是类型参数必须表示一个类，否则就不允许使用T::范围操作符。我使用int类型参数同时违反了这两条约束。例如，Visual C++编译器会生成下面的错误信息：</p>
<p>error C2825: 'T': must be a class or namespace when followed by '::' </p>
<p><br>　　C++模板机制受到的一条批评意见是：缺乏用于描述这种类型约束的形式语法（请注意，在参数化类型的原始设计图纸中，Bjarne Stroustrup论述了曾经考虑过提供显式约束语法，但是他对这种语法不太满意，并选择了在那个时候不提供这种机制）。也就是说，在一般情况下，用户在阅读源代码或相关的文档，或者编译自己的代码并阅读随后的编译器错误消息的时候，才能意识到模板有隐含约束。<br><br>　　如果你必须提供一个与模板不匹配的类型参数该怎么办呢？一方面，我们能做的事情很少。你编写的任何类都有一定的假设，这些假设表现为某些使用方面的约束。很难设计出适合每种情况的类；设计出适合每种情况和每种可能的类型参数的模板类更加困难。 <br><br>　　另一方面，存在大量的模板特性为用户提供了"迂回"空间。例如，类模板成员函数不会绑定到类型参数，直到在代码中使用该函数为止（这个时候才绑定）。因此，如果你使用模板类的时候，没有使用那些使类型参数失效的方法，就不会遇到问题。<br><br>　　如果这样也不可行，那么还可以提供该方法的一个专门的版本，让它与你的类型参数关联。在这种情况下，你需要提供Demonstration&lt;int&gt;::方法的一个专用的实例，或者，更为普遍的情况是，在提供整数类型参数的时候，提供整个模板类的专门的实现方式。 <br><br>　　一般来说，当你提到参数化类型可以支持多种类型的时候，你一般谈到的是参数化的被动使用--也就是说，主要是类型的存储和检索，而不是积极地操作（处理）它。<br><br>　　作为模板的设计人员，你必须知道自己的实现对类型参数的隐含约束条件，并且努力去确保这些条件不是多余的。例如，要求类型参数提供等于和小于操作是合理的；但是要求它支持小于或等于或XOR位运算符就不太合理了。你可以通过把这些操作分解到不同的接口中，或者要求额外的、表示函数、委托或函数对象的参数来放松对操作符的依赖性。例如，代码2显示了一个本地C++程序员使用内建的等于操作符实现的搜索方法。<br><br>　　代码2：不利于模板的搜索实现</p>
<p>template &lt;class elemType, int size=1024&gt;<br>ref class Container <br>{<br>　array&lt;elemType&gt; ^m_buf;<br>　int next;<br><br>　public:<br>　　bool search( elemType et )<br>　　{<br>　　　for each ( elemType e in m_buf )<br>　　　　if ( et == e )<br>　　　　　return true;<br>　　　　return false;<br>　　}<br><br>　　Container() <br>　　{ <br>　　　m_buf = gcnew array&lt;elemType&gt;(size); <br>　　　next = 0; <br>　　}<br><br>　　void add( elemType et )<br>　　{ <br>　　　if ( next &gt;= size ) <br>　　　　throw gcnew Exception; <br>　　　　m_buf[ next++ ] = et; <br>　　}<br><br>　　elemType get( int ix )<br>　　{<br>　　　if ( ix &lt; next ) <br>　　　　return m_buf[ ix ]; <br>　　　throw gcnew Exception; <br>　　}<br>　　// ...<br>　};</p>
<p><br>&nbsp;</p>
<p>　　在这个搜索函数中没有任何错误。但是，它不太利于使用模板，因为类型参数与等于操作符紧密耦合了。更为灵活的方案是提供第二个搜索方法，允许用户传递一个对象来进行比较操作。你可以使用函数成员模板来实现这个功能。函数成员模板提供了一个额外的类型参数。请看一看代码3。<br><br>　　代码3：使用模板</p>
<p>template &lt;class elemType, int size=1024&gt;<br>ref class Container <br>{<br>　// 其它的都相同 ...<br>　// 这是一个函数成员模板...<br>　// 它可以同时引用包含的类参数和自有参数...<br><br>　template &lt;class Comparer&gt;<br>　bool search( elemType et, Comparer comp )<br>　{<br>　　for each ( elemType e in m_buf )<br>　　　if ( comp( et, e ) )<br>　　　　return true;<br>　<br>　　　return false;<br>　}<br>　// ...<br>};</p>
<p><br>　　现在用户可以选择使用哪一个方法来搜索内容了：紧密耦合的等于操作符搜索效率较高，但是不适合于所有类型；较灵活的成员模板搜索要求传递用于比较的类型。<br><br>　　哪些对象适用这种比较目的？函数对象就是普通的用于这种目的的C++设计模式。例如，下面就是一个比较两个字符串是否相等的函数对象：</p>
<p>class EqualGuy {<br>　public: <br>　　bool operator()( String^ s1, String^ s2 )<br>　　{<br>　　　return s1-&gt;CompareTo( s2 ) == 0;<br>　　}<br>};</p>
<p><br>　　代码4中的代码显示了你如何调用这两个版本的搜索成员函数模板和传统的版本。<br><br>　　代码4：两个搜索函数</p>
<p>int main()<br>{<br>　Container&lt;String^&gt; ^sxc = gcnew Container&lt;String^&gt;;<br>　sxc-&gt;add( "Pooh" );<br>　sxc-&gt;add( "Piglet" );<br><br>　// 成员模板搜索 ...<br>　if ( sxc-&gt;search( "Pooh", EqualGuy() ) )<br>　　Console::WriteLine( "found" );<br>　else Console::WriteLine( "not found" );<br><br>　　// 传统的等于搜索 ...<br>　　if ( sxc-&gt;search( "Pooh" ) )<br>　　　Console::WriteLine( "found" );<br>　　else Console::WriteLine( "not found" );<br>}</p>
<p><br>　　一旦有了模板的概念，你就会发现使用模板几乎没有什么事情不是实现。至少感觉是这样的。</p>
　　泛型约束<br><br>　　与模板不同，泛型定义支持形式约束语法，这些语法用于描述可以合法地绑定的类型参数。在我详细介绍约束功能之前，我们简短地考虑一下为什么泛型选择了提供约束功能，而模板选择了不提供这个功能。我相信，最主要的原因是两种机制的绑定时间之间差异。模板在编译的过程中绑定，因此无效的类型会让程序停止编译。用户必须立即解决这个问题或者把它重新处理成非模板编程方案。执行程序的完整性不存在风险。<br><br>　　另一方面，泛型在运行时绑定，在这个时候才发现用户指定的类型无效就已经太迟了。因此通用语言结构（CLI）需要一些静态（也就是编译时）机制来确保在运行时只会绑定有效的类型。与泛型相关的约束列表是编译时过滤器，也就是说，如果违反的时候，会阻止程序的建立。 <br><br>　　我们来看一个例子。图5显示了用泛型实现的容器类。它的搜索方法假设类型参数衍生自Icomparable，因此它实现了该接口的CompareTo方法的一个实例。请注意，容器的大小是在构造函数中由用户提供的，而不是作为第二个、非类型参数提供的。你应该记得泛型不支持非类型参数的。<br><br>　　代码5：作为泛型实现的容器<o:p></o:p>
<p>generic &lt;class elemType&gt;<br>public ref class Container <br>{<br>　array&lt;elemType&gt; ^m_buf;<br>　int next;<br>　int size;<br>　<br>public:<br>　bool search( elemType et )<br>　{<br>　　for each ( elemType e in m_buf )<br>　　　if ( et-&gt;CompareTo( e ))<br>　　　　return true;<br>　　　return false;<br>　}<br><br>　Container( int sz ) <br>　{ <br>　　m_buf = gcnew array&lt;elemType&gt;(size = sz); <br>　　next = 0; <br>　}<br><br>　// add() 和 get() 是相同的 ... <br><br>};<o:p></o:p></p>
<p><br>　　该泛型类的实现在编译的时候失败了，遇到了如下所示的致命的编译诊断信息：<o:p></o:p></p>
<p>error C2039: 'CompareTo' : is not a member of 'System::Object'<o:p></o:p></p>
<p><br>　　你也许有点糊涂了，这是怎么回事？没有人认为它是System::Object的成员啊。但是，在这种情况下你就错了。在默认情况下，泛型参数执行最严格的可能的约束：它把自己的所有类型约束为Object类型。这个约束条件是对的，因为只允许CLI类型绑定到泛型上，当然，所有的CLI类型都多多少少地衍生自Object。因此在默认情况下，作为泛型的作者，你的操作非常安全，但是可以使用的操作也是有限的。<br><br>　　你可能会想，好吧，我减小灵活性，避免编译器错误，用等于操作符代替CompareTo方法，但是它却引起了更严重的错误：<o:p></o:p></p>
<p>error C2676: binary '==' : 'elemType' does not define this operator<br>or a conversion to a type acceptable to the predefined operator<o:p></o:p></p>
<p><br>　　同样，发生的情况是，每个类型参数开始的时候都被Object的四个公共的方法包围着：ToString、GetType、GetHashCode和Equals。其效果是，这种在单独的类型参数上列出约束条件的工作表现了对初始的强硬约束条件的逐步放松。换句话说，作为泛型的作者，你的任务是按照泛型约束列表的约定，采用可以验证的方式来扩展那些允许的操作。我们来看看如何实现这样的事务。<br><br>　　我们用约束子句来引用约束列表，使用非保留字"where"实现。它被放置在参数列表和类型声明之间。实际的约束包含一个或多个接口类型和/或一个类类型的名称。这些约束显示了参数类型希望实现的或者衍生出类型参数的基类。每种类型的公共操作集合都被添加到可用的操作中，供类型参数使用。因此，为了让你的elemType参数调用CompareTo，你必须添加与Icomparable接口关联的约束子句，如下所示：<o:p></o:p></p>
<p>generic &lt;class elemType&gt;<br>where elemType : IComparable<br>public ref class Container <br>{<br>　// 类的主体没有改变 ... <br>};<o:p></o:p></p>
<p><br>　　这个约束子句扩展了允许elemType实例调用的操作集合，它是隐含的Object约束和显式的Icomparable约束的公共操作的结合体。该泛型定义现在可以编译和使用了。当你指定一个实际的类型参数的时候（如下面的代码所示），编译器将验证实际的类型参数是否与将要绑定的类型参数的约束相匹配： <o:p></o:p></p>
<p>int main()<br>{<br>　// 正确的：String和int实现了IComparable<br>　Container&lt;String^&gt; ^sc; <br>　Container&lt;int&gt; ^ic; <br><br>　//错误的：StringBuilder没有实现IComparable <br>　Container&lt;StringBuilder^&gt; ^sbc; <br>}<o:p></o:p></p>
<p><br>　　编译器会提示某些违反了规则的信息，例如sbc的定义。但是泛型的实际的绑定和构造已经由运行时完成了。<br><br>　　接着，它会同时验证泛型在定义点（编译器处理你的实现的时候）和构造点（编译器根据相关的约束条件检查类型参数的时候）是否违反了约束。无论在那个点失败都会出现编译时错误。<o:p></o:p></p>
<p>　　约束子句可以每个类型参数包含一个条目。条目的次序不一定跟参数列表的次序相同。某个参数的多个约束需要使用逗号分开。约束在与每个参数相关的列表中必须唯一，但是可以出现在多个约束列表中。例如：<o:p></o:p></p>
<p>generic &lt;class T1, class T2, class T3&gt;<br>where T1 : IComparable, ICloneable, Image<br>where T2 : IComparable, ICloneable, Image<br>where T3 : ISerializable, CompositeImage<br>public ref class Compositor <br>{<br>　// ...<br>};<o:p></o:p></p>
<p><br>　　在上面的例子中，出现了三个约束子句，同时指定了接口类型和一个类类型（在每个列表的末尾）。这些约束是有额外的意义的，即类型参数必须符合所有列出的约束，而不是符合它的某个子集。我的同事Jon Wray指出，由于你是作为泛型的作者来扩展操作集合的，因此如果放松了约束条件，那么该泛型的用户在选择类型参数的时候就得增加更多的约束。<br><br>　　T1、T2和T3子句可以按照其它的次序放置。但是，不允许跨越两个或多个子句指定某个类型参数的约束列表。例如，下面的代码就会出现违反语法错误：<o:p></o:p></p>
<p>generic &lt;class T1, class T2, class T3&gt;<br>// 错误的：同一个参数不允许有两个条目<br>where T1 : IComparable, ICloneable<br>where T1 : Image<br>public ref class Compositor <br>{<br>　// ...<br>};<o:p></o:p></p>
<p><br>　　类约束类型必须是未密封的（unsealed）参考类（数值类和密封类都是不允许的，因为它们不允许继承）。有四个System名字空间类是禁止出现在约束子句中的，它们分别是：System::Array、System::Delegate、 System::Enum和System::ValueType。由于CLI只支持单继承（single inheritance），约束子句只支持一个类类型的包含。约束类型至少要像泛型或函数那样容易访问。例如，你不能声明一个公共泛型并列出一个或多个内部可视的约束。<br><br>　　任何类型参数都可以绑定到一个约束类型。下面是一个简单的例子：<o:p></o:p></p>
<p>generic &lt;class T1, class T2&gt;<br>where T1 : IComparable&lt;T1&gt;<br>where T2 : IComparable&lt;T2&gt;<br>public ref class Compositor <br>{<br>　// ... <br>};<o:p></o:p></p>
<p><br>　　约束是不能继承的。例如，如果我从Compositor继承得到下面的类，Compositor的T1和T2上的Icomparable约束不会应用在 BlackWhite_Compositor类的同名参数上：<o:p></o:p></p>
<p>generic &lt;class T1, class T2&gt;<br>public ref class BlackWhite_Compositor : Compositor<br>{<br>　// ... <br>};<o:p></o:p></p>
<p><br>　　当这些参数与基类一起使用的时候，这就有几分设计方面的便利了。为了保证Compositor的完整性，BlackWhite_Compositor必须把Compositor约束传播给那些传递到Compositor子对象的所有参数。例如，正确的声明如下所示：<o:p></o:p></p>
<p>generic &lt;class T1, class T2&gt;<br>where T1 : IComparable&lt;T1&gt;<br>where T2 : IComparable&lt;T2&gt;<br>public ref class BlackWhite_Compositor : Compositor<br>{<br>　// ...<br>};<o:p></o:p></p>
<p><br>　　包装<br><br>　　你已经看到了，在C++/CLI下面，你可以选择CLR泛型或C++模板。现在你所拥有的知识已经可以根据特定的需求作出明智的选择了。在两种机制下，超越元素的存储和检索功能的参数化类型都包含了每种类型参数必须支持操作的假设。<br><br>　　使用模板的时候，这些假设都是隐含的。这给模板的作者带来了很大的好处，他们对于能够实现什么样的功能有很大的自由度。但是，这对于模板的使用者是不利的，他们经常面对某些可能的类型参数上的没有正式文档记载的约束集合。违反这些约束集合就会导致编译时错误，因此它对于运行时的完整性不是威胁，模板类的使用可以阻止失败出现。这种机制的设计偏好倾向于实现者。 <br><br>　　使用泛型的时候，这些假设都被明显化了，并与约束子句中列举的基本类型集合相关联。这对泛型的使用者是有利的，并且保证传递给运行时用于类型构造的任何泛型都是正确的。据我看来，它在设计的自由度上有一些约束，并且使某些模板设计习惯稍微难以受到支持。对这些形式约束的违反，无论使在定义点还是在用户指定类型参数的时候，都会导致编译时错误。这种机制的设计偏好倾向于消费者。<o:p></o:p></p>
<p>　　从设计的角度来说很难证明它的正确性，因为一般来说容器不会集中在在单个默认类型上。<br><br>　　指针也可以作为非类型参数，因为对象或函数的地址在编译时就已知了，因此是一个常量表达式。例如，你可能希望为堆栈类提供第三个参数，这个参数指明遇到特定条件的时候使用的回调处理程序。明智地使用typedef可以大幅度简化那些表面上看起来很复杂的声明，如下所示：<o:p></o:p></p>
typedef void (*handler)( ... array&lt;Object^&gt;^ );<br>template &lt;class elemType, int size, handler cback &gt;<br>public ref class tStack {};<br>　　当然，你可以为处理程序提供默认值--在这个例子中，是一个已有的方法的地址。例如，下面的缓冲区声明就提供了大小和处理程序：<br><br>void defaultHandler( ... array&lt;Object^&gt;^ ){ ... }<br><br>template &lt; class elemType, <br>int size = 1024, <br>handler cback = &amp;defaultHandler &gt;<br>public ref class tStack {};<br>　　由于默认值的位置次序优先于命名次序，因此如果不提供明确的大小值（即使这个大小与默认值是重复的），就无法提供重载的处理程序的。下面就是可能用到的修改堆栈的方法：<br><br>void demonstration() <br>{<br>　// 默认的大小和处理程序<br>　tStack&lt;String^&gt; ^ts1 = nullptr; <br>　// 默认的处理程序<br>　tStack&lt;String^, 128&gt; ^ts2 = gcnew tStack&lt;String^, 128&gt;;<br>　// 重载所有的三个参数<br>　tStack&lt;String^, 512, &amp;yourHandler&gt; ^ts3;<br>}<br><br>　　模板支持的第二种额外的参数就是template模板参数--也就是这个模板参数本身表现为一个模板。例如：<br><br>// template模板参数<br>template &lt;template &lt;class T&gt; class arena, class arenaType&gt;<br>class Editor { <br>arena&lt;arenaType&gt; m_arena; <br>// ...<br>};<br>　　Editor模板类列出了两个模板参数arena和arenaType。ArenaType是一个模板类型参数；你可以传递整型、字符串型、自定义类型等等。Arena是一个template模板参数。带有单个模板类型参数的任何模板类都可以绑定到arena。m_arena是一个绑定到arenaType模板类型参数的模板类实例。例如：<br><br>// 模板缓冲区类<br>template &lt;class elemType&gt;<br>public ref class tBuffer {};<br><br>void f()<br>{<br>　Editor&lt;tBuffer,String^&gt; ^textEditor;<br>　Editor&lt;tBuffer,char&gt; ^blitEditor;<br>　// ...<br>} 
<img src ="http://www.cppblog.com/twzheng/aggbug/23591.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-05-08 00:04 <a href="http://www.cppblog.com/twzheng/articles/23591.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>现代C++中的预处理宏</title><link>http://www.cppblog.com/twzheng/articles/22681.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 23 Apr 2007 15:19:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/22681.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/22681.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/22681.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/22681.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/22681.html</trackback:ping><description><![CDATA[<div>&nbsp;</div>
<div>
<div><a href="http://blog.csdn.net/alais/archive/2006/04/29/696978.aspx">&nbsp;[转]现代C++中的预处理宏</a> </div>
<div>
<p align=center><span>现代<span>C++中的预处理宏</span></span></p>
<p align=center><span>--徐东来</span></p>
<p><strong><span>摘要：</span></strong><span>在<span>C++从C继承的遗产中，预处理宏是其中的一部分。在现代C++的发展过程中，预处理宏是否还有意义？本文将讨论之。</span></span></p>
<p><strong><span>关键字：</span></strong><span>预处理 宏<span> #define #pragma</span></span></p>
<p><span><font face=宋体 size=3>&nbsp;</font></span></p>
<p><span><span>&nbsp;&nbsp; </span>C++中有那么多灵活的特性，例如重载、类型安全的模板、const关键字等等，为什么程序员还要写&#8220;#define&#8221;这样的预处理指令？</span></p>
<p><span><span>&nbsp;&nbsp; </span>典型的一个例子，大家都知道&#8220;const int a=100;&#8221;就比&#8220;#define a 100&#8221;要好，因为const提供类型安全、避免了预处理的意外修改等。</span></p>
<p><span><span>&nbsp;&nbsp; </span>然而，还是有一些理由让我们去使用#define。</span></p>
<h1><font size=5><span>一、使用预处理宏</span></font></h1>
<p><span><span>1）<span>&nbsp;&nbsp; </span></span></span><span>守护头文件</span></p>
<p><span>为了防止头文件被多次包含，这是一种常用技巧。</span></p>
<p><span>#ifndef MYPROG_X_H</span></p>
<p><span>#define MYPROG_X_H</span></p>
<p><span>// &#8230; 头文件x.h的其余部分</span></p>
<p><span>#endif</span></p>
<p><span><span>2）<span>&nbsp;&nbsp; </span></span></span><span>使用预处理特性</span></p>
<p><span>在调试代码中，插入行号或编译时间这类信息通常很有用，可以使用预定义的标准宏，例如<span>__FILE__、__LINE__、__DATE__和__TIME__。</span></span></p>
<p><span><span>3）<span>&nbsp;&nbsp; </span></span></span><span>编译时期选择代码</span></p>
<p><span><span>A.&nbsp;</span></span><span>调试代码</span></p>
<p><span>选择性的输出一些调试信息：</span></p>
<p><span>void f()</span></p>
<p><span>{</span></p>
<p><span>#ifdef _DEBUG</span></p>
<p><span><span>&nbsp;&nbsp; </span>cerr&lt;&lt;&#8221;调试信息&#8221;&lt;&lt;endl;</span></p>
<p><span>#endif</span></p>
<p><span>// .. f()的其他部分</span></p>
<p><span>}</span></p>
<p><span>通常我们也可以用条件判断来代替：</span></p>
<p><span>void f()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp; </span>if(_DEBUG)</span></p>
<p><span><span>&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp; </span>cerr&lt;&lt;&#8221;调试信息&#8221;&lt;&lt;endl;</span></p>
<p><span>}</span></p>
<p><span>// .. f()的其他部分</span></p>
<p><span>}</span></p>
<p><span><span>B.&nbsp;</span></span><span>特定平台代码</span></p>
<p><span>同一函数同一功能在不同的编译平台上可能有不同的表现形式，我们可以通过定义宏来区分不同的平台。</span></p>
<p><span><span>C.&nbsp;</span></span><span>不同的数据表示方式</span></p>
<p><span>&lt;&lt;深入浅出MFC&gt;&gt;这本书对MFC框架中宏的使用解析的很透彻，也让我们领略到宏的强大功能。可以参看DECLARE_MESSAGE_MAP(),</span></p>
<p><span>BEGIN_MESSAGE_MAP,</span><span><font face=宋体 size=3> </font></span><span>END_MESSAGE_MAP的实现。</span></p>
<p><span><span>4）<span>&nbsp;&nbsp; </span></span></span><span>#pragma的使用，例如用#pragma禁止掉无伤大雅的警告，用于可移植性的条件编译中。例如，</span></p>
<p><span>包含<span>winsock2 lib文件：</span></span></p>
<p><span>#pragma comment(lib,</span><span><font face=宋体>&#8221;</font></span><span>ws2_32</span><span><font face=宋体>&#8221;</font></span><span>) </span></p>
<p><span>用如下预处理宏，可以使结构按<span>1字结对齐：</span></span></p>
<p><span>#pragma pack(push)</span></p>
<p><span>#pragma pack(1)</span></p>
<p><span>// </span><span><font face=宋体>&#8230;</font></span><span> 结构定义</span></p>
<p><span>#pragma pack(pop)</span></p>
<p><span><span>&nbsp;&nbsp; </span>&nbsp;&nbsp;禁止掉某些警告信息：</span></p>
<p><span>#pragma warning( push )</span></p>
<p><span>#pragma warning( disable : 4705 )</span></p>
<p><span>#pragma warning( disable : 4706 )</span></p>
<p><span>#pragma warning( error : 164 )// 把164号警告作为错误报出</span></p>
<p><span>// Some code</span></p>
<p><span>#pragma warning( pop ) </span></p>
<p>&nbsp;</p>
<h1><font size=5><span>二、宏的常见陷阱</span></font></h1>
<p><span><span>&nbsp;&nbsp; </span>下面示范如何写一个简单的预处理宏max()；这个宏有两个参数，比较并返回其中较大的一个值。在写这样一个宏时，容易犯哪些错误？有四大易犯错误。</span></p>
<p><span><span>1）<span>&nbsp;&nbsp; </span></span></span><span>不要忘记为参数加上括号</span></p>
<p><span>// 例1：括号陷阱一：参数</span></p>
<p><span>//</span></p>
<p><span>#define max(a, b) a &lt; b ? b : a</span></p>
<p><span>例如：</span></p>
<p><span>max(i += 2, j)</span></p>
<p><span>展开后：</span></p>
<p><span>i += 2 &lt; j ? j : i += 2</span></p>
<p><span>考虑运算符优先级和语言规则，实际上是：</span></p>
<p><span>i += ((2 &lt; j) ? j : i += 2)</span></p>
<p><span>这种错误可能需要长时间的调试才可以发现。</span></p>
<p><span><span>2）<span>&nbsp;&nbsp; </span></span></span><span>不要忘记为整个展开式加上括号</span></p>
<p><span>// 例2：括号陷阱二：展开式</span></p>
<p><span>//</span></p>
<p><span>#define max(a, b) (a) &lt; (b) ? (b) : (a)</span></p>
<p><span><span>&nbsp;&nbsp; </span>例如：</span></p>
<p><span><span>&nbsp;&nbsp; </span>m = max(j, k) + 42;</span></p>
<p><span><span>&nbsp;&nbsp; </span>展开后为：</span></p>
<p><span><span>&nbsp;&nbsp; </span>m = (j) &lt; (k) ? (j) : (k) + 42;</span></p>
<p><span>考虑运算符优先级和语言规则，实际上是：</span></p>
<p><span><span>&nbsp;&nbsp; </span>m = ((j) &lt; (k)) ? (j) : ((k) + 42);</span></p>
<p><span><span>&nbsp;&nbsp; </span>如果j &gt;= k, m被赋值k+42,正确；如果j &lt; k, m被赋值j,是错误的。如果给展开式加上括号，就解决了这个问题。</span></p>
<p><span><span>3）<span>&nbsp;&nbsp; </span></span></span><span>当心多参数运算</span></p>
<p><span>// 例3：多参数运算</span></p>
<p><span>//</span></p>
<p><span>#define max(a, b) ((a) &lt; (b) ? (b) : (a))</span></p>
<p><span>max(++j, k);</span></p>
<p><span><span>&nbsp;&nbsp; </span>如果++j的结果大于k，j会递增两次，这可能不是程序员想要的：</span></p>
<p><span>((++j) &lt; (k) ? (k) : (++j))</span></p>
<p><span><span>&nbsp;&nbsp; </span>类似的：</span></p>
<p><span>max(f(), pi)</span></p>
<p><span>展开后：</span></p>
<p><span>((f()) &lt; (pi) ? (pi) : (f()))</span></p>
<p><span>如果<span>f()的结果大于等于pi，f()会执行两次，这绝对缺乏效率，而且可能是错误的。</span></span></p>
<p><span><span>4）<span>&nbsp;&nbsp; </span></span></span><span>名字冲突</span></p>
<p><span>宏只是执行文本替换，而不管文本在哪儿，这意味着只要使用宏，就要小心对这些宏命名。具体来说，这个<span>max宏最大的问题是，极有可能会和标准的max()函数模板冲突：</span></span></p>
<p><span>// 例4：<span>名字冲突</span></span></p>
<p><span>//</span></p>
<p><span>#define max(a,b) ((a) &lt; (b) ? (b) : (a))</span></p>
<p><span>#include &lt;algorithm&gt; // 冲突!</span></p>
<p><span>在<span>&lt;algorithm&gt;中，有如下：</span></span></p>
<p><span>template&lt;typename T&gt; const T&amp;</span></p>
<p><span>max(const T&amp; a, const T&amp; b);</span></p>
<p><span>宏将它替换为如下，将无法编译：</span></p>
<p><span>template&lt;typename T&gt; const T&amp;</span></p>
<p><span>((const T&amp; a) &lt; (const T&amp; b) ? (const T&amp; b) : (const T&amp; a));</span></p>
<p><span>所以，我们尽量避免命名的冲突，想出一个不平常的，难以拼写的名字，这样才能最大可能地避免与其他名字空间冲突。</span></p>
<p>&nbsp;</p>
<p><span>宏的其他缺陷：</span></p>
<p><span><span>5）<span>&nbsp;&nbsp; </span></span></span><span>宏不能递归</span></p>
<p><span><span>&nbsp;&nbsp; 容易理解。</span></span></p>
<p><span><span>6）<span>&nbsp;&nbsp; </span></span></span><span>宏没有地址</span></p>
<p><span>你可能得到任何自由函数或成员函数的指针，但不可能得到一个宏的指针，因为宏没有地址。宏之所以没有地址，原因很显然<span>===宏不是代码，宏不会以自身的形势存在，因为它是一种被美化了的文本替换规则。</span></span></p>
<p><span><span>7）<span>&nbsp;&nbsp; </span></span></span><span>宏有碍调试</span></p>
<p><span>在编译器看到代码之前，宏就会修改相应的代码，因而，他会严重改变变量名称和其他名称；此外，在调试阶段，无法跟踪到宏的内部。</span></p>
</div>
</div>
<img src ="http://www.cppblog.com/twzheng/aggbug/22681.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-04-23 23:19 <a href="http://www.cppblog.com/twzheng/articles/22681.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转] 深入探讨MFC消息循环和消息泵</title><link>http://www.cppblog.com/twzheng/articles/22071.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 16 Apr 2007 16:12:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/22071.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/22071.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/22071.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/22071.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/22071.html</trackback:ping><description><![CDATA[<span>&nbsp;[转] 深入探讨MFC消息循环和消息泵<br><br>
<h2 align=center>深入探讨MFC消息循环和消息泵</h2>
<p align=center><strong>作者：周焱</strong></p>
<p><strong><font color=#0909f7>首 先，应该清楚MFC的消息循环(::GetMessage,::PeekMessage)，消息泵(CWinThread::PumpMessage)和 MFC的消息在窗口之间的路由是两件不同的事情。在MFC的应用程序中(应用程序类基于CWinThread继承)，必须要有一个消息循环，他的作用是从 应用程序的消息队列中读取消息，并把它派送出去(::DispatchMessage)。而消息路由是指消息派送出去之后，系统(USER32.DLL) 把消息投递到哪个窗口，以及以后消息在窗口之间的传递是怎样的。</font></strong></p>
<p>消息分为队列消息(进入线程的消息队列) 和非队列消息(不进入线程的消息队列)。对于队列消息，最常见的是鼠标和键盘触发的消息，例如WM_MOUSERMOVE,WM_CHAR等消息；还有例 如：WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后，相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息，然后输 送到系统消息队列，由Windows系统负责把消息加入到相应线程的消息队列中，于是就有了消息循环(从消息队列中读取并派送消息)。还有一种是非队列消 息，他绕过系统队列和消息队列，直接将消息发送到窗口过程。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。创建窗口时发送WM_CREATE消息。在后面你将看到，MS这么设计是很有道理的，以及他的整套实现机制。</p>
<p>这里讲述MFC的消息循环，消息泵。先看看程序启动时，怎么进入消息循环的：<br>_tWinMain -&gt;AfxWinMain -&gt;AfxWinInit -&gt;CWinThread::InitApplication -&gt;CWinThread::InitInstance -&gt;CWinThread::Run</p>
<p>非对话框程序的消息循环的事情都从这CWinThread的一Run开始...</p>
<p><font color=#ff3300><strong>第一部分：非对话框程序的消息循环机制。</strong></font></p>
<p><font color=#88777e>//thrdcore.cpp<br></font>// main running routine until thread exits<br>int CWinThread::Run()<br>{<br>ASSERT_VALID(this);</p>
<p>// for tracking the idle time state<br>BOOL bIdle = TRUE;<br>LONG lIdleCount = 0;</p>
<p>// acquire and dispatch messages until a WM_QUIT message is received.<br>for (;;)<br>{<br><font color=#3300ff>// phase1: check to see if we can do idle work</font><br>while (bIdle &amp;&amp;<br>&nbsp;&nbsp; !::PeekMessage(&amp;m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))<br>{<br>&nbsp;&nbsp; // call OnIdle while in bIdle state<br>&nbsp;&nbsp; if (!OnIdle(lIdleCount++))<br>&nbsp;&nbsp;&nbsp; bIdle = FALSE; // assume "no idle" state<br>}</p>
<p><font color=#3300ff>// phase2: pump messages while available<br></font>do<br>{<br>&nbsp;&nbsp; // pump message, but quit on WM_QUIT<br>&nbsp;&nbsp; if (!PumpMessage())<br>&nbsp;&nbsp;&nbsp; return ExitInstance();</p>
<p>&nbsp;&nbsp; // reset "no idle" state after pumping "normal" message<br>&nbsp;&nbsp; if (IsIdleMessage(&amp;m_msgCur))<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; bIdle = TRUE;<br>&nbsp;&nbsp;&nbsp; lIdleCount = 0;<br>&nbsp;&nbsp; }</p>
<p>} while (::PeekMessage(&amp;m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));<br>}&nbsp;&nbsp;&nbsp; <font color=#0033ff>//无限循环，退出条件是收到WM_QUIT消息。</font></p>
<p>ASSERT(FALSE); <font color=#0033ff>// not reachable<br></font>}</p>
<p>这是一个无限循环，他的退出条件是收到WM_QUIT消息：</p>
<p>if (!PumpMessage())<br>&nbsp;&nbsp;&nbsp; return ExitInstance();</p>
<p>在PumpMessage中，如果收到WM_QUIT消息，那么返回FALSE,所以ExitInstance()函数执行，跳出循环，返回程序的退出代码。所以，一个程序要退出，只用在代码中调用函数</p>
<p><font color=#3300ff>VOID PostQuitMessage( int nExitCode )。指定退出代码nExitCode就可以退出程序。</font></p>
<p>下面讨论一下这个函数Run的流程，分两步：</p>
<p>1, 第一个内循环phase1。bIdle代表程序是否空闲。他的意思就是，如果程序是空闲并且消息队列中没有要处理的消息，那么调用虚函数OnIdle进行 空闲处理。在这个处理中将更新UI界面(比如工具栏按钮的enable和disable状态)，删除临时对象(比如用FromHandle得到的对象指 针。由于这个原因，在函数之间传递由FromHandle得到的对象指针是不安全的，因为他没有持久性)。OnIdle是可以重载的，你可以重载他并返回 TRUE使消息循环继续处于空闲状态。</p>
<p><font color=#3300ff>NOTE：MS用临时对象是出于效率上的考虑，使内存 有效利用，并能够在空闲时自动撤销资源。关于由句柄转换成对象，可以有若干种方法。一般是先申明一个对象obj,然后使用obj.Attatch来和一个 句柄绑定。这样产生的对象是永久的，你必须用obj.Detach来释放对象。</font></p>
<p>2，第二个内循环phase2。在这个循环内先启动消息泵(PumpMessage),如果不是WM_QUIT消息，消息泵将消息发送出去(::DispatchMessage)。消息的目的地是消息结构中的hwnd字段所对应的窗口。<br><font color=#88777e>//thrdcore.cpp<br></font>BOOL CWinThread::PumpMessage()<br>{<br>ASSERT_VALID(this);<br><br><font color=#3300ff>//如果是WM_QUIT就退出函数(return FALSE)，这将导致程序结束.<br></font>if (!::GetMessage(&amp;m_msgCur, NULL, NULL, NULL)) {<br>#ifdef _DEBUG<br>&nbsp;&nbsp; if (afxTraceFlags &amp; traceAppMsg)<br>&nbsp;&nbsp;&nbsp; TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n");<br>&nbsp;&nbsp; m_nDisablePumpCount++; // application must die<br>&nbsp;&nbsp;&nbsp; // Note: prevents calling message loop things in 'ExitInstance'<br>&nbsp;&nbsp;&nbsp; // will never be decremented<br>#endif<br>&nbsp;&nbsp; return FALSE;<br>}</p>
<p>#ifdef _DEBUG<br>if (m_nDisablePumpCount != 0)<br>{<br>&nbsp;&nbsp; TRACE0("Error: CWinThread::PumpMessage called when not permitted.\n");<br>&nbsp;&nbsp; ASSERT(FALSE);<br>}<br>#endif</p>
<p>#ifdef _DEBUG<br>if (afxTraceFlags &amp; traceAppMsg)<br>&nbsp;&nbsp; _AfxTraceMsg(_T("PumpMessage"), &amp;m_msgCur);<br>#endif</p>
<p>// process this message</p>
<p>if (m_msgCur.message != WM_KICKIDLE &amp;&amp; !PreTranslateMessage(&amp;m_msgCur))<br>{<br>&nbsp;&nbsp; ::TranslateMessage(&amp;m_msgCur); <font color=#0033ff>//键转换</font><br>&nbsp;&nbsp; ::DispatchMessage(&amp;m_msgCur); <font color=#0033ff>//派送消息</font><br>}<br>return TRUE;<br>}</p>
<p>在 这一步有一个特别重要的函数大家一定认识：PreTranslateMessage。这个函数在::DispatchMessage发送消息到窗口之前， 进行对消息的预处理。PreTranslateMessage函数是CWinThread的成员函数，大家重载的时候都是在View类或者主窗口类中，那 么，它是怎么进入别的类的呢？代码如下：<br><font color=#88777e>//thrdcore.cpp</font><br>BOOL CWinThread::PreTranslateMessage(MSG* pMsg)<br>{<br>ASSERT_VALID(this);</p>
<p><font color=#0033ff>// 如果是线程消息，那么将会调用线程消息的处理函数</font><br>if (pMsg-&gt;hwnd == NULL &amp;&amp; DispatchThreadMessageEx(pMsg))<br>&nbsp;&nbsp; return TRUE;</p>
<p>// walk from target to main window<br>CWnd* pMainWnd = AfxGetMainWnd();<br>if (CWnd::WalkPreTranslateTree(pMainWnd-&gt;GetSafeHwnd(), pMsg))<br>&nbsp;&nbsp; return TRUE;</p>
<p>// in case of modeless dialogs, last chance route through main<br>//&nbsp;&nbsp; window's accelerator table<br>if (pMainWnd != NULL)<br>{<br>&nbsp;&nbsp; CWnd* pWnd = CWnd::FromHandle(pMsg-&gt;hwnd);<br>&nbsp;&nbsp; if (pWnd-&gt;GetTopLevelParent() != pMainWnd)<br>&nbsp;&nbsp;&nbsp; return pMainWnd-&gt;PreTranslateMessage(pMsg);<br>}</p>
<p>return FALSE;&nbsp;&nbsp; // no special processing<br>}</p>
<p>由上面这个函数可以看出：</p>
<p>第一，如果(pMsg-&gt;hwnd == NULL),说明这是一个线程消息。调用CWinThread::DispatchThreadMessageEx到消息映射表找到消息入口，然后调用消息处理函数。</p>
<p><font color=#3300ff>NOTE: 一般用PostThreadMessage函数发送线程之间的消息，他和窗口消息不同，需要指定线程id,消息激被系统放入到目标线程的消息队列中；用 ON_THREAD_MESSAGE( message, memberFxn )宏可以映射线程消息和他的处理函数。这个宏必须在应用程序类(从CWinThread继承)中，因为只有应用程序类才处理线程消息。如果你在别的类(比 如视图类)中用这个宏，线程消息的消息处理函数将得不到线程消息。</font></p>
<p>第二，消息的目标窗口的 PreTranslateMessage函数首先得到消息处理权，如果函数返回FALSE，那么他的父窗口将得到消息的处理权，直到主窗口；如果函数返回 TRUE(表示消息已经被处理了)，那么就不需要调用父类的PreTranslateMessage函数。这样，保证了消息的目标窗口以及他的父窗口都可 以有机会调用PreTranslateMessage--在消息发送到窗口之前进行预处理(如果自己处理完然后返回FALSE的话 -_-b),如果你想要消息不传递给父类进行处理的话，返回TRUE就行了。</p>
<p>第三，如果消息的目标窗口和主窗口没有父子关系，那么再调用主 窗口的PreTranslateMessage函数。为什么这样？由第二步知道，一个窗口的父窗口不是主窗口的话，尽管它的 PreTranslateMessage返回FALSE，主窗口也没有机会调用PreTranslateMessage函数。我们知道，加速键的转换一般 在框架窗口的PreTranslateMessage函数中。我找遍了MFC中关于加速键转换的处理，只有CFrameWnd, CMDIFrameWnd，CMDIChildWnd等窗口类有。所以，第三步的意思是，如果消息的目标窗口(他的父窗口不是主窗口，比如一个这样的非模 式对话框)使消息的预处理继续漫游的话(他的PreTranslateMessage返回FALSE)，那么给一次机会给主窗口调用 PreTranslateMessage(万一他是某个加速键消息呢？)，这样能够保证在有非模式对话框的情况下还能保证主窗口的加速键好使。<br>我做了一个小例子，在对话框类的PreTranslateMessage中，返回FALSE。在主窗口显示这个非模式对话框，在对话框拥有焦点的时候，仍然能够激活主窗口的快捷键。</p>
<p><font color=#0033ff>总之，整个框架就是让每个消息的目标窗口(包括他的父窗口)都有机会参与消息到来之前的处理。呵呵~</font></p>
<p>至 此，非对话框的消息循环和消息泵的机制就差不多了。这个机制在一个无限循环中，不断地从消息队列中获取消息，并且保证了程序的线程消息能够得到机会处理， 窗口消息在预处理之后被发送到相应的窗口处理过程。那么，还有一点疑问，为什么要一会儿调用::PeekMessage,一会儿调用:: GetMessage呢，他们有什么区别？</p>
<p><font color=#0033ff>NOTE：一般来说，GetMessage被设计用来高效地从消息队列获取消息。如果队列中没有消息，那么函数GetMessage将导致线程休眠(让出CPU时间)。而PeekMessage是判断消息队列中如果没有消息，它马上返回0，不会导致线程处于睡眠状态。</font></p>
<p>在 上面的phase1第一个内循环中用到了PeekMessage，它的参数PM_NOREMOVE表示并不从消息队列中移走消息，而是一个检测查询，如果 消息队列中没有消息他立刻返回0，如果这时线程空闲的话将会引起消息循环调用OnIdle处理过程(上面讲到了这个函数的重要性)。如果将:: PeekMessage改成::GetMessage(***),那么如果消息队列中没有消息，线程将休眠，直到线程下一次获得CPU时间并且有消息出现 才可能继续执行，这样，消息循环的空闲时间没有得到应用，OnIdle也将得不到执行。这就是为什么既要用::PeekMessage(查询),又要 用::GetMessage(做实际的工作)的缘故。</p>
<p><strong><font color=#ff6600>第二部分: 对话框程序的消息循环机制</font></strong></p>
<p><strong></strong></p>
<p>基于对话框的MFC工程和上面的消息循环机制不一样。实际上MFC的对话框工程程序就是模式对话框。他和上面讲到的非对话框程序的不同之处，主要在于应用程序对象的InitInstance()不一样。</p>
<p><font color=#88777e>//dlg_5Dlg.cpp</font><br>BOOL CDlg_5App::InitInstance()<br>{<br>AfxEnableControlContainer();<br>#ifdef _AFXDLL<br>Enable3dControls();&nbsp;&nbsp;&nbsp; // Call this when using MFC in a shared DLL<br>#else<br>Enable3dControlsStatic(); // Call this when linking to MFC statically<br>#endif</p>
<p>CDlg_5Dlg dlg; <font color=#0909f7>//定义一个对话框对象</font><br>m_pMainWnd = &amp;dlg;<br>int nResponse = dlg.DoModal(); <font color=#0033ff>//对话框的消息循环在这里面开始</font><br>if (nResponse == IDOK)<br>{<br>&nbsp;&nbsp; // TODO: Place code here to handle when the dialog is<br>&nbsp;&nbsp; // dismissed with OK<br>}<br>else if (nResponse == IDCANCEL)<br>{<br>&nbsp;&nbsp; // TODO: Place code here to handle when the dialog is<br>&nbsp;&nbsp; // dismissed with Cancel<br>}</p>
<p>// Since the dialog has been closed, return FALSE so that we exit the<br>// application, rather than start the application's message pump.<br>return FALSE;<br>}</p>
<p><font color=#0909f7>NOTE: InitInstance函数返回FALSE，由最上面程序启动流程可以看出，CWinThread::Run是不会得到执行的。也就是说，上面第一部分 说的消息循环在对话框中是不能执行的。实际上，对话框也有消息循环，她的消息循环在CDialog::DoModal()虚函数中的一个 RunModalLoop函数中。</font></p>
<p>这个函数的实现体在CWnd类中：<br>int CWnd::RunModalLoop(DWORD dwFlags)<br>{<br>ASSERT(::IsWindow(m_hWnd)); // window must be created<br>ASSERT(!(m_nFlags &amp; WF_MODALLOOP)); // window must not already be in modal state</p>
<p>// for tracking the idle time state<br>BOOL bIdle = TRUE;<br>LONG lIdleCount = 0;<br>BOOL bShowIdle = (dwFlags &amp; MLF_SHOWONIDLE) &amp;&amp; !(GetStyle() &amp; WS_VISIBLE);<br>HWND hWndParent = ::GetParent(m_hWnd);<br>m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);<br>MSG* pMsg = &amp;AfxGetThread()-&gt;m_msgCur;</p>
<p>// acquire and dispatch messages until the modal state is done<br>for (;;)<br>{<br>&nbsp;&nbsp; ASSERT(ContinueModal());</p>
<p><font color=#0033ff>&nbsp;&nbsp; // phase1: check to see if we can do idle work</font><br>&nbsp;&nbsp; while (bIdle &amp;&amp;<br>&nbsp;&nbsp;&nbsp; !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; ASSERT(ContinueModal());</p>
<p>&nbsp;&nbsp;&nbsp; // show the dialog when the message queue goes idle<br>&nbsp;&nbsp;&nbsp; if (bShowIdle)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; ShowWindow(SW_SHOWNORMAL);<br>&nbsp;&nbsp;&nbsp;&nbsp; UpdateWindow();<br>&nbsp;&nbsp;&nbsp;&nbsp; bShowIdle = FALSE;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; // call OnIdle while in bIdle state<br>&nbsp;&nbsp;&nbsp; if (!(dwFlags &amp; MLF_NOIDLEMSG) &amp;&amp; hWndParent != NULL &amp;&amp; lIdleCount == 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; // send WM_ENTERIDLE to the parent<br>&nbsp;&nbsp;&nbsp;&nbsp; ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; if ((dwFlags &amp; MLF_NOKICKIDLE) ||<br>&nbsp;&nbsp;&nbsp;&nbsp; !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; // stop idle processing next time<br>&nbsp;&nbsp;&nbsp;&nbsp; bIdle = FALSE;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp; }</p>
<p><font color=#0909f7>// phase2: pump messages while available<br></font>&nbsp;&nbsp; do<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; ASSERT(ContinueModal());</p>
<p>&nbsp;&nbsp;&nbsp; // pump message, but quit on WM_QUIT<br>&nbsp;&nbsp;&nbsp;<font color=#0909f7>//PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。</font><br>&nbsp;&nbsp;&nbsp; if (!AfxGetThread()-&gt;PumpMessage())<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; AfxPostQuitMessage(0);<br>&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; // show the window when certain special messages rec'd<br>&nbsp;&nbsp;&nbsp; if (bShowIdle &amp;&amp;<br>&nbsp;&nbsp;&nbsp;&nbsp; (pMsg-&gt;message == 0x118 || pMsg-&gt;message == WM_SYSKEYDOWN))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; ShowWindow(SW_SHOWNORMAL);<br>&nbsp;&nbsp;&nbsp;&nbsp; UpdateWindow();<br>&nbsp;&nbsp;&nbsp;&nbsp; bShowIdle = FALSE;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if (!ContinueModal())<br>&nbsp;&nbsp;&nbsp;&nbsp; goto ExitModal;</p>
<p>&nbsp;&nbsp;&nbsp; // reset "no idle" state after pumping "normal" message<br>&nbsp;&nbsp;&nbsp; if (AfxGetThread()-&gt;IsIdleMessage(pMsg))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; bIdle = TRUE;<br>&nbsp;&nbsp;&nbsp;&nbsp; lIdleCount = 0;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp; } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));<br>} <font color=#0909f7>//无限循环</font></p>
<p>ExitModal:<br>m_nFlags &amp;= ~(WF_MODALLOOP|WF_CONTINUEMODAL);<br>return m_nModalResult;<br>}</p>
<p>先说说怎么退出这个无限循环，在代码中：<br>if (!ContinueModal())<br>goto ExitModal;<br>决定是否退出循环，消息循环函数返回也就是快要结束结束程序了。<br>BOOL CWnd::ContinueModal()<br>{<br>return m_nFlags &amp; WF_CONTINUEMODAL;<br>}</p>
<p><font color=#0033ff>NOTE: CWnd::ContinueModal()函数检查对话框是否继续模式。返回TRUE,表示现在是模式的；返回FALSE，表示对话框已经不是模式(将要结束)。</font></p>
<p>如 果要结束对话框，在内部最终会调用函数CWnd::EndModalLoop，它取消m_nFlags的模式标志(消息循环中的 ContinueModal函数将返回FALSE，消息循环将结束，程序将退出)；然后激发消息循环读取消息。也就是说，结束模式对话框是一个标志，改变 这个标志就可以了。他的代码是：</p>
<p><font color=#88777e>//wincore.cpp<br></font>void CWnd::EndModalLoop(int nResult)<br>{<br>ASSERT(::IsWindow(m_hWnd));</p>
<p>// this result will be returned from CWnd::RunModalLoop<br>m_nModalResult = nResult;</p>
<p>// make sure a message goes through to exit the modal loop<br>if (m_nFlags &amp; WF_CONTINUEMODAL)<br>{<br>&nbsp;&nbsp; m_nFlags &amp;= ~WF_CONTINUEMODAL;<br>&nbsp;&nbsp; PostMessage(WM_NULL);<br>}<br>}</p>
<p><font color=#0909f7>NOTE: PostMessage(NULL)是有用的。如果消息队列中没有消息的话，可能消息循环中的ContinueModal()不会马上执行，发送一个空消息是激发消息循环马上工作。</font></p>
<p>下面说一下CWnd::RunModalLoop函数中的消息循环究竟干了些什么事情:<br>1, 第一个内循环。首先从消息队列中查询消息，如果对话框空闲，而且消息队列中没有消息，他做三件事情，大家应到都能从字面上明白什么意思。最重要的是发送 WM_KICKIDLE消息。为什么呢？第一部分讲到了，非对话框程序用OnIdle来更新用户界面(UI)，比如工具栏，状态栏。那么，如果对话框中也 有工具栏和状态栏呢，在哪里更新(网上有很多这样的程序)？可以处理WM_KICKIDLE消息：<br><br>LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; //调用CWnd::UpdateDialogControls更新用户界面<br>&nbsp;&nbsp;&nbsp;&nbsp; UpdateDialogControls(this, TRUE);<br>&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p><font color=#0033ff>NOTE: CWnd::UpdateDialog函数发送CN_UPDATE_COMMAND_UI消息给所有的用户界面对话框控件。</font> </p>
<p>2, 第二个内循环。最重要的还是PumpMessage派送消息到目标窗口。其他的，像第二个if语句，0x118消息好像是WM_SYSTIMER消息(系 统用来通知光标跳动的一个消息)。也就是说，如果消息为WM_SYSTIMER或者WM_SYSKEYDOWN,并且空闲显示标志为真的话，就显示窗口并 通知窗口立刻重绘。</p>
<p><font color=#0909f7>总之，对话框的消息循环机制和非对话框(比如SDI,MDI)还是类似 的，仅仅侧重点不同。模式对话框是模式显示，自然有他的特点。下面部分讨论一下模式对话框和非模式对话框的区别。因为模式对话框有自己的特殊消息循环；而 非模式对话框，共用程序的消息循环，和普通的窗口已经没有什么大的区别了。</font></p>
<p><font color=#ff3300><strong>第三部分：模式对话框和非模式对话框的区别</strong></font></p>
<p><strong></strong></p>
<p>这个话题已经有很多人讨论，我说说我所理解的意思。<br>在MFC 框架中，一个对话框对象DoModal一下就能产生一个模式对话框，Create一下就能产生一个非模式对话框。实际上，无论是模式对话框还是非模式对话 框，在MFC内部都是调用::CreateDialogIndirect(***)函数来创建非模式对话框。只是模式对话框作了更多的工作，包括使父窗口 无效，然后进入自己的消息循环等等。::CreateDialogIndirect(***)函数最终调用CreateWindowEx函数通知系统创建 窗体并返回句柄，他内部没有实现自己的消息循环。<br>非模式对话框创建之后立即返回，并且和主程序共用一个消息循环。非模式对话框要等对话框结束之后才返回，自己有消息循环。比如下面的代码：<br>CMyDlg* pdlg = new CMyDlg;<br>pdlg -&gt;Create(IDD_DIALOG1);<br>pdlg-&gt;ShowWindow(SW_SHOW);<br>MessageBox("abc");<br>非模式对话框和消息框MessageBox几乎是同时弹出来。而如果将Create改成DoModal，那么，只能弹出模式对话框，在关闭了对话框之后(模式对话框自己的消息循环结束)，消息框才弹出来。</p>
<p><font color=#0033ff>NOTE： 可以在模式对话框中调用GetParent()-&gt;EnableWindow(true);这样，主窗口的菜单，工具栏又激活了，能用了。MFC使 用非模式对话框来模拟模式对话框，而在win32 SDK程序中，模式对话框激发他的父窗口Enable操作是没有效果的。</font></p>
<p><font color=#ff3300><strong>关于消息循环总结：</strong></font></p>
<p><br>1， 我们站在一个什么高度看消息循环？消息循环其实没有什么深奥的道理。如果一个邮递员要不断在一个城市中送信，我们要求他做什么？要求他来回跑，但他一次只 能在一个地方出现。如果我们的应用程序只有一个线程的话，我们要他不断地为窗口传递消息，我们怎么做？在一个循环中不断的检测消息，并将他发送到适当的窗 口。窗口可以有很多个，但消息循环只有一个，而且每时每刻最多只有一个地方在执行代码。为什么？ 看第二点。</p>
<p>2，因为是单线程的(程序进程 启动的时候，只有而且有一个线程，我们称他为主线程),所以就像邮递员一样，每次只能在某一个地方干活。什么意思呢？举个例子，用:: DiapatchMessage派送消息，在窗口处理过程(WinProc,窗口函数)返回之前，他是阻塞的,不会立即返回，也就是消息循环此时不能再从 消息队列中读取消息，直到::DispatchMessage返回。如果你在窗口函数中执行一个死循环操作，就算你用PostQuitMessage函数 退出，程序也会down掉。<br>while(1)<br>{<br>&nbsp;&nbsp;&nbsp; PostQuitMessage(0); <font color=#3300ff>//程序照样down.</font><br>}<br>所 以，当窗口函数处理没有返回的时候，消息循环是不会从消息队列中读取消息的。这也是为什么在模式对话框中要自己用无限循环来继续消息循环，因为这个无限循 环阻塞了原来的消息循环，所以，在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取 消息并派送消息了。要不然程序就不会响应了，这不是我们所希望的。<br>所以说，消息循环放在程序的什么的地方都基本上是过的去的，比如放在DLL里 面。但是，最好在任何时候，只有一个消息循环在工作(其他的都被阻塞了)。然后，我们要作好的一件事情，就是怎么从消息循环中退出！当然用WM_QUIT 是可以拉~(PostThreadMessage也是个好主意)，这个消息循环退出后，可能程序退出，也可能会激活另外一个被阻塞的消息循环，程序继续运 行。这要看你怎么想，怎么去做。最后一个消息循环结束的时候，也许就是程序快结束的时候，因为主线程的执行代码也快要完了(除非BT的再作个死循环)。</p>
<p><font color=#0033ff>NOTE: 让windows系统知道创建一个线程的唯一方法是调用API CreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)。好像windows核心编程说，在win2000下， 系统用CreateRemoteThread函数来创建线程，CreateThread在内部调用CreateRemoteThread。不过这不是争论 的焦点，至少win98下CreateRemoteThread并不能正常工作，还是CreateThread主持大局。</font></p>
<p>3，在整个消息循环的机制中，还必须谈到窗口函数的可重入性。什么意思？就是窗口函数(他是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。比如在窗口过程中，向自己的窗口SendMessage(***);那么执行过程是怎样的？<br>我们知道，SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。那么窗口在处理消息，然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行，程序不就互相死锁了吗？ <br>其 实是不会的。windows设计一套适合SendMessage的算法，他判断如果发送的消息是属于本线程创建的窗口的，那么直接由user32模块调用 窗口函数(可能就有窗口重入)，并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子，我们调用SendMessage(***)发送消息到本 窗口，那么窗口过程再次被调用，处理完消息之后将结果返回，然后SendMessage之后的程序接着执行。对于非队列消息，如果没有窗口重入，不知道会 是什么样子。</p>
<p><font color=#0909f7>NOTE: 由于窗口的可重入性。在win32 SDK程序中应尽量少用全局变量和静态变量，因为在窗口函数执行过程中可能窗口重入，如果重入后将这些变量改了，但你的程序在窗口重入返回之后继续执行， 可能就是使用已经改变的全局或静态变量。在MFC中(所有窗口的窗口函数基本上都是AfxWndProc)，按照类的思想进行了组织，一般变量都是类中 的，好管理的多。</font></p>
<p>4,MFC中窗口类(比如C**View,CFrameWnd等)中的MessageBox函数，以及 AfxMessageBox函数都是阻塞原有的消息循环的。由消息框内部的一个消息循环来从消息队列中读取消息，并派送消息(和模式对话框类似)。实际 上，这些消息函数最终调用的是::MessageBox，它在消息框内部实现了一个消息循环(原有的主程序消息循环被阻塞了)。论坛中碰到过几次关于计时 器和消息框的问题，看下面的代码：<br>void CTest_recalclayoutView::OnTimer(UINT nIDEvent) <br>{<br>// TODO: Add your message handler code here and/or call default<br>MessageBox("abc");<br>while(1); <font color=#0909f7>//设计一个死循环<br></font>CView::OnTimer(nIDEvent);<br>}<br>咱 让OnTimer大约5秒钟弹出一个消息框。那么，消息框不断的被弹出来，只要消息框不被关闭，那么程序就不会进入死循环。实际上，每次弹出对话框，都是 最上层的那个消息框掌握着消息循环，其他的消息循环被阻塞了。只要不关闭最上面的消息框，while(1);就得不到执行。如果点了关闭，程序就进入了死 循环，只能用ctrl+alt+del来解决问题了。</p>
<p>5，消息循环在很多地方都有应用。比如应用在线程池中。一个线程的执行周期一般在线程 函数返回之后结束，那么怎么延长线程的生命周期呢？一种方法就是按照消息循环的思想，在线程中加入消息循环，不断地从线程队列读取消息，并处理消息，线程 的生命周期就保持着直到这个消息循环的退出。</p>
<p><font color=#3300ff>NOTE：只要线程有界面元素或者调用GetMessage,或者有线程消息发送过来，系统就会为线程创建一个消息队列。</font></p>
<p>&nbsp;</p>
<p>6, 在单线程程序中，如果要执行一个长时间的复杂操作而且界面要有相应的话，可以考虑用自己的消息泵。比如，可以将一个阻塞等待操作放在一个循环中，并将超时 值设置得比较小，然后每个等待的片段中用消息泵继续消息循环，使界面能够响应用户操作。等等之类，都可以应用消息泵(调用一个类似这样的函数)：<br>BOOL CChildView::PeekAndPump()<br>{<br>MSG msg;<br>while(::PeekMessage(&amp;msg,NULL,0,0,PM_NOREMOVE))<br>{<br>&nbsp;&nbsp; if(!AfxGetApp()-&gt;PumpMessage())<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; ::PostQuitMessage(0);<br>&nbsp;&nbsp;&nbsp; return false;<br>&nbsp;&nbsp; }<br>} <br>return true;<br>}<br>其实，用多线程也能解决复杂运算时的界面问题，但是没有这么方便，而且一般要加入线程通信和同步，考虑的事情更多一点。</p>
<p>&nbsp;</p>
<p>综上所述，MFC消息循环就那么回事，主要思想还是和SDK中差不多。这种思想主要的特点表现在迎合MFC整个框架上，为整个框架服务，为应用和功能服务。这是我的理解。呵呵~</p>
</span>
<img src ="http://www.cppblog.com/twzheng/aggbug/22071.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/twzheng/" target="_blank">谭文政</a> 2007-04-17 00:12 <a href="http://www.cppblog.com/twzheng/articles/22071.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用VC.NET制作启动屏幕</title><link>http://www.cppblog.com/twzheng/articles/20818.html</link><dc:creator>谭文政</dc:creator><author>谭文政</author><pubDate>Mon, 02 Apr 2007 03:36:00 GMT</pubDate><guid>http://www.cppblog.com/twzheng/articles/20818.html</guid><wfw:comment>http://www.cppblog.com/twzheng/comments/20818.html</wfw:comment><comments>http://www.cppblog.com/twzheng/articles/20818.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/twzheng/comments/commentRss/20818.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/twzheng/services/trackbacks/20818.html</trackback:ping><description><![CDATA[[转]用VC.NET制作启动屏幕<br><br>[源] <a href="http://upschool.com.cn/edu/1333/2006/85/10du135181_1.shtml">http://upschool.com.cn/edu/1333/2006/85/10du135181_1.shtml</a><br><br>基本原理是利用对话框窗口的特性，在启动时首先创建对话框，设置一个获得位图文件的句柄，利用Picture控件的SetBitmap(HBITMAP hBitmap)方法，使位图文件充满整个Picture控件窗口，最后设置定时器，整个启动屏幕就制作完成。<br>　　下面介绍具体实现步骤。<br><br>　　1. 打开VS.net的开发环境，新建VC++.net中的MFC应用程序，输入工程名称MySample，点击&#8220;确定&#8221;，&#8220;完成&#8221;；<br><br>　　2. 打开资源视图，添加对话框，更改ID为ID_DIALOG_SPLASH，设置对话框Center属性为True，Border属性为&#8220;无&#8221;；<br><br>　　3. 在对话框中添加Picture控件，设置Picture控件的ID号为ID_PIC，Type属性为&#8220;位图&#8221;，Center image属性为True，然后调整标尺使对话框没有外框，调整对话框的大小以适应启动屏幕的要求，调整Picture的宽高使其充满整个对话框窗口；<br><br>　　4. 双击对话框窗口，弹出类向导对话框，在类名中键入CSplash，选择基类为Cdialog，点击确定，定义控件Picture的控件变量为m_pic；<br><br>　　5. 在Csplash类中重载OnInitDialog()函数，添加消息WM_CHAR，WM_TIMER，WM_LBUTTONUP，WM_MBUTTONUP，WM_RBUTTONUP；<br><br>　　6. 在OnInitDialog()函数中添加代码如下： <br><br>
<table class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
    <tbody>
        <tr>
            <td>BOOL CSplash::OnInitDialog()<br>{ <br>　CDialog::OnInitDialog();<br>　//设置启动窗口背景,在整个窗口中充满位图<br>　CRect lRect;<br>　m_pic.GetClientRect(&amp;lRect);//得到Picture控件的窗口大小<br>　lRect.NormalizeRect(); <br>　//设置位图句柄<br>　HBITMAP hbitmap=(HBITMAP)LoadImage(NULL,"splash.bmp",//更改你喜欢的位图文件<br>　IMAGE_BITMAP,lRect.Width(),lRect.Height(),<br>　LR_LOADFROMFILE|LR_CREATEDIBSECTION);<br>　m_pic.SetBitmap(hbitmap);<br><br>　//设置定时器<br>　SetTimer(1,500,NULL); <br>　return TRUE; <br>}</td>
        </tr>
    </tbody>
</table>
<br>　　7. 在OnTimer(UINT nIDEvent) 函数中添加代码如下：<br><br>
<table class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
    <tbody>
        <tr>