﻿<?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++博客-道。道。道</title><link>http://www.cppblog.com/eday/</link><description>安全特性不等于安全的特性</description><language>zh-cn</language><lastBuildDate>Mon, 13 Apr 2026 09:37:42 GMT</lastBuildDate><pubDate>Mon, 13 Apr 2026 09:37:42 GMT</pubDate><ttl>60</ttl><item><title>利用宏来生成C++函数的注释, 减少写代码时重复输入的烦恼</title><link>http://www.cppblog.com/eday/archive/2007/07/15/28079.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sun, 15 Jul 2007 14:47:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/07/15/28079.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在定义函数时，一直以来都是手工加入函数注释，虽然每个函数的注释内容并不是太多，但总是会有很多重复性的工作，哎。这两天有点时间看看有什么方法了，昨天发现原来 Vistual Studio 2005里有个宏IDE工具可以实现我的目的（嘿嘿见笑，怎么早没发现），研究一下了...，晕死是Basic语言，还好要实现我的功能...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/07/15/28079.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/28079.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-07-15 22:47 <a href="http://www.cppblog.com/eday/archive/2007/07/15/28079.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用VS2005也能制作体积很小的Win32程序(2KB - 3KB)</title><link>http://www.cppblog.com/eday/archive/2007/05/23/24707.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 23 May 2007 11:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/05/23/24707.html</guid><description><![CDATA[<p>话就不多说了，你一定能看懂。这里运行时库一定要指定为"多线程/MT"，否则最后Release版本的程序在其他机器上无法运行．<br>命令行 /MT&nbsp; 或 <br><img height=420 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_msvcrt.JPG" width=660 border=0><br><br>这里指定自定义入口函数名 命令行 /entry:Start&nbsp; 或<img height=420 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_vc8link.JPG" width=659 border=0><br><br><strong>//Win32控制台程序<br></strong>//----------------------------------------------<br>//stdafx.h file<br>//----------------------------------------------<br>#pragma once</p>
<p>#ifndef _WIN32_WINNT&nbsp;<br>#define _WIN32_WINNT 0x0501<br>#endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p>#include &lt;Windows.h&gt;<br></p>
<p>//----------------------------------------------<br>//console.cpp<br>//----------------------------------------------<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br><br>HANDLE&nbsp;hStdIn;<br>HANDLE&nbsp;hStdOut;<br><br>BOOL&nbsp;__stdcall&nbsp;CtrlHandler(DWORD&nbsp;CtrlType)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(CtrlType</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">CTRL_C_EVENT&nbsp;</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000">&nbsp;CtrlType</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">CTRL_BREAK_EVENT)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hStdIn);<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;TRUE;<br>}<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;Start()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;hStdIn&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetStdHandle(STD_INPUT_HANDLE);<br>&nbsp;&nbsp;&nbsp;&nbsp;hStdOut&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetStdHandle(STD_OUTPUT_HANDLE);<br>&nbsp;&nbsp;&nbsp;&nbsp;SetConsoleMode(hStdIn,ENABLE_LINE_INPUT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">ENABLE_ECHO_INPUT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">ENABLE_PROCESSED_INPUT);<br>&nbsp;&nbsp;&nbsp;&nbsp;SetConsoleCtrlHandler(CtrlHandler,TRUE);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hHeap&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;GetProcessHeap();<br>&nbsp;&nbsp;&nbsp;&nbsp;PVOID&nbsp;szBuffer&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;HeapAlloc(hHeap,</span><span style="COLOR: #000000">HEAP_ZERO_MEMORY,</span><span style="COLOR: #000000">1024</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;dwBytesRead,dwBytesWrite;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(TRUE){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">ReadConsole(hStdIn,szBuffer,</span><span style="COLOR: #000000">1024</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytesRead,NULL)&nbsp;</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000">&nbsp;((</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)szBuffer)[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">]&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">q</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WriteConsole(hStdOut,szBuffer,dwBytesRead,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytesWrite,NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;HeapFree(hHeap,HEAP_NO_SERIALIZE,szBuffer);<br>&nbsp;&nbsp;&nbsp;&nbsp;ExitProcess(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br>}</span></div>
<p><br><strong>//Windows程序</strong><br>//----------------------------------------------------<br>//stdafx.h<br>//----------------------------------------------------<br>#ifndef WINVER&nbsp;&nbsp;&nbsp;&nbsp;<br>#define WINVER 0x0501&nbsp;&nbsp;<br>#endif</p>
<p>#ifndef _WIN32_WINNT&nbsp;&nbsp;<br>#define _WIN32_WINNT 0x0501&nbsp;<br>#endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p>#ifndef _WIN32_WINDOWS&nbsp;&nbsp;<br>#define _WIN32_WINDOWS 0x0410 <br>#endif</p>
<p>#ifndef _WIN32_IE&nbsp;&nbsp;&nbsp;<br>#define _WIN32_IE 0x0600&nbsp;<br>#endif</p>
<p>#define WIN32_LEAN_AND_MEAN&nbsp;&nbsp;</p>
<p>#include &lt;Windows.h&gt;<br><br>//----------------------------------------------------<br>// winapp.cpp<br>//----------------------------------------------------<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;__stdcall&nbsp;Start()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;MessageBoxA(NULL,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Hello&nbsp;World!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">?</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,MB_OK);<br>&nbsp;&nbsp;&nbsp;&nbsp;ExitProcess(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br>}</span></div>
<br>// Release版本<br><img height=119 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_depend1.JPG" width=441 border=0><br><br><span style="COLOR: #0000ff">//制作Win32 - DLL文件的方法与前面相同</span><br><br>以上仅使用<span style="COLOR: #ff0000">Windows标准库</span>，最后生成的程序只有 3KB大小，基本上和汇编写的程序大小差不多了．　<br>&nbsp;<br>如果要再小点，我们可以把 只读数据、导入表以及导出表节.rdata与代码节.text合并。(这里提到的节区是以VC编译器为准，不同的编译器对节的命名也许会有些不同)<br>连接器命令行添加 <font face="Courier New">/merge:.rdata=.text 或 </font><br><img height=423 alt="" src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_dmerge.JPG" width=661 border=0><br><br>现在再看看大小 :(&nbsp; 2KB 了。<br>
<img src ="http://www.cppblog.com/eday/aggbug/24707.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-05-23 19:51 <a href="http://www.cppblog.com/eday/archive/2007/05/23/24707.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从BMP得到ICON句柄最简单的方法</title><link>http://www.cppblog.com/eday/archive/2007/05/05/23459.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sat, 05 May 2007 12:37:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/05/05/23459.html</guid><description><![CDATA[<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;HBITMAP&nbsp;newimg&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(HBITMAP)LoadImage(AfxGetInstanceHandle(),_T(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">f:\\1.bmp</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">),IMAGE_BITMAP,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,LR_LOADFROMFILE);<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(newimg)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CBitmap&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pBitmap&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;CBitmap::FromHandle(newimg);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BITMAP&nbsp;bmpData;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(pBitmap</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">GetBitmap(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">bmpData))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ICONINFO&nbsp;iconInfo&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;{&nbsp;</span><span style="COLOR: #0000ff">true</span><span style="COLOR: #000000">,&nbsp;bmpData.bmWidth</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">,&nbsp;bmpData.bmHeight</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">,&nbsp;newimg,&nbsp;newimg&nbsp;&nbsp;};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HICON&nbsp;bIcon&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;CreateIconIndirect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">iconInfo);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(bIcon)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CClientDC&nbsp;dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawIcon(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,bIcon);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::DestroyIcon(bIcon);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br></span></div>
<br>显示ICON仅仅为了演示， 这里主要的目的是得到 HICON 句柄。 
<img src ="http://www.cppblog.com/eday/aggbug/23459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-05-05 20:37 <a href="http://www.cppblog.com/eday/archive/2007/05/05/23459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在CWnd派生窗口中动态创建CheckBox,RadioButton,  去除控件背景(WM_PAINT)</title><link>http://www.cppblog.com/eday/archive/2007/04/19/22263.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 18 Apr 2007 18:04:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/04/19/22263.html</guid><description><![CDATA[在非Dialog窗口中动态创建CheckBox, RadioButton 时， 总有默认的灰色背景， 想了许多方法都未能去除，到最后我只好自己处理WM_PAINT消息了， 郁闷!<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">template</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;BASE_CLASS</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;CTransparentButton&nbsp;:&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;BASE_CLASS<br>{<br></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;LRESULT&nbsp;WindowProc(UINT&nbsp;message,&nbsp;WPARAM&nbsp;wParam,&nbsp;LPARAM&nbsp;lParam)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000">&nbsp;(message)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;WM_PAINT:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPaintDC&nbsp;dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CString&nbsp;iText;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CRect&nbsp;boxRc,&nbsp;textRc;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;wh&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">14</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;boxRc.SetRectEmpty();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetClientRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">textRc);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;textRc.bottom = 20;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;boxRc.top&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;((</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">)textRc.Height()</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">)</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">7</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;textRc.left&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;boxRc.right&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;boxRc.bottom&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;boxRc.top</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">wh;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;textRc.left&nbsp;</span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000">(GetButtonStyle())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;BS_AUTOCHECKBOX:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(GetCheck())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawFrameControl(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">boxRc,DFC_BUTTON,DFCS_CHECKED);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawFrameControl(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">boxRc,DFC_BUTTON,DFCS_BUTTONCHECK);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;BS_AUTORADIOBUTTON:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(GetCheck())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawFrameControl(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">boxRc,DFC_BUTTON,DFCS_BUTTONRADIO);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;boxRc.DeflateRect(</span><span style="COLOR: #000000">6</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CBrush&nbsp;bkBrush(RGB(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.SelectObject(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">bkBrush);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.RoundRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">boxRc,CPoint(boxRc.Width()</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">,boxRc.Height()</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawFrameControl(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">boxRc,DFC_BUTTON,DFCS_BUTTONRADIO);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetWindowText(iText);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.SelectStockObject(DEFAULT_GUI_FONT);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dc.DrawText(iText,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">textRc,DT_VCENTER</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_SINGLELINE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">break</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;BASE_CLASS::WindowProc(message,&nbsp;wParam,&nbsp;lParam);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>};</span></div>
<br>使用方法：<br>protected:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CTransparentButton&lt;CButton&gt;&nbsp;&nbsp;m_checkbox;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CTransparentButton&lt;CButton&gt;&nbsp;&nbsp;m_radiobtn1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CTransparentButton&lt;CButton&gt;&nbsp;&nbsp;m_radiobtn2;<br><br>//-----------------------------------------------------------------<br>CCustomWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (CWnd::OnCreate(lpCreateStruct) == -1)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //创建控件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_checkbox.Create(_T("CheckBox"),WS_CHILD|WS_VISIBLE|<span style="COLOR: #ff00ff">BS_AUTOCHECKBOX</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CRect(0,20,80,40), this, IDC_CHECKBOX1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_radiobtn1.Create(_T("RadioButton1"),WS_CHILD|WS_VISIBLE|<span style="COLOR: #ff00ff">BS_AUTORADIOBUTTON</span> ,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CRect(0,50,80,70), this, IDC_RADIOBTN1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_radiobtn2.Create(_T("RadioButton2"),WS_CHILD|WS_VISIBLE|<span style="COLOR: #ff00ff">BS_AUTORADIOBUTTON</span> ,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CRect(0,80,80,100), this, IDC_RADIOBTN2);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //....................<br>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<img src ="http://www.cppblog.com/eday/aggbug/22263.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-04-19 02:04 <a href="http://www.cppblog.com/eday/archive/2007/04/19/22263.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win32 调用系统命令取得结果</title><link>http://www.cppblog.com/eday/archive/2007/03/29/20811.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 28 Mar 2007 16:16:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/03/29/20811.html</guid><description><![CDATA[<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">----------------------------------------------------------------------------------<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;Use:&gt;&nbsp;<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CString&nbsp;resultContext;<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExecuteCmdEx(&nbsp;(LPTSTR)(LPCTSTR)CString("net&nbsp;help"),&nbsp;resultContext);<br></span><span style="COLOR: #008000">//</span> <span style="COLOR: #008000">----------------------------------------------------------------------------------</span> <span style="COLOR: #008000"><br></span><span style="COLOR: #000000">BOOL&nbsp;ExecuteCmdEx(LPTSTR&nbsp;cmdline,&nbsp;CString</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">&nbsp;outputResult)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;SECURITY_ATTRIBUTES&nbsp;sa;<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.nLength&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(SECURITY_ATTRIBUTES);<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.lpSecurityDescriptor&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;sa.bInheritHandle&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;TRUE;<br>&nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hInput,hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">if</span> <span style="COLOR: #000000">&nbsp;(</span> <span style="COLOR: #000000">!</span> <span style="COLOR: #000000">CreatePipe(</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">hInput,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">hOutput,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">sa,</span> <span style="COLOR: #000000">0</span> <span style="COLOR: #000000">))&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;FALSE;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;STARTUPINFO&nbsp;si;<br>&nbsp;&nbsp;&nbsp;&nbsp;PROCESS_INFORMATION&nbsp;pi;<br>&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(&nbsp;</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">si,&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(si)&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(&nbsp;</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">pi,&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(pi)&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;si.cb&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(si);<br>&nbsp;&nbsp;&nbsp;&nbsp;si.hStdError&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.hStdOutput&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;hOutput;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.wShowWindow&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;SW_HIDE;<br>&nbsp;&nbsp;&nbsp;&nbsp;si.dwFlags&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;STARTF_USESHOWWINDOW&nbsp;</span> <span style="COLOR: #000000">|</span> <span style="COLOR: #000000">&nbsp;STARTF_USESTDHANDLES;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">if</span> <span style="COLOR: #000000">&nbsp;(</span> <span style="COLOR: #000000">!</span> <span style="COLOR: #000000">CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">si,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">pi)){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hInput);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hOutput);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;FALSE;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;WaitForSingleObject(&nbsp;pi.hProcess,&nbsp;INFINITE&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(&nbsp;pi.hProcess&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(&nbsp;pi.hThread&nbsp;);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;rByte&nbsp;</span> <span style="COLOR: #000000">=</span> <span style="COLOR: #000000">&nbsp;</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">&nbsp;outputBuffer[</span> <span style="COLOR: #000000">4096</span> <span style="COLOR: #000000">];<br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">while</span> <span style="COLOR: #000000">(rByte</span> <span style="COLOR: #000000">==</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ZeroMemory(outputBuffer,</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">)</span> <span style="COLOR: #000000">*</span> <span style="COLOR: #000000">4096</span> <span style="COLOR: #000000">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ReadFile(hInput,outputBuffer,</span> <span style="COLOR: #0000ff">sizeof</span> <span style="COLOR: #000000">(</span> <span style="COLOR: #0000ff">char</span> <span style="COLOR: #000000">)</span> <span style="COLOR: #000000">*</span> <span style="COLOR: #000000">4095</span> <span style="COLOR: #000000">,</span> <span style="COLOR: #000000">&amp;</span> <span style="COLOR: #000000">rByte,&nbsp;NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;outputResult&nbsp;</span> <span style="COLOR: #000000">+=</span> <span style="COLOR: #000000">&nbsp;outputBuffer;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hInput);<br>&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hOutput);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;</span> <span style="COLOR: #0000ff">return</span> <span style="COLOR: #000000">&nbsp;TRUE;<br>}</span> </div>
<img src ="http://www.cppblog.com/eday/aggbug/20811.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-03-29 00:16 <a href="http://www.cppblog.com/eday/archive/2007/03/29/20811.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决窗口刷新闪烁</title><link>http://www.cppblog.com/eday/archive/2007/03/19/20106.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sun, 18 Mar 2007 16:14:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/03/19/20106.html</guid><description><![CDATA[
		<p>一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化，所以不可避免在窗口移动或者改变大小的时候出现闪烁。</p>
		<p>先来谈谈闪烁产生的原因</p>
		<p>原因一：<br />如果熟悉显卡原理的话，调用GDI函数向屏幕输出的时候并不是立刻就显示在屏幕<br />上只是写到了显存里，而显卡每隔一段时间把显存的内容输出到屏幕上，这就是刷新周期。</p>
		<p>一般显卡的刷新周期是 1/80秒左右，具体数字可以自己设置的。</p>
		<p>这样问题就来了，一般画图都是先画背景色，然后再把内容画上去，如果这两次操作不在同一个<br />刷新周期内完成，那么给人的视觉感受就是，先看到只有背景色的图像，然后看到画上内容的图像，<br />这样就会感觉闪烁了。</p>
		<p>解决方法：尽量快的输出图像，使输出在一个刷新周期内完成，如果输出内容很多比较慢，那么采用<br />内存缓冲的方法，先把要输出的内容在内存准备好，然后一次输出到显存。要知道一次API调用一般可以<br />在一个刷新周期内完成。</p>
		<p>对于GDI，用创建内存DC的方法就可以了</p>
		<p>原因二：</p>
		<p>复杂的界面有多层窗口组成，当windows在窗口改变大小的时候是先重画父窗口，然后重画子窗口，子父<br />窗口重画的过程一般无法在一个刷新周期内完成，所以会呈现闪烁。</p>
		<p>我们知道父窗口上被子窗口挡住的部分其实没必要重画的</p>
		<p>解决方法：给窗口加个风格 WS_CLIPCHILDREN ,这样父窗口上被子窗口挡住的部分就不会重画了。</p>
		<p>如果同级窗口之间有重叠，那么需要再加上 WS_CLIPSIBLINGS 风格</p>
		<p>原因三：</p>
		<p>有时候需要在窗口上使用一些控件，比如IE，当你的窗口改变大小的时候IE会闪烁，即使你有了WS_CLIPCHILDREN<br />也没用。原因在于窗口的类风格有CS_HREDRAW 或者 CS_VREDRAW，这两个风格表示窗口在宽度或者高度变化的时候<br />重画，但是这样就会引起IE闪烁</p>
		<p>解决方法：注册窗口类的时候不要使用这两个风格，如果窗口需要在改变大小的时候重画，那么可以在WM_SIZE的时候<br />调用RedrawWindow。</p>
		<p>原因四：</p>
		<p>界面上窗口很多，而且改变大小时很多窗口都要移动和改变大小，如果使用MoveWindow或者SetWindowPos两个API来<br />改变窗口的大小和位置，由于他们是等待窗口重画完成后才返回，所以过程很慢，这样视觉效果就可能会闪烁。</p>
		<p>解决方法：</p>
		<p>使用以下API来处理窗口移动，BeginDeferWindowPos, DeferWindowPos，EndDeferWindowPos<br />先调用 BeginDeferWindowPos 设定需要移动的窗口的个数<br />使用DeferWindowPos，来移动窗口，这个API并不真的造成窗口移动<br />EndDeferWindowPos 一次性完成所有窗口的大小和位置的改变。</p>
		<p>有个地方要特别注意，要仔细计算清楚要移动多少个窗口，BeginDeferWindowPos设定<br />的个数一定要和实际的个数一致，否则在Win9x下，如果实际移动的窗口数多于调用BeginDeferWindowPos<br />时设定的个数，可能会造成系统崩溃。在Windows NT系列下不会有这样的问题。</p>
		<p>如果你在属性里设置了 拖动窗口显示窗口内容的话，屏幕看起来会闪许多。你可以通过api SystemParameters（） ，把它去掉在你的应用程序里。这样在用户看来会好一点。这只是我个人建议。</p>
		<p>----------------------------<br /><br />        1、将Invalidate()替换为InvalidateRect()<br /><br />　　Invalidate()会导致整个窗口的图象重画，需要的时间比较长，而InvalidateRect()仅仅重画Rect区域内的内容，所以所需时间会少一些。虫虫以前很懒，经常为一小块区域的重画就调用Invalidate()，不愿意自己去计算需要重画的Rect，但是事实是，如果你确实需要改善闪烁的情况，计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。 <br /><br />　　2、禁止系统搽除你的窗口<br /><br />　　系统在需要重画窗口的时候会帮你用指定的背景色来搽除窗口。可是，也许需要重画的区域也许非常小。或者，在你重画这些东西之间还要经过大量的计算才能开始。这个时候你可以禁止系统搽掉原来的图象。直到你已经计算好了所有的数据，自己把那些需要搽掉的部分用背景色覆盖掉（如：dc.FillRect(rect,&amp;brush);rect是需要搽除的区域，brush是带背景色的刷子），再画上新的图形。要禁止系统搽除你的窗口，可以重载OnEraseBkgnd()函数，让其直接返回pUE就可以了。如 <br /><br /></p>
		<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1">
				<tbody>
						<tr>
								<td>BOOL CMyWin::OnEraseBkgnd(CDC* pDC) <br />{ <br />　return pUE; <br />　//return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。 <br />} </td>
						</tr>
				</tbody>
		</table>
		<br />　　3、有效的进行搽除<br /><br />　　搽除背景的时候，不要该搽不该搽的地方都搽。比如，你在一个窗口上放了一个很大的Edit框，几乎占了整个窗口，那么你频繁的搽除整个窗口背景将导致Edit不停重画形成剧烈的闪烁。事实上你可以CRgn创建一个需要搽除的区域，只搽除这一部分。如 <br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>GetClientRect(rectClient); <br />rgn1.CreateRectRgnIndirect(rectClient); <br />rgn2.CreateRectRgnIndirect(m_rectEdit); <br />if(rgn1.CombineRgn(&amp;rgn1,&amp;rgn2,RGN_XOR) == ERROR)//处理后的rgn1只包括了Edit框之外的客户区域，这样，Edit将不会被我的背景覆盖而导致重画。 <br />{ <br />　ASSERT(FALSE); <br />　return ; <br />} <br />brush.CreateSolidBrush(m_clrBackgnd); <br />pDC-&gt;FillRgn(&amp;rgn1,&amp;brush); <br />brush.DeleteObject(); </td></tr></tbody></table><br />　　注意：在使用这个方法的时候要同时使用方法二。别忘了，到时候又说虫虫的办法不灵。 <br /><br />　　4、使用MemoryDC先在内存里把图画好，再复制到屏幕上<br /><br />　　这对于一次画图过程很长的情况比较管用。毕竟内存操作比较快，而且复制到屏幕又是一次性的，至少不会出现可以明显看出一个东东从左画到右的情况。 <br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>void CMyWin::OnPaint() <br />{ <br />　CPaintDC dc1(this); // device context for painting <br />　dcMemory.CreateCompatibleDC(&amp;dc1); <br />　CBitmap bmp;//这里的Bitmap是必须的，否则当心弄出一个大黑块哦。 <br />　bmp.CreateCompatibleBitmap(&amp;dc1,rectClient.Width(),rectClient.Height()); <br />　dcMemory.SelectObject(&amp;bmp); <br /><br />　//接下来你想怎么画就怎么画吧。 <br />　//dcMemory.FillRect(rectClient,&amp;brush); <br /><br />　dc1.BitBlt(0,0,rectClient.Width(),rectClient.Height(),&amp;dcMemory,0,0,SRCCOPY); <br />　dcMemory.DeleteDC(); <br />　// Do not call CWnd::OnPaint() for painting messages <br />} </td></tr></tbody></table><br />　<b>争议</b><br /><br />　　上述方法确实有效，但在有很多控件的情况下，计算一个窗口中需要擦除并重绘的“空白区域”是一件很麻烦的事情。为了方便这种方法的实际应用，我写了一组宏来完成”计算空白区域“的功能：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td><p>/************************************************************************/<br />/* MFC版<br />/* 宏功能: 界面刷新时仅刷新指定控件以外的空白区域;可有效避免窗口闪烁<br />/* 使用于: WM_ERASEBKGND 消息处理函数/************************************************************************/<br />#define ERASE_BKGND_BEGIN \<br />CRect bgRect;\<br />GetClientRect(&amp;bgRect);\<br />CRgn bgRgn;\<br />bgRgn.CreateRectRgnIndirect(bgRect);<br />//#define ERASE_BKGND_BEGIN <br />// Marco parameter 'IDC' specifies the identifier of the control <br />#define ADD_NOERASE_CONTROL(IDC)\<br />{\<br />　CRect controlRect;\<br />　GetDlgItem(IDC)-&gt;GetWindowRect(&amp;controlRect);\<br />　CRgn controlRgn;\<br />　controlRgn.CreateRectRgnIndirect(controlRect);\<br />　if(bgRgn.CombineRgn(&amp;bgRgn, &amp;controlRgn, RGN_XOR)==ERROR)\<br />　　return false;\<br />}<br /><br />// Marco parameter 'noEraseRect' specifies a screen coordinates based RECT, <br />// which needn't erase.<br />#define ADD_NOERASE_RECT(noEraseRect)\<br />{\<br />　CRgn noEraseRgn;\<br />　noEraseRgn.CreateRectRgnIndirect(noEraseRect);\<br />　if(bgRgn.CombineRgn(&amp;bgRgn, &amp;noEraseRgn, RGN_XOR)==ERROR)\<br />　　return false;\<br />}<br /><br />// Marco parameter 'pDC' is a kind of (CDC *) type.<br />// Marco parameter 'clBrushColor' specifies the color to brush the area.<br />#define ERASE_BKGND_END(pDC, clBrushColor)\<br />CBrush brush;\<br />brush.CreateSolidBrush(clBrushColor);\<br />CPoint saveOrg = (pDC)-&gt;GetWindowOrg();\<br />(pDC)-&gt;SetWindowOrg(bgRect.TopLeft());\<br />(pDC)-&gt;FillRgn(&amp;bgRgn, &amp;brush);\<br />(pDC)-&gt;SetWindowOrg(saveOrg);\<br />brush.DeleteObject();\<br />//#define ERASE_BKGND_END<br />/*************************************************/<br /><br /><br />/************************************************************************/<br />/* WTL版<br />/* 宏功能: 界面刷新时仅刷新指定控件以外的空白区域;可有效避免窗口闪烁<br />/* 使用于: WM_ERASEBKGND 消息处理函数<br />/************************************************************************/<br />#define ERASE_BKGND_BEGIN \<br /> CRect bgRect;\<br /> GetClientRect(&amp;bgRect);\<br /> CRgn bgRgn;\<br /> bgRgn.CreateRectRgnIndirect(bgRect);<br />//#define ERASE_BKGND_BEGIN <br />// Marco parameter 'IDC' specifies the identifier of the control <br />#define ADD_NOERASE_CONTROL(IDC)\<br />{\<br /> CRect controlRect;\<br /> GetDlgItem(IDC)-&gt;GetWindowRect(&amp;controlRect);\<br /> CRgn controlRgn;\<br /> controlRgn.CreateRectRgnIndirect(controlRect);\<br /> if(bgRgn.CombineRgn(&amp;bgRgn, &amp;controlRgn, RGN_XOR)==ERROR)\<br />  return false;\<br />}</p><p>// Marco parameter 'noEraseRect' specifies a screen coordinates based RECT, <br />// which needn't erase.<br />#define ADD_NOERASE_RECT(noEraseRect)\<br />{\<br /> CRgn noEraseRgn;\<br /> noEraseRgn.CreateRectRgnIndirect(noEraseRect);\<br /> if(bgRgn.CombineRgn(bgRgn.m_hRgn, noEraseRgn.m_hRgn, RGN_XOR)==ERROR)\<br />  return false;\<br />}</p><p>// Marco parameter 'pDC' is a kind of (CDC *) type.<br />// Marco parameter 'clBrushColor' specifies the color to brush the area.<br />#define ERASE_BKGND_END(pDC, clBrushColor)\<br /> CBrush brush;\<br /> brush.CreateSolidBrush(clBrushColor);\<br /> CPoint saveOrg;\<br /> (pDC)-&gt;GetWindowOrg(&amp;saveOrg);\<br /> (pDC)-&gt;SetWindowOrg(bgRect.TopLeft());\<br /> (pDC)-&gt;FillRgn(bgRgn.m_hRgn, brush.m_hBrush);\<br /> (pDC)-&gt;SetWindowOrg(saveOrg);\<br /> brush.DeleteObject();\<br />//#define ERASE_BKGND_END<br />/*************************************************/<br /></p></td></tr></tbody></table><br />　　说明：<br /><br />　　1)宏 ERASE_BKGND_BEGIN 和 ERASE_BKGND_END(pDC, clBrushColor) 搭配使用。<br /><br />　　2)宏 ADD_NOERASE_CONTROL(IDC) 和 ADD_NOERASE_RECT(noEraseRect) 根据需要放在上面两个宏的中间，用来添加不需要重绘背景的区域(正是这些区域导致了闪烁)，使用次数不限。其中参数noEraseRect是一个屏幕坐标系的RECT类型或CRect类型。<br /><br />　　使用举例1：<br /><br />　　在当前窗体的类中重写WM_ERASEBKGND消息处理函数如下：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>BOOL CMyWnd::OnEraseBkgnd(CDC* pDC) <br />{<br />　ERASE_BKGND_BEGIN;<br />　ADD_NOERASE_RGN(IDC_BUTTON2);<br />　ADD_NOERASE_RGN(IDC_BUTTON1);<br />　ADD_NOERASE_RGN(IDC_LIST_STAT);<br />　ERASE_BKGND_END(pDC, GetSysColor(COLOR_3DFACE));<br />　return false;<br />}</td></tr></tbody></table><br />　　上面的IDC_BUTTON2，IDC_BUTTON1，IDC_LIST_STAT即窗体上的控件。<br /><br />　　你可以指定其他已存在的控件。<br /><br />　　这样，窗口在擦除背景时，将只对上述控件以后的”空白区域“使用系统色重绘，有效避免了闪烁。<br /><br />　　备注：<br /><br />　　重载WM_ERASEBKGND消息处理函数OnEraseBkgnd的方法，选择View-&gt;ClassWizard-&gt;classinfo选项卡:message filter下拉框:<br /><br />　　选择window,然后再选择message maps选项卡,在messages下拉框应该可以找到wm_erasebkgnd.双击添加.<br /><br />　　使用举例2：防止CListCtrl在拉动窗口时闪烁。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e7e9e9" border="1"><tbody><tr><td>/* * No further full-erasing is required, <br />* to prevent screen flashing caused by background erase and view repaint. <br />* Only erase the blank area. <br />*/<br /><br />BOOL CExListCtrl::OnEraseBkgnd(CDC* pDC) {<br />　//compute the holding-data-items area of this list control CRect rect; <br />　CPoint dataRgnTopLeftPoint; <br />　CPoint dataRgnBottomRightPoint; <br />　GetItemPosition(0 , &amp;dataRgnTopLeftPoint); <br />　GetItemPosition(GetItemCount() , &amp;dataRgnBottomRightPoint); <br />　if(!GetHeaderCtrl()-&gt;GetItemRect(GetHeaderCtrl()-&gt;GetItemCount()-1, rect)) return <br />　CListCtrl::OnEraseBkgnd(pDC);<br />　dataRgnBottomRightPoint.x = rect.right;<br />　rect.SetRect(dataRgnTopLeftPoint, (CPoint)(dataRgnBottomRightPoint - CPoint(2,2)));<br />　ClientToScreen(dataRgnRect); <br />　//compute and erase the blank area. Using the Marco. ERASE_BKGND_BEGIN; <br />　ADD_NOERASE_RECT(dataRgnRect);<br />　ERASE_BKGND_END(pDC, GetBkColor());<br />　return false;<br />}</td></tr></tbody></table><br />　　说明：CListCtrl在拉动的时候，会前以背景色重刷背景，再在上面绘制有数据的Items， 而没有数据的区域则保持背景色。因此，如果在BOOL CExListCtrl::OnEraseBkgnd(CDC* pDC) 函数中简单的return false，那么没有数据的区域将显示不正常。 故举例2中先计算出有数据的items的区域，这是不需要以背景重刷的区域。 再使用本文的宏，就可以有效避免CListCtrl在拉动时候的闪烁。<img src ="http://www.cppblog.com/eday/aggbug/20106.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-03-19 00:14 <a href="http://www.cppblog.com/eday/archive/2007/03/19/20106.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>鼠标屏幕取词技术的原理和实现</title><link>http://www.cppblog.com/eday/archive/2007/02/28/19065.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 28 Feb 2007 12:44:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/28/19065.html</guid><description><![CDATA[“鼠标屏幕取词”技术是在电子字典中得到广泛地应用的，如四通利方和金山词霸等软件，这个技术看似简单，其实在WINDOWS系统中实现却是非常复杂的，总的来说有两种实现方式：<br />    第一种：采用截获对部分GDI的API调用来实现,如TextOut,TextOutA等。<br />    第二种：对每个设备上下文(DC)做一分Copy,并跟踪所有修改上下文(DC)的操作。      <br /> <br />   第二种方法更强大,但兼容性不好，而第一种方法使用的截获WindowsAPI的调用，这项技术的强大可能远远超出了您的想象，毫不夸张的说，利用WindowsAPI拦截技术，你可以改造整个操作系统，事实上很多外挂式Windows中文平台就是这么实现的！而这项技术也正是这篇文章的主题。<br /><br />    截WindowsAPI的调用，具体的说来也可以分为两种方法：<br />    第一种方法通过直接改写WinAPI 在内存中的映像，嵌入汇编代码，使之被调用时跳转到指定的地址运行来截获；第二种方法则改写IAT（Import Address Table 输入地址表），重定向WinAPI函数的调用来实现对WinAPI的截获。<br /><br />    第一种方法的实现较为繁琐，而且在Win95、98下面更有难度，这是因为虽然微软说WIN16的API只是为了兼容性才保留下来，程序员应该尽可能地调用32位的API,实际上根本就不是这样！WIN 9X内部的大部分32位API经过变换调用了同名的16位API，也就是说我们需要在拦截的函数中嵌入16位汇编代码！<br /><br />    我们将要介绍的是第二种拦截方法，这种方法在Win95、98和NT下面运行都比较稳定，兼容性较好。由于需要用到关于Windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、PE（Portable Executable）文件格式和IAT（输入地址表）等较底层的知识，所以我们先对涉及到的这些知识大概地做一个介绍，最后会给出拦截部分的关键代码。<br /><br />      先说Windows虚拟内存的管理。Windows9X给每一个进程分配了4GB的地址空间，对于NT来说，这个数字是2GB，系统保留了2GB到 4GB之间的地址空间禁止进程访问，而在Win9X中，2GB到4GB这部分虚拟地址空间实际上是由所有的WIN32进程所共享的，这部分地址空间加载了共享Win32 DLL、内存映射文件和VXD、内存管理器和文件系统码，Win9X中这部分对于每一个进程都是可见的，这也是Win9X操作系统不够健壮的原因。Win9X中为16位操作系统保留了0到4MB的地址空间，而在4MB到2GB之间也就是Win32进程私有的地址空间，由于 每个进程的地址空间都是相对独立的，也就是说，如果程序想截获其它进程中的API调用，就必须打破进程边界墙，向其它的进程中注入截获API调用的代码，这项工作我们交给钩子函数（SetWindowsHookEx）来完成，关于如何创建一个包含系统钩子的动态链接库，《电脑高手杂志》在第？期已经有过专题介绍了，这里就不赘述了。所有系统钩子的函数必须要在动态库里，这样的话，当进程隐式或显式调用一个动态库里的函数时，系统会把这个动态库映射到这个进程的虚拟地址空间里，这使得DLL成为进程的一部分，以这个进程的身份执行，使用这个进程的堆栈，也就是说动态链接库中的代码被钩子函数注入了其它GUI进程的地址空间（非GUI进程，钩子函数就无能为力了），<br />当包含钩子的DLL注入其它进程后，就可以取得映射到这个进程虚拟内存里的各个模块（EXE和DLL）的基地址，<br />如：HMODULE hmodule=GetModuleHandle(“Mypro.exe”);<br /><br />在MFC程序中,我们可以用AfxGetInstanceHandle()函数来得到模块的基地址。EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win32工程时，VC＋＋链接器使用缺省的基地址0x00400000。可以通过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x00400000处，DLL也随之有不同的基地址，通常被映射到不同进程<br />的相同的虚拟地址空间处。<br />系统将EXE和DLL原封不动映射到虚拟内存空间中，它们在内存中的结构与磁盘上的静态文件结构是一样的。即PE (Portable Executable) 文件格式。我们得到了进程模块的基地址以后，就可以根据PE文件的格式穷举这个模块的IMAGE_IMPORT_DESCRIPTOR数组，看看进程空间中是否引入了我们需要截获的函数所在的动态链接库，比如需要截获“TextOutA”，就必须检查“Gdi32.dll”是否被引入了。说到这里，我们有必要介绍一下PE文件的格式，如右图，这是PE文件格式的大致框图，最前面是文件头，我们不必理会，从PE File Optional Header后面开始，就是文件中各个段的说明，说明后面才是真正的段数据，而实际上我们关心的只有一个段，那就是“.idata”段，这个段中包含了所有的引入函数信息，还有IAT（Import Address Table）的RVA（Relative Virtual Address）地址。<br />说到这里，截获WindowsAPI的整个原理就要真相大白了。实际上所有进程对给定的API函数的调用总是通过PE文件的一个地方来转移的，这就是一个该模块(可以是EXE或DLL)的“.idata”段中的IAT输入地址表（Import Address Table）。在那里有所有本模块调用的其它DLL的函数名及地址。对其它DLL的函数调用实际上只是跳转到输入地址表，由输入地址表再跳转到DLL真正的函数入口。 
<p></p><p>具体来说，我们将通过IMAGE_IMPORT_DESCRIPTOR数组来访问“.idata”段中引入的DLL的信息，然后通过IMAGE_THUNK_DATA数组来针对一个被引入的DLL访问该DLL中被引入的每个函数的信息，找到我们需要截获的函数的跳转地址，然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。<br />   讲了这么多原理，现在让我们回到“鼠标屏幕取词”的专题上来。除了API函数的截获，要实现“鼠标屏幕取词”，还需要做一些其它的工作，简单的说来，可以把一个完整的取词过程归纳成以下几个步骤：<br />1． 安装鼠标钩子，通过钩子函数获得鼠标消息。<br />使用到的API函数：SetWindowsHookEx<br />2． 得到鼠标的当前位置，向鼠标下的窗口发重画消息，让它调用系统函数重画窗口。<br />     使用到的API函数：WindowFromPoint，ScreenToClient，InvalidateRect<br />3． 截获对系统函数的调用，取得参数，也就是我们要取的词。<br />对于大多数的Windows应用程序来说，如果要取词，我们需要截获的是“Gdi32.dll”中的“TextOutA”函数。<br />我们先仿照TextOutA函数写一个自己的MyTextOutA函数，如：<br />BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,int cbString)<br />{<br />       // 这里进行输出lpszString的处理<br />           // 然后调用正版的TextOutA函数<br />}<br />把这个函数放在安装了钩子的动态连接库中，然后调用我们最后给出的HookImportFunction函数来截获进程<br />对TextOutA函数的调用，跳转到我们的MyTextOutA函数，完成对输出字符串的捕捉。HookImportFunction的<br />用法：<br /> HOOKFUNCDESC hd;<br /> PROC         pOrigFuns;<br /> hd.szFunc="TextOutA";<br /> hd.pProc=(PROC)MyTextOutA;<br /> HookImportFunction (AfxGetInstanceHandle(),"gdi32.dll",&amp;hd,pOrigFuns);<br /><br />下面给出了HookImportFunction的源代码，相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的<br />很难，Ok,Let’s Go： </p><p></p><p>///////////////////////////////////////////// Begin ///////////////////////////////////////////////////////////////<br />#include &lt;crtdbg.h&gt;</p><p>// 这里定义了一个产生指针的宏<br />#define MakePtr(cast, ptr, AddValue) (cast)((DWORD)(ptr)+(DWORD)(AddValue))</p><p>// 定义了HOOKFUNCDESC结构,我们用这个结构作为参数传给HookImportFunction函数<br />typedef struct tag_HOOKFUNCDESC<br />{<br />  LPCSTR szFunc; // The name of the function to hook.<br />  PROC pProc;    // The procedure to blast in.<br />} HOOKFUNCDESC , * LPHOOKFUNCDESC;</p><p>// 这个函数监测当前系统是否是WindowNT<br />BOOL IsNT();</p><p>// 这个函数得到hModule -- 即我们需要截获的函数所在的DLL模块的引入描述符(import descriptor)<br />PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule);</p><p>// 我们的主函数<br />BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule, <br />                         LPHOOKFUNCDESC paHookFunc, PROC* paOrigFuncs)<br />{<br />/////////////////////// 下面的代码检测参数的有效性 ////////////////////////////<br /> _ASSERT(szImportModule);<br /> _ASSERT(!IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC)));<br />#ifdef _DEBUG<br /> if (paOrigFuncs) _ASSERT(!IsBadWritePtr(paOrigFuncs, sizeof(PROC)));<br /> _ASSERT(paHookFunc.szFunc);<br /> _ASSERT(*paHookFunc.szFunc != '\0');<br />        _ASSERT(!IsBadCodePtr(paHookFunc.pProc));<br />#endif<br /> if ((szImportModule == NULL) || (IsBadReadPtr(paHookFunc, sizeof(HOOKFUNCDESC))))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return FALSE;<br /> }<br />//////////////////////////////////////////////////////////////////////////////</p><p> // 监测当前模块是否是在2GB虚拟内存空间之上<br /> // 这部分的地址内存是属于Win32进程共享的<br /> if (!IsNT() &amp;&amp; ((DWORD)hModule &gt;= 0x80000000))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_HANDLE, SLE_ERROR);<br />  return FALSE;<br /> }<br />     // 清零<br /> if (paOrigFuncs) memset(paOrigFuncs, NULL, sizeof(PROC)); </p><p> // 调用GetNamedImportDescriptor()函数,来得到hModule -- 即我们需要<br /> // 截获的函数所在的DLL模块的引入描述符(import descriptor)<br /> PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);<br /> if (pImportDesc == NULL)<br /> return FALSE; // 若为空,则模块未被当前进程所引入</p><p> //  从DLL模块中得到原始的THUNK信息,因为pImportDesc-&gt;FirstThunk数组中的原始信息已经<br /> //  在应用程序引入该DLL时覆盖上了所有的引入信息,所以我们需要通过取得pImportDesc-&gt;OriginalFirstThunk<br /> //  指针来访问引入函数名等信息<br /> PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, <br />                                               pImportDesc-&gt;OriginalFirstThunk);<br /><br /> </p><p></p><p> //  从pImportDesc-&gt;FirstThunk得到IMAGE_THUNK_DATA数组的指针,由于这里在DLL被引入时已经填充了<br /> //  所有的引入信息,所以真正的截获实际上正是在这里进行的<br /> PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hModule, pImportDesc-&gt;FirstThunk);</p><p> //  穷举IMAGE_THUNK_DATA数组,寻找我们需要截获的函数,这是最关键的部分!<br /> while (pOrigThunk-&gt;u1.Function)<br /> {<br />  // 只寻找那些按函数名而不是序号引入的函数<br />  if (IMAGE_ORDINAL_FLAG != (pOrigThunk-&gt;u1.Ordinal &amp; IMAGE_ORDINAL_FLAG))<br />  {<br />   // 得到引入函数的函数名<br />   PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hModule,<br />               pOrigThunk-&gt;u1.AddressOfData);</p><p>   // 如果函数名以NULL开始,跳过,继续下一个函数   <br />   if ('\0' == pByName-&gt;Name[0])<br />    continue;</p><p>   // bDoHook用来检查是否截获成功<br />   BOOL bDoHook = FALSE;</p><p>   // 检查是否当前函数是我们需要截获的函数<br />   if ((paHookFunc.szFunc[0] == pByName-&gt;Name[0]) &amp;&amp;<br />    (strcmpi(paHookFunc.szFunc, (char*)pByName-&gt;Name) == 0))<br />   {<br />    // 找到了!<br />    if (paHookFunc.pProc)<br />    bDoHook = TRUE;<br />   }<br />   if (bDoHook)<br />   {<br />    // 我们已经找到了所要截获的函数,那么就开始动手吧<br />    // 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取<br />    MEMORY_BASIC_INFORMATION mbi_thunk;<br />    VirtualQuery(pRealThunk, &amp;mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));<br />    _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, <br />                        PAGE_READWRITE, &amp;mbi_thunk.Protect));</p><p>    // 保存我们所要截获的函数的正确跳转地址<br />    if (paOrigFuncs)<br />      paOrigFuncs = (PROC)pRealThunk-&gt;u1.Function;</p><p>    // 将IMAGE_THUNK_DATA数组中的函数跳转地址改写为我们自己的函数地址!<br />    // 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用<br />    pRealThunk-&gt;u1.Function = (PDWORD)paHookFunc.pProc;</p><p>    // 操作完毕!将这一块虚拟内存改回原来的保护状态<br />    DWORD dwOldProtect;<br />    _ASSERT(VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, <br />                        mbi_thunk.Protect, &amp;dwOldProtect));<br />    SetLastError(ERROR_SUCCESS);<br />    return TRUE;<br />   }<br /><br />  }<br />  // 访问IMAGE_THUNK_DATA数组中的下一个元素<br />  pOrigThunk++;<br />  pRealThunk++;<br /> }<br /> return TRUE;<br />} </p><p></p><p>// GetNamedImportDescriptor函数的实现<br />PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportModule)<br />{<br /> // 检测参数<br /> _ASSERT(szImportModule);<br /> _ASSERT(hModule);<br /> if ((szImportModule == NULL) || (hModule == NULL))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 得到Dos文件头<br /> PIMAGE_<a href="http://tech.acnow.net/html/OS/DOS/" target="_blank">DOS</a>_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;</p><p> // 检测是否MZ文件头<br /> if (IsBadReadPtr(pDOSHeader, sizeof(IMAGE_DOS_HEADER)) || <br />  (pDOSHeader-&gt;e_magic != IMAGE_DOS_SIGNATURE))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 取得PE文件头<br /> PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader-&gt;e_lfanew);</p><p> // 检测是否PE映像文件<br /> if (IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) || <br />   (pNTHeader-&gt;Signature != IMAGE_NT_SIGNATURE))<br /> {<br />  _ASSERT(FALSE);<br />  SetLastErrorEx(ERROR_INVALID_PARAMETER, SLE_ERROR);<br />  return NULL;<br /> }</p><p> // 检查PE文件的引入段(即 .idata section)<br /> if (pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)<br />  return NULL;</p><p> // 得到引入段(即 .idata section)的指针<br /> PIMAGE_IMPORT_DESCRIPTOR pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOSHeader,<br />  pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);</p><p> // 穷举PIMAGE_IMPORT_DESCRIPTOR数组寻找我们需要截获的函数所在的模块<br /> while (pImportDesc-&gt;Name)<br /> {<br />  PSTR szCurrMod = MakePtr(PSTR, pDOSHeader, pImportDesc-&gt;Name);<br />  if (stricmp(szCurrMod, szImportModule) == 0)<br />      break; // 找到!中断循环<br />  // 下一个元素<br />  pImportDesc++;<br /> }</p><p> // 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!<br /> if (pImportDesc-&gt;Name == NULL)<br />  return NULL;</p><p> // 返回函数所找到的模块描述符(import descriptor)<br /> return pImportDesc;<br />}</p><p>// IsNT()函数的实现<br />BOOL IsNT()<br />{<br /> OSVERSIONINFO stOSVI;<br /> memset(&amp;stOSVI, NULL, sizeof(OSVERSIONINFO));<br /> stOSVI.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);<br /> BOOL bRet = GetVersionEx(&amp;stOSVI);<br /> _ASSERT(TRUE == bRet);<br /> if (FALSE == bRet) return FALSE;<br /> return (VER_PLATFORM_WIN32_NT == stOSVI.dwPlatformId);<br />}<br />/////////////////////////////////////////////// End //////////////////////////////////////////////////////////////////////</p><p>   不知道在这篇文章问世之前，有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术，也只有尝试过的朋友才能体会到其间的不易，尤其在探索API函数的截获时，手头的几篇资料没有一篇是涉及到关键代码的，重要的地方都是一笔代过，MSDN更是显得苍白而无力，也不知道除了IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA，微软还隐藏了多少秘密，好在硬着头皮还是把它给攻克了，希望这篇文章对大家能有所帮助。<br /><br /></p><img src ="http://www.cppblog.com/eday/aggbug/19065.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-28 20:44 <a href="http://www.cppblog.com/eday/archive/2007/02/28/19065.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>截取系统 API 调用</title><link>http://www.cppblog.com/eday/archive/2007/02/26/18987.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Mon, 26 Feb 2007 05:43:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/26/18987.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在很多情况下，为了测试代码或扩展操作系统的功能，软件开发人员或测试人员必须截取系统函数调用。有一些软件包能够提供该功能，如微软公司的 Detours* 库，或 OK Thinking Software 的 Syringe*。但是从另一个角度而言，开发人员可能希望不需借助第三方软件，自己就能实现该功能。本文描述了函数截取的几种不同方式，并详细介绍了无需使用商业软件包，也不需受 GNU*（通用公共许可...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/02/26/18987.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/18987.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-26 13:43 <a href="http://www.cppblog.com/eday/archive/2007/02/26/18987.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++代码优化</title><link>http://www.cppblog.com/eday/archive/2007/02/17/18844.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sat, 17 Feb 2007 14:05:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/17/18844.html</guid><description><![CDATA[     谈到优化，很多人都会直接想到汇编。难道优化只能在汇编层次吗？当然不是，C++层次一样可以作代码优化，其中有些常常是意想不到的。在C++层次进行优化，比在汇编层次优化具有更好的移植性，应该是优化中的首选做法。 <br />1.确定浮点型变量和表达式是 float 型 <br />    为了让编译器产生更好的代码(比如说产生3DNow! 或SSE指令的代码)，必须确定浮点型变量和表达式是 float 型的。要特别注意的是，以 "；F"； 或 "；f"； 为后缀（比如：3.14f）的浮点常量才是 float 型，否则默认是 double 型。为了避免 float 型参数自动转化为 double，请在函数声明时使用 float。 <br />2.使用32位的数据类型 <br />　　编译器有很多种，但它们都包含的典型的32位类型是：int，signed，signed int，unsigned，unsigned int，long，signed long，long int，signed long int，unsigned long，unsigned long int。尽量使用32位的数据类型，因为它们比16位的数据甚至8位的数据更有效率。 <br />3.明智使用有符号整型变量 <br />　　在很多情况下，你需要考虑整型变量是有符号还是无符号类型的。比如，保存一个人的体重数据时不可能出现负数，所以不需要使用有符号类型。但是，如果是要保存温度数据，就必须使用到有符号的变量。 <br />　　在许多地方，考虑是否使用有符号的变量是必要的。在一些情况下，有符号的运算比较快；但在一些情况下却相反。 <br />　　比如：整型到浮点转化时，使用大于16位的有符号整型比较快。因为x86构架中提供了从有符号整型转化到浮点型的指令，但没有提供从无符号整型转化到浮点的指令。看看编译器产生的汇编代码： <br />　　不好的代码： <br />编译前      编译后 <br />double x；    mov [foo + 4], 0 <br />unsigned int i；   mov eax, i <br />x = i；     mov [foo], eax <br />     flid qword ptr [foo] <br />     fstp qword ptr [x] <br />　　上面的代码比较慢。不仅因为指令数目比较多，而且由于指令不能配对造成的FLID指令被延迟执行。最好用以下代码代替： <br />    推荐的代码： <br />编译前     编译后 <br />double x；    fild dword ptr <br />int i；     fstp qword ptr [x] <br />x = i； <br />　　在整数运算中计算商和余数时，使用无符号类型比较快。以下这段典型的代码是编译器产生的32位整型数除以4的代码： <br />　　不好的代码  <br />编译前      编译后 <br />int i；     mov eax, i <br />i = i / 4；     cdq <br />     and edx, 3 <br />     add eax, edx <br />     sar eax, 2 <br />     mov i, eax <br />    推荐的代码<br />编译前      编译后 <br />unsigned int i；    shr i, 2 <br />i = i / 4； <br />　总结：<br />　无符号类型用于：除法和余数,循环计数,数组下标<br />  有符号类型用于：整型到浮点的转化<br />4.while VS. for <br />　　在编程中，我们常常需要用到无限循环，常用的两种方法是while (1) 和 for (；；)。这两种方法效果完全一样，但那一种更好呢？然我们看看它们编译后的代码： <br />编译前      编译后 <br />while (1)；     mov eax,1 <br />     test eax,eax <br />     je foo+23h <br />     jmp foo+18h <br />编译前      编译后  <br />for (；；)；     jmp foo+23h <br />　　一目了然，for (；；)指令少，不占用寄存器，而且没有判断跳转，比while (1)好。 <br />5.使用数组型代替指针型 <br />　　使用指针会使编译器很难优化它。因为缺乏有效的指针代码优化的方法，编译器总是假设指针可以访问内存的任意地方，包括分配给其他变量的储存空间。所以为了编译器产生优化得更好的代码，要避免在不必要的地方使用指针。一个典型的例子是访问存放在数组中的数据。C++ 允许使用操作符 [] 或指针来访问数组，使用数组型代码会让优化器减少产生不安全代码的可能性。比如，x[0] 和x[2] 不可能是同一个内存地址，但 *p 和 *q 可能。强烈建议使用数组型，因为这样可能会有意料之外的性能提升。 <br />    不好的代码<br />typedef struct<br />{<br />　　float x,y,z,w；<br />} VERTEX；<br />typedef struct 
<p>{<br />　　float m[4][4]；<br />} MATRIX；<br />void XForm(float* res, const float* v, const float* m, int nNumVerts)<br />{<br />　　float dp；<br />　　int i；<br />　　　const VERTEX* vv = (VERTEX *)v；<br />　　　for (i = 0； i &lt;； nNumVerts； i++)<br />　　{<br />　　　　dp = vv-&gt;；x * *m ++；<br />　　　　dp += vv-&gt;；y * *m ++；<br />　　　　dp += vv-&gt;；z * *m ++；<br />　　　　dp += vv-&gt;；w * *m ++；<br />　　　　*res ++ = dp；　　　　　　// 写入转换了的 x<br />　　　　dp = vv-&gt;；x * *m ++；<br />　　　　dp += vv-&gt;；y * *m ++；<br />　　　　dp += vv-&gt;；z * *m ++；<br />　　　　dp += vv-&gt;；w * *m ++；<br />　　　　*res ++ = dp；　　　　　// 写入转换了的 y<br />　　　　dp = vv-&gt;；x * *m ++；<br />　　　　dp += vv-&gt;；y * *m ++；<br />　　　　dp += vv-&gt;；z * *m ++；<br />　　　　dp += vv-&gt;；w * *m ++；<br />　　　　*res ++ = dp；　　　　// 写入转换了的 z<br />　　　　dp = vv-&gt;；x * *m ++；<br />　　　　dp += vv-&gt;；y * *m ++；<br />　　　　dp += vv-&gt;；z * *m ++；<br />　　　　dp += vv-&gt;；w * *m ++；<br />　　　　*res ++ = dp；　　　　// 写入转换了的 w<br />　　　　vv ++；　　　　　　　 // 下一个矢量<br />　　　　m -= 16；<br />　　}<br />}<br />    推荐的代码 <br />typedef struct<br />{<br />　　float x,y,z,w；<br />} VERTEX；<br />typedef struct<br />{<br />　　float m[4][4]；<br />} MATRIX；<br />void XForm (float* res, const float* v, const float* m, int nNumVerts)<br />{<br />　　int i；<br />　　const VERTEX* vv = (VERTEX*)v；<br />　　const MATRIX* mm = (MATRIX*)m；<br />　　VERTEX* rr = (VERTEX*)res；<br />　　for (i = 0； i &lt;； nNumVerts； i++)<br />　　{<br />　　　　rr-&gt;；x = vv-&gt;；x * mm-&gt;；m[0][0] + vv-&gt;；y * mm-&gt;；m[0][1]<br />　　　　　　　　+ vv-&gt;；z * mm-&gt;；m[0][2] + vv-&gt;；w * mm-&gt;；m[0][3]；<br />　　　　rr-&gt;；y = vv-&gt;；x * mm-&gt;；m[1][0] + vv-&gt;；y * mm-&gt;；m[1][1]<br />　　　　　　　　+ vv-&gt;；z * mm-&gt;；m[1][2] + vv-&gt;；w * mm-&gt;；m[1][3]；<br />　　　　rr-&gt;；z = vv-&gt;；x * mm-&gt;；m[2][0] + vv-&gt;；y * mm-&gt;；m[2][1]<br />　　　　　　　　+ vv-&gt;；z * mm-&gt;；m[2][2] + vv-&gt;；w * mm-&gt;；m[2][3]；<br />　　　　rr-&gt;；w = vv-&gt;；x * mm-&gt;；m[3][0] + vv-&gt;；y * mm-&gt;；m[3][1]<br />　　　　　　　　+ vv-&gt;；z * mm-&gt;；m[3][2] + vv-&gt;；w * mm-&gt;；m[3][3]；<br />　　}<br />} <br />　　注意: 源代码的转化是与编译器的代码发生器相结合的。从源代码层次很难控制产生的机器码。依靠编译器和特殊的源代码，有可能指针型代码编译成的机器码比同等条件下的数组型代码运行速度更快。明智的做法是在源代码转化后检查性能是否真正提高了，再选择使用指针型还是数组型。 <br />6.充分分解小的循环 <br />　　要充分利用CPU的指令缓存，就要充分分解小的循环。特别是当循环体本身很小的时候，分解循环可以提高性能。BTW:很多编译器并不能自动分解循环。 <br />不好的代码 推荐的代码 <br />// 3D转化：把矢量 V 和 4x4 矩阵 M 相乘<br />for (i = 0； i &lt;； 4； i ++)<br />{<br />　　r = 0；<br />　　for (j = 0； j &lt;； 4； j ++)<br />　　{<br />　　　　r += M[j]*V[j]；<br />　　}<br />}<br />r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3]；<br />r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3]；<br />r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3]；<br />r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3]； <br />7.避免没有必要的读写依赖 <br />　　当数据保存到内存时存在读写依赖，即数据必须在正确写入后才能再次读取。虽然AMD Athlon等CPU有加速读写依赖延迟的硬件，允许在要保存的数据被写入内存前读取出来，但是，如果避免了读写依赖并把数据保存在内部寄存器中，速度会更快。在一段很长的又互相依赖的代码链中，避免读写依赖显得尤其重要。如果读写依赖发生在操作数组时，许多编译器不能自动优化代码以避免读写依赖。所以推荐程序员手动去消除读写依赖，举例来说，引进一个可以保存在寄存器中的临时变量。这样可以有很大的性能提升。下面一段代码是一个例子： <br />    不好的代码<br />float x[VECLEN], y[VECLEN], z[VECLEN]；<br />...... <br />for (unsigned int k = 1； k &lt;； VECLEN； k ++)<br />{<br />　　x[k] = x[k-1] + y[k]；<br />}<br />for (k = 1； k &lt;； VECLEN； k++)<br />{<br />　　x[k] = z[k] * (y[k] - x[k-1])；<br />}<br />　　 推荐的代码 <br />float x[VECLEN], y[VECLEN], z[VECLEN]；<br />...... <br />float t(x[0])；<br />for (unsigned int k = 1； k &lt;； VECLEN； k ++)<br />{<br />　　t = t + y[k]；<br />　　x[k] = t；<br />}<br />t = x[0]；<br />for (k = 1； k &lt;； VECLEN； k ++)<br />{<br />　　t = z[k] * (y[k] - t)；<br />　　x[k] = t；<br />} <br />8.Switch 的用法 <br />　　Switch 可能转化成多种不同算法的代码。其中最常见的是跳转表和比较链/树。推荐对case的值依照发生的可能性进行排序，把最有可能的放在第一个，当switch用比较链的方式转化时，这样可以提高性能。此外，在case中推荐使用小的连续的整数，因为在这种情况下，所有的编译器都可以把switch 转化成跳转表。 <br />    不好的代码<br />int days_in_month, short_months, normal_months, long_months；<br />...... <br />switch (days_in_month)<br />{<br />　　case 28:<br />　　case 29:<br />　　　　short_months ++；<br />　　　　break；<br />　　case 30:<br />　　　　normal_months ++；<br />　　　　break；<br />　　case 31:<br />　　　　long_months ++；<br />　　　　break；<br />　　default:<br />　　　　cout &lt;；&lt;； "；month has fewer than 28 or more than 31 days"； &lt;；&lt;； endl；<br />　　　　break；<br />}<br />    推荐的代码 <br />int days_in_month, short_months, normal_months, long_months；<br />...... <br />switch (days_in_month)<br />{<br />　　case 31:<br />　　　　long_months ++；<br />　　　　break；<br />　　case 30:<br />　　　　normal_months ++；<br />　　　　break；<br />　　case 28:<br />　　case 29:<br />　　　　short_months ++； <br />　　　　break；<br />　　default:<br />　　　　cout &lt;；&lt;； "；month has fewer than 28 or more than 31 days"； &lt;；&lt;； endl；<br />　　　　break；<br />} <br />9.所有函数都应该有原型定义 <br />　　一般来说，所有函数都应该有原型定义。原型定义可以传达给编译器更多的可能用于优化的信息。 <br />　　尽可能使用常量(const)。C++ 标准规定，如果一个const声明的对象的地址不被获取，允许编译器不对它分配储存空间。这样可以使代码更有效率，而且可以生成更好的代码。 <br />10.提升循环的性能<br />　　要提升循环的性能，减少多余的常量计算非常有用（比如，不随循环变化的计算）。 <br />　　不好的代码(在for()中包含不变的if()) 推荐的代码 <br />for( i ... )<br />{<br />　　if( CONSTANT0 )<br />　　{<br />　　　　DoWork0( i )； // 假设这里不改变CONSTANT0的值<br />　　}<br />　　else<br />　　{<br />　　　　DoWork1( i )； // 假设这里不改变CONSTANT0的值<br />　　}<br />}<br />if( CONSTANT0 )<br />{<br />　　for( i ... )<br />　　{<br />　　　　DoWork0( i )；<br />　　}<br />}<br />else<br />{<br />　　for( i ... )<br />　　{<br />　　　　DoWork1( i )；<br />　　}<br />} <br />　　如果已经知道if()的值，这样可以避免重复计算。虽然不好的代码中的分支可以简单地预测，但是由于推荐的代码在进入循环前分支已经确定，就可以减少对分支预测的依赖。 　　把本地函数声明为静态的(static) <br />　　如果一个函数在实现它的文件外未被使用的话，把它声明为静态的(static)以强制使用内部连接。否则，默认的情况下会把函数定义为外部连接。这样可能会影响某些编译器的优化——比如，自动内联。 <br />11.考虑动态内存分配 <br />　　动态内存分配（C++中的"；new"；）可能总是为长的基本类型（四字对齐）返回一个已经对齐的指针。但是如果不能保证对齐，使用以下代码来实现四字对齐。这段代码假设指针可以映射到 long 型。 <br />　　例子 <br />　　double* p = (double*)new BYTE[sizeof(double) * number_of_doubles+7L]；<br />    double* np = (double*)((long(p) + 7L) &amp;； –8L)； <br />　　现在，你可以使用 np 代替 p 来访问数据。注意：释放储存空间时仍然应该用delete p。 <br />12.使用显式的并行代码 <br />　　尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链。因为浮点操作有很长的潜伏期，所以不管它被映射成 x87 或 3DNow! 指令，这都很重要。很多高级语言，包括C++，并不对产生的浮点表达式重新排序，因为那是一个相当复杂的过程。需要注意的是，重排序的代码和原来的代码在代数上一致并不等价于计算结果一致，因为浮点操作缺乏精确度。在一些情况下，这些优化可能导致意料之外的结果。幸运的是，在大部分情况下，最后结果可能只有最不重要的位（即最低位）是错误的。 <br />　　不好的代码<br />double a[100], sum；<br />int i；<br />sum = 0.0f；<br />for (i=0； i&lt;；100； i++)<br />　　sum += a； <br />    推荐的代码 <br />double a[100], sum1, sum2, sum3, sum4, sum；<br />int i；<br />sum1 = sum2 = sum3 = sum4 = 0.0；<br />for (i = 0； i &lt;； 100； i += 4)<br />{<br />　　sum1 += a；<br />　　sum2 += a[i+1]；<br />　　sum3 += a[i+2]；<br />　　sum4 += a[i+3]；<br />}<br />sum = (sum4+sum3)+(sum1+sum2)； <br />　　要注意的是：使用4 路分解是因为这样使用了4阶段流水线浮点加法，浮点加法的每一个阶段占用一个时钟周期，保证了最大的资源利用率。 <br />13.提出公共子表达式 <br />　　在某些情况下，C++编译器不能从浮点表达式中提出公共的子表达式，因为这意味着相当于对表达式重新排序。需要特别指出的是，编译器在提取公共子表达式前不能按照代数的等价关系重新安排表达式。这时，程序员要手动地提出公共的子表达式（在VC.net里有一项“全局优化”选项可以完成此工作，但效果就不得而知了）。 <br />推荐的代码 <br />float a, b, c, d, e, f；<br />...<br />e = b * c / d；<br />f = b / d * a；<br />float a, b, c, d, e, f；<br />...<br />const float t(b / d)；<br />e = c * t；<br />f = a * t； <br />推荐的代码 <br />float a, b, c, e, f；<br />...<br />e = a / c；<br />f = b / c；<br />float a, b, c, e, f；<br />...<br />const float t(1.0f / c)；<br />e = a * t；<br />f = b * t； <br />14.结构体成员的布局 <br />　　很多编译器有“使结构体字，双字或四字对齐”的选项。但是，还是需要改善结构体成员的对齐，有些编译器可能分配给结构体成员空间的顺序与他们声明的不同。但是，有些编译器并不提供这些功能，或者效果不好。所以，要在付出最少代价的情况下实现最好的结构体和结构体成员对齐，建议采取这些方法： <br />　　A按类型长度排序 <br />　　把结构体的成员按照它们的类型长度排序，声明成员时把长的类型放在短的前面。 <br />　　把结构体填充成最长类型长度的整倍数 <br />　　把结构体填充成最长类型长度的整倍数。照这样，如果结构体的第一个成员对齐了，所有整个结构体自然也就对齐了。下面的例子演示了如何对结构体成员进行重新排序： <br />　　不好的代码，普通顺序 推荐的代码，新的顺序并手动填充了几个字节<br />struct<br />{<br />　　char a[5]；<br />　　long k；<br />　　double x；<br />} baz；<br />struct<br />{<br />　　double x；<br />　　long k；<br />　　char a[5]；<br />char pad[7]；<br />} baz； </p><p>　　这个规则同样适用于类的成员的布局。 <br />　　B按数据类型的长度排序本地变量 <br />　　当编译器分配给本地变量空间时，它们的顺序和它们在源代码中声明的顺序一样，和上一条规则一样，应该把长的变量放在短的变量前面。如果第一个变量对齐了，其它变量就会连续的存放，而且不用填充字节自然就会对齐。有些编译器在分配变量时不会自动改变变量顺序，有些编译器不能产生4字节对齐的栈，所以4字节可能不对齐。下面这个例子演示了本地变量声明的重新排序： <br />　　不好的代码，普通顺序 推荐的代码，改进的顺序 <br />short ga, gu, gi；<br />long foo, bar；<br />double x, y, z[3]；<br />char a, b；<br />float baz；<br />double z[3]；<br />double x, y；<br />long foo, bar；<br />float baz；<br />short ga, gu, gi； <br />14.避免不必要的整数除法 <br />　　整数除法是整数运算中最慢的，所以应该尽可能避免。一种可能减少整数除法的地方是连除，这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出，所以只能在一定范围的除法中使用。 <br />　　不好的代码 推荐的代码 <br />int i, j, k, m；<br />m = i / j / k；<br />int i, j, k, m；<br />m = i / (j * k)； <br />15.把频繁使用的指针型参数拷贝到本地变量 <br />　　避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是否存在冲突，所以指针型参数往往不能被编译器优化。这样是数据不能被存放在寄存器中，而且明显地占用了内存带宽。注意，很多编译器有“假设不冲突”优化开关（在VC里必须手动添加编译器命令行/Oa或/Ow），这允许编译器假设两个不同的指针总是有不同的内容，这样就不用把指针型参数保存到本地变量。否则，请在函数一开始把指针指向的数据保存到本地变量。如果需要的话，在函数结束前拷贝回去。 　　<br />    不好的代码 <br />// 假设 q != r<br />void isqrt(unsigned long a, unsigned long* q, unsigned long* r)<br />{<br />　　*q = a；<br />　　if (a &gt;； 0)<br />　　{<br />　　　　while (*q &gt;； (*r = a / *q))<br />　　　　{<br />　　　　　　*q = (*q + *r) &gt;；&gt;； 1；<br />　　　　}<br />　　}<br />　　*r = a - *q * *q；<br />}<br />    推荐的代码<br />// 假设 q != r<br />void isqrt(unsigned long a, unsigned long* q, unsigned long* r)<br />{<br />　　unsigned long qq, rr；<br />　　qq = a；<br />　　if (a &gt;； 0)<br />　　{<br />　　　　while (qq &gt;； (rr = a / qq))<br />　　　　{<br />　　　　　　qq = (qq + rr) &gt;；&gt;； 1；<br />　　　　}<br />　　}<br />　　rr = a - qq * qq；<br />　　*q = qq；<br />　　*r = rr；<br />} <br />16.赋值与初始化<br />先看看以下代码： <br />class CInt<br />{<br />　　int m_i； <br />public:<br />　　CInt(int a = 0):m_i(a) { cout &lt;；&lt;； "；CInt"； &lt;；&lt;； endl； }<br />　　~CInt() { cout &lt;；&lt;； "；~CInt"； &lt;；&lt;； endl； } <br />　　CInt operator + (const CInt&amp;； a) { return CInt(m_i + a.GetInt())； } <br />　　void SetInt(const int i)　　{ m_i = i； }<br />　　int GetInt() const　　　　　　{ return m_i； }<br />}；<br />    不好的代码 <br />void main()<br />{<br />　　CInt a, b, c；<br />　　a.SetInt(1)；<br />　　b.SetInt(2)；<br />　　c = a + b；<br />}<br />    推荐的代码<br />void main()<br />{<br />　　CInt a(1), b(2)；<br />　　CInt c(a + b)；<br />} <br />　　这两段代码所作的事都一样，但那一个更好呢？看看输出结果就会发现，不好的代码输出了四个"；CInt"；和四个"；~CInt"；，而推荐的代码只输出三个。也就是说，第二个例子比第一个例子少生成一次临时对象。Why? 请注意，第一个中的c用的是先声明再赋值的方法，第二个用的是初始化的方法，它们有本质的区别。第一个例子的"；c = a + b"；先生成一个临时对象用来保存a + b的值，再把该临时对象用位拷贝的方法给c赋值，然后临时对象被销毁。这个临时对象就是那个多出来的对象。第二个例子直接用拷贝构造函数的方法对c初始化，不产生临时对象。所以，尽量在需要使用一个对象时才声明，并用初始化的方法赋初值。 <br />17.尽量使用成员初始化列表 <br />　　在初始化类的成员时，尽量使用成员初始化列表而不是传统的赋值方式。 <br />　　不好的代码 <br />class CMyClass<br />{<br />　　string strName； <br />public:<br />　　CMyClass(const string&amp;； str)；<br />}； <br />CMyClass::CMyClass(const string&amp;； str)<br />{<br />　　strName = str；<br />}<br />    推荐的代码<br />class CMyClass<br />{<br />　　string strName；<br />　　int i；<br />public:<br />　　CMyClass(const string&amp;； str)；<br />}； <br />CMyClass::CMyClass(const string&amp;；str)<br />   :strName(str)<br />{</p><p>} <br />　　不好的例子用的是赋值的方式。这样，strName会先被建立（调用了string的默认构造函数），再由参数str赋值。而推荐的例子用的是成员初始化列表，strName直接构造为str，少调用一次默认构造函数，还少了一些安全隐患。</p><img src ="http://www.cppblog.com/eday/aggbug/18844.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-17 22:05 <a href="http://www.cppblog.com/eday/archive/2007/02/17/18844.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C代码优化</title><link>http://www.cppblog.com/eday/archive/2007/02/17/18843.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sat, 17 Feb 2007 14:04:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/17/18843.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: C代码优化方案																																																																																																1、选择合适的算法和数据结构																																										...&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2007/02/17/18843.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/18843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-17 22:04 <a href="http://www.cppblog.com/eday/archive/2007/02/17/18843.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件加密技术和注册机制</title><link>http://www.cppblog.com/eday/archive/2007/02/11/18667.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Sun, 11 Feb 2007 13:41:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/11/18667.html</guid><description><![CDATA[
		<p>　　本文是一篇软件加密技术的基础性文章，简要介绍了软件加密的一些基本常识和一些加密产品，适用于国内软件开发商或者个人共享软件开发者阅读参考。</p>
		<p>　　<strong>1、加密技术概述</strong></p>
		<p>　　一个密码系统的安全性只在于密钥的保密性，而不在算法的保密性。</p>
		<p>　　对纯数据的加密的确是这样。对于你不愿意让他看到这些数据（数据的明文）的人，用可靠的加密算法，只要破解者不知道被加密数据的密码，他就不可解读这些数据。</p>
		<p>　　但是，软件的加密不同于数据的加密，它只能是“隐藏”。不管你愿意不愿意让他（合法用户，或 Cracker）看见这些数据（软件的明文），软件最终总要在机器上运行，对机器，它就必须是明文。既然机器可以“看见”这些明文，那么 Cracker，通过一些技术，也可以看到这些明文。</p>
		<p>　　于是，从理论上，任何软件加密技术都可以破解。只是破解的难度不同而已。有的要让最高明的 Cracker 忙上几个月，有的可能不费吹灰之力，就被破解了。</p>
		<p>　　所以，反盗版的任务（技术上的反盗版，而非行政上的反盗版）就是增加 Cracker 的破解难度。让他们花费在破解软件上的成本，比他破解这个软件的获利还要高。这样 Cracker 的破解变得毫无意义——谁会花比正版软件更多的钱去买盗版软件？</p>
		<p>　　<strong>2、密码学简介</strong></p>
		<p>　　<strong>2.1   概念</strong></p>
		<p>　　<strong>（1） 发送者和接收者</strong></p>
		<p>　　假设发送者想发送消息给接收者，且想安全地发送信息：她想确信偷听者不能阅读发送的消息。</p>
		<p>　　<strong>（2） 消息和加密</strong></p>
		<p>　　消息被称为明文。用某种方法伪装消息以隐藏它的内容的过程称为加密，加了密的消息称为密文，而把密文转变为明文的过程称为解密。</p>
		<p>　　明文用M（消息）或P（明文）表示，它可能是比特流（文本文件、位图、数字化的语音流或数字化的视频图像）。至于涉及到计算机，P是简单的二进制数据。明文可被传送或存储，无论在哪种情况，M指待加密的消息。</p>
		<p>　　密文用C表示，它也是二进制数据，有时和M一样大，有时稍大（通过压缩和加密的结合，C有可能比P小些。然而，单单加密通常达不到这一点）。加密函数E作用于M得到密文C，用数学表示为：</p>
		<p>　　E（M）=C.</p>
		<p>　　相反地，解密函数D作用于C产生M</p>
		<p>　　D（C）=M.</p>
		<p>　　先加密后再解密消息，原始的明文将恢复出来，下面的等式必须成立：</p>
		<p>　　D（E（M））=M</p>
		<p>　　<strong>（3） 鉴别、完整性和抗抵赖</strong></p>
		<p>　　除了提供机密性外，密码学通常有其它的作用：.</p>
		<p>　　（a） 鉴别</p>
		<p>　　消息的接收者应该能够确认消息的来源；入侵者不可能伪装成他人。</p>
		<p>　　（b） 完整性检验</p>
		<p>　　消息的接收者应该能够验证在传送过程中消息没有被修改；入侵者不可能用假消息代替合法消息。</p>
		<p>　　（c） 抗抵赖</p>
		<p>　　发送者事后不可能虚假地否认他发送的消息。</p>
		<p>　　<strong>（4） 算法和密钥</strong></p>
		<p>　　密码算法也叫密码，是用于加密和解密的数学函数。（通常情况下，有两个相关的函数：一个用作加密，另一个用作解密）</p>
		<p>　　如果算法的保密性是基于保持算法的秘密，这种算法称为受限制的算法。受限制的算法具有历史意义，但按现在的标准，它们的保密性已远远不够。大的或经常变换的用户组织不能使用它们，因为每有一个用户离开这个组织，其它的用户就必须改换另外不同的算法。如果有人无意暴露了这个秘密，所有人都必须改变他们的算法。</p>
		<p>　　更糟的是，受限制的密码算法不可能进行质量控制或标准化。每个用户组织必须有他们自己的唯一算法。这样的组织不可能采用流行的硬件或软件产品。但窃听者却可以买到这些流行产品并学习算法，于是用户不得不自己编写算法并予以实现，如果这个组织中没有好的密码学家，那么他们就无法知道他们是否拥有安全的算法。</p>
		<p>　　尽管有这些主要缺陷，受限制的算法对低密级的应用来说还是很流行的，用户或者没有认识到或者不在乎他们系统中内在的问题。</p>
		<p>　　现代密码学用密钥解决了这个问题，密钥用K表示。K可以是很多数值里的任意值。密钥K的可能值的范围叫做密钥空间。加密和解密运算都使用这个密钥（即运算都依赖于密钥，并用K作为下标表示），这样，加/解密函数现在变成：</p>
		<p>　　EK（M）=C</p>
		<p>　　DK（C）=M.</p>
		<p>　　DK（EK（M））=M.</p>
		<p>　　有些算法使用不同的加密密钥和解密密钥，也就是说加密密钥K1与相应的解密密钥K2不同，在这种情况下：</p>
		<p>　　EK1（M）=C</p>
		<p>　　DK2（C）=M</p>
		<p>　　DK2 （EK1（M））=M</p>
		<p>　　所有这些算法的安全性都基于密钥的安全性；而不是基于算法的细节的安全性。这就意味着算法可以公开，也可以被分析，可以大量生产使用算法的产品，即使偷听者知道你的算法也没有关系；如果他不知道你使用的具体密钥，他就不可能阅读你的消息。</p>
		<p>　　密码系统由算法、以及所有可能的明文、密文和密钥组成的。</p>
		<p>　　基于密钥的算法通常有两类：对称算法和公开密钥算法。下面将分别介绍：</p>
		<p>　　<strong>2.2   对称密码算法</strong></p>
		<p>　　对称算法有时又叫传统密码算法，就是加密密钥能够从解密密钥中推算出来，反过来也成立。在大多数对称算法中，加/解密密钥是相同的。这些算法也叫秘密密钥算法或单密钥算法，它要求发送者和接收者在安全通信之前，商定一个密钥。对称算法的安全性依赖于密钥，泄漏密钥就意味着任何人都能对消息进行加/解密。只要通信需要保密，密钥就必须保密。</p>
		<p>　　对称算法的加密和解密表示为：</p>
		<p>　　EK（M）=C</p>
		<p>　　DK（C）=M</p>
		<p>　　对称算法可分为两类。一次只对明文中的单个比特（有时对字节）运算的算法称为序列算法或序列密码。另一类算法是对明文的一组比特亚行运算，这些比特组称为分组，相应的算法称为分组算法或分组密码。现代计算机密码算法的典型分组长度为64比特——这个长度大到足以防止分析破译，但又小到足以方便使用（在计算机出现前，算法普遍地每次只对明文的一个字符运算，可认为是序列密码对字符序列的运算）。</p>
		<p>　　<strong>2.3   公开密码算法</strong></p>
		<p>　　公开密钥算法（也叫非对称算法）是这样设计的：用作加密的密钥不同于用作解密的密钥，而且解密密钥不能根据加密密钥计算出来（至少在合理假定的长时间内）。之所以叫做公开密钥算法，是因为加密密钥能够公开，即陌生者能用加密密钥加密信息，但只有用相应的解密密钥才能解密信息。在这些系统中，加密密钥叫做公开密钥（简称公钥），解密密钥叫做私人密钥（简称私钥）。私人密钥有时也叫秘密密钥。为了避免与对称算法混淆，此处不用秘密密钥这个名字。</p>
		<p>　　用公开密钥K加密表示为</p>
		<p>　　EK（M）=C.</p>
		<p>　　虽然公开密钥和私人密钥是不同的，但用相应的私人密钥解密可表示为：</p>
		<p>　　DK（C）=M</p>
		<p>　　有时消息用私人密钥加密而用公开密钥解密，这用于数字签名（后面将详细介绍），尽管可能产生混淆，但这些运算可分别表示为：</p>
		<p>　　EK（M）=C</p>
		<p>　　DK（C）=M</p>
		<p>　　当前的公开密码算法的速度，比起对称密码算法，要慢的多，这使得公开密码算法在大数据量的加密中应用有限。</p>
		<p>　　<strong>2.4   单向散列函数</strong></p>
		<p>　　单向散列函数 H（M） 作用于一个任意长度的消息 M，它返回一个固定长度的散列值 h，其中 h 的长度为 m .</p>
		<p>　　输入为任意长度且输出为固定长度的函数有很多种，但单向散列函数还有使其单向的其它特性：</p>
		<p>　　（1） 给定 M ，很容易计算 h ；</p>
		<p>　　（2） 给定 h ，根据 H（M） = h 计算 M 很难 ；</p>
		<p>　　（3） 给定 M ，要找到另一个消息 M‘ 并满足 H（M） = H（M’） 很难。</p>
		<p>　　在许多应用中，仅有单向性是不够的，还需要称之为“抗碰撞”的条件：</p>
		<p>　　要找出两个随机的消息 M 和 M‘，使 H（M） = H（M’） 满足很难。</p>
		<p>　　由于散列函数的这些特性，由于公开密码算法的计算速度往往很慢，所以，在一些密码协议中，它可以作为一个消息 M 的摘要，代替原始消息 M，让发送者为 H（M） 签名而不是对 M 签名 .</p>
		<p>　　如 SHA 散列算法用于数字签名协议 DSA中。</p>
		<p>　　<strong>2.5   数字签名</strong></p>
		<p>　　提到数字签名就离不开公开密码系统和散列技术。</p>
		<p>　　有几种公钥算法能用作数字签名。在一些算法中，例如RSA，公钥或者私钥都可用作加密。用你的私钥加密文件，你就拥有安全的数字签名。在其它情况下，如DSA，算法便区分开来了？？数字签名算法不能用于加密。这种思想首先由Diffie和Hellman提出 .</p>
		<p>　　基本协议是简单的 ：</p>
		<p>　　（1） A 用她的私钥对文件加密，从而对文件签名。</p>
		<p>　　（2） A 将签名的文件传给B.</p>
		<p>　　（3） B用A的公钥解密文件，从而验证签名。</p>
		<p>　　这个协议中，只需要证明A的公钥的确是她的。如果B不能完成第（3）步，那么他知道签名是无效的。</p>
		<p>　　这个协议也满足以下特征：</p>
		<p>　　（1） 签名是可信的。当B用A的公钥验证信息时，他知道是由A签名的。</p>
		<p>　　（2） 签名是不可伪造的。只有A知道她的私钥。</p>
		<p>　　（3） 签名是不可重用的。签名是文件的函数，并且不可能转换成另外的文件。</p>
		<p>　　（4） 被签名的文件是不可改变的。如果文件有任何改变，文件就不可能用A的公钥验证。</p>
		<p>　　（5） 签名是不可抵赖的。B不用A的帮助就能验证A的签名。</p>
		<p>　　在实际应用中，因为公共密码算法的速度太慢，签名者往往是对消息的散列签名而不是对消息本身签名。这样做并不会降低签名的可信性。</p>
		<p>　　<strong>3    当前流行的一些软件保护技术</strong></p>
		<p>　　3.1   序列号保护</p>
		<p>　　数学算法一项都是密码加密的核心，但在一般的软件加密中，它似乎并不太为人们关心，因为大多数时候软件加密本身实现的都是一种编程的技巧。但近几年来随着序列号加密程序的普及，数学算法在软件加密中的比重似乎是越来越大了。</p>
		<p>　　看看在网络上大行其道的序列号加密的工作原理。当用户从网络上下载某个shareware——共享软件后，一般都有使用时间上的限制，当过了共享软件的试用期后，你必须到这个软件的公司去注册后方能继续使用。注册过程一般是用户把自己的私人信息（一般主要指名字）连同信用卡号码告诉给软件公司，软件公司会根据用户的信息计算出一个序列码，在用户得到这个序列码后，按照注册需要的步骤在软件中输入注册信息和注册码，其注册信息的合法性由软件验证通过后，软件就会取消掉本身的各种限制，这种加密实现起来比较简单，不需要额外的成本，用户购买也非常方便，在互联网上的软件80%都是以这种方式来保护的。</p>
		<p>　　软件验证序列号的合法性过程，其实就是验证用户名和序列号之间的换算关系是否正确的过程。其验证最基本的有两种，一种是按用户输入的姓名来生成注册码，再同用户输入的注册码比较，公式表示如下：</p>
		<p>　　序列号 = F（用户名）</p>
		<p>　　但这种方法等于在用户软件中再现了软件公司生成注册码的过程，实际上是非常不安全的，不论其换算过程多么复杂，解密者只需把你的换算过程从程序中提取出来就可以编制一个通用的注册程序。</p>
		<p>　　另外一种是通过注册码来验证用户名的正确性，公式表示如下：</p>
		<p>　　用户名称 = F逆（序列号） （如ACDSEE）</p>
		<p>　　这其实是软件公司注册码计算过程的反算法，如果正向算法与反向算法不是对称算法的话，对于解密者来说，的确有些困难，但这种算法相当不好设计。</p>
		<p>　　于是有人考虑到以下的算法：</p>
		<p>　　F1（用户名称） = F2（序列号）</p>
		<p>　　F1、F2是两种完全不同的的算法，但用户名通过F1算法计算出的特征字等于序列号通过F2算法计算出的特征字，这种算法在设计上比较简单，保密性相对以上两种算法也要好的多。如果能够把F1、F2算法设计成不可逆算法的话，保密性相当的好；可一旦解密者找到其中之一的反算法的话，这种算法就不安全了。一元算法的设计看来再如何努力也很难有太大的突破，那么二元呢？</p>
		<p>　　特定值 = F（用户名，序列号）</p>
		<p>　　这个算法看上去相当不错，用户名称与序列号之间的关系不再那么清晰了，但同时也失去了用户名于序列号的一一对应关系，软件开发者必须自己维护用户名称与序列号之间的唯一性，但这似乎不是难以办到的事，建个数据库就可以了。当然也可以把用户名称和序列号分为几个部分来构造多元的算法。</p>
		<p>　　特定值 = F（用户名1，用户名2，...序列号1，序列号2...）</p>
		<p>　　现有的序列号加密算法大多是软件开发者自行设计的，大部分相当简单。而且有些算法作者虽然下了很大的功夫，效果却往往得不到它所希望的结果。</p>
		<p>　　3.2   时间限制</p>
		<p>　　有些程序的试用版每次运行都有时间限制，例如运行10分钟或20分钟就停止工作，必须重新运行该程序才能正常工作。这些程序里面自然有个定时器来统计程序运行的时间。</p>
		<p>　　这种方法使用的较少。</p>
		<p>　　3.3   Key File 保护</p>
		<p>　　Key File（注册文件）是一种利用文件来注册软件的保护方式。Key File一般是一个小文件，可以是纯文本文件，也可以是包含不可显示字符的二进制文件，其内容是一些加密过或未加密的数据，其中可能有用户名、注册码等信息。文件格式则由软件作者自己定义。试用版软件没有注册文件，当用户向作者付费注册之后，会收到作者寄来的注册文件，其中可能包含用户的个人信息。用户只要将该文件放入指定的目录，就可以让软件成为正式版。该文件一般是放在软件的安装目录中或系统目录下。软件每次启动时，从该文件中读取数据，然后利用某种算法进行处理，根据处理的结果判断是否为正确的注册文件，如果正确则以注册版模式来运行。</p>
		<p>　　这种保护方法使用也不多。</p>
		<p>　　3.4   CD-check</p>
		<p>　　即光盘保护技术。程序在启动时判断光驱中的光盘上是否存在特定的文件，如果不存在则认为用户没有正版光盘，拒绝运行。在程序运行的过程当中一般不再检查光盘的存在与否。Windows下的具体实现一般是这样的：先用GetLogicalDriveStrings（ ）或GetLogicalDrives（ ）得到系统中安装的所有驱动器的列表，然后再用GetDriveType（ ）检查每一个驱动器，如果是光驱则用CreateFileA（ ）或FindFirstFileA（ ）等函数检查特定的文件存在与否，并可能进一步地检查文件的属性、大小、内容等。</p>
		<p>　　3.5   软件狗</p>
		<p>　　软件狗是一种智能型加密工具。它是一个安装在并口、串口等接口上的硬件电路，同时有一套使用于各种语言的接口软件和工具软件。当被狗保护的软件运行时，程序向插在计算机上的软件狗发出查询命令，软件狗迅速计算查询并给出响应，正确的响应保证软件继续运行。如果没有软件狗，程序将不能运行，复杂的软硬件技术结合在一起防止软件盗版。真正有商业价值得软件一般都用软件狗来保护。</p>
		<p>　　平时常见的狗主要有“洋狗”（国外狗）和“土狗”（国产狗）。这里“洋狗”主要指美国的彩虹和以色列的HASP，“土狗”主要有金天地（现在与美国彩虹合资，叫“彩虹天地”）、深思、尖石。总的说来，“洋狗”在软件接口、加壳、反跟踪等“软”方面没有“土狗”好，但在硬件上破解难度非常大；而“土狗”在软的方面做的很好，但在硬件上不如“洋狗”，稍有单片机功力的人，都可以复制。</p>
		<p>　　3.6   软盘加密</p>
		<p>　　通过在软盘上格式化一些非标准磁道，在这些磁道上写入一些数据，如软件的解密密钥等等。这种软盘成为“钥匙盘”。软件运行时用户将软盘插入，软件读取这些磁道中的数据，判断是否合法的“钥匙盘”。</p>
		<p>　　软盘加密还有其它一些技术，如弱位加密等等。</p>
		<p>　　随着近年来软盘的没落，这种方法基本上退出了历史舞台。</p>
		<p>　　3.7   将软件与机器硬件信息结合</p>
		<p>　　用户得到（买到或从网上下载）软件后，安装时软件从用户的机器上取得该机器的一些硬件信息（如硬盘序列号、BOIS序列号等等），然后把这些信息和用户的序列号、用户名等进行计算，从而在一定程度上将软件和硬件部分绑定。用户需要把这一序列号用Email、电话或邮寄等方法寄给软件提供商或开发商，软件开发商利用注册机（软件）产生该软件的注册号寄给用户即可。软件加密虽然加密强度比硬件方法较弱，但它具有非常廉价的成本、方便的使用方法等优点。非常适合做为采用光盘（CDROM）等方式发授软件的加密方案。</p>
		<p>　　此种加密算法的优点</p>
		<p>　　·    不同机器注册码不同。用户获得一个密码只能在一台机器上注册使用软件。不同于目前大多软件采用的注册方法，即只要知道注册码，可在任何机器上安装注册。</p>
		<p>　　·    不需要任何硬件或软盘</p>
		<p>　　·    可以选择控制软件运行在什么机器、运行多长时间或次数等</p>
		<p>　　·    可让软件在不注册前的功能为演示软件，只能运行一段时间或部分功能。注册后就立即变为正式软件</p>
		<p>　　·    采用特别技术，解密者很难找到产生注册号码的规律</p>
		<p>　　·    在使用注册号产生软件（注册机）时可采用使用密码、密钥盘、总次数限制等方法</p>
		<p>　　·    方便易用，价格低廉。</p>
		<p>　　这种加密还有以下特点</p>
		<p>　　1、 注册加密的软件，只能在一台机器上安装使用。把软件拷贝到其它机器上不能运行。</p>
		<p>　　2、 若用户想在另一机器上安装运行，必须把软件在这一机器上运行时的序列号，寄给软件出版商换取注册密码。当然应再交一份软件费用。</p>
		<p>　　3、 此加密方法特别适应在因特网上发布的软件及用光盘发布的软件。</p>
		<p>　　注释：</p>
		<p>　　1、“加密技术概述”部分内容参考了大学教材“密码学基础”。</p>
		<p>　　2、“当前流行的一些软件保护技术”部分内容参考了“加密与解密--软件保护技术及完全解决方案”一文。</p>
<img src ="http://www.cppblog.com/eday/aggbug/18667.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-11 21:41 <a href="http://www.cppblog.com/eday/archive/2007/02/11/18667.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++资源之不完全导引</title><link>http://www.cppblog.com/eday/archive/2007/02/05/18382.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Mon, 05 Feb 2007 02:07:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/02/05/18382.html</guid><description><![CDATA[
		<p>　　1，前言</p>
		<p>　　无数次听到“我要开始学习C++!”的呐喊，无数次听到“C++太复杂了，我真的学不会”的无奈。Stan Lippman先生曾在《C++ Primer》一书中指出“C++是最为难学的高级程序设计语言之一”，人们常将“之一”去掉以表达自己对C++的敬畏。诚然，C++程序设计语言对于学习者的确有很多难以逾越的鸿沟，体系结构的庞大，应接不暇并不断扩充的特性……除此之外，参考资料之多与冗杂使它的学习者望而却步，欲求深入者苦不堪言。希望这一份不完全导引能够成为您C++学习之路上的引路灯。</p>
		<p>　　撰写本文的初衷并不打算带领大家体验古老的C++历史，如果你想了解C++的历史与其前期发展中诸多技术的演变，你应当去参考Bjarne的《The Design and Evolution of C++》。当然也不打算给大家一个无所不包的宝典（并非不想：其一是因水平有限，其二无奈C++之博大精深），所给出的仅仅是一些我们认为对于想学习C++的广大读者来说最重要并且触手可及的开发与学习资源。</p>
		<p>　　本文介绍并分析了一些编译器，开发环境，库，少量的书籍以及参考网站，并且尽可能尝试着给出一个利用这些资源的导引，望对如同我们一样的初学者能够有所裨益。</p>
		<p>    　2，编译器</p>
		<p>　　在C++之外的任何语言中，编译器都从来没有受到过如此之重视。因为C++是一门相当复杂的语言，所以编译器也难于构建。直到最近我们才开始能够使用上完全符合C++标准的编译器（哦，你可能会责怪那些编译器厂商不能尽早的提供符合标准的编译器，这只能怪他们各自维系着自身的一套别人不愿接受的标准）。什么？你说这无关紧要？哦，不，你所需要的是和标准化C++高度兼容的编译环境。长远来看<br />，只有这样的编译器对C++开发人员来说才是最有意义的工具，尤其是对于程序设计语言的学习者。一至性让代码具备可移植性，并让一门语言及其库的应用更为广泛。嗯，是的，我们这里只打算介绍一些公认的优秀编译器。</p>
		<p>　　2.1 Borland C++</p>
		<p>　　这个是Borland C++ Builder和Borland C++ Builder X这两种开发环境的后台编译器。（哦，我之所以将之分为两种开发环境你应当能明白为什么，正如Delphi7到Delphi8的转变，是革命性的两代。）Borland C++由老牌开发工具厂商Borland倾力打造。该公司的编译器素以速度快，空间效率高著称，Borland C++ 系列编译器秉承了这个传统，属于非常优质的编译器。标准化方面早在5.5版本的编译器中对标准化C++的兼容就达到了92.73%。目前最新版本是Borland C++ Builder X中的6.0版本，官方称100%符合ANSI/ISO的C++标准以及C99标准。嗯…这正是我前面所指的“完全符合C++标准的编译器”。</p>
		<p>　　2.2 Visual C++</p>
		<p>　　这个正是我们熟知的Visual Studio 和 Visual Studio.net 2002, 2003以及2005 Whidbey中带的C++编译器。由Microsoft公司研制。在Visual Studio 6.0中，因为编译器有太多地方不能与后来出现的C++标准相吻合而饱受批评（想想你在使用STL的时候编译时报出的那些令人厌恶的error和warning吧）。VC++6.0对标准化C+<br />+的兼容只有83.43%。但是随着C++编译器设计大师Stanley Lippman以及诸多C++社群达人的加盟，在Visual Studio.NET 2003中，Visual C++编译器已经成为一个非常成熟可*的C++编译器了。Dr.Dobb's Journal的评测显示Visual C++7.1对标准C++的兼容性高达98.22%，一度成为CBX之前兼容性最好的编译器。结合强大的Visual Studio.NET开发环境，是一个非常不错的选择。至于Whidbey时代的Visual C++,似乎微软所最关注的是C++/CLI……我们不想评论微软下一代的C++编译器对标准化兼容如何，但他确实越来越适合.NET (其实你和我的感觉可能是一样的，微软不应当把标准C++这块肥肉丢给Borland,然而微软可能并不这样认为)。</p>
		<p>　　2.3 GNU C++</p>
		<p>　　著名的开源C++编译器。是类Unix操作系统下编写C++程序的首选。特点是有非常好的移植性，你可以在非常广泛的平台上使用它，同时也是编写跨平台，嵌入式程序很好的选择。另外在符合标准这个方面一直都非常好，GCC3.3大概能够达到96.15%。但是由于其跨平台的特性，在代码尺寸速度等优化上略微差一点。</p>
		<p>　　基于GNU C++的编译器有很多，比如：</p>
		<p>　　(1) Mingw</p>
		<p>　　<a href="http://www.mingw.org/">http://www.mingw.org/</a></p>
		<p>　　GCC的一个Windows的移植版本（Dev-C++的后台）</p>
		<p>　　(2) Cygwin</p>
		<p>　　<a href="http://sources.redhat.com/cygwin/">http://sources.redhat.com/cygwin/</a></p>
		<p>　　GCC的另外一个Windows移植版本是Cygwin的一部分，Cygwin是Windows下的一个Unix仿真环境。严格的说是模拟GNU的环境，这也就是"Gnu's Not Unix"要表达的意思，噢，扯远了，这并不是我们在这里关心的实质内容。</p>
		<p>　　(3) Djgpp</p>
		<p>　　<a href="http://www.delorie.com/djgpp/">http://www.delorie.com/djgpp/</a></p>
		<p>　　这是GCC的DOS移植版本。</p>
		<p>　　(4) RSXNT</p>
		<p>　　<a href="http://www.mathematik.uni-bielefeld.de/~rainer/">http://www.mathematik.uni-bielefeld.de/~rainer/</a></p>
		<p>　　这是GCC的DOS和Windows移植版本。</p>
		<p>　　(5) Intel C++</p>
		<p>　　著名CPU制造厂商Intel出品的编译器，Special Design for Intel x86！对于Intel x86结构的CPU经过特别的优化。在有些应用情况下，特别是数值计算等高性能应用，仅仅采用Intel的编译器编译就能大幅度的提高性能。</p>
		<p>　　(6) Digital Mars C++</p>
		<p>　　网络上提供免费下载，Zortech/Symantec C++的继承者，其前身在当年惨烈的C++四国战中也是主角之一。</p>
		<p>　　3，开发环境</p>
		<p>　　开发环境对于程序员的作用不言而喻。选择自己朝夕相处的环境也不是容易的事情，特别是在IDE如此丰富的情况下。下面就是我们推荐的一些常见的C++开发环境，并没有包括一些小型的，罕见的IDE。其中任何一款都是功能丰富，可以用作日常开发使用的。对于不同层面的开发者，请参见内文关于适用对象的描述。</p>
		<p>　　3.1 Visual Studio 6.0</p>
		<p>　　这个虽然是Microsoft公司的老版本的开发环境，但是鉴于其后继版本VisualStudio.NET的庞大身躯，以及初学者并不那么高的功能要求，所以推荐这个开发环境给C++的初学者，供其学习C++的最基本的部分，比如C的那部分子集，当然你别指望他能够支持最新的C99标准。在日常的开发中，仍然有很多公司使用这个经典稳定的环境，比如笔者就看曾亲见有些公司将其编译器替换为GCC做手机开发之用。</p>
		<p>　　3.2 Visual Studio.NET 2003</p>
		<p>　　作为Microsoft公司官方正式发布的最新版本开发环境，其中有太多激动人心的功能。结合其最新的C++编译器。对于机器配置比较好的开发人员来说，使用这个开发环境将能满足其大部分的要求。这里不打算单独说Visual Studio Whidbey,虽然Visual Studio .NET 2005 - Whidbey社区预览版已经推出，但暂不是很稳定，读者可以亲身去体验。</p>
		<p>　　3.3 Borland C++ Builder 6</p>
		<p>　　这个并不是Borland的C++开发环境的最新版本。选择它的原因是它不是用Java写的IDE，速度比较快。它有一个很完善的GUI窗体设计器，和Delphi共用一个VCL。由于这些特点，比较适合初学者上手。但是由于其GUI的中心位置，可能不利于对于C++语言的学习。而且其为了支持VCL这个Object Pascal写的库也对C++进行了一些私有的扩充。使得人们有一个不得不接受的事实：“Borland C++ Builder 6的高手<br />几乎都是Delphi高手”。</p>
		<p>　　3.4 Borland C++ Builder X</p>
		<p>　　正如前文所述，虽然版本号上和前面那个IDE非常相象，但是其实它们是完全不同的两个集成开发环境。C++Builder更多的是一个和Delphi同步的C++版本的开发环境，C++BuilderX则是完全从C++的角度思考得出的一个功能丰富的IDE。其最大的特点是跨平台，跨编译器，多种Framework的集成，并且有一个WxWindows为基础的GUI设计器。尤其是采用了纯C++来重写了整个Framework,摒弃了以前令人无奈的版本。对于C++的开发来说，从编译器，到库，到功能集成都是非常理想的。可以预见，Borland C++ Builder X 2.0很值得C++爱好者期待。唯一令人难堪之处是作为一个C++的开发工具，其IDE是用Java写的，在配置不够理想的机器上请慎重考虑再安装。</p>
		<p>　　3.5 Emacs + GCC</p>
		<p>　　前面讲的大部分是Windows环境下的集成开发环境。Linux上的开发者更倾向于使用Emacs来编辑C++的文件，用Makefile来命令GCC做编译。虽然看上去比较松散，但是这些东西综合起来还是一个开0发环境。如果你能够娴熟的使用这样的环境写程序，你的水平应该足够指导我们来写这篇陋文了。</p>
		<p>　　3.6 Dev C++</p>
		<p>　　GCC是一个很好的编译器。在Windows上的C++编译器一直和标准有着一段距离的时候，GCC就是一个让Windows下开发者流口水的编译器。Dev-C++就是能够让GCC跑在Windows下的工具，作为集成开发环境，还提供了同专业IDE相媲美的语法高亮，代码提示，调试等功能。由于使用Delphi开发，占用内存少，速度很快，比较适合轻量级的学习和使用。</p>
		<p>　　3.7 Eclipse + CDT</p>
		<p>　　Eclipse可是近来大名鼎鼎的开发工具。最新一期的Jolt大奖就颁给了这个杰出的神物。说其神奇是因为，它本身是用Java写的，但是拥有比一般Java写的程序快得多的速度。而且因为其基于插件组装一切的原则，使得能够有CDT这样的插件把Eclipse变成一个C/C++的开发环境。如果你一直用Eclipse写Java的程序，不妨用它体验一下C++开发的乐趣。</p>
		<p>--------------------------------------------------------------------------------</p>
		<p>　　4，工具</p>
		<p>　　C++的辅助工具繁多，我们分门别类的为大家作介绍：</p>
		<p>　　4.1 文档类</p>
		<p>　　(1) Doxygen</p>
		<p>　　参考站点：<a href="http://www.doxygen.org/">http://www.doxygen.org/</a></p>
		<p>　　Doxygen是一种适合C风格语言（如C++、C、IDL、Java甚至包括C#和PHP）的、开放源码的、基于命令行的文档产生器。</p>
		<p>　　(2) C++2HTML</p>
		<p>　　参考站点：<a href="http://www.bedaux.net/cpp2html/">http://www.bedaux.net/cpp2html/</a></p>
		<p>　　把C++代码变成语法高亮的HTML</p>
		<p>　　(3) CodeColorizer</p>
		<p>　　参考站点：<a href="http://www.chami.com/colorizer/">http://www.chami.com/colorizer/</a></p>
		<p>　　它能把好几种语言的源代码着色为HTML</p>
		<p>　　(4) Doc-O-Matic</p>
		<p>　　参考站点：<a href="http://www.doc-o-matic.com/">http://www.doc-o-matic.com/</a></p>
		<p>　　Doc-O_Matic为你的C/C++，C++.net，Delphi/Pascal, VB.NET，C#和Java程序或者组件产生准确的文档。Doc-O-Matic使用源代码中的符号和注释以及外部的文档文件创建与流行的文档样式一致的文档。</p>
		<p>　　(5) DocVizor</p>
		<p>　　参考站点：<a href="http://www.ucancode.net/Products/DocBuilder/Features.htm">http://www.ucancode.net/Products/DocBuilder/Features.htm</a></p>
		<p>　　DocVizor满足了面向对象软件开发者的基本要求——它让我们能够看到C++工程中的类层次结构。DocVizor快速地产生完整可供打印的类层次结构图，包括从第三方库中来的那些类，除此之外DocVizor还能从类信息中产生HTML文件。</p>
		<p>　　(6) SourcePublisher C++</p>
		<p>　　参考站点：<a href="http://www.scitools.com/sourcepublisher_c.html">http://www.scitools.com/sourcepublisher_c.html</a></p>
		<p>　　给源代码产生提供快速直观的HTML报表，包括代码，类层次结构，调用和被调用树，包含和被包含树。支持多种操作系统。</p>
		<p>　　(7) Understand</p>
		<p>　　参考站点：<a href="http://www.scitools.com/ucpp.html">http://www.scitools.com/ucpp.html</a></p>
		<p>　　分析任何规模的C或者C++工程，帮助我们更好的理解以及编写文档。</p>
		<p>　　4.2 代码类</p>
		<p>　　(1) CC-Rider</p>
		<p>　　参考站点：<a href="http://www.cc-rider.com/">http://www.cc-rider.com/</a></p>
		<p>　　CC-Rider是用于C/C++程序强大的代码可视化工具，通过交互式浏览、编辑及自动文件来促进程序的维持和发展。</p>
		<p>　　(2) CodeInspect</p>
		<p>　　参考站点：<a href="http://www.yokasoft.com/">http://www.yokasoft.com/</a></p>
		<p>　　一种新的C/C++代码分析工具。它检查我们的源代码找出非标准的，可能的，以及普通的错误代码。</p>
		<p>　　(3) CodeWizard</p>
		<p>　　参考站点：<a href="http://www.parasoft.com/">http://www.parasoft.com/</a></p>
		<p>　　先进的C/C++源代码分析工具，使用超过500个编码规范自动化地标明危险的，但是编译器不能检查到的代码结构。</p>
		<p>　　(4) C++ Validation Test Suites</p>
		<p>　　参考站点：<a href="http://www.plumhall.com/suites.html">http://www.plumhall.com/suites.html</a></p>
		<p>　　一组用于测试编译器和库对于标准吻合程度的代码库。</p>
		<p>　　(5) CppRefactory</p>
		<p>　　参考站点：<a href="http://cpptool.sourceforge.net/">http://cpptool.sourceforge.net/</a></p>
		<p>　　CPPRefactory是一个使得开发者能够重构他们的C++代码的程序。目的是使得C++代码的重构能够尽可能的有效率和简单。</p>
		<p>　　(6) Lzz</p>
		<p>　　参考站点：<a href="http://www.lazycplusplus.com/">http://www.lazycplusplus.com/</a></p>
		<p>　　Lzz是一个自动化许多C++编程中的体力活的工具。它能够节省我们许多事件并且使得编码更加有乐趣。给出一系列的声明，Lzz会给我们创建头文件和源文件。</p>
		<p>　　(7) QA C++ Generation 2000</p>
		<p>　　参考站点：<a href="http://www.programmingresearch.com/solutions/qacpp.htm">http://www.programmingresearch.com/solutions/qacpp.htm</a></p>
		<p>　　它关注面向对象的C++源代码，对有关于设计，效率，可*性，可维护性的部分提出警告信息。</p>
		<p>　　(8) s-mail project - Java to C++DOL</p>
		<p>　　参考站点：<a href="http://sadlocha.strefa.pl/s-mail/ja2dol.html">http://sadlocha.strefa.pl/s-mail/ja2dol.html</a></p>
		<p>　　把Java源代码翻译为相应的C++源代码的命令行工具。</p>
		<p>　　(9) SNIP from Cleanscape Software International</p>
		<p>　　参考站点：<a href="http://www.cleanscape.net/stdprod/snip/index.html">http://www.cleanscape.net/stdprod/snip/index.html</a></p>
		<p>　　一个填平编码和设计之间沟壑的易于使用的C++开发工具，节省大量编辑和调试的事件，它还使得开发者能够指定设计模式作为对象模型，自动从对象模型中产生C++的类。</p>
		<p>　　(10) SourceStyler C++</p>
		<p>　　参考站点：<a href="http://www.ochresoftware.com/">http://www.ochresoftware.com/</a></p>
		<p>　　对C/C++源代码提供完整的格式化和排版控制的工具。提供多于75个的格式化选项以及完全支持ANSI C++。</p>
		<p>　　4.3 编译类</p>
		<p>　　(1) Compilercache</p>
		<p>　　参考站点：<a href="http://www.erikyyy.de/compilercache/">http://www.erikyyy.de/compilercache/</a></p>
		<p>　　Compilercache是一个对你的C和C++编译器的封装脚本。每次我们进行编译，封装脚本，把编译的结果放入缓存，一旦编译相同的东西，结果将从缓存中取出而不是再次编译。</p>
		<p>　　(2) Ccache</p>
		<p>　　参考站点：<a href="http://ccache.samba.org/">http://ccache.samba.org/</a></p>
		<p>　　Ccache是一个编译器缓存。它使用起来就像C/C++编译器的缓存预处理器，编译速度通常能提高普通编译过程的5~10倍。</p>
		<p>　　(3) Cmm (C++ with MultiMethods)</p>
		<p>　　参考站点：<a href="http://www.op59.net/cmm/cmm-0.28/users.html">http://www.op59.net/cmm/cmm-0.28/users.html</a></p>
		<p>　　这是一种C++语言的扩展。读入Cmm源代码输出C++的源代码，功能是对C++语言添加了对multimethod的支持。</p>
		<p>　　(4) The Frost Project</p>
		<p>　　参考站点：<a href="http://frost.flewid.de/">http://frost.flewid.de/</a></p>
		<p>　　Forst使得你能够在C++程序中像原生的C++特性一样使用multimethod以及虚函数参数。它是一个编译器的外壳。</p>
		<p>　　4.4 测试和调试类</p>
		<p>　　(1) CPPUnit</p>
		<p>　　CppUnit 是个基于 LGPL 的开源项目，最初版本移植自 JUnit，是一个非常优秀的开源测试框架。CppUnit 和 JUnit 一样主要思想来源于极限编程。主要功能就是对单元测试进行管理，并可进行自动化测试。<br /><br /></p>
		<p>　　(2) C++Test</p>
		<p>　　参考站点：<a href="http://www.parasoft.com/">http://www.parasoft.com/</a></p>
		<p>　　C++ Test是一个单元测试工具，它自动化了C和C++类，函数或者组件的测试。</p>
		<p>
				<br />　　(3) Cantata++</p>
		<p>　　参考站点：<a href="http://www.iplbath.com/products/tools/pt400.shtml">http://www.iplbath.com/products/tools/pt400.shtml</a></p>
		<p>　　设计的目的是为了满足在合理的经济开销下使用这个工具可以让开发工程师开展单元测试和集成测试的需求.</p>
		<p>　　(4) Purify</p>
		<p>　　参考站点：<a href="http://www-900.ibm.com/cn/software/rational/products/purif">http://www-900.ibm.com/cn/software/rational/products/purif</a><br />yplus/index.shtml</p>
		<p>　　IBM Rational PurifyPlus是一套完整的运行时分析工具，旨在提高应用程序的可*性和性能。PurifyPlus将内存错误和泄漏检测、应用程序性能描述、代码覆盖分析等功能组合在一个单一、完整的工具包中。</p>
		<p>　　(5) BoundsChecker</p>
		<p>　　BoundsChecker是一个C++运行时错误检测和调试工具。它通过在Visual Studio内自动化调试过程加速开发并且缩短上市的周期。BoundsChecker提供清楚，详细的程序错误分析，许多是对C++独有的并且在static，stack和heap内存中检测和诊断错误，以及发现内存和资源的泄漏。　　<br /><br />　　(6) Insure++</p>
		<p>　　参考站点：<a href="http://www.parasoft.com/">http://www.parasoft.com/</a></p>
		<p>　　一个自动化的运行时程序测试工具，检查难以察觉的错误,如内存覆盖，内存泄漏，内存分配错误，变量初始化错误，变量定义冲突，指针错误，库错误，逻辑错误和算法错误等。</p>
		<p>　　(7) GlowCode</p>
		<p>　　参考站点：<a href="http://www.glowcode.com/">http://www.glowcode.com/</a></p>
		<p>　　GlowCode包括内存泄漏检查，code profiler，函数调用跟踪等功能。给C++开发者提供完整的错误诊断，和运行时性能分析工具包。</p>
		<p>　　(8) Stack Spy</p>
		<p>　　参考站点：<a href="http://www.imperioustech.com/">http://www.imperioustech.com/</a></p>
		<p>　　它能捕捉stack corruption, stack over run, stack overflow等有关栈的错误。</p>
		<p>------------------------------------------------------------------------<br />　5，库</p>
		<p>　　在C++中，库的地位是非常高的。C++之父 Bjarne Stroustrup先生多次表示了设计库来扩充功能要好过设计更多的语法的言论。现实中，C++的库门类繁多，解决的问题也是极其广泛，库从轻量级到重量级的都有。不少都是让人眼界大开，亦或是望而生叹的思维杰作。由于库的数量非常庞大，而且限于笔者水平，其中很多并不了解。所以文中所提的一些库都是比较著名的大型库。</p>
		<p>　　5.1 标准库</p>
		<p>　　标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年，直到标准的出台才正式定型，但是在标准库的实现上却很令人欣慰得看到多种实现，并且已被实践证明为有工业级别强度的佳作。</p>
		<p>　　(1) Dinkumware C++ Library</p>
		<p>　　参考站点：<a href="http://www.dinkumware.com/">http://www.dinkumware.com/</a></p>
		<p>　　P.J. Plauger编写的高品质的标准库。P.J. Plauger博士是Dr. Dobb's程序设计杰出奖的获得者。其编写的库长期被Microsoft采用，并且最近Borland也取得了其OEM的license，在其C/C++的产品中采用Dinkumware的库。</p>
		<p>　　(2) RogueWave Standard C++ Library</p>
		<p>　　参考站点：<a href="http://www.roguewave.com/">http://www.roguewave.com/</a></p>
		<p>　　这个库在Borland C++ Builder的早期版本中曾经被采用，后来被其他的库给替换了。笔者不推荐使用。</p>
		<p>　　(3) SGI STL</p>
		<p>　　参考站点：<a href="http://www.roguewave.com/">http://www.roguewave.com/</a></p>
		<p>　　SGI公司的C++标准模版库。</p>
		<p>　　(4) STLport</p>
		<p>　　参考站点：<a href="http://www.stlport.org/">http://www.stlport.org/</a></p>
		<p>　　SGI STL库的跨平台可移植版本。</p>
		<p>　　5.2 “准”标准库 - Boost</p>
		<p>　　参考站点：<a href="http://www.boost.org/">http://www.boost.org/</a></p>
		<p>　　国内镜像：<a href="http://www.c-view.org/tech/lib/boost/index.htm">http://www.c-view.org/tech/lib/boost/index.htm</a></p>
		<p>　　Boost库是一个经过千锤百炼、可移植、提供源代码的C++库，作为标准库的后备，是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起，在C++社区中影响甚大，其成员已近2000人。 Boost库为我们带来了最新、最酷、最实用的技术，是不折不扣的“准”标准库。</p>
		<p>　　Boost中比较有名气的有这么几个库：</p>
		<p>　　Regex</p>
		<p>　　正则表达式库</p>
		<p>　　Spirit</p>
		<p>　　LL parser framework，用C++代码直接表达EBNF</p>
		<p>　　Graph</p>
		<p>　　图组件和算法</p>
		<p>　　Lambda</p>
		<p>　　在调用的地方定义短小匿名的函数对象，很实用的functional功能</p>
		<p>　　concept check</p>
		<p>　　检查泛型编程中的concept</p>
		<p> </p>
		<p>　　Mpl</p>
		<p>　　用模板实现的元编程框架</p>
		<p> </p>
		<p>　　Thread</p>
		<p>　　可移植的C++多线程库</p>
		<p> </p>
		<p>　　Python</p>
		<p>　　把C++类和函数映射到Python之中</p>
		<p>　　Pool</p>
		<p>　　内存池管理</p>
		<p> </p>
		<p>　　smart_ptr</p>
		<p>　　5个智能指针，学习智能指针必读，一份不错的参考是来自CUJ的文章：</p>
		<p>　　Smart Pointers in Boost，哦，这篇文章可以查到，CUJ是提供在线浏览的。中文版见笔者在《Dr. Dobb's Journal软件研发杂志》第7辑上的译文。</p>
		<p>　　Boost总体来说是实用价值很高，质量很高的库。并且由于其对跨平台的强调，对标准C++的强调，是编写平台无关，现代C++的开发者必备的工具。但是Boost中也有很多是实验性质的东西，在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展，其构造用尽精巧的手法，不要贸然的花费时间研读。Boost另外一面，比如Graph这样的库则是具有工业强度，结构良好，非常值得研读的精品代码，并且也可以放心的在产品代码中多多利用。</p>
		<p>　　5.3 GUI</p>
		<p>　　在众多C++的库中，GUI部分的库算是比较繁荣，也比较引人注目的。在实际开发中，GUI库的选择也是非常重要的一件事情，下面我们综述一下可选择的GUI库，各自的特点以及相关工具的支持。</p>
		<p>　　(1) MFC</p>
		<p>　　大名鼎鼎的微软基础类库（Microsoft Foundation Class）。大凡学过VC++的人都应该知道这个库。虽然从技术角度讲，MFC是不大漂亮的，但是它构建于Windows API 之上，能够使程序员的工作更容易,编程效率高，减少了大量在建立 Windows 程序时必须编写的代码，同时它还提供了所有一般 C++ 编程的优点，例如继承和封装。MFC 编写的程序在各个版本的Windows操作系统上是可移植的，例如，在Windows 3.1下编写的代码可以很容易地移植到 Windows NT 或 Windows 95 上。但是在最近发展以及官方支持上日渐势微。</p>
		<p>　　(2) QT</p>
		<p>　　参考网站：<a href="http://www.trolltech.com/">http://www.trolltech.com/</a></p>
		<p>　　Qt是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能。Qt是完全面向对象的很容易扩展，并且允许真正地组件编程。自从1996年早些时候，Qt进入商业领域，它已经成为全世界范围内数千种成功的应用程序的基础。Qt也是流行的Linux桌面环境KDE 的基础，同时它还支持Windows、Macintosh、Unix/X11等多种平台。</p>
		<p>　　(3) WxWindows</p>
		<p>　　参考网站：<a href="http://www.wxwindows.org/">http://www.wxwindows.org/</a></p>
		<p>　　跨平台的GUI库。因为其类层次极像MFC，所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的GUI库，支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++ Builder X的GUI设计器就是基于这个库的。</p>
		<p>　　(4) Fox</p>
		<p>　　参考网站：<a href="http://www.fox-toolkit.org/">http://www.fox-toolkit.org/</a></p>
		<p>　　开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受出发，从而开始了对这个库的开发。有兴趣的可以尝试一下。</p>
		<p>
				<br />　　(5) WTL</p>
		<p>　　基于ATL的一个库。因为使用了大量ATL的轻量级手法，模板等技术，在代码尺寸，以及速度优化方面做得非常到位。主要面向的使用群体是开发COM轻量级供网络下载的可视化控件的开发者。</p>
		<p>　　(6) GTK</p>
		<p>　　参考网站：<a href="http://gtkmm.sourceforge.net/">http://gtkmm.sourceforge.net/</a></p>
		<p>　　GTK是一个大名鼎鼎的C的开源GUI库。在Linux世界中有Gnome这样的杀手应用。而GTK就是这个库的C++封装版本。</p>
		<p>　　5.4 网络通信</p>
		<p>　　(1) ACE</p>
		<p>　　参考网站：<a href="http://www.cs.wustl.edu/~schmidt/ACE.html">http://www.cs.wustl.edu/~schmidt/ACE.html</a></p>
		<p>　　C++库的代表，超重量级的网络通信开发框架。ACE自适配通信环境（AdaptiveCommunication Environment）是可以自由使用、开放源代码的面向对象框架，在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++包装外观（Wrapper Facade）和框架组件，可跨越多种平台完成通用的通信软件任务，其中包括：事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通<br />信、共享内存管理、消息路由、分布式服务动态（重）配置、并发执行和同步，等等。</p>
		<p>　　(2) StreamModule</p>
		<p>　　参考网站：<a href="http://www.omnifarious.org/StrMod/">http://www.omnifarious.org/StrMod/</a></p>
		<p>　　设计用于简化编写分布式程序的库。尝试着使得编写处理异步行为的程序更容易，而不是用同步的外壳包起异步的本质。</p>
		<p>　　(3) SimpleSocket</p>
		<p>　　参考网站：<a href="http://home.hetnet.nl/~lcbokkers/simsock.htm">http://home.hetnet.nl/~lcbokkers/simsock.htm</a></p>
		<p>　　这个类库让编写基于socket的客户/服务器程序更加容易。</p>
		<p>　　(4) A Stream Socket API for C++</p>
		<p>　　参考网站：<a href="http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.h">http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.h</a><br />tml</p>
		<p>　　又一个对Socket的封装库。</p>
		<p>　　5.5 XML</p>
		<p>　　(1) Xerces</p>
		<p>　　参考网站：<a href="http://xml.apache.org/xerces-c/">http://xml.apache.org/xerces-c/</a></p>
		<p>　　Xerces-C++ 是一个非常健壮的XML解析器，它提供了验证，以及SAX和DOM API。XML验证在文档类型定义(Document Type Definition，DTD)方面有很好的支持，并且在2001年12月增加了支持W3C XML Schema 的基本完整的开放标准。</p>
		<p>　　(2) XMLBooster</p>
		<p>　　参考网站：<a href="http://www.xmlbooster.com/">http://www.xmlbooster.com/</a></p>
		<p>　　这个库通过产生特制的parser的办法极大的提高了XML解析的速度，并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。</p>
		<p>　　(3) Pull Parser</p>
		<p>　　参考网站：<a href="http://www.extreme.indiana.edu/xgws/xsoap/xpp/">http://www.extreme.indiana.edu/xgws/xsoap/xpp/</a></p>
		<p>　　这个库采用pull方法的parser。在每个SAX的parser底层都有一个pull的parser，这个xpp把这层暴露出来直接给大家使用。在要充分考虑速度的时候值得尝试。<br /><br />　　(4) Xalan</p>
		<p>　　参考网站：<a href="http://xml.apache.org/xalan-c/">http://xml.apache.org/xalan-c/</a></p>
		<p>　　Xalan是一个用于把XML文档转换为HTML，纯文本或者其他XML类型文档的XSLT处理器。</p>
		<p>　　(5) CMarkup</p>
		<p>　　参考网站：<a href="http://www.firstobject.com/xml.htm">http://www.firstobject.com/xml.htm</a></p>
		<p>　　这是一种使用EDOM的XML解析器。在很多思路上面非常灵活实用。值得大家在DOM和SAX之外寻求一点灵感。</p>
		<p>　　(6) libxml++</p>
		<p>　　<a href="http://libxmlplusplus.sourceforge.net/">http://libxmlplusplus.sourceforge.net/</a></p>
		<p>　　libxml++是对著名的libxml XML解析器的C++封装版本</p>
		<p>　　5.6 科学计算</p>
		<p>　　(1) Blitz++</p>
		<p>　　参考网站：<a href="http://www.oonumerics.org/blitz/">http://www.oonumerics.org/blitz/</a></p>
		<p>　　Blitz++ 是一个高效率的数值计算函数库，它的设计目的是希望建立一套既具像C++ 一样方便，同时又比Fortran速度更快的数值计算环境。通常，用C++所写出的数值程序，比 Fortran慢20%左右，因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术，程序执行甚至可以比Fortran更快。Blitz++目前仍在发展中，对于常见的SVD，FFTs，QMRES等常见的线性代数方法并不提供，不过使用者可以很容易地利用Blitz++所提供的函数来构建。</p>
		<p>　　(2) POOMA</p>
		<p>　　参考网站：<a href="http://www.codesourcery.com/pooma/pooma">http://www.codesourcery.com/pooma/pooma</a></p>
		<p>　　POOMA是一个免费的高性能的C++库，用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程序开发，对并行机器进行了优化以达到最高的效率，方便在工业和研究环境中使用。</p>
		<p>　　(3) MTL</p>
		<p>　　参考网站：<a href="http://www.osl.iu.edu/research/mtl/">http://www.osl.iu.edu/research/mtl/</a></p>
		<p>　　Matrix Template Library(MTL)是一个高性能的泛型组件库，提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能编译器的情况下，比如Intel的编译器，从产生的汇编代码可以看出其与手写几乎没有两样的效能。</p>
		<p>　　(4) CGAL</p>
		<p>　　参考网站：<a href="http://www.cgal.org/">http://www.cgal.org/</a></p>
		<p>　　Computational Geometry Algorithms Library的目的是把在计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。</p>
		<p>　　5.7 游戏开发</p>
		<p>　　(1) Audio/Video 3D C++ Programming Library</p>
		<p>　　参考网站：<a href="http://www.galacticasoftware.com/products/av/">http://www.galacticasoftware.com/products/av/</a></p>
		<p>　　AV3D是一个跨平台，高性能的C++库。主要的特性是提供3D图形，声效支持（SB,以及S3M），控制接口（键盘，鼠标和遥感），XMS。</p>
		<p>　　(2) KlayGE</p>
		<p>　　参考网站：<a href="http://home.g365.net/enginedev/">http://home.g365.net/enginedev/</a></p>
		<p>　　国内游戏开发高手自己用C++开发的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎，并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。</p>
		<p>　　(3) OGRE</p>
		<p>　　参考网站：<a href="http://www.ogre3d.org/">http://www.ogre3d.org/</a></p>
		<p>　　OGRE（面向对象的图形渲染引擎）是用C++开发的，使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备的应用程序或游戏。引擎中的类库对更底层的系统库（如：Direct3D和OpenGL）的全部使用细节进行了抽象，并提供了基于现实世界对象的接口和其它类。</p>
		<p>　　5.8 线程</p>
		<p>　　(1) C++ Threads</p>
		<p>　　参考网站：<a href="http://threads.sourceforge.net/">http://threads.sourceforge.net/</a></p>
		<p>　　这个库的目标是给程序员提供易于使用的类，这些类被继承以提供在Linux环境中很难看到的大量的线程方面的功能。</p>
		<p>　　(2) ZThreads</p>
		<p>　　参考网站：<a href="http://zthread.sourceforge.net/">http://zthread.sourceforge.net/</a></p>
		<p>　　一个先进的面向对象，跨平台的C++线程和同步库。<br /><br /></p>
		<p>　　5.9 序列化</p>
		<p>　　(1) s11n</p>
		<p>　　参考网站：<a href="http://s11n.net/">http://s11n.net/</a></p>
		<p>　　一个基于STL的C++库，用于序列化POD，STL容器以及用户定义的类型。</p>
		<p>　　(2) Simple XML Persistence Library</p>
		<p>　　参考网站：<a href="http://sxp.sourceforge.net/">http://sxp.sourceforge.net/</a></p>
		<p>　　这是一个把对象序列化为XML的轻量级的C++库。</p>
		<p>　　5.10 字符串</p>
		<p>　　(1) C++ Str Library</p>
		<p>　　参考网站：<a href="http://www.utilitycode.com/str/">http://www.utilitycode.com/str/</a></p>
		<p>　　操作字符串和字符的库，支持Windows和支持gcc的多种平台。提供高度优化的代码，并且支持多线程环境和Unicode，同时还有正则表达式的支持。</p>
		<p>　　(2) Common Text Transformation Library</p>
		<p>　　参考网站：<a href="http://cttl.sourceforge.net/">http://cttl.sourceforge.net/</a></p>
		<p>　　这是一个解析和修改STL字符串的库。CTTL substring类可以用来比较，插入，替换以及用EBNF的语法进行解析。</p>
		<p>　　(3) GRETA</p>
		<p>　　参考网站：<a href="http://research.microsoft.com/projects/greta/">http://research.microsoft.com/projects/greta/</a></p>
		<p>　　这是由微软研究院的研究人员开发的处理正则表达式的库。在小型匹配的情况下有非常优秀的表现。</p>
		<p>　　5.11 综合</p>
		<p>　　(1) P::Classes</p>
		<p>　　参考网站：<a href="http://pclasses.com/">http://pclasses.com/</a></p>
		<p>　　一个高度可移植的C++应用程序框架。当前关注类型和线程安全的signal/slot机制，i/o系统包括基于插件的网络协议透明的i/o架构，基于插件的应用程序消息日志框架，访问sql数据库的类等等。</p>
		<p>　　(2) ACDK - Artefaktur Component Development Kit</p>
		<p>　　参考网站：<a href="http://acdk.sourceforge.net/">http://acdk.sourceforge.net/</a></p>
		<p>　　这是一个平台无关的C++组件框架，类似于Java或者.NET中的框架（反射机制，线程，Unicode，废料收集，I/O，网络，实用工具，XML，等等），以及对Java, Perl, Python, TCL, Lisp, COM 和 CORBA的集成。</p>
		<p>　　(3) dlib C++ library</p>
		<p>　　参考网站：<a href="http://www.cis.ohio-state.edu/~kingd/dlib/">http://www.cis.ohio-state.edu/~kingd/dlib/</a></p>
		<p>　　各种各样的类的一个综合。大整数，Socket，线程，GUI，容器类,以及浏览目录的API等等。</p>
		<p>　　(4) Chilkat C++ Libraries</p>
		<p>　　参考网站：<a href="http://www.chilkatsoft.com/cpp_libraries.asp">http://www.chilkatsoft.com/cpp_libraries.asp</a></p>
		<p>　　这是提供zip，e-mail，编码，S/MIME，XML等方面的库。</p>
		<p>　　(5) C++ Portable Types Library (PTypes)</p>
		<p>　　参考网站：<a href="http://www.melikyan.com/ptypes/">http://www.melikyan.com/ptypes/</a></p>
		<p>　　这是STL的比较简单的替代品，以及可移植的多线程和网络库。</p>
		<p>　　(6) LFC</p>
		<p>　　参考网站：<a href="http://lfc.sourceforge.net/">http://lfc.sourceforge.net/</a></p>
		<p>　　哦，这又是一个尝试提供一切的C++库</p>
		<p>　　5.12 其他库</p>
		<p>　　(1) Loki</p>
		<p>　　参考网站：<a href="http://www.moderncppdesign.com/">http://www.moderncppdesign.com/</a></p>
		<p>　　哦，你可能抱怨我早该和Boost一起介绍它，一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。</p>
		<p>　　(2) ATL</p>
		<p>　　ATL(Active Template Library)</p>
		<p>　　是一组小巧、高效、灵活的类，这些类为创建可互操作的COM组件提供了基本的设施。</p>
		<p>　　(3) FC++: The Functional C++ Library</p>
		<p>　　这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣，可以去看看函数式程序设计的世界。大师Peter Norvig在 “Teach Yourself rogramming in Ten Years”一文中就将函数式语言列为至少应当学习的6类编程语言之一。</p>
		<p>　　(4) FACT!</p>
		<p>　　参考网站：<a href="http://www.kfa-juelich.de/zam/FACT/start/index.html">http://www.kfa-juelich.de/zam/FACT/start/index.html</a></p>
		<p>　　另外一个实现函数式语言特性的库</p>
		<p>　　(5) Crypto++</p>
		<p>　　提供处理密码，消息验证，单向hash，公匙加密系统等功能的免费库。</p>
		<p>　　还有很多非常激动人心或者是极其实用的C++库，限于我们的水平以及文章的篇幅不能包括进来。在对于这些已经包含近来的库的介绍中，由于并不是每一个我们都使用过，所以难免有偏颇之处，请读者见谅。</p>
		<p>--------------------------------------------------------------------------------</p>
		<p>　　6，书籍</p>
		<p>　　以前熊节先生曾撰文评论相对于Java程序设计语言，C++的好书多如牛毛。荣耀先生在《程序员》杂志上撰文《C++程序设计之四书五经》也将本领域内几乎所有的经典书籍作了全面的介绍,任何关于书的评论此时看来便是很多余的了。个人浅见，除非你打算以C++作为唯一兴趣或者生存之本，一般读者确实没有足够的时间和必要将20余本书籍全部阅读。更有参考价值的是荣耀先生的另一篇文章：《至少应该阅<br />读的九本C++著作》，可以从下面的地址浏览到此文：</p>
		<p>　　<a href="http://www.royaloo.com/articles/articles_2003/9CppBooks.htm">http://www.royaloo.com/articles/articles_2003/9CppBooks.htm</a></p>
		<p>　　下面几本书对于走在C++初学之路上的读者是我们最愿意推荐给大家的：</p>
		<p>　　(1) 《C++ Primer》</p>
		<p>　　哦，也许你会抱怨我们为什么不先介绍TCPL,但对于走在学习之路上的入门者，本书内容更为全面，更为详细易懂，我们称它为“C++的超级宝典”并不过分。配有一本不错的习题解答《C++ Primer Answer Book》可以辅助你的学习之路。</p>
		<p>　　(2) 《Essential C++》</p>
		<p>　　如果说《C++ Primer》是C++领域的超级宝典，那么此书作为掌握C++的大局观当之无愧。正如《.NET大局观》一书能够让读者全揽.NET，本书讲述了C++中最核心的全部主题。书虽不厚，内容精炼，不失为《C++ Primer》读者茶余饭后的主题回顾之作。</p>
		<p>　　(3) 《The C++ Programming Language》</p>
		<p>　　Bjarne为你带来的C++教程，真正能够告诉你怎么用才叫真正的C++的唯一一本书。虽然如同“某某程序设计语言”这样的书籍会给大家一个内容全揽，入门到精通的感觉，但本书确实不太适合初学者阅读。如果你自认为是一名很有经验的C++程序员，那至少也要反复咀嚼Bjarne先生所强调的若干内容。</p>
		<p>　　(4) 《Effective C++》，《More Effective C++》</p>
		<p>　　是的，正如一些C++爱好者经常以读过与没有读过上述两本作品来区分你是否是C++高手。我们也极力推崇这两本著作。在各种介绍C++专家经验的书籍里面，这两本是最贴近语言本质，看后最能够有脱胎换骨感觉的书，读此书你需每日三省汝身。</p>
		<p>　　技术书籍仁者见仁，过多的评论反无太多意义，由读者喜好选择最适合自己的书方为上策。</p>
		<p>--------------------------------------------------------------------------------</p>
		<p>　　7，资源网站</p>
		<p>　　正如我们可以通过计算机历史上的重要人物了解计算机史的发展，C++相关人物的网站也可以使我们得到最有价值的参考与借鉴，下面的人物我们认为没有介绍的必要，只因下面的人物在C++领域的地位众所周知，我们只将相关的资源进行罗列以供读者学习，他们有的工作于贝尔实验室，有的工作于知名编译器厂商，有的在不断推进语言的标准化，有的为读者撰写了多部千古奇作……<br />　　(1) Bjarne Stroustrup<br />　　<a href="http://www.research.att.com/~bs/">http://www.research.att.com/~bs/</a></p>
		<p>　　(2) Stanley B. Lippman<br />　　<a href="http://blogs.msdn.com/slippman/">http://blogs.msdn.com/slippman/</a><br />　　中文版 <a href="http://www.zengyihome.net/slippman/index.htm">http://www.zengyihome.net/slippman/index.htm</a></p>
		<p>　　(3) Scott Meyers<br />　　<a href="http://www.aristeia.com/">http://www.aristeia.com/</a></p>
		<p>　　(4) David Musser<br />　　<a href="http://www.cs.rpi.edu/~musser/">http://www.cs.rpi.edu/~musser/</a></p>
		<p>　　(5) Bruce Eckel<br />　　<a href="http://www.bruceeckel.com/">http://www.bruceeckel.com/</a></p>
		<p>　　(6) Nicolai M. Josuttis<br />　　<a href="http://www.josuttis.com/">http://www.josuttis.com/</a></p>
		<p>　　(7) Herb Sutter<br />　　<a href="http://www.gotw.ca/">http://www.gotw.ca/</a></p>
		<p>　　(8) Andrei Alexandrescu<br />　　<a href="http://www.coderncppdesign.com/">http://www.coderncppdesign.com/</a></p>
		<p>　　(9) 侯捷先生<br />　　<a href="http://www.jjhou.com/">http://www.jjhou.com/</a></p>
		<p>　　(10) 孟岩先生<br />　　先生繁忙于工作，痴迷于技术，暂无个人主页，关于先生的作品可以通过CSDN的专栏和侯先生的主页访问到。</p>
		<p>　　(11) 荣耀先生<br />　　<a href="http://www.royaloo.com/">http://www.royaloo.com/</a></p>
		<p>　　(12) 潘爱民先生<br />　　<a href="http://www.icst.pku.edu.cn/panaimin/pam_homepage.htm">http://www.icst.pku.edu.cn/panaimin/pam_homepage.htm</a></p>
		<p>　　除了上述大师的主页外，以下的综合类C++学习参考站点是我们非常愿意向大家推荐的：</p>
		<p>　　(1) CodeProject<br />　　<a href="http://www.codeproject.com/">http://www.codeproject.com/</a></p>
		<p>　　(2) CodeGuru<br />　　<a href="http://www.codeguru.com/">http://www.codeguru.com/</a></p>
		<p>　　(3) Dr. Dobb's Journal<br />　　<a href="http://www.ddj.com/">http://www.ddj.com/</a></p>
		<p>　　(4) C/C++ Users Journal<br />　　<a href="http://www.cuj.com/">http://www.cuj.com/</a></p>
		<p>　　(5) C维视点<br />　　<a href="http://www.c-view.org/">http://www.c-view.org/</a></p>
		<p>　　(6) allaboutprogram<br />　　<a href="http://www.allaboutprogram.com/">http://www.allaboutprogram.com/</a><br />　　其他资料</p>
		<p>　　(1) ISO IEC JTC1/SC22/WG21 - C++：标准C++的权威参考<br />　　<a href="http://anubis.dkuug.dk/jtc1/sc22/wg21/">http://anubis.dkuug.dk/jtc1/sc22/wg21/</a></p>
		<p>　　(2) C++ FAQ LITE — Frequently Asked Questions: 最为全面的C++FAQ<br />　　<a href="http://www.sunistudio.com/cppfaq/index.html">http://www.sunistudio.com/cppfaq/index.html</a><br />　　C/C++ 新闻组：<br />　　你不妨尝试从这里提问和回答问题，很多不错的Q&amp;A资源......</p>
		<p>　　(1) .alt.comp.lang.learn.c-c++<br />　　这个简单些，如果你和我一样是个菜鸟</p>
		<p>　　(2) .comp.lang.c++.moderated<br />  嗯，这个显然水平高一些</p>
		<p>　　(3) .comp.std.c++<br />　　如果你需要讨论标准C++相关话题的话</p>
		<p>--------------------------------------------------------------------------------</p>
		<p>　　8，不得不写的结束语</p>
		<p>　　结束的时候也是总结现状，展望未来的时候。虽然C++从脱胎于C开始，一路艰难坎坷的走过来，但是无论如何C++已经取得了工业基础的地位。文章列举的大量相关资源就是最好的证明，而业界的大量用C++写成的产品代码以及大量的C++职业工程师则是最直接的证明。同时，我们可以看到各个高校的计算机专业都开设有C++这门课程，网络上对于C++的学习讨论也从来都没有停过。但是，在Java和.NET两大企业开发平台的围攻下，给人的感觉是C++越来越“不行”了。</p>
		<p>　　C++在面向企业的软件开发中，在开发便捷性等方面的确要比Java和C#差很多，其中一个问题是C++语言本身比较复杂，学习曲线比较陡峭，另外一个问题是C++标准化的时间太长，丧失了很多的壮大机会，耗费了很多精力在厂商的之间的斗争上，而C++的标准库离一个完善的程序开发框架还缺少太多太多的内容，各个第三方的类库和框架又在一致性和完整性上没法和随平台提供的框架相提并论。难道C++真的要退出历史舞台了？</p>
		<p>　　从C++目前的活跃程度，以及应用现状来说是完全能够肯定C++仍然是软件工业的基础，也不会退出历史舞台的。另外从Boost，Loki这些库中我们也能够看到C++的发展非常活跃，对于新技术新思维非常激进，C++仍然广泛受到关注。从ACE在高性能通信领域的应用，以及MTL这样的库在数值计算领域的出色表现，我们可以看到C++在高性能应用场合下的不可替代的作用，而嵌入式系统这样的内存受限开发平台<br />，比如Symbian OS上，C++已经发挥着并且将发挥更大的作用。可以预见的是以后的软件无论上层的应用怎么变，它的底层核心都会是由C/C++这样的系统级软件编写的，比如Java虚拟机，.NET Framwork。因为只有这样的系统级软件才能完全彻底的发挥机器的功能。</p>
		<p>　　需要看到的是两个趋势，一个趋势是C++变得更加复杂，更加学院派，通过模板等有潜力的语法因素构造越来越精巧的库成为了现代C++的热点，虽然在利用库实现新的编程范式，乃至设计模式等方面很有开创意义，也确实产生了一些能够便捷开发的工具，但是更多的是把C++变得更加强大，更加复杂，也更加难懂，似乎也更加学院派，不得不说它正在向边缘化道路发展。另一个趋势是C++在主流的企业应用开<br />发中已经逐渐退出了，ERP这样的企业软件开发中基本上不会考虑C++，除非需要考虑性能或者和遗留代码的集成这些因素。C++退守到系统级别语言，成为软件工业的基础是大势所趋。然而反思一下，真的是退守么？自从STL出现，无数的人风起云涌的开始支持C++,他们狂呼“我看到深夜消失了，目标软件工程的出现。我看到了可维护的代码。”是的，STL在可维护性下做得如此出色。但是又怎样呢？STL为C++铺平了现代软件工程的道路，而在上层应用程序软件开发领域这块场地早不单独属于C++,很多程序设计语言都做得很出色，疯狂的支持者会毫不犹豫地说我们应当支持C++,因为它是世界上最棒的语言。而坦率地说，你的腰杆真的那么硬么？也许只是在逃避一些事实。C++是优秀的，这不可否认，STL的出现让C++一度走上了最辉煌的时刻，然而现在看来……我的一位恩师曾言：真正能够将STL应用得淋漓尽致的人很<br />保守地说国内也不超过200人，或许不加入STL能够使C++向着它应当发展的方向发展的更好，而现在看来，C++也应当回首到真正属于他的那一片圣地上……</p>
<img src ="http://www.cppblog.com/eday/aggbug/18382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-02-05 10:07 <a href="http://www.cppblog.com/eday/archive/2007/02/05/18382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用Winsock实现语音全双工通信使用</title><link>http://www.cppblog.com/eday/archive/2007/01/31/18209.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 31 Jan 2007 03:47:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/31/18209.html</guid><description><![CDATA[　　摘要：在Windows 95环境下，基于TCP/IP协议，用Winsock完成了话音的端到端传输。采用双套接字技术，阐述了主要函数的使用要点，以及基于异步选择机制的应用方法。同时，给出了相应的实例程序。<br /><br />　　<b>一、引言</b><br /><br />　　Windows 95作为微机的操作系统，已经完全融入了网络与通信功能，不仅可以建立纯Windows 95环境下的“对等网络”，而且支持多种协议，如TCP/IP、IPX/SPX、NETBUI等。在TCP/IP协议组中，TPC是一种面向连接的协义，为用户提供可靠的、全双工的字节流服务，具有确认、流控制、多路复用和同步等功能，适于数据传输。UDP协议则是无连接的，每个分组都携带完整的目的地址，各分组在系统中独立传送。它不能保证分组的先后顺序，不进行分组出错的恢复与重传，因此不保证传输的可靠性，但是，它提供高传输效率的数据报服务，适于实时的语音、图像传输、广播消息等网络传输。<br /><br />　　Winsock接口为进程间通信提供了一种新的手段，它不但能用于同一机器中的进程之间通信，而且支持网络通信功能。随着Windows 95的推出。Winsock已经被正式集成到了Windows系统中，同时包括了16位和32位的编程接口。而Winsock的开发工具也可以在Borland C++4.0、Visual C++2.0这些C编译器中找到，主要由一个名为winsock.h的头文件和动态连接库winsock.dll或wsodk32.dll组成，这两种动态连接库分别用于Win16和Win32的应用程序。<br /><br />　　本文针对话音的全双工传输要求，采用UDP协议实现了实时网络通信。使用VisualC++2.0编译环境，其动态连接库名为wsock32.dll。 <br /><br /><span class="f14">　<b>　二、主要函数的使用要点</b><br /><br />　　通过建立双套接字，可以很方便地实现全双工网络通信。<br /><br />　　1.套接字建立函数：<br /><br /><p></p><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>SOCKET socket(int family,int type,int protocol) </td></tr></tbody></table><br />　　对于UDP协议，写为：<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>SOCKRET s;<br />s=socket(AF_INET,SOCK_DGRAM,0);<br />或s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)</td></tr></tbody></table><br />　　为了建立两个套接字，必须实现地址的重复绑定，即，当一个套接字已经绑定到某本地地址后，为了让另一个套接字重复使用该地址，必须为调用bind()函数绑定第二个套接字之前，通过函数setsockopt()为该套接字设置SO_REUSEADDR套接字选项。通过函数getsockopt()可获得套接字选项设置状态。需要注意的是，两个套接字所对应的端口号不能相同。此外，还涉及到套接字缓冲区的设置问题，按规定，每个区的设置范围是：不小于512个字节，大大于8k字节，根据需要，文中选用了4k字节。<br /><br />　　2.套接字绑定函数<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>int bind(SOCKET s,struct sockaddr_in*name,int namelen)</td></tr></tbody></table><br />　　s是刚才创建好的套接字，name指向描述通讯对象的结构体的指针，namelen是该结构体的长度。该结构体中的分量包括：IP地址(对应name.sin_addr.s_addr)、端口号(name.sin_port)、地址类型(name.sin_family，一般都赋成AF_INET，表示是internet地址)。<br /><br />　　(1)IP地址的填写方法：在全双工通信中，要把用户名对应的点分表示法地址转换成32位长整数格式的IP地址，使用inet_addr()函数。<br /><br />　　(2)端口号是用于表示同一台计算机不同的进程(应用程序)，其分配方法有两种：1)进程可以让系统为套接字自动分配一端口号，只要在调用bind前将端口号指定为0即可。由系统自动分配的端口号位于1024~5000之间，而1~1023之间的任一TCP或UDP端口都是保留的，系统不允许任一进程使用保留端口，除非其有效用户ID是零(超级用户)。<br /><br />　　2)进程可为套接字指定一特定端口。这对于需要给套接字分配一众所端口的服务器是很有用的。指定范围为1024和65536之间。可任意指定。<br /><br />　　在本程序中，对两个套接字的端口号规定为2000和2001，前者对应发送套接字，后者对应接收套接字。<br /><br />　　端口号要从一个16位无符号数(u_short类型数)从主机字节顺序转换成网络字节顺序，使用htons()函数。<br /><br />　　根据以上两个函数，可以给出双套接字建立与绑定的程序片断。<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>//设置有关的全局变量<br />SOCKET sr,ss;<br />HPSTR sockBufferS,sockBufferR;<br />HANDLE hSendData,hReceiveData;<br />DWROD dwDataSize=1024*4;<br />struct sockaddr_in therel.there2;<br />#DEFINE LOCAL_HOST_ADDR 200.200.200.201<br />#DEFINE REMOTE_HOST-ADDR 200.200.200.202<br />#DEFINE LOCAL_HOST_PORT 2000<br />#DEFINE LOCAL_HOST_PORT 2001<br />//套接字建立函数 <br />BOOL make_skt(HWND hwnd)<br />{<br />struct sockaddr_in here,here1;<br />ss=socket(AF_INET,SOCK_DGRAM,0);<br />sr=socket(AF_INET,SOCK_DGRAM,0);<br />if((ss==INVALID_SOCKET)||(sr==INVALID_SOCKET))<br />{<br />MessageBox(hwnd,“套接字建立失败!”，“”,MB_OK);<br />return(FALSE);<br />}<br />here.sin_family=AF_INET;<br />here.sin_addr.s_addr=inet_addr(LOCAL_HOST_ADDR);<br />here.sin_port=htons(LICAL_HOST_PORT);<br />//another socket<br />herel.sin_family=AF_INET;<br />herel.sin_addr.s_addr(LOCAL_HOST_ADDR);<br />herel.sin_port=htons(LOCAL_HOST_PORT1);<br />SocketBuffer();//套接字缓冲区的锁定设置<br />setsockopt(ss,SOL_SOCKET,SO_SNDBUF,(char FAR*)sockBufferS,dwDataSize);<br />if(bind(ss,(LPSOCKADDR)&amp;here,sizeof(here)))<br />{<br />MessageBox(hwnd,“发送套接字绑定失败!”，“”，MB_OK);<br />return(FALSE);<br />}<br />setsockopt(sr SQL_SOCKET,SO_RCVBUF|SO_REUSEADDR,(char FAR*)<br />sockBufferR,dwDataSize);<br />if(bind(sr,(LPSOCKADDR)&amp;here1,sizeof(here1)))<br />{<br />MessageBox(hwnd,“接收套接字绑定失败!”，“”，MB_OK);<br />return(FALSE);<br />}<br />return(TRUE);<br />}<br />//套接字缓冲区设置 <br />void sockBuffer(void)<br />{<br />hSendData=GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,dwDataSize);<br />if(!hSendData)<br />{<br />MessageBox(hwnd,“发送套接字缓冲区定位失败!”，NULL,<br />MB_OK|MB_ICONEXCLAMATION);<br />return;<br />}<br />if((sockBufferS=GlobalLock(hSendData)==NULL)<br />{<br />MessageBox(hwnd,“发送套接字缓冲区锁定失败!”，NULL,<br />MB_OK|MB_ICONEXCLAMATION);<br />GlobalFree(hRecordData[0];<br />return;<br />}<br />hReceiveData=globalAlloc(GMEM_MOVEABLE|GMEM_SHARE,dwDataSize);<br />if(!hReceiveData)<br />{<br />MessageBox(hwnd,"“接收套接字缓冲区定位败!”，NULL<br />MB_OK|MB_ICONEXCLAMATION);<br />return;<br />}<br />if((sockBufferT=Globallock(hReceiveData))=NULL)<br />MessageBox(hwnd,"发送套接字缓冲区锁定失败!”，NULL,<br />MB_OK|MB_ICONEXCLAMATION);<br />GlobalFree(hRecordData[0]);<br />return;<br />}<br />{</td></tr></tbody></table><br />　　3.数据发送与接收函数；<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>int sendto(SOCKET s.char*buf,int len,int flags,struct sockaddr_in to,int<br />tolen);<br />int recvfrom(SOCKET s.char*buf,int len,int flags,struct sockaddr_in <br />fron,int*fromlen)</td></tr></tbody></table><br />　　其中，参数flags一般取0。<br /><br />　　recvfrom()函数实际上是读取sendto()函数发过来的一个数据包，当读到的数据字节少于规定接收的数目时，就把数据全部接收，并返回实际接收到的字节数；当读到的数据多于规定值时，在数据报文方式下，多余的数据将被丢弃。而在流方式下，剩余的数据由下recvfrom()读出。为了发送和接收数据，必须建立数据发送缓冲区和数据接收缓冲区。规定：IP层的一个数据报最大不超过64K(含数据报头)。当缓冲区设置得过多、过大时，常因内存不够而导致套接字建立失败。在减小缓冲区后，该错误消失。经过实验，文中选用了4K字节。<br /><br />　　此外，还应注意这两个函数中最后参数的写法，给sendto()的最后参数是一个整数值，而recvfrom()的则是指向一整数值的指针。<br /><br />　　4.套接字关闭函数：closesocket(SOCKET s)<br /><br />　　通讯结束时，应关闭指定的套接字，以释与之相关的资源。<br /><br />　　在关闭套接字时，应先对锁定的各种缓冲区加以释放。其程序片断为：<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>void CloseSocket(void)<br />{<br />GlobalUnlock(hSendData);<br />GlobalFree(hSenddata);<br />GlobalUnlock(hReceiveData);<br />GlobalFree(hReceiveDava);<br />if(WSAAysncSelect(ss,hwnd,0,0)=SOCKET_ERROR)<br />{<br />MessageBos(hwnd,“发送套接字关闭失败!”，“”，MB_OK);<br />return;<br />}<br />if(WSAAysncSelect(sr,hwnd,0,0)==SOCKET_ERROR)<br />{ <br />MessageBox(hwnd,“接收套接字关闭失败!”，“”，MB_OK);<br />return;<br />}<br />WSACleanup();<br />closesockent(ss);<br />closesockent(sr);<br />return;<br />}</td></tr></tbody></table><br /><span class="f14">　　<b>三、Winsock的编程特点与异步选择机制</b><br /><br />　　1 阻塞及其处理方式<br /><br />　　在网络通讯中，由于网络拥挤或一次发送的数据量过大等原因，经常会发生交换的数据在短时间内不能传送完，收发数据的函数因此不能返回，这种现象叫做阻塞。Winsock对有可能阻塞的函数提供了两种处理方式：阻塞和非阻塞方式。在阻塞方式下，收发数据的函数在被调用后一直要到传送完毕或者出错才能返回。在阻塞期间，被阻的函数不会断调用系统函数GetMessage()来保持消息循环的正常进行。对于非阻塞方式，函数被调用后立即返回，当传送完成后由Winsock给程序发一个事先约定好的消息。<br /><br />　　在编程时，应尽量使用非阻塞方式。因为在阻塞方式下，用户可能会长时间的等待过程中试图关闭程序，因为消息循环还在起作用，所以程序的窗口可能被关闭，这样当函数从Winsock的动态连接库中返回时，主程序已经从内存中删除，这显然是极其危险的。<br /><br />　　2 异步选择函数WSAAsyncSelect()的使用<br /><br />　　Winsock通过WSAAsyncSelect()自动地设置套接字处于非阻塞方式。使用WindowsSockets实现Windows网络程序设计的关键就是它提供了对网络事件基于消息的异步存取，用于注册应用程序感兴趣的网络事件。它请求Windows Sockets DLL在检测到套接字上发生的网络事件时，向窗口发送一个消息。对UDP协议，这些网络事件主要为：<br /><br />　　FD_READ 期望在套接字收到数据(即读准备好)时接收通知；<br /><br />　　FD_WRITE 期望在套接字可发送数(即写准备好)时接收通知；<br /><br />　　FD_CLOSE 期望在套接字关闭时接电通知<br /><br />　　消息变量wParam指示发生网络事件的套接字，变量1Param的低字节描述发生的网络事件，高字包含错误码。如在窗口函数的消息循环中均加一个分支：<br /><br /><table cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>int ok=sizeof(SOCKADDR);<br />case wMsg;<br />switch(1Param)<br />{<br />case FD_READ:<br />//套接字上读数据<br />if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&amp;there1,<br /><br />(int FAR*)&amp;ok)==SOCKET_ERROR0<br />{<br />MessageBox)hwnd,“数据接收失败!”，“”，MB_OK);<br />return(FALSE);<br />}<br />case FD_WRITE:<br />//套接字上写数据<br />}<br />break；</td></tr></tbody></table><br />　　在程序的编制中，应根据需要灵活地将WSAAsyncSelect()函灵敏放在相应的消息循环之中，其它说明可参见文献[1]。此外，应该指出的是，以上程序片断中的消息框主要是为程序调试方便而设置的，而在正式产品中不再出现。同时，按照程序容错误设计，应建立一个专门的容错处理函数。程序中可能出现的各种错误都将由该函数进行处理，依据错误的危害程度不同，建立几种不同的处理措施。这样，才能保证双方通话的顺利和可靠。<br /><br />　<b>　四、结论</b><br /><br />　　本文是多媒体网络传输项目的重要内容之一，目前，结合硬件全双工语音卡等设备，已经成功地实现了话音的全双工的通信。有关整个多媒体传输系统设计的内容，将有另文叙述。 </span><br /></span><img src ="http://www.cppblog.com/eday/aggbug/18209.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-31 11:47 <a href="http://www.cppblog.com/eday/archive/2007/01/31/18209.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用VC++6.0的Sockets API实现一个聊天室程序</title><link>http://www.cppblog.com/eday/archive/2007/01/31/18207.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 31 Jan 2007 03:34:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/31/18207.html</guid><description><![CDATA[　　<b><font color="#ac000">1.VC++网络编程及Windows Sockets API简介</font></b><br /><br />　　VC++对网络编程的支持有socket支持，WinInet支持，MAPI和ISAPI支持等。其中，Windows Sockets API是TCP/IP网络环境里，也是Internet上进行开发最为通用的API。最早美国加州大学Berkeley分校在UNIX下为TCP/IP协议开发了一个API，这个API就是著名的Berkeley Socket接口(套接字)。在桌面操作系统进入Windows时代后，仍然继承了Socket方法。在TCP/IP网络通信环境下，Socket数据传输是一种特殊的I/O，它也相当于一种文件描述符，具有一个类似于打开文件的函数调用-socket()。可以这样理解：Socket实际上是一个通信端点，通过它，用户的Socket程序可以通过网络和其他的Socket应用程序通信。Socket存在于一个"通信域"(为描述一般的线程如何通过Socket进行通信而引入的一种抽象概念)里，并且与另一个域的Socket交换数据。Socket有三类。第一种是SOCK_STREAM(流式)，提供面向连接的可靠的通信服务，比如telnet,http。第二种是SOCK_DGRAM(数据报)，提供无连接不可靠的通信，比如UDP。第三种是SOCK_RAW(原始)，主要用于协议的开发和测试，支持通信底层操作，比如对IP和ICMP的直接访问。<br /><br />　　<b><font color="#ac000">2.Windows Socket机制分析</font></b><br /><br />　　2.1一些基本的Socket系统调用<br /><br />　　主要的系统调用包括：socket()-创建Socket；bind()-将创建的Socket与本地端口绑定；connect()与accept()-建立Socket连接；listen()-服务器监听是否有连接请求；send()-数据的可控缓冲发送；recv()-可控缓冲接收；closesocket()-关闭Socket。<br /><br />　　2.2Windows Socket的启动与终止<br /><br />　　启动函数WSAStartup()建立与Windows Sockets DLL的连接，终止函数WSAClearup()终止使用该DLL，这两个函数必须成对使用。<br /><br />　　2.3异步选择机制<br /><br />　　Windows是一个非抢占式的操作系统，而不采取UNIX的阻塞机制。当一个通信事件产生时，操作系统要根据设置选择是否对该事件加以处理，WSAAsyncSelect()函数就是用来选择系统所要处理的相应事件。当Socket收到设定的网络事件中的一个时，会给程序窗口一个消息，这个消息里会指定产生网络事件的Socket，发生的事件类型和错误码。<br /><br />　　2.4异步数据传输机制<br /><br />　　WSAAsyncSelect()设定了Socket上的须响应通信事件后，每发生一个这样的事件就会产生一个WM_SOCKET消息传给窗口。而在窗口的回调函数中就应该添加相应的数据传输处理代码。<br /><br />　　<b><font color="#ac000">3.聊天室程序的设计说明</font></b><br /><br />　　3.1实现思想<br /><br />　　在Internet上的聊天室程序一般都是以服务器提供服务端连接响应，使用者通过客户端程序登录到服务器，就可以与登录在同一服务器上的用户交谈，这是一个面向连接的通信过程。因此，程序要在TCP/IP环境下，实现服务器端和客户端两部分程序。<br /><br />　　3.2服务器端工作流程<br /><br />　　服务器端通过socket()系统调用创建一个Socket数组后(即设定了接受连接客户的最大数目)，与指定的本地端口绑定bind()，就可以在端口进行侦听listen()。如果有客户端连接请求，则在数组中选择一个空Socket，将客户端地址赋给这个Socket。然后登录成功的客户就可以在服务器上聊天了。<br /><br />　　3.3客户端工作流程<br /><br />　　客户端程序相对简单，只需要建立一个Socket与服务器端连接，成功后通过这个Socket来发送和接收数据就可以了。<br /><br />　　<b><font color="#ac000">4.核心代码分析</font></b><br /><br />　　限于篇幅，这里仅给出与网络编程相关的核心代码，其他的诸如聊天文字的服务器和客户端显示读者可以自行添加。<br /><br />　　4.1服务器端代码<br /><br />　　开启服务器功能:<br /><br /><table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>void OnServerOpen() //开启服务器功能<br />{ <br />　WSADATA wsaData;<br />　int iErrorCode;<br />　char chInfo[64];<br />　if (WSAStartup(WINSOCK_VERSION, &amp;wsaData)) //调用Windows Sockets DLL<br />　　{ MessageBeep(MB_ICONSTOP);<br />　　　MessageBox("Winsock无法初始化!", AfxGetAppName(), MB_OK|MB_ICONSTOP);<br />　　　WSACleanup();<br />　　　return; }<br />　else<br />　　WSACleanup(); <br />　　if (gethostname(chInfo, sizeof(chInfo)))<br />　　{ ReportWinsockErr("\n无法获取主机!\n ");<br />　　　return; }<br />　　CString csWinsockID = "\n==&gt;&gt;服务器功能开启在端口：No. ";<br />　　csWinsockID += itoa(m_pDoc-&gt;m_nServerPort, chInfo, 10);<br />　　csWinsockID += "\n";<br />　　PrintString(csWinsockID); //在程序视图显示提示信息的函数，读者可自行创建<br />　　m_pDoc-&gt;m_hServerSocket=socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL); <br />　　//创建服务器端Socket，类型为SOCK_STREAM，面向连接的通信<br />　　if (m_pDoc-&gt;m_hServerSocket == INVALID_SOCKET)<br />　　{ ReportWinsockErr("无法创建服务器socket!");<br />　　　return;}<br />　　m_pDoc-&gt;m_sockServerAddr.sin_family = AF_INET;<br />　　m_pDoc-&gt;m_sockServerAddr.sin_addr.s_addr = INADDR_ANY; <br />　　m_pDoc-&gt;m_sockServerAddr.sin_port = htons(m_pDoc-&gt;m_nServerPort);<br />　　if (bind(m_pDoc-&gt;m_hServerSocket, (LPSOCKADDR)&amp;m_pDoc-&gt;m_sockServerAddr, 　　<br />　　　　 sizeof(m_pDoc-&gt;m_sockServerAddr)) == SOCKET_ERROR) //与选定的端口绑定<br />　　　{ReportWinsockErr("无法绑定服务器socket!");<br />　　　　return;}<br />　　　iErrorCode=WSAAsyncSelect(m_pDoc-&gt;m_hServerSocket,m_hWnd,<br />　　　WM_SERVER_ACCEPT, FD_ACCEPT);<br />　　　//设定服务器相应的网络事件为FD_ACCEPT，即连接请求，<br />　　　// 产生相应传递给窗口的消息为WM_SERVER_ACCEPT<br />　　if (iErrorCode == SOCKET_ERROR) <br />　　　{ ReportWinsockErr("WSAAsyncSelect设定失败!");<br />　　　　return;} <br />　　if (listen(m_pDoc-&gt;m_hServerSocket, QUEUE_SIZE) == SOCKET_ERROR) //开始监听客户连接请求<br />　　　{ReportWinsockErr("服务器socket监听失败!");<br />　　　　m_pParentMenu-&gt;EnableMenuItem(ID_SERVER_OPEN, MF_ENABLED);<br />　　　　return;}<br />　　m_bServerIsOpen = TRUE; //监视服务器是否打开的变量<br />　return; <br />}</td></tr></tbody></table><br />　　响应客户发送聊天文字到服务器：ON_MESSAGE(WM_CLIENT_READ, OnClientRead)<br /><br /><table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>LRESULT OnClientRead(WPARAM wParam, LPARAM lParam)<br />{<br />　int iRead;<br />　int iBufferLength;<br />　int iEnd;<br />　int iRemainSpace;<br />　char chInBuffer[1024];<br />　int i;<br />　for(i=0;(i<maxclient)&&(m_aclientsocket[i]!><br /> 　　//MAXClient是服务器可响应连接的最大数目<br />　　{}<br />　if(i==MAXClient) return 0L;<br />　　iBufferLength = iRemainSpace = sizeof(chInBuffer);<br />　　iEnd = 0;<br />　　iRemainSpace -= iEnd;<br />　　iBytesRead = recv(m_aClientSocket[i], (LPSTR)(chInBuffer+iEnd), iSpaceRemaining, NO_FLAGS); 　　//用可控缓冲接收函数recv()来接收字符<br />　　iEnd+=iRead;<br />　if (iBytesRead == SOCKET_ERROR)<br />　　ReportWinsockErr("recv出错!");<br />　　chInBuffer[iEnd] = '\0';<br />　if (lstrlen(chInBuffer) != 0)<br />　　{PrintString(chInBuffer); //服务器端文字显示<br />　　　OnServerBroadcast(chInBuffer); //自己编写的函数，向所有连接的客户广播这个客户的聊天文字<br />　　}<br />　return(0L);<br />}</maxclient)&&(m_aclientsocket[i]!></td></tr></tbody></table><br />　　对于客户断开连接，会产生一个FD_CLOSE消息，只须相应地用closesocket()关闭相应的Socket即可，这个处理比较简单。<br /><br />　　4.2客户端代码<br /><br />　　连接到服务器：<br /><br /><table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" border="0"><tbody><tr><td>void OnSocketConnect()<br />{ WSADATA wsaData;<br />　DWORD dwIPAddr;<br />　SOCKADDR_IN sockAddr;<br />　if(WSAStartup(WINSOCK_VERSION,&amp;wsaData)) //调用Windows Sockets DLL<br />　{MessageBox("Winsock无法初始化!",NULL,MB_OK);<br />　　return;<br />　}<br />　m_hSocket=socket(PF_INET,SOCK_STREAM,0); //创建面向连接的socket<br />　sockAddr.sin_family=AF_INET; //使用TCP/IP协议<br />　sockAddr.sin_port=m_iPort; //客户端指定的IP地址<br />　sockAddr.sin_addr.S_un.S_addr=dwIPAddr;<br />　int nConnect=connect(m_hSocket,(LPSOCKADDR)&amp;sockAddr,sizeof(sockAddr)); //请求连接<br />　if(nConnect)<br />　　ReportWinsockErr("连接失败!");<br />　else<br />　　MessageBox("连接成功!",NULL,MB_OK);<br />　　int iErrorCode=WSAAsyncSelect(m_hSocket,m_hWnd,WM_SOCKET_READ,FD_READ); <br />　　//指定响应的事件，为服务器发送来字符<br />　if(iErrorCode==SOCKET_ERROR)<br />　MessageBox("WSAAsyncSelect设定失败!");<br />}</td></tr></tbody></table><br />　　接收服务器端发送的字符也使用可控缓冲接收函数recv()，客户端聊天的字符发送使用数据可控缓冲发送函数send()，这两个过程比较简单，在此就不加赘述了。<br /><br />　　<b><font color="#ac000">5.小结</font></b><br /><br />　　通过聊天室程序的编写，可以基本了解Windows Sockets API编程的基本过程和精要之处。本程序在VC++6.0下编译通过，在使用windows 98/NT的局域网里运行良好。<br /><br /><img src ="http://www.cppblog.com/eday/aggbug/18207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-31 11:34 <a href="http://www.cppblog.com/eday/archive/2007/01/31/18207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Sockets API实现网络异步通讯</title><link>http://www.cppblog.com/eday/archive/2007/01/31/18206.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 31 Jan 2007 03:32:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/31/18206.html</guid><description><![CDATA[
		<strong>摘要：</strong>本文对如何使用面向连接的流式套接字实现对网卡的编程以及如何实现异步网络通讯等问题进行了讨论与阐述。 <br /><br />　　<b>一、 引言</b><br /><br />　　在80年代初，美国加利福尼亚大学伯克利分校的研究人员为TCP/IP网络通信开发了一个专门用于网络通讯开发的API。这个API就是Socket接口（套接字）--当今在TCP/IP网络最为通用的一种API，也是在互联网上进行应用开发最为通用的一种API。在微软联合其它几家公司共同制定了一套Windows下的网络编程接口Windows Sockets规范后，由于在其规范中引入了一些异步函数，增加了对网络事件异步选择机制，因此更加符合Windows的消息驱动特性，使网络开发人员可以更加方便的进行高性能网络通讯程序的设计。本文接下来就针对Windows Sockets API进行面向连接的流式套接字编程以及对异步网络通讯的编程实现等问题展开讨论。<br /><br />　　<b>二、 面向连接的流式套接字编程模型的设计</b><br /><br />　　本文在方案选择上采用了在网络编程中最常用的一种模型--客户机/服务器模型。这种客户/服务器模型是一种非对称式编程模式。该模式的基本思想是把集中在一起的应用划分成为功能不同的两个部分，分别在不同的计算机上运行，通过它们之间的分工合作来实现一个完整的功能。对于这种模式而言其中一部分需要作为服务器，用来响应并为客户提供固定的服务；另一部分则作为客户机程序用来向服务器提出请求或要求某种服务。<br /><br />　　本文选取了基于TCP/IP的客户机/服务器模型和面向连接的流式套接字。其通信原理为：服务器端和客户端都必须建立通信套接字，而且服务器端应先进入监听状态，然后客户端套接字发出连接请求，服务器端收到请求后，建立另一个套接字进行通信，原来负责监听的套接字仍进行监听，如果有其它客户发来连接请求，则再建立一个套接字。默认状态下最多可同时接收5个客户的连接请求，并与之建立通信关系。因此本程序的设计流程应当由服务器首先启动，然后在某一时刻启动客户机并使其与服务器建立连接。服务器与客户机开始都必须调用Windows Sockets API函数socket()建立一个套接字sockets,然后服务器方调用bind()将套接字与一个本地网络地址捆扎在一起，再调用listen()使套接字处于一种被动的准备接收状态，同时规定它的请求队列长度。在此之后服务器就可以通过调用accept()来接收客户机的连接。<br /><br />　　相对于服务器，客户端的工作就显得比较简单了，当客户端打开套接字之后，便可通过调用connect()和服务器建立连接。连接建立之后，客户和服务器之间就可以通过连接发送和接收资料。最后资料传送结束，双方调用closesocket()关闭套接字来结束这次通讯。整个通讯过程的具体流程框图可大致用下面的流程图来表示：<br /><br /><table width="90%" align="center"><tbody><tr><td><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_table.gif" /><br />　　　　　　　　面向连接的流式套接字编程流程示意图 </td></tr></tbody></table><br /><strong>三、 软件设计要点以及异步通讯的实现<br /><br /></strong>　　根据前面设计的程序流程，可将程序划分为两部分：服务器端和客户端。而且整个实现过程可以大致用以下几个非常关键的Windows Sockets API函数将其惯穿下来：<br /><br />　　服务器方：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>socket()-&gt;bind()-&gt;listen-&gt;accept()-&gt;recv()/send()-&gt;closesocket()</td></tr></tbody></table><br />　　客户机方：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>socket()-&gt;connect()-&gt;send()/recv()-&gt;closesocket()</td></tr></tbody></table><br />　　有鉴于以上几个函数在整个网络编程中的重要性，有必要结合程序实例对其做较深入的剖析。服务器端应用程序在使用套接字之前，首先必须拥有一个Socket，系统调用socket()函数向应用程序提供创建套接字的手段。该套接字实际上是在计算机中提供了一个通信埠，可以通过这个埠与任何一个具有套接字接口的计算机通信。应用程序在网络上传输、接收的信息都通过这个套接字接口来实现的。在应用开发中如同使用文件句柄一样，可以对套接字句柄进行读写操作：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>sock=socket(AF_INET,SOCK_STREAM,0);</td></tr></tbody></table><br />　　函数的第一个参数用于指定地址族，在Windows下仅支持AF_INET(TCP/IP地址)；第二个参数用于描述套接字的类型，对于流式套接字提供有SOCK_STREAM；最后一个参数指定套接字使用的协议，一般为0。该函数的返回值保存了新套接字的句柄，在程序退出前可以用 closesocket(sock);函数来将其释放。服务器方一旦获取了一个新的套接字后应通过bind()将该套接字与本机上的一个端口相关联：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>sockin.sin_family=AF_INET;<br />sockin.sin_addr.s_addr=0;<br />sockin.sin_port=htons(USERPORT);<br />bind(sock,(LPSOCKADDR)&amp;sockin,sizeof(sockin)));</td></tr></tbody></table><br />　　该函数的第二个参数是一个指向包含有本机IP地址和端口信息的sockaddr_in结构类型的指针，其成员描述了本地端口号和本地主机地址，经过bind()将服务器进程在网络上标识出来。需要注意的是由于1024以内的埠号都是保留的埠号因此如无特别需要一般不能将sockin.sin_port的埠号设置为1024以内的值。然后调用listen()函数开始侦听，再通过accept()调用等待接收连接以完成连接的建立：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>//连接请求队列长度为1，即只允许有一个请求,若有多个请求,<br />//则出现错误，给出错误代码WSAECONNREFUSED。<br />listen(sock,1);<br />//开启线程避免主程序的阻塞<br />AfxBeginThread(Server,NULL);<br />……<br />UINT Server(LPVOID lpVoid)<br />{<br />……<br />int nLen=sizeof(SOCKADDR);<br />pView-&gt;newskt=accept(pView-&gt;sock,(LPSOCKADDR)&amp; pView-&gt;sockin,(LPINT)&amp; nLen);<br />…… <br />WSAAsyncSelect(pView-&gt;newskt,pView-&gt;m_hWnd,WM_SOCKET_MSG,FD_READ|FD_CLOSE);<br />return 1; <br />}<br /></td></tr></tbody></table><br />　　这里之所以把accept()放到一个线程中去是因为在执行到该函数时如没有客户连接服务器的请求到来，服务器就会停在accept语句上等待连接请求的到来，这势必会引起程序的阻塞，虽然也可以通过设置套接字为非阻塞方式使在没有客户等待时可以使accept（）函数调用立即返回，但这种轮询套接字的方式会使CPU处于忙等待方式，从而降低程序的运行效率大大浪费系统资源。考虑到这种情况，将套接字设置为阻塞工作方式，并为其单独开辟一个子线程，将其阻塞控制在子线程范围内而不会造成整个应用程序的阻塞。对于网络事件的响应显然要采取异步选择机制，只有采取这种方式才可以在由网络对方所引起的不可预知的网络事件发生时能马上在进程中做出及时的响应处理，而在没有网络事件到达时则可以处理其他事件，这种效率是很高的，而且完全符合Windows所标榜的消息触发原则。前面那段代码中的WSAAsyncSelect()函数便是实现网络事件异步选择的核心函数。<br /><br /><span class="f14">　　通过第四个参数注册应用程序感兴取的网络事件，在这里通过FD_READ|FD_CLOSE指定了网络读和网络断开两种事件，当这种事件发生时变会发出由第三个参数指定的自定义消息WM_SOCKET_MSG，接收该消息的窗口通过第二个参数指定其句柄。在消息处理函数中可以通过对消息参数低字节进行判断而区别出发生的是何种网络事件：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>void CNetServerView::OnSocket(WPARAM wParam,LPARAM lParam)<br />{<br />int iReadLen=0;<br />int message=lParam &amp; 0x0000FFFF;<br />switch(message)<br />{ <br />case FD_READ://读事件发生。此时有字符到达，需要进行接收处理<br />char cDataBuffer[MTU*10];<br />//通过套接字接收信息<br />iReadLen = recv(newskt,cDataBuffer,MTU*10,0);<br />//将信息保存到文件<br />if(!file.Open("ServerFile.txt",CFile::modeReadWrite))<br />file.Open("E:ServerFile.txt",CFile::modeCreate|CFile::modeReadWrite);<br />file.SeekToEnd();<br />file.Write(cDataBuffer,iReadLen);<br />file.Close(); <br />break;<br />case FD_CLOSE://网络断开事件发生。此时客户机关闭或退出。<br />……//进行相应的处理<br />break;<br />default:<br />break;<br />}<br />}</td></tr></tbody></table><br />　　在这里需要实现对自定义消息WM_SOCKET_MSG的响应，需要在头文件和实现文件中分别添加其消息映射关系：<br /><br />　　头文件：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>//{{AFX_MSG(CNetServerView)<br />//}}AFX_MSG<br />void OnSocket(WPARAM wParam,LPARAM lParam);<br />DECLARE_MESSAGE_MAP()</td></tr></tbody></table><br />　　实现文件：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>BEGIN_MESSAGE_MAP(CNetServerView, CView)<br />//{{AFX_MSG_MAP(CNetServerView)<br />//}}AFX_MSG_MAP<br />ON_MESSAGE(WM_SOCKET_MSG,OnSocket)<br />END_MESSAGE_MAP()<br /></td></tr></tbody></table><br />　　在进行异步选择使用WSAAsyncSelect()函数时，有以下几点需要引起特别的注意：<br /><br />　　1． 连续使用两次WSAAsyncSelect()函数时，只有第二次设置的事件有效，如：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);<br />WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);</td></tr></tbody></table><br />　　这样只有当FD_CLOSE事件发生时才会发送wMsg2消息。<br /><br />　　2．可以在设置过异步选择后通过再次调用WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所设置的异步事件。<br /><br />　　3．Windows Sockets DLL在一个网络事件发生后，通常只会给相应的应用程序发送一个消息，而不能发送多个消息。但通过使用一些函数隐式地允许重发此事件的消息，这样就可能再次接收到相应的消息。<br /><br />　　4．在调用过closesocket()函数关闭套接字之后不会再发生FD_CLOSE事件。<br /><br />　　以上基本完成了服务器方的程序设计，下面对于客户端的实现则要简单多了，在用socket()创建完套接字之后只需通过调用connect()完成同服务器的连接即可，剩下的工作同服务器完全一样：用send()/recv()发送/接收收据，用closesocket()关闭套接字：<br /><br /><table width="90%" bgcolor="#ffffff"><tbody><tr><td>sockin.sin_family=AF_INET; //地址族<br />sockin.sin_addr.S_un.S_addr=IPaddr; //指定服务器的IP地址<br />sockin.sin_port=m_Port; //指定连接的端口号<br />int nConnect=connect(sock,(LPSOCKADDR)&amp;sockin,sizeof(sockin));<br /></td></tr></tbody></table><br />　　本文采取的是可靠的面向连接的流式套接字。在数据发送上有write()、writev()和send()等三个函数可供选择，其中前两种分别用于缓冲发送和集中发送，而send()则为可控缓冲发送，并且还可以指定传输控制标志为MSG_OOB进行带外数据的发送或是为MSG_DONTROUTE寻径控制选项。在信宿地址的网络号部分指定数据发送需要经过的网络接口，使其可以不经过本地寻径机制直接发送出去。这也是其同write()函数的真正区别所在。由于接收数据系统调用和发送数据系统调用是一一对应的，因此对于数据的接收，在此不再赘述，相应的三个接收函数分别为：read()、readv()和recv()。由于后者功能上的全面，本文在实现上选择了send()-recv()函数对，在具体编程中应当视具体情况的不同灵活选择适当的发送-接收函数对。<br /><br />　　<b>小结：</b>TCP/IP协议是目前各网络操作系统主要的通讯协议，也是 Internet的通讯协议，本文通过Windows Sockets API实现了对基于TCP/IP协议的面向连接的流式套接字网络通讯程序的设计，并通过异步通讯和多线程等手段提高了程序的运行效率，避免了阻塞的发生。<br /></span><img src ="http://www.cppblog.com/eday/aggbug/18206.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-31 11:32 <a href="http://www.cppblog.com/eday/archive/2007/01/31/18206.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP/IP Winsock编程要点</title><link>http://www.cppblog.com/eday/archive/2007/01/31/18203.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 31 Jan 2007 02:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/31/18203.html</guid><description><![CDATA[利用Winsock编程由同步和异步方式，同步方式逻辑清晰，编程专注于应用，在抢先式的多任务操作系统中(WinNt、Win2K)采用多线程方式效率基本达到异步方式的水平，应此以下为同步方式编程要点。 <br /><br />　　1、快速通信 <br /><br />　　Winsock的Nagle算法将降低小数据报的发送速度，而系统默认是使用Nagle算法,使用 <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>int setsockopt( <br /><br />SOCKET s, <br /><br />int level, <br /><br />int optname, <br /><br />const char FAR *optval, <br /><br />int optlen <br /><br />);函数关闭它 </td></tr></tbody></table><br />　　例子： <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>SOCKET sConnect; <br /><br />sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); <br /><br />int bNodelay = 1; <br /><br />int err; <br /><br />err = setsockopt( <br /><br />sConnect, <br /><br />IPPROTO_TCP, <br /><br />TCP_NODELAY, <br /><br />(char *)&amp;bNodelay, <br /><br />sizoeof(bNodelay));//不采用延时算法 <br /><br />if (err != NO_ERROR) <br /><br />TRACE ("setsockopt failed for some reason\n");; </td></tr></tbody></table><br />　　2、SOCKET的SegMentSize和收发缓冲 <br /><br />　　TCPSegMentSize是发送接受时单个数据报的最大长度，系统默认为1460，收发缓冲大小为8192。 <br /><br />　　在SOCK_STREAM方式下，如果单次发送数据超过1460，系统将分成多个数据报传送，在对方接受到的将是一个数据流，应用程序需要增加断帧的判断。当然可以采用修改注册表的方式改变1460的大小，但MicrcoSoft认为1460是最佳效率的参数，不建议修改。 <br /><br />　　在工控系统中，建议关闭Nagle算法，每次发送数据小于1460个字节（推荐1400），这样每次发送的是一个完整的数据报，减少对方对数据流的断帧处理。 <br /><br />　　3、同步方式中减少断网时connect函数的阻塞时间 <br /><br />　　同步方式中的断网时connect的阻塞时间为20秒左右，可采用gethostbyaddr事先判断到服务主机的路径是否是通的，或者先ping一下对方主机的IP地址。 <br /><br />　　A、采用gethostbyaddr阻塞时间不管成功与否为4秒左右。 <br /><br />　　例子： <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>LONG lPort=3024; <br /><br />struct sockaddr_in ServerHostAddr;//服务主机地址 <br /><br />ServerHostAddr.sin_family=AF_INET; <br /><br />ServerHostAddr.sin_port=::htons(u_short(lPort)); <br /><br />ServerHostAddr.sin_addr.s_addr=::inet_addr("192.168.1.3"); <br /><br />HOSTENT* pResult=gethostbyaddr((const char *) &amp; <br /><br />(ServerHostAddr.sin_addr.s_addr),4,AF_INET); <br /><br />if(NULL==pResult) <br /><br />{ <br /><br />int nErrorCode=WSAGetLastError(); <br /><br />TRACE("gethostbyaddr errorcode=%d",nErrorCode); <br /><br />} <br /><br />else <br /><br />{ <br /><br />TRACE("gethostbyaddr %s\n",pResult-&gt;h_name);; <br /><br />} </td></tr></tbody></table><br />　　B、采用PING方式时间约2秒左右 <br /><br />　　暂略 <br /><br />4、同步方式中解决recv，send阻塞问题 <br /><br />　　采用select函数解决，在收发前先检查读写可用状态。 <br /><br />　　A、读 <br /><br />　　例子： <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为0-10毫秒 <br /><br />int nSelectRet; <br /><br />int nErrorCode; <br /><br />FD_SET fdr = {1, sConnect}; <br /><br />nSelectRet=::select(0, &amp;fdr, NULL, NULL, &amp;tv01);//检查可读状态 <br /><br />if(SOCKET_ERROR==nSelectRet) <br /><br />{ <br /><br />nErrorCode=WSAGetLastError(); <br /><br />TRACE("select read status errorcode=%d",nErrorCode); <br /><br />::closesocket(sConnect); <br /><br />goto 重新连接（客户方），或服务线程退出（服务方）; <br /><br />} <br /><br />if(nSelectRet==0)//超时发生，无可读数据 <br /><br />{ <br /><br />继续查读状态或向对方主动发送 <br /><br />} <br /><br />else <br /><br />{ <br /><br />读数据 <br /><br />} </td></tr></tbody></table><br />　　B、写 <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为9-10毫秒 <br /><br />int nSelectRet; <br /><br />int nErrorCode; <br /><br />FD_SET fdw = {1, sConnect}; <br /><br />nSelectRet=::select(0, NULL, NULL,&amp;fdw, &amp;tv01);//检查可写状态 <br /><br />if(SOCKET_ERROR==nSelectRet) <br /><br />{ <br /><br />nErrorCode=WSAGetLastError(); <br /><br />TRACE("select write status errorcode=%d",nErrorCode); <br /><br />::closesocket(sConnect); <br /><br />//goto 重新连接（客户方），或服务线程退出（服务方）; <br /><br />} <br /><br />if(nSelectRet==0)//超时发生，缓冲满或网络忙 <br /><br />{ <br /><br />//继续查写状态或查读状态 <br /><br />} <br /><br />else <br /><br />{ <br /><br />//发送 <br /><br />} </td></tr></tbody></table><br />　　5、改变TCP收发缓冲区大小 <br /><br />　　系统默认为8192，利用如下方式可改变。 <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>SOCKET sConnect; <br /><br />sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); <br /><br />int nrcvbuf=1024*20; <br /><br />int err=setsockopt( <br /><br />sConnect, <br /><br />SOL_SOCKET, <br /><br />SO_SNDBUF,//写缓冲，读缓冲为SO_RCVBUF <br /><br />(char *)&amp;nrcvbuf, <br /><br />sizeof(nrcvbuf)); <br /><br />if (err != NO_ERROR) <br /><br />{ <br /><br />TRACE("setsockopt Error!\n"); <br /><br />} <br /><br />在设置缓冲时，检查是否真正设置成功用 <br /><br />int getsockopt( <br /><br />SOCKET s, <br /><br />int level, <br /><br />int optname, <br /><br />char FAR *optval, <br /><br />int FAR *optlen <br /><br />); </td></tr></tbody></table><br />　　6、服务方同一端口多IP地址的bind和listen <br /><br />　　在可靠性要求高的应用中，要求使用双网和多网络通道，再服务方很容易实现，用如下方式可建立客户对本机所有IP地址在端口3024下的请求服务。 <br /><br /><table cellspacing="0" cellpadding="0" width="600" bgcolor="#ffffff" border="0"><tbody><tr><td>SOCKET hServerSocket_DS=INVALID_SOCKET; <br /><br />struct sockaddr_in HostAddr_DS;//服务器主机地址 <br /><br />LONG lPort=3024; <br /><br />HostAddr_DS.sin_family=AF_INET; <br /><br />HostAddr_DS.sin_port=::htons(u_short(lPort)); <br /><br />HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY); <br /><br />hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP); <br /><br />if(hServerSocket_DS==INVALID_SOCKET) <br /><br />{ <br /><br />AfxMessageBox("建立数据服务器SOCKET 失败!"); <br /><br />return FALSE; <br /><br />} <br /><br />if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct <br /><br />sockaddr *)(&amp;(HostAddr_DS)),sizeof(SOCKADDR))) <br /><br />{ <br /><br />int nErrorCode=WSAGetLastError (); <br /><br />TRACE("bind error=%d\n",nErrorCode); <br /><br />AfxMessageBox("Socket Bind 错误!"); <br /><br />return FALSE; <br /><br />} <br /><br />if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10个客户 <br /><br />{ <br /><br />AfxMessageBox("Socket listen 错误!"); <br /><br />return FALSE; <br /><br />} <br /><br />AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL); </td></tr></tbody></table><br />　　在客户方要复杂一些，连接断后，重联不成功则应换下一个IP地址连接。也可采用同时连接好后备用的方式。 <br /><br />　　7、用TCP/IP Winsock实现变种Client/Server <br /><br />　　传统的Client/Server为客户问、服务答，收发是成对出现的。而变种的Client/Server是指在连接时有客户和服务之分，建立好通信连接后，不再有严格的客户和服务之分，任何方都可主动发送，需要或不需要回答看应用而言，这种方式在工控行业很有用，比如RTDB作为I/O Server的客户，但I/O Server也可主动向RTDB发送开关状态变位、随即事件等信息。在很大程度上减少了网络通信负荷、提高了效率。 <br /><br />　　采用1-6的TCP/IP编程要点，在Client和Server方均已接收优先，适当控制时序就能实现。<br /><img src ="http://www.cppblog.com/eday/aggbug/18203.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-31 10:53 <a href="http://www.cppblog.com/eday/archive/2007/01/31/18203.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于Visual C++的Winsock API研究</title><link>http://www.cppblog.com/eday/archive/2007/01/31/18202.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Wed, 31 Jan 2007 02:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/31/18202.html</guid><description><![CDATA[        为了方便网络编程，90年代初，由Microsoft联合了其他几家公司共同制定了一套WINDOWS下的网络编程接口，即Windows Sockets规范，它不是一种网络协议,而是一套开放的、支持多种协议的Windows下的网络编程接口。现在的Winsock已经基本上实现了与协议无关，你可以使用Winsock来调用多种协议的功能，但较常使用的是TCP/IP协议。Socket实际在计算机中提供了一个通信端口，可以通过这个端口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输，接收的信息都通过这个Socket接口来实现。<br /><br />　　微软为VC定义了Winsock类如CAsyncSocket类和派生于CAsyncSocket 的CSocket类，它们简单易用，读者朋友当然可以使用这些类来实现自己的网络程序，但是为了更好的了解Winsock API编程技术，我们这里探讨怎样使用底层的API函数实现简单的 Winsock 网络应用程式设计，分别说明如何在Server端和Client端操作Socket，实现基于TCP/IP的数据传送，最后给出相关的源代码。<br /><br />　　在VC中进行WINSOCK的API编程开发的时候，需要在项目中使用下面三个文件，否则会出现编译错误。<br /><br />　　1．WINSOCK.H: 这是WINSOCK API的头文件，需要包含在项目中。<br /><br />　　2．WSOCK32.LIB: WINSOCK API连接库文件。在使用中，一定要把它作为项目的非缺省的连接库包含到项目文件中去。 <br /><br />　　3．WINSOCK.DLL: WINSOCK的动态连接库，位于WINDOWS的安装目录下。<br /><br />　　<font color="#ac000"><b>一、服务器端操作 socket（套接字）</b></font><br /><br />　　1)在初始化阶段调用WSAStartup()<br /><br />　　此函数在应用程序中初始化Windows Sockets DLL ，只有此函数调用成功后，应用程序才可以再调用其他Windows Sockets DLL中的API函数。在程式中调用该函数的形式如下：WSAStartup((WORD)((1&lt;&lt;8|1)，（LPWSADATA）&amp;WSAData)，其中(1&lt;&lt;8|1)表示我们用的是WinSocket1.1版本，WSAata用来存储系统传回的关于WinSocket的资料。<br /><br />　　2)建立Socket<br /><br />　　初始化WinSock的动态连接库后，需要在服务器端建立一个监听的Socket，为此可以调用Socket()函数用来建立这个监听的Socket，并定义此Socket所使用的通信协议。此函数调用成功返回Socket对象，失败则返回INVALID_SOCKET(调用WSAGetLastError()可得知原因，所有WinSocket 的函数都可以使用这个函数来获取失败的原因)。<br /><br />SOCKET PASCAL FAR socket( int af, int type, int protocol )<br />参数: af:目前只提供 PF_INET(AF_INET)；<br />type：Socket 的类型 (SOCK_STREAM、SOCK_DGRAM)；<br />protocol：通讯协定(如果使用者不指定则设为0)；<br /><br />如果要建立的是遵从TCP/IP协议的socket，第二个参数type应为SOCK_STREAM，如为UDP（数据报）的socket，应为SOCK_DGRAM。<br /><br />　　3)绑定端口<br /><br />　　接下来要为服务器端定义的这个监听的Socket指定一个地址及端口（Port），这样客户端才知道待会要连接哪一个地址的哪个端口，为此我们要调用bind()函数，该函数调用成功返回0，否则返回SOCKET_ERROR。<br />int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR *name,int namelen );<br /><br />参 数： s：Socket对象名；<br />name：Socket的地址值，这个地址必须是执行这个程式所在机器的IP地址；<br />namelen：name的长度；<br /><br />　　如果使用者不在意地址或端口的值，那么可以设定地址为INADDR_ANY，及Port为0，Windows Sockets 会自动将其设定适当之地址及Port (1024 到 5000之间的值)。此后可以调用getsockname()函数来获知其被设定的值。<br /><br />　　4）监听<br /><br />　　当服务器端的Socket对象绑定完成之后,服务器端必须建立一个监听的队列来接收客户端的连接请求。listen()函数使服务器端的Socket 进入监听状态，并设定可以建立的最大连接数(目前最大值限制为 5, 最小值为1)。该函数调用成功返回0，否则返回SOCKET_ERROR。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR listen( SOCKET s, int backlog );<br />参 数： s：需要建立监听的Socket；<br />backlog：最大连接个数；</td></tr></tbody></table><br />　　服务器端的Socket调用完listen（）后，如果此时客户端调用connect（）函数提出连接申请的话，Server 端必须再调用accept() 函数，这样服务器端和客户端才算正式完成通信程序的连接动作。为了知道什么时候客户端提出连接要求，从而服务器端的Socket在恰当的时候调用accept()函数完成连接的建立，我们就要使用WSAAsyncSelect（）函数，让系统主动来通知我们有客户端提出连接请求了。该函数调用成功返回0，否则返回SOCKET_ERROR。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR WSAAsyncSelect( SOCKET s, HWND hWnd,unsigned int wMsg, long lEvent );<br />参数： s：Socket 对象；<br />hWnd ：接收消息的窗口句柄；<br />wMsg：传给窗口的消息；<br />lEvent：被注册的网络事件，也即是应用程序向窗口发送消息的网路事件，该值为下列值FD_READ、FD_WRITE、FD_OOB、FD_ACCEPT、FD_CONNECT、FD_CLOSE的组合，各个值的具体含意为FD_READ：希望在套接字S收到数据时收到消息；FD_WRITE：希望在套接字S上可以发送数据时收到消息；FD_ACCEPT：希望在套接字S上收到连接请求时收到消息；FD_CONNECT：希望在套接字S上连接成功时收到消息；FD_CLOSE：希望在套接字S上连接关闭时收到消息；FD_OOB：希望在套接字S上收到带外数据时收到消息。 </td></tr></tbody></table><br />　　具体应用时，wMsg应是在应用程序中定义的消息名称，而消息结构中的lParam则为以上各种网络事件名称。所以，可以在窗口处理自定义消息函数中使用以下结构来响应Socket的不同事件：　　<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>switch(lParam)　<br />　 {case FD_READ:<br />　　　 …　　<br />　 break;<br />case FD_WRITE、<br />　　　 …<br />　 break;<br />　　　 …<br />} </td></tr></tbody></table><br />　　5）服务器端接受客户端的连接请求<br /><br />　　当Client提出连接请求时，Server 端hwnd视窗会收到Winsock Stack送来我们自定义的一个消息，这时，我们可以分析lParam，然后调用相关的函数来处理此事件。为了使服务器端接受客户端的连接请求，就要使用accept() 函数，该函数新建一Socket与客户端的Socket相通，原先监听之Socket继续进入监听状态，等待他人的连接要求。该函数调用成功返回一个新产生的Socket对象，否则返回INVALID_SOCKET。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>SOCKET PASCAL FAR accept( SCOKET s, struct sockaddr FAR *addr,int FAR *addrlen );<br />参数：s：Socket的识别码；<br />addr：存放来连接的客户端的地址；<br />addrlen：addr的长度</td></tr></tbody></table><br />　　6）结束 socket 连接<br /><br />　　结束服务器和客户端的通信连接是很简单的，这一过程可以由服务器或客户机的任一端启动，只要调用closesocket()就可以了，而要关闭Server端监听状态的socket，同样也是利用此函数。另外，与程序启动时调用WSAStartup()憨数相对应，程式结束前，需要调用 WSACleanup() 来通知Winsock Stack释放Socket所占用的资源。这两个函数都是调用成功返回0，否则返回SOCKET_ERROR。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR closesocket( SOCKET s );<br />参 数：s：Socket 的识别码；<br />int PASCAL FAR WSACleanup( void );<br />参 数： 无</td></tr></tbody></table><br /><strong><font color="#ac0000">二、客户端Socket的操作<br /><br /></font></strong>　　1）建立客户端的Socket<br /><br />　　客户端应用程序首先也是调用WSAStartup() 函数来与Winsock的动态连接库建立关系，然后同样调用socket() 来建立一个TCP或UDP socket（相同协定的 sockets 才能相通，TCP 对 TCP，UDP 对 UDP）。与服务器端的socket 不同的是，客户端的socket 可以调用 bind() 函数，由自己来指定IP地址及port号码；但是也可以不调用 bind()，而由 Winsock来自动设定IP地址及port号码。<br /><br />　　2）提出连接申请<br /><br />　　客户端的Socket使用connect()函数来提出与服务器端的Socket建立连接的申请，函数调用成功返回0，否则返回SOCKET_ERROR。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR connect( SOCKET s, const struct sockaddr FAR *name, int namelen );<br />参 数：s：Socket 的识别码；<br />name：Socket想要连接的对方地址；<br />namelen：name的长度</td></tr></tbody></table><br />　　<font color="#ac000"><b>三、数据的传送</b></font><br /><br />　　虽然基于TCP/IP连接协议（流套接字）的服务是设计客户机/服务器应用程序时的主流标准，但有些服务也是可以通过无连接协议（数据报套接字）提供的。先介绍一下TCP socket 与UDP socket 在传送数据时的特性：Stream (TCP) Socket 提供双向、可靠、有次序、不重复的资料传送。Datagram (UDP) Socket 虽然提供双向的通信，但没有可靠、有次序、不重复的保证，所以UDP传送数据可能会收到无次序、重复的资料，甚至资料在传输过程中出现遗漏。由于UDP Socket 在传送资料时，并不保证资料能完整地送达对方，所以绝大多数应用程序都是采用TCP处理Socket，以保证资料的正确性。一般情况下TCP Socket 的数据发送和接收是调用send() 及recv() 这两个函数来达成，而 UDP Socket则是用sendto() 及recvfrom() 这两个函数，这两个函数调用成功发挥发送或接收的资料的长度，否则返回SOCKET_ERROR。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR send( SOCKET s, const char FAR *buf,int len, int flags );<br />参数：s：Socket 的识别码<br />buf：存放要传送的资料的暂存区<br />len buf：的长度<br />flags：此函数被调用的方式</td></tr></tbody></table><br />　　对于Datagram Socket而言，若是 datagram 的大小超过限制，则将不会送出任何资料，并会传回错误值。对Stream Socket 言，Blocking 模式下，若是传送系统内的储存空间不够存放这些要传送的资料，send()将会被block住，直到资料送完为止；如果该Socket被设定为 Non-Blocking 模式，那么将视目前的output buffer空间有多少，就送出多少资料，并不会被 block 住。flags 的值可设为 0 或 MSG_DONTROUTE及 MSG_OOB 的组合。<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR recv( SOCKET s, char FAR *buf, int len, int flags );<br />参数：s：Socket 的识别码<br />buf：存放接收到的资料的暂存区<br />len buf：的长度<br />flags：此函数被调用的方式</td></tr></tbody></table><br />　　对Stream Socket 言，我们可以接收到目前input buffer内有效的资料，但其数量不超过len的大小。<br /><br />　　<b><font color="#ac000">四、自定义的CMySocket类的实现代码：</font></b><br /><br />　　根据上面的知识，我自定义了一个简单的CMySocket类，下面是我定义的该类的部分实现代码：<br /><br /><table width="100%" bgcolor="#ffffff"><tbody><tr><td>//////////////////////////////////////<br />CMySocket::CMySocket() : file://类的构造函数<br />{ <br />　WSADATA wsaD; <br />　memset( m_LastError, 0, ERR_MAXLENGTH );<br />　// m_LastError是类内字符串变量,初始化用来存放最后错误说明的字符串；<br />　// 初始化类内sockaddr_in结构变量，前者存放客户端地址，后者对应于服务器端地址;<br />　memset( &amp;m_sockaddr, 0, sizeof( m_sockaddr ) ); <br />　memset( &amp;m_rsockaddr, 0, sizeof( m_rsockaddr ) );<br />　int result = WSAStartup((WORD)((1&lt;&lt;8|1)， &amp;wsaD);//初始化WinSocket动态连接库;<br />　if( result != 0 ) // 初始化失败；<br />　{ set_LastError( "WSAStartup failed!", WSAGetLastError() );<br />　　return;<br />　}<br />}<br /><br />//////////////////////////////<br />CMySocket::~CMySocket() { WSACleanup(); }//类的析构函数；<br />////////////////////////////////////////////////////<br />int CMySocket::Create( void )<br />　{// m_hSocket是类内Socket对象，创建一个基于TCP/IP的Socket变量，并将值赋给该变量；<br />　　if ( (m_hSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP )) == INVALID_SOCKET )<br />　　{<br />　　　set_LastError( "socket() failed", WSAGetLastError() );<br />　　　return ERR_WSAERROR;<br />　　}<br />　　return ERR_SUCCESS; <br />　}<br />///////////////////////////////////////////////<br />int CMySocket::Close( void )//关闭Socket对象；<br />{<br />　if ( closesocket( m_hSocket ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "closesocket() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　file://重置sockaddr_in 结构变量；<br />　memset( &amp;m_sockaddr, 0, sizeof( sockaddr_in ) ); <br />　memset( &amp;m_rsockaddr, 0, sizeof( sockaddr_in ) );<br />　return ERR_SUCCESS;<br />}<br />/////////////////////////////////////////<br />int CMySocket::Connect( char* strRemote, unsigned int iPort )//定义连接函数；<br />{<br />　if( strlen( strRemote ) == 0 || iPort == 0 )<br />　　return ERR_BADPARAM;<br />　hostent *hostEnt = NULL;<br />　long lIPAddress = 0;<br />　hostEnt = gethostbyname( strRemote );//根据计算机名得到该计算机的相关内容；<br />　if( hostEnt != NULL )<br />　{<br />　　lIPAddress = ((in_addr*)hostEnt-&gt;h_addr)-&gt;s_addr;<br />　　m_sockaddr.sin_addr.s_addr = lIPAddress;<br />　}<br />　else<br />　{<br />　　m_sockaddr.sin_addr.s_addr = inet_addr( strRemote );<br />　}<br />　m_sockaddr.sin_family = AF_INET;<br />　m_sockaddr.sin_port = htons( iPort );<br />　if( connect( m_hSocket, (SOCKADDR*)&amp;m_sockaddr, sizeof( m_sockaddr ) ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "connect() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />///////////////////////////////////////////////////////<br />int CMySocket::Bind( char* strIP, unsigned int iPort )//绑定函数；<br />{<br />　if( strlen( strIP ) == 0 || iPort == 0 )<br />　　return ERR_BADPARAM;<br />　memset( &amp;m_sockaddr,0, sizeof( m_sockaddr ) );<br />　m_sockaddr.sin_family = AF_INET;<br />　m_sockaddr.sin_addr.s_addr = inet_addr( strIP );<br />　m_sockaddr.sin_port = htons( iPort );<br />　if ( bind( m_hSocket, (SOCKADDR*)&amp;m_sockaddr, sizeof( m_sockaddr ) ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "bind() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />//////////////////////////////////////////<br />int CMySocket::Accept( SOCKET s )//建立连接函数，S为监听Socket对象名；<br />{ <br />　int Len = sizeof( m_rsockaddr );<br />　memset( &amp;m_rsockaddr, 0, sizeof( m_rsockaddr ) );<br />　if( ( m_hSocket = accept( s, (SOCKADDR*)&amp;m_rsockaddr, &amp;Len ) ) == INVALID_SOCKET )<br />　{<br />　　set_LastError( "accept() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />/////////////////////////////////////////////////////<br />int CMySocket::asyncSelect( HWND hWnd, unsigned int wMsg, long lEvent )<br />file://事件选择函数；<br />{<br />　if( !IsWindow( hWnd ) || wMsg == 0 || lEvent == 0 )<br />　　return ERR_BADPARAM;<br />　if( WSAAsyncSelect( m_hSocket, hWnd, wMsg, lEvent ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "WSAAsyncSelect() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />////////////////////////////////////////////////////<br />int CMySocket::Listen( int iQueuedConnections )//监听函数；<br />{<br />　if( iQueuedConnections == 0 )<br />　　return ERR_BADPARAM;<br />　if( listen( m_hSocket, iQueuedConnections ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "listen() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />////////////////////////////////////////////////////<br />int CMySocket::Send( char* strData, int iLen )//数据发送函数；<br />{<br />　if( strData == NULL || iLen == 0 )<br />　　return ERR_BADPARAM;<br />　if( send( m_hSocket, strData, iLen, 0 ) == SOCKET_ERROR )<br />　{<br />　　set_LastError( "send() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ERR_SUCCESS;<br />}<br />/////////////////////////////////////////////////////<br />int CMySocket::Receive( char* strData, int iLen )//数据接收函数；<br />{<br />　if( strData == NULL )<br />　　return ERR_BADPARAM;<br />　int len = 0;<br />　int ret = 0;<br />　ret = recv( m_hSocket, strData, iLen, 0 );<br />　if ( ret == SOCKET_ERROR )<br />　{<br />　　set_LastError( "recv() failed", WSAGetLastError() );<br />　　return ERR_WSAERROR;<br />　}<br />　return ret;<br />}<br />void CMySocket::set_LastError( char* newError, int errNum )<br />file://WinSock API操作错误字符串设置函数；<br />{<br />　memset( m_LastError, 0, ERR_MAXLENGTH ); <br />　memcpy( m_LastError, newError, strlen( newError ) );<br />　m_LastError[strlen(newError)+1] = '\0';<br />}</td></tr></tbody></table><br />　　有了上述类的定义，就可以在网络程序的服务器和客户端分别定义CMySocket对象，建立连接，传送数据了。例如，为了在服务器和客户端发送数据，需要在服务器端定义两个CMySocket对象ServerSocket1和ServerSocket2，分别用于监听和连接，客户端定义一个CMySocket对象ClientSocket，用于发送或接收数据，如果建立的连接数大于一，可以在服务器端再定义CMySocket对象，但要注意连接数不要大于五。<br /><br />　　由于Socket API函数还有许多，如获取远端服务器、本地客户机的IP地址、主机名等等，读者可以再此基础上对CMySocket补充完善，实现更多的功能。<br /><img src ="http://www.cppblog.com/eday/aggbug/18202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-31 10:51 <a href="http://www.cppblog.com/eday/archive/2007/01/31/18202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Socket1.1 程序设计</title><link>http://www.cppblog.com/eday/archive/2007/01/30/18190.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Tue, 30 Jan 2007 14:07:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/30/18190.html</guid><description><![CDATA[
		<strong>一、简介<br /><br /></strong>　　Windows Sockets 是从 Berkeley Sockets 扩展而来的，其在继承 Berkeley Sockets 的基础上，又进行了新的扩充。这些扩充主要是提供了一些异步函数，并增加了符合WINDOWS消息驱动特性的网络事件异步选择机制。<br /><br />　　Windows Sockets由两部分组成：开发组件和运行组件。<br /><br />　　开发组件：Windows Sockets 实现文档、应用程序接口(API)引入库和一些头文件。<br /><br />　　运行组件：Windows Sockets 应用程序接口的动态链接库(WINSOCK.DLL)。 
<p>　　<b>二、主要扩充说明</b></p><p>　　1、异步选择机制：<br /><br />　　Windows Sockets 的异步选择函数提供了消息机制的网络事件选择，当使用它登记网络事件发生时，应用程序相应窗口函数将收到一个消息，消息中指示了发生的网络事件，以及与事件相关的一些信息。<br /><br />　　Windows Sockets 提供了一个异步选择函数 WSAAsyncSelect()，用它来注册应用程序感兴趣的网络事件，当这些事件发生时，应用程序相应的窗口函数将收到一个消息。<br /><br />　　函数结构如下：</p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);</td></tr></tbody></table><p>　　参数说明：<br /><br />　　　hWnd：窗口句柄<br /><br />　　　wMsg：需要发送的消息<br /><br />　　　lEvent：事件（以下为事件的内容）<br /></p><table cellspacing="0" width="72%" border="1"><tbody><tr><td>值：</td><td>含义：</td></tr><tr><td>FD_READ</td><td>期望在套接字上收到数据（即读准备好）时接到通知</td></tr><tr><td>FD_WRITE</td><td>期望在套接字上可发送数据（即写准备好）时接到通知</td></tr><tr><td>FD_OOB</td><td>期望在套接字上有带外数据到达时接到通知</td></tr><tr><td>FD_ACCEPT</td><td>期望在套接字上有外来连接时接到通知</td></tr><tr><td>FD_CONNECT</td><td>期望在套接字连接建立完成时接到通知</td></tr><tr><td>FD_CLOSE</td><td>期望在套接字关闭时接到通知</td></tr></tbody></table><p>　　例如：我们要在套接字读准备好或写准备好时接到通知，语句如下： <br /></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>rc=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);</td></tr></tbody></table><p>　　如果我们需要注销对套接字网络事件的消息发送，只要将 lEvent 设置为0 </p><p>　　2、异步请求函数<br /><br />　　在 Berkeley Sockets 中请求服务是阻塞的，WINDOWS SICKETS 除了支持这一类函数外，还增加了相应的异步请求函数(WSAAsyncGetXByY();)。 <br /><br />　　3、阻塞处理方法<br /><br />　　Windows Sockets 为了实现当一个应用程序的套接字调用处于阻塞时，能够放弃CPU让其它应用程序运行，它在调用处于阻塞时便进入一个叫“HOOK”的例程，此例程负责接收和分配WINDOWS消息，使得其它应用程序仍然能够接收到自己的消息并取得控制权。<br /><br />　　WINDOWS 是非抢先的多任务环境，即若一个程序不主动放弃其控制权，别的程序就不能执行。因此在设计Windows Sockets 程序时，尽管系统支持阻塞操作，但还是反对程序员使用该操作。但由于 SUN 公司下的 Berkeley Sockets 的套接字默认操作是阻塞的，WINDOWS 作为移植的 SOCKETS 也不可避免对这个操作支持。<br /><br />　　在Windows Sockets 实现中，对于不能立即完成的阻塞操作做如下处理：DLL初始化→循环操作。在循环中，它发送任何 WINDOWS 消息，并检查这个 Windows Sockets 调用是否完成，在必要时，它可以放弃CPU让其它应用程序执行（当然使用超线程的CPU就不会有这个麻烦了^_^）。我们可以调用 WSACancelBlockingCall() 函数取消此阻塞操作。<br /><br />　　在 Windows Sockets 中，有一个默认的阻塞处理例程 BlockingHook() 简单地获取并发送 WINDOWS 消息。如果要对复杂程序进行处理，Windows Sockets 中还有 WSASetBlockingHook() 提供用户安装自己的阻塞处理例程能力；与该函数相对应的则是 SWAUnhookBlockingHook()，它用于删除先前安装的任何阻塞处理例程，并重新安装默认的处理例程。请注意，设计自己的阻塞处理例程时，除了函数 WSACancelBlockingHook() 之外，它不能使用其它的 Windows Sockets API 函数。在处理例程中调用 WSACancelBlockingHook()函数将取消处于阻塞的操作，它将结束阻塞循环。</p><p>　　4、出错处理<br /><br />　　Windows Sockets 为了和以后多线程环境（WINDOWS/UNIX）兼容，它提供了两个出错处理函数来获取和设置当前线程的最近错误号。（WSAGetLastEror()和WSASetLastError()）</p><p>　　5、启动与终止<br /><br />　　使用函数 WSAStartup() 和 WSACleanup() 启动和终止套接字。<br /><br /><strong>三、Windows Sockets网络程序设计核心</strong></p><p></p><p>　　我们终于可以开始真正的 Windows Sockets 网络程序设计了。不过我们还是先看一看每个 Windows Sockets 网络程序都要涉及的内容。让我们一步步慢慢走。</p><p>　　1、启动与终止<br /><br />　　在所有 Windows Sockets 函数中，只有启动函数 WSAStartup() 和终止函数 WSACleanup() 是必须使用的。<br /><br />　　启动函数必须是第一个使用的函数，而且它允许指定 Windows Sockets API 的版本，并获得 SOCKETS的特定的一些技术细节。本结构如下：<br /></p><p></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>int PASCAL FAR WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);</td></tr></tbody></table><p>　　其中 wVersionRequested 保证 SOCKETS 可正常运行的 DLL 版本，如果不支持，则返回错误信息。<br />我们看一下下面这段代码，看一下如何进行 WSAStartup() 的调用<br /></p><p></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>WORD wVersionRequested;// 定义版本信息变量<br />WSADATA wsaData;//定义数据信息变量<br />int err;//定义错误号变量<br />wVersionRequested = MAKEWORD(1,1);//给版本信息赋值<br />err = WSAStartup(wVersionRequested, &amp;wsaData);//给错误信息赋值<br />if(err!=0)<br />{<br />return;//告诉用户找不到合适的版本<br />}<br />//确认 Windows Sockets DLL 支持 1.1 版本<br />//DLL 版本可以高于 1.1<br />//系统返回的版本号始终是最低要求的 1.1，即应用程序与DLL 中可支持的最低版本号<br />if(LOBYTE(wsaData.wVersion)!= 1|| HIBYTE(wsaData.wVersion)!=1)<br />{<br />WSACleanup();//告诉用户找不到合适的版本<br />return;<br />}<br />//Windows Sockets DLL 被进程接受，可以进入下一步操作</td></tr></tbody></table><p>　　关闭函数使用时，任何打开并已连接的 SOCK_STREAM 套接字被复位，但那些已由 closesocket() 函数关闭的但仍有未发送数据的套接字不受影响，未发送的数据仍将被发送。程序运行时可能会多次调用 WSAStartuo() 函数，但必须保证每次调用时的 wVersionRequested 的值是相同的。</p><p>　　2、异步请求服务<br /><br />　　Windows Sockets 除支持 Berkeley Sockets 中同步请求，还增加了了一类异步请求服务函数 WSAAsyncGerXByY()。该函数是阻塞请求函数的异步版本。应用程序调用它时，由 Windows Sockets DLL 初始化这一操作并返回调用者，此函数返回一个异步句柄，用来标识这个操作。当结果存储在调用者提供的缓冲区，并且发送一个消息到应用程序相应窗口。常用结构如下：<br /></p><p></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>HANDLE taskHnd;<br />char hostname="rs6000";<br />taskHnd = WSAAsyncBetHostByName(hWnd,wMsg,hostname,buf,buflen); </td></tr></tbody></table><p>　　需要注意的是，由于 Windows 的内存对像可以设置为可移动和可丢弃，因此在操作内存对象是，必须保证 WIindows Sockets DLL 对象是可用的。 <br /><br />　　3、异步数据传输<br /><br />　　使用 send() 或 sendto() 函数来发送数据，使用 recv() 或recvfrom() 来接收数据。Windows Sockets 不鼓励用户使用阻塞方式传输数据，因为那样可能会阻塞整个 Windows 环境。下面我们看一个异步数据传输实例：<br /><br />　　假设套接字 s 在连接建立后，已经使用了函数 WSAAsyncSelect() 在其上注册了网络事件 FD_READ 和 FD_WRITE，并且 wMsg 值为 UM_SOCK，那么我们可以在 Windows 消息循环中增加如下的分支语句：</p><p></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>case UM_SOCK:<br />switch(lParam)<br />{<br />case FD_READ:<br />len = recv(wParam,lpBuffer,length,0);<br />break;<br />case FD_WRITE:<br />while(send(wParam,lpBuffer,len,0)!=SOCKET_ERROR)<br />break;<br />}<br />break;</td></tr></tbody></table><p>　　4、出错处理<br /><br />　　Windows 提供了一个函数来获取最近的错误码 WSAGetLastError()，推荐的编写方式如下： <br /></p><p></p><table width="100%" bgcolor="#ffffff"><tbody><tr><td>len = send (s,lpBuffer,len,0);<br />of((len==SOCKET_ERROR)&amp;&amp;(WSAGetLastError()==WSAWOULDBLOCK)){...}</td></tr></tbody></table><img src ="http://www.cppblog.com/eday/aggbug/18190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-30 22:07 <a href="http://www.cppblog.com/eday/archive/2007/01/30/18190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Winsocket编程之套接字原理</title><link>http://www.cppblog.com/eday/archive/2007/01/30/18189.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Tue, 30 Jan 2007 14:04:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/30/18189.html</guid><description><![CDATA[
		<strong>一、客户机/服务器模式<br /><br /></strong>　　在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model)。该模式的建立基于以下两点：1、非对等作用；2、通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式： <iframe align="right" marginwidth="0" marginheight="0" src="http://www.chinabyte.com/tag/cont_flash_software.html" frameborder="0" width="360" scrolling="no" height="300"></iframe><p>　　首先服务器方要先启动，并根据请示提供相应服务：（过程如下）<br /><br />　　1、打开一通信通道并告知本地主机，它愿意在某一个公认地址上接收客户请求。<br /><br />　　2、等待客户请求到达该端口。<br /><br />　　3、接收到重复服务请求，处理该请求并发送应答信号。<br /><br />　　4、返回第二步，等待另一客户请求<br /><br />　　5、关闭服务器。<br /><br />　　客户方：<br /><br />　　1、打开一通信通道，并连接到服务器所在主机的特定端口。<br /><br />　　2、向服务器发送服务请求报文，等待并接收应答；继续提出请求……<br /><br />　　3、请求结束后关闭通信通道并终止。</p><p>　　<b>二、基本套接字</b><br /><br />　　为了更好说明套接字编程原理，给出几个基本的套接字，在以后的篇幅中会给出更详细的使用说明。<br /><br />　　1、创建套接字——socket()<br /><br />　　功能：使用前创建一个新的套接字<br /><br />　　格式：SOCKET PASCAL FAR socket(int af,int type,int procotol);<br /><br />　　参数：af: 通信发生的区域<br /><br />　　type: 要建立的套接字类型<br /><br />　　procotol: 使用的特定协议</p><p>　　2、指定本地地址——bind()<br /><br />　　功能：将套接字地址与所创建的套接字号联系起来。<br /><br />　　格式：int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);<br /><br />　　参数：s: 是由socket()调用返回的并且未作连接的套接字描述符（套接字号）。<br /><br />　　其它：没有错误，bind()返回0，否则SOCKET_ERROR<br /><br />　　地址结构说明：<br /><br />struct sockaddr_in<br />{<br />short sin_family;//AF_INET<br />u_short sin_port;//16位端口号，网络字节顺序<br />struct in_addr sin_addr;//32位IP地址，网络字节顺序<br />char sin_zero[8];//保留<br />}</p><p>　　3、建立套接字连接——connect()和accept()<br /><br />　　功能：共同完成连接工作<br /><br />　　格式：int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);<br /><br />　　SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen);<br /><br />　　参数：同上</p><p>　　4、监听连接——listen()<br /><br />　　功能：用于面向连接服务器，表明它愿意接收连接。<br /><br />　　格式：int PASCAL FAR listen(SOCKET s, int backlog);<br /></p><p>　　5、数据传输——send()与recv()<br /><br />　　功能：数据的发送与接收<br /><br />　　格式：int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);<br /><br />　　int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);<br /><br />　　参数：buf:指向存有传输数据的缓冲区的指针。 <br /><br />　　6、多路复用——select()<br /><br />　　功能：用来检测一个或多个套接字状态。<br /><br />　　格式：int PASCAL FAR select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds, <br />fd_set FAR * exceptfds,const struct timeval FAR * timeout);<br /><br />　　参数：readfds:指向要做读检测的指针<br /><br />　　　　　writefds:指向要做写检测的指针<br /><br />　　　　　exceptfds:指向要检测是否出错的指针<br /><br />　　　　　timeout:最大等待时间</p><p>　　7、关闭套接字——closesocket()<br /><br />　　功能：关闭套接字s<br /><br />　　格式：BOOL PASCAL FAR closesocket(SOCKET s);<br /><br /><strong>三、典型过程图<br /><br /></strong>　　2.1 面向连接的套接字的系统调用时序图<br /></p><p></p><p><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_54933.gif" /><br /><br />　　2.2 无连接协议的套接字调用时序图<br /><br /><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_54934.gif" /><br /><br />  　2.3 面向连接的应用程序流程图<br /><br /><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_54935.gif" /></p><p><br /></p><img src ="http://www.cppblog.com/eday/aggbug/18189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-30 22:04 <a href="http://www.cppblog.com/eday/archive/2007/01/30/18189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Winsocket编程之TCP/IP体系结构</title><link>http://www.cppblog.com/eday/archive/2007/01/30/18188.html</link><dc:creator>独孤九剑</dc:creator><author>独孤九剑</author><pubDate>Tue, 30 Jan 2007 13:59:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/30/18188.html</guid><description><![CDATA[
		<strong>一、TCP/IP 体系结构与特点</strong>
		<p>　　1、TCP/IP体系结构</p>
		<p>　　TCP/IP协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输层服务，而IP则是提供网络层服务。TCP/IP包括以下协议：（结构如图1.1）</p>
		<p>
				<img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_54678.gif" />
				<br />(图1.1) </p>
		<p>　　IP： 网间协议(Internet Protocol) 负责主机间数据的路由和网络上数据的存储。同时为ICMP，TCP，　　　UDP提供分组发送服务。用户进程通常不需要涉及这一层。<br /><br />　　ARP： 地址解析协议(Address Resolution Protocol)<br />　　　此协议将网络地址映射到硬件地址。<br /><br />　　RARP： 反向地址解析协议(Reverse Address Resolution Protocol)<br />　　　此协议将硬件地址映射到网络地址<br /><br />　　ICMP： 网间报文控制协议(Internet Control Message Protocol)<br />　　　此协议处理信关和主机的差错和传送控制。<br /><br />　　TCP： 传送控制协议(Transmission Control Protocol)<br />　　　这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务，并为数据可靠传输建立检查。（注：大多数网络用户程序使用TCP）<br /><br />　　UDP： 用户数据报协议(User Datagram Protocol)<br />　　　这是提供给用户进程的无连接协议，用于传送数据而不执行正确性检查。<br /><br />　　FTP： 文件传输协议(File Transfer Protocol)<br />　　　允许用户以文件操作的方式（文件的增、删、改、查、传送等）与另一主机相互通信。<br /><br />　　SMTP： 简单邮件传送协议(Simple Mail Transfer Protocol)<br />　　　SMTP协议为系统之间传送电子邮件。<br /><br />　　TELNET：终端协议(Telnet Terminal Procotol)<br />　　　允许用户以虚终端方式访问远程主机<br /><br />　　HTTP： 超文本传输协议(Hypertext Transfer Procotol)<br />　　<br />　　TFTP: 简单文件传输协议(Trivial File Transfer Protocol)</p>
		<p>　　2、TCP/IP特点<br /><br />　　TCP/IP协议的核心部分是传输层协议(TCP、UDP)，网络层协议(IP)和物理接口层，这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时，编程界面有两种形式：一、是由内核心直接提供的系统调用；二、使用以库函数方式提供的各种函数。前者为核内实现，后者为核外实现。用户服务要通过核外的应用程序才能实现，所以要使用套接字(socket)来实现。<br /><br />　　图1.2是TCP/IP协议核心与应用程序关系图。<br /><br /><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/r_54679.gif" /><br />(图1.2)</p>
		<p>　　<b>二、专用术语</b><br /><br />　　1、套接字<br /></p>
		<p>　　套接字是网络的基本构件。它是可以被命名和寻址的通信端点，使用中的每一个套接字都有其类型和一个与之相连听进程。套接字存在通信区域（通信区域又称地址簇）中。套接字只与同一区域中的套接字交换数据（跨区域时，需要执行某和转换进程才能实现）。WINDOWS 中的套接字只支持一个域——网际域。套接字具有类型。<br /><br />　　WINDOWS SOCKET 1.1 版本支持两种套接字：流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM) <br /><br />　　2、WINDOWS SOCKETS 实现<br /><br />　　一个WINDOWS SOCKETS 实现是指实现了WINDOWS SOCKETS规范所描述的全部功能的一套软件。一般通过DLL文件来实现</p>
		<p>　　3、阻塞处理例程<br /><br />　　阻塞处理例程(blocking hook,阻塞钩子)是WINDOWS SOCKETS实现为了支持阻塞套接字函数调用而提供的一种机制。</p>
		<p>　　4、多址广播（multicast，多点传送或组播）<br /><br />　　是一种一对多的传输方式，传输发起者通过一次传输就将信息传送到一组接收者，与单点传送<br />(unicast)和广播(Broadcast)相对应。</p>
<img src ="http://www.cppblog.com/eday/aggbug/18188.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">独孤九剑</a> 2007-01-30 21:59 <a href="http://www.cppblog.com/eday/archive/2007/01/30/18188.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>