﻿<?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++博客-@Koven.Z-随笔分类-C++</title><link>http://www.cppblog.com/kovenzgf/category/15379.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 17 Dec 2010 11:58:09 GMT</lastBuildDate><pubDate>Fri, 17 Dec 2010 11:58:09 GMT</pubDate><ttl>60</ttl><item><title>ADO 自定义常量 详解</title><link>http://www.cppblog.com/kovenzgf/archive/2010/12/16/136648.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Thu, 16 Dec 2010 09:22:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/12/16/136648.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/136648.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/12/16/136648.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/136648.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/136648.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这些常量是 ADO 预先定义的，包含在 adovbs.inc 或 adojava.inc 中，这些文件存放在 \programFiles\CommonFiles\system\ado\ 目录中。对于 ASP，既可以包含这些文件，也可以使用一个 METADATA 标记引用类型库。&lt;!--METADATATYPE=&#8220;typelib&#8221;unid=&#8220;{0000020...&nbsp;&nbsp;<a href='http://www.cppblog.com/kovenzgf/archive/2010/12/16/136648.html'>阅读全文</a><img src ="http://www.cppblog.com/kovenzgf/aggbug/136648.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-12-16 17:22 <a href="http://www.cppblog.com/kovenzgf/archive/2010/12/16/136648.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WIN32 匈牙利 表示法 前缀 说明</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/19/134066.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Fri, 19 Nov 2010 02:38:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/19/134066.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/134066.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/19/134066.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/134066.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/134066.html</trackback:ping><description><![CDATA[<img height=364 alt="" src="http://www.cppblog.com/images/cppblog_com/kovenzgf/WIN32.jpg" width=590 border=0><br><br>转自 《WINDOWS 程序设计》
<img src ="http://www.cppblog.com/kovenzgf/aggbug/134066.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-19 10:38 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/19/134066.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>复制构造函数（拷贝构造函数）</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133702.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Mon, 15 Nov 2010 12:51:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133702.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/133702.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133702.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/133702.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/133702.html</trackback:ping><description><![CDATA[<strong>复制构造函数发生时间</strong><br>1.类作为函数参数<br>2.函数返回值<br>3.对已经定义的对象进行赋值操作<br><br><strong>复制构造函数做了什么</strong><br>1.内置类型，直接赋值（指针除外，以下有特殊说明）<br>2.类类型，调用该类的复制构造函数<br>3.指针类型，只赋值指针中的地址，不赋值指针指向的对象，即所谓的浅拷贝，我们实际上需要的是深拷贝（这是一般要重写复制构造函数的主要原因，我认为是唯一原因）<br><br><strong>一些技巧</strong><br>1.要禁止赋值，显式的把赋值构造函数声明为private，同时不定义即可<br>2.复制构造函数最困难的部分在于认识到他的必要性<br><br><br><strong>示例，string的拷贝构造函数</strong>：<br>&nbsp; String::String(const String &amp;other)
<p><span><span>&nbsp;&nbsp;&nbsp; </span>{<span>&nbsp;&nbsp; </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;// 允许操作other的私有成员m_data</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>int length = strlen(other.m_data);<span>&nbsp;&nbsp; </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>m_data = new char[length+1];</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>strcpy(m_data, other.m_data);</span></p>
<p><span>&nbsp;&nbsp;&nbsp;}<br><br>个人建议，看看《高质量C++编程》相关部分，收获颇丰。</span></p>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/133702.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-15 20:51 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/15/133702.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC ADO 数据库</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133624.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Mon, 15 Nov 2010 01:46:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133624.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/133624.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/15/133624.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/133624.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/133624.html</trackback:ping><description><![CDATA[<a href="http://haitanghua.cn/article/html/47538.html">http://haitanghua.cn/article/html/47538.html</a>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/133624.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-15 09:46 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/15/133624.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket I/O模型全接触 </title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/10/133176.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Wed, 10 Nov 2010 01:40:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/10/133176.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/133176.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/10/133176.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/133176.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/133176.html</trackback:ping><description><![CDATA[<p>本文简单介绍了当前Windows支持的各种Socket I/O模型，如果你发现其中存在什么错误请务必赐教。</p>
<p>&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模型</p>
<p>&nbsp;&nbsp;&nbsp; 老陈有一个在外地工作的女儿，不能经常回来，老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。<br>&nbsp;&nbsp;&nbsp; 这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket I/O模型~~~</p>
<p>一：select模型</p>
<p>老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱，看是否有女儿的信~~~~~<br>在这种情况下，&#8220;下楼检查信箱&#8221;然后回到楼上耽误了老陈太多的时间，以至于老陈无法做其他工作。<br>select模型和老陈的这种情况非常相似：周而复始地去检查......如果有数据......接收/发送.......</p>
<p>使用线程来select应该是通用的做法：</p>
<p><br>procedure TListenThread.Execute;<br>var<br>addr&nbsp;&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>fd_read : TFDSet;<br>timeout : TTimeVal;<br>ASock,<br>MainSock : TSocket;<br>len, i&nbsp;&nbsp; : Integer;<br>begin<br>MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );<br>addr.sin_family := AF_INET;<br>addr.sin_port := htons(5678);<br>addr.sin_addr.S_addr := htonl(INADDR_ANY);<br>bind( MainSock, @addr, sizeof(addr) );<br>listen( MainSock, 5 );</p>
<p>while (not Terminated) do<br>begin<br>&nbsp;&nbsp;&nbsp; FD_ZERO( fd_read );<br>&nbsp;&nbsp;&nbsp; FD_SET( MainSock, fd_read );<br>&nbsp;&nbsp;&nbsp; timeout.tv_sec := 0;<br>&nbsp;&nbsp;&nbsp; timeout.tv_usec := 500;<br>&nbsp;&nbsp;&nbsp; if select( 0, @fd_read, nil, nil, @timeout ) &gt; 0 then {//至少有1个等待Accept的connection}<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if FD_ISSET( MainSock, fd_read ) then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i:=0 to fd_read.fd_count-1 do {//注意，fd_count &lt;= 64，也就是说select只能同时管理最多64个连接}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; len := sizeof(addr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ASock := accept( MainSock, addr, len );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ASock &lt;&gt; INVALID_SOCKET then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .{//为ASock创建一个新的线程，在新的线程中再不停地select}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end; <br>&nbsp;&nbsp;&nbsp; end; <br>end; {//while (not self.Terminated)}</p>
<p>shutdown( MainSock, SD_BOTH );<br>closesocket( MainSock );<br>end;</p>
<p>&nbsp;</p>
<p>二：WSAAsyncSelect模型</p>
<p>后来，老陈使用了微软公司的新式信箱。这种信箱非常先进，一旦信箱里有新的信件，盖茨就会给老陈打电话：喂，大爷，你有新的信件了！从此，老陈再也不必频繁上下楼检查信箱了，牙也不疼了，你瞅准了，蓝天......不是，微软~~~~~~~~<br>微软提供的WSAAsyncSelect模型就是这个意思。</p>
<p>WSAAsyncSelect模型是Windows下最简单易用的一种Socket I/O模型。使用这种模型时，Windows会把网络事件以消息的形势通知应用程序。<br>首先定义一个消息标示常量：<br>const WM_SOCKET = WM_USER + 55;<br>再在主Form的private域添加一个处理此消息的函数声明：</p>
<p><br>private<br>procedure WMSocket(var Msg: TMessage); message WM_SOCKET;<br>{然后就可以使用WSAAsyncSelect了：}</p>
<p><br>Code<br>var<br>addr : TSockAddr;<br>sock : TSocket;</p>
<p>sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );<br>addr.sin_family := AF_INET;<br>addr.sin_port := htons(5678);<br>addr.sin_addr.S_addr := htonl(INADDR_ANY);<br>bind( m_sock, @addr, sizeof(SOCKADDR) );</p>
<p>WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE );</p>
<p>listen( m_sock, 5 );<br>&nbsp;&nbsp; <br>应用程序可以对收到WM_SOCKET消息进行分析，判断是哪一个socket产生了网络事件以及事件类型：</p>
<p><br>procedure TfmMain.WMSocket(var Msg: TMessage);<br>var<br>sock&nbsp;&nbsp;&nbsp; : TSocket;<br>addr&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>addrlen : Integer;<br>buf&nbsp;&nbsp;&nbsp;&nbsp; : Array [0..4095] of Char;<br>begin<br>{//Msg的WParam是产生了网络事件的socket句柄，LParam则包含了事件类型}<br>case WSAGetSelectEvent( Msg.LParam ) of<br>&nbsp;&nbsp;&nbsp; FD_ACCEPT :<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; addrlen := sizeof(addr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sock := accept( Msg.WParam, addr, addrlen );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if sock &lt;&gt; INVALID_SOCKET then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; FD_CLOSE : closesocket( Msg.WParam );<br>&nbsp;&nbsp;&nbsp; FD_READ : recv( Msg.WParam, buf[0], 4096, 0 );<br>&nbsp;&nbsp;&nbsp; FD_WRITE : ;<br>end; <br>end;</p>
<p>三：WSAEventSelect模型</p>
<p>后来，微软的信箱非常畅销，购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话，累得腰酸背痛，喝蚁力神都不好使~~~~~~<br>微软改进了他们的信箱：在客户的家中添加一个附加装置，这个装置会监视客户的信箱，每当新的信件来临，此装置会发出&#8220;新信件到达&#8221;声，提醒老陈去收信。盖茨终于可以睡觉了。</p>
<p>同样要使用线程：</p>
<p><br>procedure TListenThread.Execute;<br>var<br>hEvent : WSAEvent;<br>ret&nbsp;&nbsp;&nbsp; : Integer;<br>ne&nbsp;&nbsp;&nbsp;&nbsp; : TWSANetworkEvents;<br>sock&nbsp;&nbsp; : TSocket;<br>adr&nbsp;&nbsp;&nbsp; : TSockAddrIn;<br>sMsg&nbsp;&nbsp; : String;<br>Index,<br>EventTotal : DWORD;<br>EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT;<br>begin<br>&nbsp;&nbsp; socket bind <br>hEvent := WSACreateEvent();<br>WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE );<br>&nbsp;&nbsp; listen</p>
<p>while ( not Terminated ) do<br>begin<br>&nbsp;&nbsp;&nbsp; Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE );<br>&nbsp;&nbsp;&nbsp; FillChar( ne, sizeof(ne), 0 );<br>&nbsp;&nbsp;&nbsp; WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne );</p>
<p>&nbsp;&nbsp;&nbsp; if ( ne.lNetworkEvents and FD_ACCEPT ) &gt; 0 then<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ne.iErrorCode[FD_ACCEPT_BIT] &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret := sizeof(adr);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if EventTotal &gt; WSA_MAXIMUM_WAIT_EVENTS-1 then{//这里WSA_MAXIMUM_WAIT_EVENTS同样是64}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket( sock );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hEvent := WSACreateEvent();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SockArray[EventTotal] := sock;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EventArray[EventTotal] := hEvent;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Inc( EventTotal );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; if ( ne.lNetworkEvents and FD_READ ) &gt; 0 then<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ne.iErrorCode[FD_READ_BIT] &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; end;<br>end;<br>end;</p>
<p>四：Overlapped I/O 事件通知模型</p>
<p>后来，微软通过调查发现，老陈不喜欢上下楼收发信件，因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术，只要用户告诉微软自己的家在几楼几号，新式信箱会把信件直接传送到用户的家中，然后告诉用户，你的信件已经放到你的家中了！老陈很高兴，因为他不必再亲自收发信件了！</p>
<p>Overlapped I/O 事件通知模型和WSAEventSelect模型在实现上非常相似，主要区别在&#8220;Overlapped&#8221;，Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED)，一次投递一个或多个Winsock I/O请求。这些提交的请求完成后，应用程序会收到通知。什么意思呢？就是说，如果你想从socket上接收数据，只需要告诉系统，由系统为你接收数据，而你需要做的只是为系统提供一个缓冲区~~~~~<br>Listen线程和WSAEventSelect模型一模一样，Recv/Send线程则完全不同：</p>
<p><br>Code<br>procedure TOverlapThread.Execute;<br>var<br>dwTemp : DWORD;<br>ret&nbsp;&nbsp;&nbsp; : Integer;<br>Index : DWORD;<br>begin<br></p>
<p>while ( not Terminated ) do<br>begin<br>&nbsp;&nbsp;&nbsp; Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE );<br>&nbsp;&nbsp;&nbsp; Dec( Index, WSA_WAIT_EVENT_0 );<br>&nbsp;&nbsp;&nbsp; if Index &gt; WSA_MAXIMUM_WAIT_EVENTS-1 then {//超时或者其他错误}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;</p>
<p>&nbsp;&nbsp;&nbsp; WSAResetEvent( FLinks.Events[Index] );<br>&nbsp;&nbsp;&nbsp; WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ );</p>
<p>&nbsp;&nbsp;&nbsp; if dwTemp = 0 then {//连接已经关闭}<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; end else<br>&nbsp;&nbsp;&nbsp; begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf );<br>&nbsp;&nbsp;&nbsp; end;</p>
<p>&nbsp;&nbsp;&nbsp; {//初始化缓冲区}<br>&nbsp;&nbsp;&nbsp; FLinks.pdwFlags[Index]^ := 0;<br>&nbsp;&nbsp;&nbsp; FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 );<br>&nbsp;&nbsp;&nbsp; FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index];<br>&nbsp;&nbsp;&nbsp; FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 );</p>
<p>&nbsp;&nbsp;&nbsp; {//递一个接收数据请求}<br>&nbsp;&nbsp;&nbsp; WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil );<br>end;<br>end;</p>
<p>五：Overlapped I/O 完成例程模型</p>
<p>老陈接收到新的信件后，一般的程序是：打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担，微软又开发了一种新的技术：用户只要告诉微软对信件的操作步骤，微软信箱将按照这些步骤去处理信件，不再需要用户亲自拆信/阅读/回复了！老陈终于过上了小资生活！</p>
<p>Overlapped I/O 完成例程要求用户提供一个回调函数，发生新的网络事件的时候系统将执行这个函数：</p>
<p><br>procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;</p>
<p>然后告诉系统用WorkerRoutine函数处理接收到的数据：<br>WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );<br>然后......没有什么然后了，系统什么都给你做了！微软真实体贴！</p>
<p><br>Code<br>while ( not Terminated ) do{//这就是一个Recv/Send线程要做的事情什么都不用做啊！！！}<br>begin<br>if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then {//}<br>begin<br>&nbsp;&nbsp;&nbsp; ;<br>end else<br>begin<br>&nbsp;&nbsp;&nbsp; continue;<br>end;<br>end;</p>
<p>&nbsp;</p>
<p>六：IOCP模型</p>
<p>微软信箱似乎很完美，老陈也很满意。但是在一些大公司情况却完全不同！这些大公司有数以万计的信箱，每秒钟都有数以百计的信件需要处理，以至于微软信箱经常因超负荷运转而崩溃！需要重新启动！微软不得不使出杀手锏......<br>微软给每个大公司派了一名名叫&#8220;Completion Port&#8221;的超级机器人，让这个机器人去处理那些信件！</p>
<p>&#8220;Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢?&#8221;-----摘自nonocast的《理解I/O Completion Port》</p>
<p>先看一下IOCP模型的实现：</p>
<p><br>Code<br>{创建一个完成端口}<br>FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );</p>
<p>{接受远程连接，并把这个连接的socket句柄绑定到刚才创建的IOCP上}<br>AConnect := accept( FListenSock, addr, len);<br>CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );</p>
<p>{创建CPU数*2 + 2个线程}<br>for i:=1 to si.dwNumberOfProcessors*2+2 do<br>begin<br>AThread := TRecvSendThread.Create( false );<br>AThread.CompletPort := FCompletPort;{//告诉这个线程，你要去这个IOCP去访问数据}<br>end;</p>
<p>OK，就这么简单，我们要做的就是建立一个IOCP，把远程连接的socket句柄绑定到刚才创建的IOCP上，最后创建n个线程，并告诉这n个线程到这个IOCP上去访问数据就可以了。</p>
<p>再看一下TRecvSendThread线程都干些什么：</p>
<p><br>Code<br>procedure TRecvSendThread.Execute;<br>var<br><br>begin<br>while (not self.Terminated) do<br>begin<br>&nbsp;&nbsp;&nbsp; {查询IOCP状态（数据读写操作是否完成）}<br>&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );</p>
<p>&nbsp;&nbsp;&nbsp; if BytesTransd &lt;&gt; 0 then<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .;{//数据读写操作完成}</p>
<p>&nbsp;&nbsp;&nbsp; {//再投递一个读数据请求}<br>&nbsp;&nbsp;&nbsp; WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );<br>end;<br>end;</p>
<p>读写线程只是简单地检查IOCP是否完成了我们投递的读写操作，如果完成了则再投递一个新的读写请求。<br>应该注意到，我们创建的所有TRecvSendThread都在访问同一个IOCP（因为我们只创建了一个IOCP），并且我们没有使用临界区！难道不会产生冲突吗？不用考虑同步问题吗？<br>呵呵，这正是IOCP的奥妙所在。IOCP不是一个普通的对象，不需要考虑线程安全问题。它会自动调配访问它的线程：如果某个socket上有一个线程A正在访问，那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的，我们无需过问。</p>
<p>呵呵，终于写完了，好累......以上所有的源代码可以从这里看到：<br><a href="http://community.csdn.net/Expert/topic/3844/3844679.xml?temp=5.836123E-02"><font color=#4c7d08>http://community.csdn.net/Expert/topic/3844/3844679.xml?temp=5.836123E-02</font></a><br>不过代码写的很简陋，请多包涵！</p>
<p>转自：<a href="http://blog.csdn.net/flyinwuhan/"><font color=#4c7d08>http://blog.csdn.net/flyinwuhan/</font></a></p>
<p><font color=#4c7d08></font></p>
<p>声明：除CSDN外的任何媒体转载必须注明作者以及&#8220;转载自CSDN&#8221;。</p>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/133176.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-10 09:40 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/10/133176.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>socket IO完成端口模型详解</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/09/133124.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Tue, 09 Nov 2010 13:50:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/09/133124.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/133124.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/09/133124.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/133124.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/133124.html</trackback:ping><description><![CDATA[<p>#include "stdafx.h"</p>
<p>#include &lt;WINSOCK2.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>typedef enum<br>{<br>&nbsp;&nbsp; RECV_POSTED<br>}OPERATION_TYPE;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //枚举,表示状态</p>
<p>typedef struct<br>{<br>WSAOVERLAPPED&nbsp;&nbsp; overlap;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>WSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NumberOfBytesRecvd;<br>DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags;<br>OPERATION_TYPE OperationType;<br>}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;&nbsp;&nbsp;&nbsp; //定义一个结构体保存IO数据</p>
<p>DWORD WINAPI WorkerThread(LPVOID);</p>
<p>int main()<br>{<br>&nbsp;&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp;&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen, sClient;<br>&nbsp;&nbsp; SOCKADDR_IN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local, client;<br>&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i, dwThreadId;<br>&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);<br>&nbsp;&nbsp; HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort = INVALID_HANDLE_VALUE;<br>&nbsp;&nbsp; SYSTEM_INFO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; systeminfo;<br>&nbsp;&nbsp; LPPER_IO_OPERATION_DATA lpPerIOData = NULL;</p>
<p>&nbsp;&nbsp; // Initialize Windows Socket library<br>&nbsp;&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp;&nbsp; // 初始化完成端口<br>&nbsp;&nbsp; CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);</p>
<p>&nbsp;&nbsp; // 有几个CPU就创建几个工作者线程<br>&nbsp;&nbsp; GetSystemInfo(&amp;systeminfo);<br>&nbsp;&nbsp; for (i = 0; i &lt; systeminfo.dwNumberOfProcessors; i++)<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &amp;dwThreadId);<br>&nbsp;&nbsp; }<br><br>&nbsp;&nbsp; // 创建套接字<br>&nbsp;&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp;&nbsp; // 绑定套接字<br>&nbsp;&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>local.sin_family = AF_INET;<br>local.sin_port = htons(PORT);<br>&nbsp;&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp;&nbsp; // 开始监听！<br>&nbsp;&nbsp; listen(sListen, 3);</p>
<p>&nbsp;&nbsp; while (TRUE)//主进程的这个循环中循环等待客户端连接，若有连接，则将该客户套接字于完成端口绑定到一起<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //然后开始异步等待接收客户传来的数据。<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; // 如果接到客户请求连接，则继续，否则等待。<br>&nbsp;&nbsp;&nbsp;&nbsp; sClient = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>//client中保存用户信息。<br>&nbsp;&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));</p>
<p>//将这个最新到来的客户套接字和完成端口绑定到一起。<br>&nbsp;&nbsp;&nbsp;&nbsp; CreateIoCompletionPort((HANDLE)sClient, CompletionPort, ( ULONG_PTR)sClient, 0);<br>//第三个参数表示传递的参数，这里就传递的客户套接字地址。最后一个参数为0 表示有和CPU一样的进程数。即1个CPU一个线程<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; // 初始化结构体<br>&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetProcessHeap(),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HEAP_ZERO_MEMORY,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE; // len=1024<br>&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage; <br>&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;OperationType = RECV_POSTED; //操作类型<br>&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(sClient,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //异步接收消息，立刻返回。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer, //获得接收的数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The number of WSABUF structures in the lpBuffers array.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd, //接收到的字节数，如果错误返回0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数，先不管<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,&nbsp;&nbsp;&nbsp;&nbsp; //输入这个结构体咯。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp; }</p>
<p><br>//posts an I/O completion packet to an I/O completion port.<br>&nbsp;&nbsp; PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL); <br>CloseHandle(CompletionPort);<br>closesocket(sListen);<br>WSACleanup();<br>return 0;<br>}<br>//工作者线程有一个参数，是指向完成端口的句柄<br>DWORD WINAPI WorkerThread(LPVOID CompletionPortID)<br>{<br>&nbsp;&nbsp; HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort=(HANDLE)CompletionPortID;<br>&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwBytesTransferred;<br>&nbsp;&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient;<br>&nbsp;&nbsp; LPPER_IO_OPERATION_DATA lpPerIOData = NULL;</p>
<p>&nbsp;&nbsp; while (TRUE)<br>&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus( //遇到可以接收数据则返回，否则等待<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;dwBytesTransferred, //返回的字数<br>&nbsp;&nbsp;&nbsp; (PULONG_PTR&amp;)sClient,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //是响应的哪个客户套接字？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPOVERLAPPED *)&amp;lpPerIOData, //得到该套接字保存的IO信息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; INFINITE);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //无限等待咯。不超时的那种。<br>&nbsp;&nbsp;&nbsp;&nbsp; if (dwBytesTransferred == 0xFFFFFFFF)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; if (lpPerIOData-&gt;OperationType == RECV_POSTED) //如果受到数据<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (dwBytesTransferred == 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Connection was closed by client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(sClient);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HeapFree(GetProcessHeap(), 0, lpPerIOData);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //释放结构体<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;szMessage[dwBytesTransferred] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(sClient, lpPerIOData-&gt;szMessage, dwBytesTransferred, 0); //将接收到的消息返回<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Launch another asynchronous operation for sClient<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;OperationType = RECV_POSTED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(sClient,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //循环接收<br>&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,<br>&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,<br>&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp; }<br>&nbsp;&nbsp; return 0;<br>}<br>/*<br>首先，说说主线程：<br>1.创建完成端口对象<br>2.创建工作者线程（这里工作者线程的数量是按照CPU的个数来决定的，这样可以达到最佳性能）<br>3.创建监听套接字，绑定，监听，然后程序进入循环<br>4.在循环中，我做了以下几件事情：<br>(1).接受一个客户端连接<br>(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort，但这次的作用不同)，<br>注意，按道理来讲，此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键，<br>一般来讲，程序都是传递一个单句柄数据结构的地址，该单句柄数据包含了和该客户端连接有关的信息，<br>由于我们只关心套接字句柄，所以直接将套接字句柄作为完成键传递；<br>(3).触发一个WSARecv异步调用，用到了&#8220;尾随数据&#8221;，使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后，<br>此外，还有操作类型等重要信息。</p>
<p>在工作者线程的循环中，我们<br>1.调用GetQueuedCompletionStatus取得本次I/O的相关信息（例如套接字句柄、传送的字节数、单I/O数据结构的地址等等）<br>2.通过单I/O数据结构找到接收数据缓冲区，然后将数据原封不动的发送到客户端<br>3.再次触发一个WSARecv异步操作</p>
<p>自己的理解，有任何问题都可以留言或者于我联系<br><br>
<hr>
<p>&#160;</p>
线程池中最佳的工作线程数应该是 CPU个数 * 2 + 2 —— WIN32多线程程序设计
<img src ="http://www.cppblog.com/kovenzgf/aggbug/133124.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-09 21:50 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/09/133124.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C变参</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132592.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Fri, 05 Nov 2010 12:08:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132592.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/132592.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132592.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/132592.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/132592.html</trackback:ping><description><![CDATA[<div id=blogDetailDiv style="FONT-SIZE: 14px">
<p>今天在学习C＋＋primer的时候 有一个章节是写省略号的，突然很感兴趣，可惜书上介绍的资料很少，于是去网上查了些资料，发现原来 这个是来源于C函数的，估计在C primer中有详细说明，我收集的如下，以及相关例子</p>
<p>简单的说</p>
<p>变长参数<br>变参是用省略号...来表示，声明的。<br>#include &lt;stdarg.h&gt; <br>标准头文件，提供va_start、va_arg、va_end<br><br>va_start实际上声明一个指针，指向一个参数，然后根据va_arg中的参数类型移动指针，得到每个参数的值。</p>
<p>C语言参数是通过堆栈传送的: printf( "%d%d...", p1, p2, p3..., pN );<br>push pN<br>...<br>push p3<br>push p2<br>push p1<br>push strPTR&nbsp;&nbsp;&nbsp; 压指针<br>call _printf<br>pop ax<br>pop ax<br>pop ax<br>...<br>pop ax&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 清参数(第N个)</p>
<p>显而易见,转入printf后所有的参数都在堆栈中,多少个不知道,但都在.<br>只须确定参数个数,但参数最下面的那个是String,里面包含了个数信息(%d,%f...)不就ok了?<br>即使参数大小各有不同,也无所谓,照样取出.但%d多于实际参数,可能会将其他堆栈数据取出显示(只要不修改,一般影响不大).<br>/////////////////////////////////////////////////////////////////</p>
<p>我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()<br>这个函数,它的定义是这样的:<br>int printf( const char* format, ...);<br>它除了有一个参数format固定以外,后面跟的参数的个数和类型是<br>可变的,例如我们可以有以下不同的调用方法:<br>printf("%d",i);<br>printf("%s",s);<br>printf("the number is %d ,string is:%s", i, s);<br>究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实<br>现的呢?本文就这个问题进行一些探讨,希望能对大家有些帮助.会C++的<br>网友知道这些问题在C++里不存在,因为C++具有多态性.但C++是C的一个<br>超集,以下的技术也可以用于C++的程序中.限于本人的水平,文中如果有<br>不当之处,请大家指正.</p>
<p>(一)写一个简单的可变参数的C函数</p>
<p>下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的<br>C函数要在程序中用到以下这些宏:<br>void va_start( va_list arg_ptr, prev_param );</p>
<p>type va_arg( va_list arg_ptr, type );</p>
<p>void va_end( va_list arg_ptr );<br>va在这里是variable-argument(可变参数)的意思.<br>这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个<br>头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数<br>参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.<br>void simple_va_fun(int i, ...)<br>{<br>va_list arg_ptr;<br>int j=0;</p>
<p>va_start(arg_ptr, i);<br>j=va_arg(arg_ptr, int);<br>va_end(arg_ptr);<br>printf("%d %d\n", i, j);<br>return;<br>}<br>我们可以在我们的头文件中这样声明我们的函数:<br>extern void simple_va_fun(int i, ...);<br>我们在程序中可以这样调用:<br>simple_va_fun(100);<br>simple_va_fun(100,200);<br>从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:<br>1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变<br>量是指向参数的指针.<br>2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第<br>一个可变参数的前一个参数,是一个固定的参数.<br>3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个<br>参数是你要返回的参数的类型,这里是int型.<br>4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使<br>用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获<br>取各个参数.<br>如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:<br>1)simple_va_fun(100);<br>结果是:100 -123456789(会变的值)<br>2)simple_va_fun(100,200);<br>结果是:100 200<br>3)simple_va_fun(100,200,300);<br>结果是:100 200<br>我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果<br>正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果<br>的原因和可变参数在编译器中是如何处理的.</p>
<p>(二)可变参数在编译器中的处理</p>
<p>我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,<br>由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下<br>面以VC++中stdarg.h里x86平台的宏定义摘录如下('\'号表示折行):</p>
<p>typedef char * va_list;</p>
<p>#define _INTSIZEOF(n) \<br>((sizeof(n)+sizeof(int)-1)&amp;~(sizeof(int) - 1) )</p>
<p>#define va_start(ap,v) ( ap = (va_list)&amp;v + _INTSIZEOF(v) )</p>
<p>#define va_arg(ap,t) \<br>( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )</p>
<p>#define va_end(ap) ( ap = (va_list)0 )</p>
<p>定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函<br>数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我<br>们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再<br>看va_start的定义,定义为&amp;v+_INTSIZEOF(v),而&amp;v是固定参数在堆栈的<br>地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆<br>栈的地址,如图:</p>
<p>高地址<br>|-----------------------------|<br>|....... |<br>|-----------------------------|<br>|第n个参数(第一个可变参数) |<br>|----------------------------|&lt;--va_start后ap指向<br>|第n-1个参数(最后一个固定参数)|<br>|-----------------------------|&lt;-- &amp;v<br>|函数返回地址 |<br>|----------------------------|<br>低地址</p>
<p>图( 1 )</p>
<p>然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我<br>们看一下va_arg取int型的返回值:<br>j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );<br>首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回<br>ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址<br>(图2).然后用*取得这个地址的内容(参数值)赋给j.</p>
<p>高地址<br>|-----------------------------|<br>|....... |<br>|-----------------------------|&lt;--va_arg后ap指向<br>|第n个参数(第一个可变参数) |<br>|-----------------------------|&lt;--va_start后ap指向<br>|第n-1个参数(最后一个固定参数)|<br>|-----------------------------|&lt;-- &amp;v<br>|函数返回地址 |<br>|----------------------------|<br>低地址<br>图( 2 )</p>
<p>最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再<br>指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不<br>会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.<br>在这里大家要注意一个问题:由于参数的地址用于va_start宏,所<br>以参数不能声明为寄存器变量或作为函数或数组类型.<br>关于va_start, va_arg, va_end的描述就是这些了,我们要注意的<br>是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.</p>
<p>(三)可变参数在编程中要注意的问题</p>
<p>因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,<br>可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能<br>地识别不同参数的个数和类型.<br>有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数<br>printf是从固定参数format字符串来分析出参数的类型,再调用va_arg<br>的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通<br>过在自己的程序里作判断来实现的.<br>另外有一个问题,因为编译器对可变参数的函数的原型检查不够严<br>格,对编程查错不利.如果simple_va_fun()改为:<br>void simple_va_fun(int i, ...)<br>{<br>va_list arg_ptr;<br>char *s=NULL;</p>
<p>va_start(arg_ptr, i);<br>s=va_arg(arg_ptr, char*);<br>va_end(arg_ptr);<br>printf("%d %s\n", i, s);<br>return;<br>}<br>可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现<br>core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出<br>错,但错误却是难以发现,不利于我们写出高质量的程序.<br>以下提一下va系列宏的兼容性.<br>System V Unix把va_start定义为只有一个参数的宏:<br>va_start(va_list arg_ptr);<br>而ANSI C则定义为:<br>va_start(va_list arg_ptr, prev_param);<br>如果我们要用system V的定义,应该用vararg.h头文件中所定义的<br>宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以<br>用ANSI C的定义就够了,也便于程序的移植.</p>
<p>例子</p>
<p>void F(int arg_count, ...)<br>{<br>&nbsp;va_list ap;<br>&nbsp;va_start(ap, arg_count);<br>&nbsp;while (arg_count--)<br>&nbsp;&nbsp;cout &lt;&lt; va_arg(ap, char);// char 这里需要确定你的参数类型，像 printf 是通过掩码<br>&nbsp;va_end(ap);</p>
<p>}<br><br>#include &lt;stdio.h&gt;<br>#include &lt;stdarg.h&gt;<br>// 不定参数函数例子，大家交流交流。<br>void sum(char *msg, ...)<br>{<br>&nbsp;&nbsp; int total = 0;<br>&nbsp;&nbsp; va_list ap;<br>&nbsp;&nbsp; int arg;<br>&nbsp;&nbsp; va_start(ap, msg);<br>&nbsp;&nbsp; while ((arg = va_arg(ap,int)) != 0) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; total += arg;<br>&nbsp;&nbsp; }<br>&nbsp;&nbsp; printf(msg, total);<br>&nbsp;&nbsp; va_end(ap);<br>}</p>
<p>int main(void) {<br>&nbsp;&nbsp; sum("The total of 1+2+3+4 is %d\n", 1,2,3,4,0);<br>&nbsp;&nbsp; return 0;<br></p>
<p>&nbsp;</p>
</div>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/132592.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-05 20:08 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/05/132592.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>成员函数作为线程函数</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132591.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Fri, 05 Nov 2010 12:05:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132591.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/132591.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132591.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/132591.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/132591.html</trackback:ping><description><![CDATA[<div id=blogDetailDiv style="FONT-SIZE: 14px">
<p>关于this指针的传递问题总结<br>1:__cdecl成员函数 通过ECX传递this指针<br>&nbsp;&nbsp;&nbsp;&nbsp; mov ecx, 对象的地址<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; call 成员函数</p>
<p>2:__stdcall成员函数 通过堆栈传递this指针<br>&nbsp;&nbsp;&nbsp;&nbsp; push 对象的地址<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; call 成员函数</p>
<p>用非静态成员作为线程函数<br>原理分析:<br>1.该线程的主函数为类的非静态成员函数,所以它认为他的调用者会为他传递一个this指针,通过堆栈传递.因为__stdcall的函数<br>2.而操作系统认为的线程主函数只有一个参数通过堆栈传递.<br>所以线程的this指针被&amp;b覆盖了,刚好把非静态成员函数作为线程主函数<br>class CObject<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CObject(int a){ m_nData = a;};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD WINAPI ThreadFuc()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Thread Run\nThread Data:%d \n", m_nData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int m_nData;<br>};<br>typedef DWORD ( CObject::*MyThread)(<br>&nbsp;&nbsp;&nbsp; LPVOID lpThreadParameter<br>&nbsp;&nbsp;&nbsp; );<br>int main(int argc, char** argv)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MyThread My = (MyThread)&amp;CObject::ThreadFuc;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CObject b(10);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE hThread = ::CreateThread(NULL, NULL, *(LPTHREAD_START_ROUTINE*)&amp;My, &amp;b, NULL, NULL);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; system("pause");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ::CloseHandle(hThread);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>} </p>
<p>以上内容转自VCKBASE，经过测试，可以正常运行。</p>
<p>使用时注意一下几点：</p>
<p>1.如果使用AfxBeginThread，强制转换的类型应该是AFX_THREADPROC</p>
<p>以下是我使用时候的代码：</p>
<p>&nbsp;typedef UINT ( CDlgMutex_Test::*WOLF_HTREADPROC)(LPVOID);</p>
<p>&nbsp;WOLF_HTREADPROC myThread1 = (WOLF_HTREADPROC)&amp;CDlgMutex_Test::Thread1;<br>&nbsp;WOLF_HTREADPROC myThread2 = (WOLF_HTREADPROC)&amp;CDlgMutex_Test::Thread2;</p>
<p>&nbsp;AfxBeginThread(*(AFX_THREADPROC*)&amp;myThread1,this);<br>&nbsp;AfxBeginThread(*(AFX_THREADPROC*)&amp;myThread2,this);</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/haoxing168/archive/2009/08/16/4452360.aspx" target=_blank><u>http://blog.csdn.net/haoxing168/archive/2009/08/16/4452360.aspx</u></a></p>
<p>&nbsp;</p>
<p>&nbsp;
<hr width="100%">
<p>&nbsp;</p>
<p>附：其他一个.CPP</p>
<p>#include &lt;windows.h&gt;<br>#include &lt;conio.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p><br>class t<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; void Run();<br>&nbsp;&nbsp;&nbsp; t();</p>
<p>protected:<br>&nbsp;&nbsp;&nbsp; char c[256];<br>&nbsp;&nbsp;&nbsp; DWORD WINAPI ThreadFunc();<br>};</p>
<p>typedef DWORD (WINAPI&nbsp; t::* pThreadFunc)();</p>
<p>t::t()<br>{<br>&nbsp;&nbsp;&nbsp; strcpy(c, "Welcome to use thread proc");<br>}</p>
<p>DWORD WINAPI t::ThreadFunc()<br>{<br>&nbsp;&nbsp;&nbsp; MessageBox(NULL, c, "xixi", MB_OK);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>typedef DWORD (WINAPI * PTHREADFUNC)(LPVOID);</p>
<p>void t::Run()<br>{</p>
<p>&nbsp;&nbsp;&nbsp; pThreadFunc p = ThreadFunc;<br>&nbsp;&nbsp;&nbsp; DWORD dwAddress;<br>&nbsp;&nbsp;&nbsp; memcpy(&amp;dwAddress,&amp;p,sizeof(p));</p>
<p>&nbsp;&nbsp;&nbsp; PTHREADFUNC p1;<br>&nbsp;&nbsp;&nbsp; memcpy(&amp;p1, &amp;dwAddress, sizeof(p1));<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; DWORD dwTID;<br>&nbsp;&nbsp;&nbsp; HANDLE hf;<br>&nbsp;&nbsp;&nbsp; hf = CreateThread(NULL, 0, p1, this, 0, &amp;dwTID);<br>&nbsp;&nbsp;&nbsp; if (hf){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(hf);<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>void main()<br>{<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; t t1;<br>&nbsp;&nbsp;&nbsp; t1.Run();<br>&nbsp;&nbsp;&nbsp; getch();<br>} <br></p>
</div>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/132591.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-05 20:05 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/05/132591.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数调用约定和名称修饰</title><link>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132590.html</link><dc:creator>@Koven.Z</dc:creator><author>@Koven.Z</author><pubDate>Fri, 05 Nov 2010 12:03:00 GMT</pubDate><guid>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132590.html</guid><wfw:comment>http://www.cppblog.com/kovenzgf/comments/132590.html</wfw:comment><comments>http://www.cppblog.com/kovenzgf/archive/2010/11/05/132590.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/kovenzgf/comments/commentRss/132590.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/kovenzgf/services/trackbacks/132590.html</trackback:ping><description><![CDATA[<div id=blogDetailDiv style="FONT-SIZE: 14px">
<p>调用约定（Calling Convention） <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 调用约定用来处理决定函数参数传送时入栈和出栈的顺序（由调用者还是被调用者把参数弹出栈），以及编译器用来识别函数名称的名称修饰约定等问题。在Microsoft VC++ 6.0中定义了下面几种调用约定，我们将结合汇编语言来一一分析它们： </p>
<p>1、__cdecl<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __cdecl是C/C++和MFC程序默认使用的调用约定，也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C调用约定（即用__cdecl关键字说明）按从右至左的顺序压参数入栈，由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的（正因为如此，实现可变参数的函数只能使用该调用约定）。另外，在函数名修饰约定方面也有所不同。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _cdecl是C和C＋＋程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码，所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由于参数按照从右向左顺序压栈，因此最开始的参数在最接近栈顶的位置，因此当采用不定个数参数时，第一个参数在栈中的位置肯定能知道，只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来，就可以使用不定参数，例如对于CRT中的sprintf函数，定义为： </p>
<p><strong>int sprintf(char* buffer,const char* format,...)</strong></p>
<p>由于所有的不定参数都可以通过format确定，因此使用不定个数的参数是没有问题的。</p>
<p>2、__stdcall </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __stdcall调用约定用于调用Win32 API函数。采用__stdcal约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，函数参数个数固定。由于函数体本身知道传进来的参数个数，因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。在32位的VC++5.0中PASCAL调用约定不再被支持（实际上它已被定义为__stdcall。除了__pascal外，__fortran和__syscall也不被支持），取而代之的是__stdcall调用约定。两者实质上是一致的，即函数的参数自右向左通过栈传递，被调用的函数在返回前清理传送参数的内存栈，但不同的是函数名的修饰部分（关于函数名的修饰部分在后面将详细说明）。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _stdcall是Pascal程序的缺省调用方式，通常用于Win32 Api中，函数采用从右到左的压栈方式，自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀，在函数名后加上"@"和参数的字节数。</p>
<p>3、__fastcall </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节（DWORD）的参数分别放在ECX和EDX寄存器，其余的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __fastcall调用约定是"人"如其名，它的主要特点就是快，因为它是通过寄存器来传送参数的（实际上，它用ECX和EDX传送前两个双字（DWORD）或更小的参数，剩下的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的内存栈），在函数名修饰约定方面，它和前两者均不同。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _fastcall方式的函数采用寄存器传递参数，VC将函数编译后会在函数名前面加上"@"前缀，在函数名后加上"@"和参数的字节数。 </p>
<p>4、thiscall</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thiscall调用约定是C++中的非静态类成员函数的默认调用约定。thiscall只能被编译器使用，没有相应的关键字，因此不能被程序员指定。采用thiscall约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，只是另外通过ECX寄存器传送一个额外的参数：this指针。<br>&nbsp;</p>
<p>5、naked属性</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 采用上面所述的四种调用约定的函数在进入函数时，编译器会产生代码来保存ESI、EDI、EBX、EBP寄存器中的值，退出函数时则产生代码恢复这些寄存器的内容。对于定义了naked属性的函数，编译器不会自动产生这样的代码，需要你手工使用内嵌汇编来控制函数实现中的堆栈管理。由于naked属性并不是类型修饰符，故必须和__declspec共同使用。下面的这段代码定义了一个使用了naked属性的函数及其实现：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; naked属性与本节关系不大，具体请参考MSDN。</p>
<p>&nbsp;</p>
<p>6、WINAPI</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 还有一个值得一提的是WINAPI宏，它可以被翻译成适当的调用约定以供函数使用。该宏定义于windef.h之中。下面是在windef.h中的部分内容：</p>
<p>#define CDECL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _cdecl<br>#define WINAPI&nbsp;&nbsp;&nbsp;&nbsp; CDECL<br>#define CALLBACK&nbsp;&nbsp; __stdcall<br>#define WINAPI&nbsp;&nbsp;&nbsp;&nbsp; __stdcall<br>#define APIENTRY&nbsp;&nbsp; WINAPI</p>
<p>由此可见，WINAPI、CALLBACK、APIENTRY等宏的作用。</p>
<p>2)名字修饰约定<br>1、修饰名(Decoration name)<br>&nbsp;&nbsp;&nbsp;&nbsp; "C"或者"C++"函数在内部（编译和链接）通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出"C++"重载函数、构造函数、析构函数，又如在汇编代码里调用"C""或"C++"函数等。<br>&nbsp;&nbsp;&nbsp;&nbsp; 修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。<br>2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同，下面分别说明。<br>a、C编译时函数名修饰约定规则：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个"@"符号和其参数的字节数，格式为_functionname@number。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __fastcall调用约定在输出函数名前加上一个"@"符号，后面也是一个"@"符号和其参数的字节数，格式为&nbsp;&nbsp;&nbsp; @functionname@number。<br>&nbsp;&nbsp;&nbsp;&nbsp; 它们均不改变输出函数名中的字符大小写，这和PASCAL调用约定不同，PASCAL约定输出的函数名无任何修饰且全部大写。<br>b、C++编译时函数名修饰约定规则：<br>__stdcall调用约定：<br>1、以"?"标识函数名的开始，后跟函数名；<br>2、函数名后面以"@@YG"标识参数表的开始，后跟参数表；<br>3、参数表以代号表示：<br>X--void ，<br>D--char，<br>E--unsigned char，<br>F--short，<br>H--int，<br>I--unsigned int，<br>J--long，<br>K--unsigned long，<br>M--float，<br>N--double，<br>_N--bool，<br>....<br>PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以"0"代替，一个"0"代表一次重复；<br>4、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； <br>5、参数表后以"@Z"标识整个名字的结束，如果该函数无参数，则以"Z"标识结束。<br>其格式为"?functionname@@YG*****@Z"或"?functionname@@YG*XZ"，例如<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int Test1（char *var1,unsigned long）-----"?Test1@@YGHPADK@Z"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void Test2（）&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -----"?Test2@@YGXXZ"</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __cdecl调用约定：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的"@@YG"变为"@@YA"。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __fastcall调用约定：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的"@@YG"变为"@@YI"。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前，也可以在编译环境的Setting...-&gt;C/C++-&gt;Code Generation项选择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd，即__cdecl。当加在输出函数前的关键字与编译环境中的选择不同时，直接加在输出函数前的关键字有效。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 因此，为了使其它语言编写的模块（如Visual Basic应用程序、Pascal或Fortran的应用程序等）可以 调用C/C++编写的DLL的函数，必须使用正确的调用约定来导出函数，并且不要让编译器对要导出的函数进行任何名称修饰。 </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所谓对齐，对Ｉｎｔｅｌ８０ｘ８６机器来说就是要求每个变量的地址都是ｓｉｚｅｏｆ（ｉｎｔ）的倍数。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定，至于函数名修饰约定，可以通过其它方法模仿。还有一个值得一提的是WINAPI宏，Windows.h支持该宏，它可以将出函数翻译成适当的调用约定，在WIN32中，它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。</p>
<p>函数调用约定导致的常见问题<br>如果定义的约定和使用的约定不一致，则将导致堆栈被破坏，导致严重问题，下面是两种常见的问题： </p>
<p>函数原型声明和函数体定义不一致 <br>DLL导入函数时声明了不同的函数约定 <br>以后者为例，假设我们在dll种声明了一种函数为： </p>
<p>__declspec(dllexport) int func(int a,int b);//注意，这里没有stdcall，使用的是cdecl<br>使用时代码为： </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef int (*WINAPI DLLFUNC)func(int a,int b);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hLib = LoadLibrary(...);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result = func(1,2);//导致错误</p>
<p>由于调用者没有理解WINAPI的含义错误的增加了这个修饰，上述代码必然导致堆栈被破坏，MFC在编译时插入的checkesp函数将告诉你，堆栈被破坏了。</p>
<p><br>相关链接：<br><a href="http://dev.csdn.net/article/29/article/33/article/31/31511.shtm" target=_blank><u>http://dev.csdn.net/article/29/article/33/article/31/31511.shtm</u></a><br><a href="http://www.ddvip.net/program/vc/index1/47.htm" target=_blank><u>http://www.ddvip.net/program/vc/index1/47.htm</u></a><br><a href="http://www.donews.net/n9871009/archive/2004/11/14/169122.aspx" target=_blank><u>http://www.donews.net/n9871009/archive/2004/11/14/169122.aspx</u></a><br><a href="http://blog.vckbase.com/arong/archive/2004/06/09/409.aspx" target=_blank><u>http://blog.vckbase.com/arong/archive/2004/06/09/409.aspx</u></a><br><br>
<hr width="100%" color=#000000>
<br>
<p>&nbsp;</p>
个人感觉要注意thiscall 和_stdcall 的区别。成员函数默认为thiscall，但可强制转换为 _stdcall(WINAPI),同时采取相应的措施，可实现static和成员函数一定程度上的转换。</div>
<img src ="http://www.cppblog.com/kovenzgf/aggbug/132590.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/kovenzgf/" target="_blank">@Koven.Z</a> 2010-11-05 20:03 <a href="http://www.cppblog.com/kovenzgf/archive/2010/11/05/132590.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>