﻿<?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++博客-道。道。道-随笔分类-Visual C++ 8.0</title><link>http://www.cppblog.com/eday/category/3080.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 19 May 2008 17:46:23 GMT</lastBuildDate><pubDate>Mon, 19 May 2008 17:46:23 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>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>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>C++ 类库</title><link>http://www.cppblog.com/eday/archive/2007/01/20/17805.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 19 Jan 2007 18:58:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2007/01/20/17805.html</guid><description><![CDATA[C++类库介绍 <br />再次体现了C++保持核心语言的效率同时大力发展应用库的发展趋势!!在C++中，库的地位是非常高的。C++之父 Bjarne Stroustrup先生多次表示了设计库来扩充功能要好过设计更多的语法的言论。现实中，C++的库门类繁多，解决的问题也是极其广泛，库从轻量级到重量级的都有。不少都是让人眼界大开，亦或是望而生叹的思维杰作。由于库的数量非常庞大，而且限于笔者水平，其中很多并不了解。所以文中所提的一些库都是比较著名的大型库。 <br /><br />标准库 <br /><br />标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年，直到标准的出台才正式定型，但是在标准库的实现上却很令人欣慰得看到多种实现，并且已被实践证明为有工业级别强度的佳作。 <br /><br />1、 Dinkumware C++ Library <br /><br />参考站点：<a href="http://www.dinkumware.com/" target="_blank"><font color="#261cdc">http://www.dinkumware.com/</font></a>'&gt;<a href="http://www.dinkumware.com/" target="_blank"><font color="#261cdc">http://www.dinkumware.com/</font></a> <br /><br />P.J. Plauger编写的高品质的标准库。P.J. Plauger博士是Dr. Dobb's程序设计杰出奖的获得者。其编写的库长期被Microsoft采用，并且最近Borland也取得了其OEM的license，在其C/C+ +的产品中采用Dinkumware的库。 <br /><br />2、 RogueWave Standard C++ Library <br /><br />参考站点：<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a> <br /><br />这个库在Borland C++ Builder的早期版本中曾经被采用，后来被其他的库给替换了。笔者不推荐使用。 <br /><br />3、SGI STL <br /><br />参考站点：<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a>'&gt;<a href="http://www.roguewave.com/" target="_blank"><font color="#261cdc">http://www.roguewave.com/</font></a> <br /><br />SGI公司的C++标准模版库。 <br /><br />4、STLport <br /><br />参考站点：<a href="http://www.stlport.org/" target="_blank"><font color="#261cdc">http://www.stlport.org/</font></a>'&gt;<a href="http://www.stlport.org/" target="_blank"><font color="#261cdc">http://www.stlport.org/</font></a> <br /><br />SGI STL库的跨平台可移植版本。 <br /><br /><br /><br />准标准库——Boost <br /><br />Boost 库是一个经过千锤百炼、可移植、提供源代码的C++库，作为标准库的后备，是C++标准化进程的发动机之一。 Boost库由C++标准委员会库工作组成员发起，在C++社区中影响甚大，其成员已近2000人。 Boost库为我们带来了最新、最酷、最实用的技术，是不折不扣的"准"标准库。 <br /><br />Boost中比较有名气的有这么几个库： <br /><br />Regex <br />正则表达式库 <br /><br />Spirit <br />LL parser framework，用C++代码直接表达EBNF <br /><br />Graph <br />图组件和算法 <br /><br />Lambda <br />在调用的地方定义短小匿名的函数对象，很实用的functional功能 <br /><br />concept check <br />检查泛型编程中的concept <br /><br />Mpl <br />用模板实现的元编程框架 <br /><br />Thread <br />可移植的C++多线程库 <br /><br />Python <br />把C++类和函数映射到Python之中 <br /><br />Pool <br />内存池管理 <br /><br />smart_ptr <br />5个智能指针，学习智能指针必读，一份不错的参考是来自CUJ的文章： <br /><br />Smart Pointers in Boost,哦，这篇文章可以查到，CUJ是提供在线浏览的。中文版见笔者在《Dr. Dobb's Journal软件研发杂志》第7辑上的译文。 <br /><br /><br />Boost 总体来说是实用价值很高，质量很高的库。并且由于其对跨平台的强调，对标准C++的强调，是编写平台无关，现代C++的开发者必备的工具。但是Boost 中也有很多是实验性质的东西，在实际的开发中实用需要谨慎。并且很多Boost中的库功能堪称对语言功能的扩展，其构造用尽精巧的手法，不要贸然的花费时间研读。Boost另外一面，比如Graph这样的库则是具有工业强度，结构良好，非常值得研读的精品代码，并且也可以放心的在产品代码中多多利用。 <br /><br />参考站点：<a href="http://www.boost.org/" target="_blank"><font color="#261cdc">http://www.boost.org</font></a>'&gt;<a href="http://www.boost.org/" target="_blank"><font color="#261cdc">http://www.boost.org</font></a>（国内镜像：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt; <a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c-view.org/tech/lib/boost/index.htm" target="_blank"><font color="#261cdc">http://www.c-view.org/tech/lib/boost/index.htm</font></a>） <br /><br />GUI <br /><br />在众多C++的库中，GUI部分的库算是比较繁荣，也比较引人注目的。在实际开发中，GUI库的选择也是非常重要的一件事情，下面我们综述一下可选择的GUI库，各自的特点以及相关工具的支持。 <br /><br />1、 MFC <br /><br />大名鼎鼎的微软基础类库（Microsoft Foundation Class）。大凡学过VC++的人都应该知道这个库。虽然从技术角度讲，MFC是不大漂亮的，但是它构建于Windows API 之上，能够使程序员的工作更容易,编程效率高，减少了大量在建立 Windows 程序时必须编写的代码，同时它还提供了所有一般 C++ 编程的优点，例如继承和封装。MFC 编写的程序在各个版本的Windows操作系统上是可移植的，例如，在 Windows 3.1下编写的代码可以很容易地移植到 Windows NT 或 Windows 95 上。但是在最近发展以及官方支持上日渐势微。 <br /><br /><br /><br />2、 QT <br /><br />参考网站：<a href="http://www.trolltech.com/" target="_blank"><font color="#261cdc">http://www.trolltech.com/</font></a>'&gt;<a href="http://www.trolltech.com/" target="_blank"><font color="#261cdc">http://www.trolltech.com/</font></a> <br /><br />Qt 是Trolltech公司的一个多平台的C++图形用户界面应用程序框架。它提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能。Qt是完全面向对象的很容易扩展，并且允许真正地组件编程。自从1996年早些时候，Qt进入商业领域，它已经成为全世界范围内数千种成功的应用程序的基础。Qt也是流行的Linux桌面环境KDE 的基础，同时它还支持Windows、Macintosh、Unix/X11等多种平台。 <br /><br /><br /><br />3、WxWindows <br /><br />参考网站：<a href="http://www.wxwindows.org/" target="_blank"><font color="#261cdc">http://www.wxwindows.org/</font></a>'&gt;<a href="http://www.wxwindows.org/" target="_blank"><font color="#261cdc">http://www.wxwindows.org/</font></a> <br /><br />跨平台的GUI库。因为其类层次极像MFC，所以有文章介绍从MFC到WxWindows的代码移植以实现跨平台的功能。通过多年的开发也是一个日趋完善的 GUI库，支持同样不弱于前面两个库。并且是完全开放源代码的。新近的C++ Builder X的GUI设计器就是基于这个库的。 <br /><br />4、Fox <br /><br />开放源代码的GUI库。作者从自己亲身的开发经验中得出了一个理想的GUI库应该是什么样子的感受出发，从而开始了对这个库的开发。有兴趣的可以尝试一下。 <br /><br />参考网站：<a href="http://www.fox/" target="_blank"><font color="#261cdc">http://www.fox</font></a>'&gt;<a href="http://www.fox-toolkit.org/" target="_blank"><font color="#261cdc">http://www.fox-toolkit.org/</font></a> <br /><br />5、 WTL <br /><br />基于ATL的一个库。因为使用了大量ATL的轻量级手法，模板等技术，在代码尺寸，以及速度优化方面做得非常到位。主要面向的使用群体是开发COM轻量级供网络下载的可视化控件的开发者。 <br /><br />6、 GTK <br /><br />参考网站：<a href="http://gtkmm.sourceforge.net/" target="_blank"><font color="#261cdc">http://gtkmm.sourceforge.net/</font></a> <br /><br />GTK是一个大名鼎鼎的C的开源GUI库。在Linux世界中有Gnome这样的杀手应用。而GTK就是这个库的C++封装版本。 <br /><br />? <br /><br />库 <br /><br /><br />网络通信 <br /><br />ACE <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cs.wustl.edu/~schmidt/ACE.html" target="_blank"><font color="#261cdc">http://www.cs.wustl.edu/~schmidt/ACE.html</font></a> <br /><br />C+ +库的代表，超重量级的网络通信开发框架。ACE自适配通信环境（Adaptive Communication Environment）是可以自由使用、开放源代码的面向对象框架，在其中实现了许多用于并发通信软件的核心模式。ACE提供了一组丰富的可复用C++ 包装外观（Wrapper Facade）和框架组件，可跨越多种平台完成通用的通信软件任务，其中包括：事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态（重）配置、并发执行和同步，等等。 <br /><br />StreamModule <br /><br />参考网站：<a href="http://www.omnifarious.org/StrMod/" target="_blank"><font color="#261cdc">http://www.omnifarious.org/StrMod/</font></a>'&gt;<a href="http://www.omnifarious.org/StrMod/" target="_blank"><font color="#261cdc">http://www.omnifarious.org/StrMod/</font></a> <br /><br />设计用于简化编写分布式程序的库。尝试着使得编写处理异步行为的程序更容易，而不是用同步的外壳包起异步的本质。 <br /><br />SimpleSocket <br /><br />参考网站：<a href="http://home.hetnet.nl/~lcbokkers/si" target="_blank"><font color="#261cdc">http://home.hetnet.nl/~lcbokkers/simsock.htm</font></a> <br /><br />这个类库让编写基于socket的客户/服务器程序更加容易。 <br /><br />A Stream Socket API for C++ <br /><br />参考网站：<a href="http://www.pcs.cnu.edu/" target="_blank"><font color="#261cdc">http://www.pcs.cnu.edu/</font></a>'&gt;<a href="http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html" target="_blank"><font color="#261cdc">http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html</font></a> <br /><br />又一个对Socket的封装库。 <br /><br />XML <br /><br />Xerces <br /><br />参考网站：<a href="http://xml.apache.org/xerces-c/" target="_blank"><font color="#261cdc">http://xml.apache.org/xerces-c/</font></a> <br /><br />Xerces-C++ 是一个非常健壮的XML解析器，它提供了验证，以及SAX和DOM API。XML验证在文档类型定义(Document Type Definition，DTD)方面有很好的支持，并且在2001年12月增加了支持W3C XML Schema 的基本完整的开放标准。 <br /><br />XMLBooster <br /><br />参考网站：<a href="http://www.xmlbooster.com/" target="_blank"><font color="#261cdc">http://www.xmlbooster.com/</font></a>'&gt;<a href="http://www.xmlbooster.com/" target="_blank"><font color="#261cdc">http://www.xmlbooster.com/</font></a> <br /><br />这个库通过产生特制的parser的办法极大的提高了XML解析的速度，并且能够产生相应的GUI程序来修改这个parser。在DOM和SAX两大主流XML解析办法之外提供了另外一个可行的解决方案。 <br /><br />Pull Parser <br /><br />参考网站：<a href="http://www.extreme.indiana.edu/xgws/xsoap/xpp/" target="_blank"><font color="#261cdc">http://www.extreme.indiana.edu/xgws/xsoap/xpp/</font></a>'&gt;<a href="http://www.extreme.indiana.edu/xgws/xsoap/xpp/" target="_blank"><font color="#261cdc">http://www.extreme.indiana.edu/xgws/xsoap/xpp/</font></a> <br /><br />这个库采用pull方法的parser。在每个SAX的parser底层都有一个pull的parser，这个xpp把这层暴露出来直接给大家使用。在要充分考虑速度的时候值得尝试。 <br /><br />Xalan <br /><br />参考网站：<a href="http://xml.apache.org/xalan-c/" target="_blank"><font color="#261cdc">http://xml.apache.org/xalan-c/</font></a> <br /><br />Xalan是一个用于把XML文档转换为HTML，纯文本或者其他XML类型文档的XSLT处理器。 <br /><br />CMarkup <br /><br />参考网站：<a href="http://www.firstobject.com/xml.htm" target="_blank"><font color="#261cdc">http://www.firstobject.com/xml.htm</font></a>'&gt;<a href="http://www.firstobject.com/xml.htm" target="_blank"><font color="#261cdc">http://www.firstobject.com/xml.htm</font></a> <br /><br />这是一种使用EDOM的XML解析器。在很多思路上面非常灵活实用。值得大家在DOM和SAX之外寻求一点灵感。 <br /><br />libxml++ <br /><br /><a href="http://libxmlplusplus.sourceforge.net/" target="_blank"><font color="#261cdc">http://libxmlplusplus.sourceforge.net/</font></a> <br /><br />libxml++是对著名的libxml XML解析器的C++封装版本 <br /><br /><br /><br />科学计算 <br /><br />Blitz++ <br /><br />参考网站：<a href="http://www.oonumerics.org/blitz/" target="_blank"><font color="#261cdc">http://www.oonumerics.org/blitz/</font></a>'&gt;<a href="http://www.oonumerics.org/blitz/" target="_blank"><font color="#261cdc">http://www.oonumerics.org/blitz/</font></a> <br /><br />Blitz++ 是一个高效率的数值计算函数库，它的设计目的是希望建立一套既具像C++ 一样方便，同时又比Fortran速度更快的数值计算环境。通常，用C++所写出的数值程序，比 Fortran慢20%左右，因此Blitz++正是要改掉这个缺点。方法是利用C++的template技术，程序执行甚至可以比Fortran更快。 Blitz++目前仍在发展中，对于常见的SVD，FFTs，QMRES等常见的线性代数方法并不提供，不过使用者可以很容易地利用Blitz++所提供的函数来构建。 <br /><br />POOMA <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.codesourcery.com/pooma/pooma" target="_blank"><font color="#261cdc">http://www.codesourcery.com/pooma/pooma</font></a> <br /><br />POOMA是一个免费的高性能的C++库，用于处理并行式科学计算。POOMA的面向对象设计方便了快速的程序开发，对并行机器进行了优化以达到最高的效率，方便在工业和研究环境中使用。 <br /><br />MTL <br /><br />参考网站：<a href="http://www.osl.iu.edu/research/mtl/" target="_blank"><font color="#261cdc">http://www.osl.iu.edu/research/mtl/</font></a>'&gt;<a href="http://www.osl.iu.edu/research/mtl/" target="_blank"><font color="#261cdc">http://www.osl.iu.edu/research/mtl/</font></a> <br /><br />Matrix Template Library(MTL)是一个高性能的泛型组件库，提供了各种格式矩阵的大量线性代数方面的功能。在某些应用使用高性能编译器的情况下，比如Intel的编译器，从产生的汇编代码可以看出其与手写几乎没有两样的效能。 <br /><br />CGAL <br /><br />参考网站：<a href="http://www.cgal.org/" target="_blank"><font color="#261cdc">www.cgal.org</font></a> <br /><br />Computational Geometry Algorithms Library的目的是把在计算几何方面的大部分重要的解决方案和方法以C++库的形式提供给工业和学术界的用户。 <br /><br /><br /><br />游戏开发 <br /><br />Audio/Video 3D C++ Programming Library <br /><br />参考网站：<a href="http://www.galacticasoftware.com/products/av/" target="_blank"><font color="#261cdc">http://www.galacticasoftware.com/products/av/</font></a>'&gt;<a href="http://www.galacticasoftware.com/products/av/" target="_blank"><font color="#261cdc">http://www.galacticasoftware.com/products/av/</font></a> <br /><br />AV3D是一个跨平台，高性能的C++库。主要的特性是提供3D图形，声效支持（SB,以及S3M），控制接口（键盘，鼠标和遥感），XMS。 <br /><br />KlayGE <br /><br />参考网站：<a href="http://home.g365.net/enginedev/" target="_blank"><font color="#261cdc">http://home.g365.net/enginedev/</font></a> <br /><br />国内游戏开发高手自己用C++开发的游戏引擎。KlayGE是一个开放源代码、跨平台的游戏引擎，并使用Python作脚本语言。KlayGE在LGPL协议下发行。感谢龚敏敏先生为中国游戏开发事业所做出的贡献。 <br /><br />OGRE <br /><br />参考网站：<a href="http://www.ogre3d.org/" target="_blank"><font color="#261cdc">http://www.ogre3d.org</font></a>'&gt;<a href="http://www.ogre3d.org/" target="_blank"><font color="#261cdc">http://www.ogre3d.org</font></a> <br /><br />OGRE （面向对象的图形渲染引擎）是用C++开发的，使用灵活的面向对象3D引擎。它的目的是让开发者能更方便和直接地开发基于3D硬件设备的应用程序或游戏。引擎中的类库对更底层的系统库（如：Direct3D和OpenGL）的全部使用细节进行了抽象，并提供了基于现实世界对象的接口和其它类。 <br /><br /><br /><br />线程 <br /><br />C++ Threads <br /><br />参考网站：<a href="http://threads.sourceforge.net/" target="_blank"><font color="#261cdc">http://threads.sourceforge.net/</font></a> <br /><br />这个库的目标是给程序员提供易于使用的类，这些类被继承以提供在Linux环境中很难看到的大量的线程方面的功能。 <br /><br />ZThreads <br /><br />参考网站：<a href="http://zthread.sourceforge.net/" target="_blank"><font color="#261cdc">http://zthread.sourceforge.net/</font></a> <br /><br />一个先进的面向对象，跨平台的C++线程和同步库。 <br /><br /><br /><br />序列化 <br /><br />s11n <br /><br />参考网站：<a href="http://s11n.net/" target="_blank"><font color="#261cdc">http://s11n.net/</font></a> <br /><br />一个基于STL的C++库，用于序列化POD，STL容器以及用户定义的类型。 <br /><br />Simple XML Persistence Library <br /><br />参考网站：<a href="http://sxp.sourceforge.net/" target="_blank"><font color="#261cdc">http://sxp.sourceforge.net/</font></a> <br /><br />这是一个把对象序列化为XML的轻量级的C++库。 <br /><br /><br /><br />字符串 <br /><br />C++ Str Library <br /><br />参考网站：<a href="http://www.utilitycode.com/str/" target="_blank"><font color="#261cdc">http://www.utilitycode.com/str/</font></a>'&gt;<a href="http://www.utilitycode.com/str/" target="_blank"><font color="#261cdc">http://www.utilitycode.com/str/</font></a> <br /><br />操作字符串和字符的库，支持Windows和支持gcc的多种平台。提供高度优化的代码，并且支持多线程环境和Unicode，同时还有正则表达式的支持。 <br /><br />Common Text Transformation Library <br /><br />参考网站：<a href="http://cttl.sourceforge.net/" target="_blank"><font color="#261cdc">http://cttl.sourceforge.net/</font></a> <br /><br />这是一个解析和修改STL字符串的库。CTTL substring类可以用来比较，插入，替换以及用EBNF的语法进行解析。 <br /><br />GRETA <br /><br />参考网站：<a href="http://research.microsoft.com/projects/greta/" target="_blank"><font color="#261cdc">http://research.microsoft.com/projects/greta/</font></a> <br /><br />这是由微软研究院的研究人员开发的处理正则表达式的库。在小型匹配的情况下有非常优秀的表现。 <br /><br />综合 <br /><br />P::Classes <br /><br />参考网站：<a href="http://pclasses.com/" target="_blank"><font color="#261cdc">http://pclasses.com/</font></a> <br /><br />一个高度可移植的C++应用程序框架。当前关注类型和线程安全的signal/slot机制，i/o系统包括基于插件的网络协议透明的i/o架构，基于插件的应用程序消息日志框架，访问sql数据库的类等等。 <br /><br />ACDK - Artefaktur Component Development Kit <br /><br />参考网站：<a href="http://acdk.sourceforge.net/" target="_blank"><font color="#261cdc">http://acdk.sourceforge.net/</font></a> <br /><br />这是一个平台无关的C++组件框架，类似于Java或者.NET中的框架（反射机制，线程，Unicode，废料收集，I/O，网络，实用工具，XML，等等），以及对Java, Perl, Python, TCL, Lisp, COM 和 CORBA的集成。 <br /><br />dlib C++ library <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cis.ohio-state.edu/~kingd/dlib/" target="_blank"><font color="#261cdc">http://www.cis.ohio-state.edu/~kingd/dlib/</font></a> <br /><br />各种各样的类的一个综合。大整数，Socket，线程，GUI，容器类,以及浏览目录的API等等。 <br /><br />Chilkat C++ Libraries <br /><br />参考网站：<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.chilkatsoft.com/cpp_libraries.asp" target="_blank"><font color="#261cdc">http://www.chilkatsoft.com/cpp_libraries.asp</font></a> <br /><br />这是提供zip，e-mail，编码，S/MIME，XML等方面的库。 <br /><br />C++ Portable Types Library (PTypes) <br /><br />参考网站：<a href="http://www.melikyan.com/ptypes/" target="_blank"><font color="#261cdc">http://www.melikyan.com/ptypes/</font></a>'&gt;<a href="http://www.melikyan.com/ptypes/" target="_blank"><font color="#261cdc">http://www.melikyan.com/ptypes/</font></a> <br /><br />这是STL的比较简单的替代品，以及可移植的多线程和网络库。 <br /><br />LFC <br /><br />参考网站：<a href="http://lfc.sourceforge.net/" target="_blank"><font color="#261cdc">http://lfc.sourceforge.net/</font></a> <br /><br />哦，这又是一个尝试提供一切的C++库 <br /><br /><br /><br />其他库 <br /><br />Loki <br /><br />参考网站：<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;http: //<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a> <br /><br />哦，你可能抱怨我早该和Boost一起介绍它，一个实验性质的库。作者在loki中把C++模板的功能发挥到了极致。并且尝试把类似设计模式这样思想层面的东西通过库来提供。同时还提供了智能指针这样比较实用的功能。 <br /><br />ATL <br /><br />ATL(Active Template Library)是一组小巧、高效、灵活的类，这些类为创建可互操作的COM组件提供了基本的设施。 <br /><br />FC++: The Functional C++ Library <br /><br />这个库提供了一些函数式语言中才有的要素。属于用库来扩充语言的一个代表作。如果想要在OOP之外寻找另一分的乐趣，可以去看看函数式程序设计的世界。大师 Peter Norvig在 "Teach Yourself Programming in Ten Years"一文中就将函数式语言列为至少应当学习的6类编程语言之一。 <br /><br />FACT! <br /><br />参考网站：<a href="http://www.kfa/" target="_blank"><font color="#261cdc">http://www.kfa</font></a>'&gt;<a href="http://www.kfa-juelich.de/zam/FACT/start/index.html" target="_blank"><font color="#261cdc">http://www.kfa-juelich.de/zam/FACT/start/index.html</font></a> <br /><br />另外一个实现函数式语言特性的库 <br /><br />Crypto++ <br /><br />提供处理密码，消息验证，单向hash，公匙加密系统等功能的免费库。 <br /><br />还有很多非常激动人心或者是极其实用的C++库，限于我们的水平以及文章的篇幅不能包括进来。在对于这些已经包含近来的库的介绍中，由于并不是每一个我们都使用过，所以难免有偏颇之处，请读者见谅。 <br /><br /><br /><br />资源网站 <br /><br />正如我们可以通过计算机历史上的重要人物了解计算机史的发展，C++相关人物的网站也可以使我们得到最有价值的参考与借鉴，下面的人物我们认为没有介绍的必要，只因下面的人物在C++领域的地位众所周知，我们只将相关的资源进行罗列以供读者学习，他们有的工作于贝尔实验室，有的工作于知名编译器厂商，有的在不断推进语言的标准化，有的为读者撰写了多部千古奇作...... <br /><br />Bjarne Stroustrup <a href="http://www.research.att.com/" target="_blank"><font color="#261cdc">http://www.research.att.com/</font></a>'&gt;<a href="http://www.research.att.com/~bs/" target="_blank"><font color="#261cdc">http://www.research.att.com/~bs/</font></a> <br /><br />Stanley B. Lippman <br /><br />http: //blogs.msdn.com/slippman/(中文版<a href="http://www.zengyihome.net/" target="_blank"><font color="#261cdc">http://www.zengyihome.net</font></a>'&gt;http: //<a href="http://www.zengyihome.net/slippman/index.htm" target="_blank"><font color="#261cdc">www.zengyihome.net/slippman/index.htm</font></a>'&gt;<a href="http://www.zengyihome.net/" target="_blank"><font color="#261cdc">http://www.zengyihome.net</font></a>'&gt;<a href="http://www.zengyihome.net/slippman/index.htm" target="_blank"><font color="#261cdc">http://www.zengyihome.net/slippman/index.htm</font></a>) <br /><br />Scott Meyers <a href="http://www.aristeia.com/" target="_blank"><font color="#261cdc">http://www.aristeia.com/</font></a>'&gt;<a href="http://www.aristeia.com/" target="_blank"><font color="#261cdc">http://www.aristeia.com/</font></a> <br /><br />David Musser <a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.c/" target="_blank"><font color="#261cdc">http://www.c</font></a>'&gt;<a href="http://www.cs.rpi.edu/~musser/" target="_blank"><font color="#261cdc">http://www.cs.rpi.edu/~musser/</font></a> <br /><br />Bruce Eckel <a href="http://www.bruceeckel.com/" target="_blank"><font color="#261cdc">http://www.bruceeckel.com</font></a>'&gt;<a href="http://www.bruceeckel.com/" target="_blank"><font color="#261cdc">http://www.bruceeckel.com</font></a> <br /><br />Nicolai M. Josuttis <a href="http://www.josuttis.com/" target="_blank"><font color="#261cdc">http://www.josuttis.com/</font></a>'&gt;<a href="http://www.josuttis.com/" target="_blank"><font color="#261cdc">http://www.josuttis.com/</font></a> <br /><br />Herb Sutter <a href="http://www.gotw.ca/" target="_blank"><font color="#261cdc">http://www.gotw.ca/</font></a>'&gt;<a href="http://www.gotw.ca/" target="_blank"><font color="#261cdc">http://www.gotw.ca/</font></a> <br /><br />Andrei Alexandrescu <a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a>'&gt;<a href="http://www.moderncppdesign.com/" target="_blank"><font color="#261cdc">http://www.moderncppdesign.com/</font></a><img src ="http://www.cppblog.com/eday/aggbug/17805.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-20 02:58 <a href="http://www.cppblog.com/eday/archive/2007/01/20/17805.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Visual C++实现Flash动画播放</title><link>http://www.cppblog.com/eday/archive/2006/12/02/15896.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 02 Dec 2006 13:36:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/12/02/15896.html</guid><description><![CDATA[　　摘要： 本文通过在VC中将外部swf格式文件内嵌为VC的内部资源，使其在程序运行过程中从资源动态释放到临时文件，从而实现VC对Flash动画的播放。<br /><br /><br />　　引言<br /><br />　　Flash动画由于可以很方便地把用户的想象通过动画显现出来，使原本只属于专业制作人员的动画制作变的异乎寻常的快捷、方便。由于Flash制作的动画在层次、内容、表现形式等诸多方面均比较出色，因此在网络上得到迅猛的发展，更有不少厂商用Flash在互联网上做起了广告和产品演示，效果丝毫不比视频的差，而体积则要小的多。Flash不仅在网络上有广泛的应用，在普通的应用程序中也可以借助Flash实现一些VC、Delphi等编程语言所难以实现的特效，比如在一些演示版的程序中完全可以将程序运行前的闪屏用Flash来制作。本文下面将通过对内嵌资源的动态释放来实现VC对Flash动画的播放，并给出了部分实现代码。<br /><br />　　嵌资源的动态释放<br /><br />　　Flash动画在此是作为程序的一个模块，虽然也可以以文件的形式作为一个外部资源来使用，但为了避免因外部模块遗失而造成程序的非正常运行，可将由Flash 5.0预先制作好的swf格式的文件以资源的形式打包到应用程序中去，而在程序运行时再将其从资源恢复到文件，使用完毕再通过程序将其从磁盘删除。<br /><br />　　在导入资源时由于swf格式文件并非VC的标准资源，所以在导入时需要在"Resource type"栏指定资源类型"SWF"，特别需要注意的是在此必须要包含引号。加入到资源后可以通过资源视图看到导入的SWF资源是以二进制形式保存的，一但加入就不能再通过资源视图对其进行编辑了。<br /><br />　　在使用SWF资源前首先要将其动态从应用程序中释放到文件中才可对资源做进一步的使用。可先通过宏MAKEINTRESOURCE（）将资源标识号IDR_SWF转换成字符串Name，再分别通过FindResource()、LoadResource()函数查找、装载该资源到内存：<br /><br />CString Type="swf"；<br />HRSRC res=FindResource (NULL,Name,Type);<br />HGLOBAL gl=LoadResource (NULL,res); <br /><br />　　当资源加载到内存后，还要通过对资源内存的锁定来返回指向资源内存的地址的指针，并籍此实现资源从内存到磁盘的保存，至于存盘的操作则由文件函数CreateFile()、和WriteFile()来完成： <br /><br />LPVOID lp=LockResource(gl); //返回指向资源内存的地址的指针。<br />CString filename="Temp.swf"； //保存的临时文件名<br />// CREATE_ALWAYS为不管文件存不存在都产生新文件。<br />fp= CreateFile(filename ,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);<br />DWORD a;<br />//sizeofResource 得到资源文件的大小<br />if (!WriteFile (fp,lp,SizeofResource (NULL,res),&amp;a,NULL))<br />return false;<br />CloseHandle (fp); //关闭句柄<br />FreeResource (gl); //释放内存 <br /><br />　　通过上述代码，可将SWF资源从应用程序中提取并释放到临时文件Temp.swf中，在此后只对此临时文件操作，与程序内嵌资源无关。<br /><br />　　Flash动画的播放<br /><br />　　swf格式的Flash动画通常主要应用在网页上，也就是说IE浏览器本身可以支持Flash动画的播放。这样就不必再单独编写用于播放swf文件的代码，从而大大减少编程的工作量。在VC ++ 6.0中新增了一个从CView派生的、用于处理网页的视类CHtmlView，由于该类是以Internet Explorer为后台支持，因此在创建工程时只需在最后一步指定视类从CHtmlView派生就可以使程序不编一行代码而具备IE浏览器的网页显示能力。<br /><br />　　程序刚生成的时候缺省的连接主页是为微软公司的主页，需要对此修改，使程序在执行时立即显示刚才提取出来的Flash临时文件Temp.swf。显示缺省主页的代码是在视类的初始化函数中进行的：<br /><br />void CEmbedModuleView::OnInitialUpdate()<br />{<br />CHtmlView::OnInitialUpdate();<br />Navigate2(_T("http://www.microsoft.com"),NULL,NULL);<br />} <br /><br />　　显然要将Navigate2（）函数的第一个参数改成Temp.swf的存放路径。刚才在释放资源到文件时并没有指定绝对路径，因此释放出来的资源文件应当和应用程序处于同一目录。但是在此处如果不写明绝对路径是无法显示该临时文件的。获取该临时文件的绝对路径可用如下方法实现：先获取应用程序本身的绝对路径，然后去处应用程序全名（程序名和扩展名）此时得到的是应用程序和临时文件所处文件夹的路径，最后只需在此基础上加上临时文件的文件名Temp.swf即可得到临时文件的全路径。下面是实现的主要代码：<br /><br /><br />//获取应用程序的全路径<br />char exeFullPath[MAX_PATH]; <br />GetModuleFileName(NULL,exeFullPath,MAX_PATH);<br />//将其格式化为字符串<br />m_TempFile.Format("%s",exeFullPath);<br />//去掉应用程序的全名（15为应用程序文件全名的长度）<br />exeFullPath[m_TempFile.GetLength()-15]='\0';<br />//得到应用程序所在路径<br />m_TempFile.Format("%s",exeFullPath);<br />//得到临时文件的全路径<br />m_TempFile+="Temp.swf"; <br /><br />　　最后将得到的临时文件的全路径m_TempFile作为参数传递给Navigate2（）即可在程序运行时把Flash动画作为主页而显示<br /><table align="center" border="0"><tbody><tr><td></td></tr></tbody></table>　　由于临时文件Temp.swf是在程序运行过程中从应用程序的资源中提取出来的，因此在程序退出之前需要将其删除。一般是在消息WM_DESTORY的响应函数里通过DeleteFile()函数来加以实现的。<br /><br />　　小结<br /><br />　　本文通过对CHtmlView和内嵌资源的动态释放实现了Flash动画在VC程序中的播放，并对资源的动态释放作了较为清晰的描述。通过类似的方法，可以将动态链接库、HTML文件等程序模块作为资源嵌入其中，在使用时再动态释放到临时文件，这样可有效避免文件模块过多时的杂乱以及程序模块丢失导致程序非正常运行等情况的发生。本文所述程序在Windows 98下，由Microsoft Visual C++ 6.0编译通过。Flash动画由 Macromedia Flash 5.0制作，所需浏览器支持为Internet Explorer 6.0。<img src ="http://www.cppblog.com/eday/aggbug/15896.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-12-02 21:36 <a href="http://www.cppblog.com/eday/archive/2006/12/02/15896.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>实现带阴影弹出窗口的技术</title><link>http://www.cppblog.com/eday/archive/2006/11/28/15709.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Mon, 27 Nov 2006 17:05:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/28/15709.html</guid><description><![CDATA[一．问题的提出 <br /><br />   在WINDOWS的WINHELPER帮助系统中大量使用一类带阴影的弹出窗口, 这类窗口非常简洁,并具有立体感,它们用来显示一些只读信息.此类弹出窗口不同于一般的窗口,它们没有标题和滚动杆,但都具有带阴影的边框, 并且其窗口的大小随显示字符串多少而自动调节,当显示信息弹出之后，任何来自键盘或鼠标的消息都将导致弹出窗口的消失。 然而WINDOWS API接口中没有现成的函数来实现此项功能,即使是最新版的 VISUAL C++ MFC也没有提供现成的类和函数来实现带阴影的此类窗口。为此,笔者基于面向对象的程序设计思想，从CWnd派生一个新类来实现这个功能，并且将该类窗口的所有函数完全封装在一起，使用就像调用“ MessageBox()”函数显示信息一样简单。 <br /><br />二．实现方法的几个关键部分说明如下 ,要解决怎样画非用户区的问题:当WINDOWS需要创建一个窗口时,它发送两个消息：WM_NCPAINT和 WM_PAINT到应用程序消息队列。WM_NCPAINT用于重画窗口的非用户区,如标题,边框和滚动杆,本程序正是响应WM_NCPAINT消息来重画带阴影的弹出窗口的边框；画客户区很简单,只需响应WM_PAINT消息处理字符的显示即可.2.如何动态调整弹出窗口的尺寸:大家知道,在一个矩形内显示文本串时,常用函数DrawText(HDC hDC,LPTSTR lpszText,int cbCount,RECT FAR* lpRect,UINT fuFormat).但是,此时我们的带阴影的弹出窗口并为建立.当然不能利用它来显示.然而,我们注意到上述函数中的最后一个参数FuFormat, 它是文字格式的组合,其中有一个鲜为人知的参数 DT_CALCRECT, 使用这个参数,字符串不显示,但它根据当前字体测量待显示串的高度, 本程序正是根据这个参数来确定弹出窗口的大小，并以此建立一个随字符串大小而变化的窗口,下面给出其实现该功能的片断: void CShadowWnd::ShowText(CString sText) dc.CreateDC("DISPLAY",NULL,NULL,NULL); //创建一个显示设备描述表 dc.SelectObject(GetStockObject(SYSTEM_FONT)); //选择字体到设备描述表 CRect rect(0,0,MAXWIDTH,0);//　 <br /><br />//获得待显示的字符串 sText 的实际高度和宽度,并将其存入矩形rect中　　　 <br /><br />  dc.DrawText(sText,rect,DT_WORDBREAK|DT_CENTER|DT_CALCRECT|DT_NOPREFIX); <br /><br />3.怎样获取对系统的控制权: <br /><br />   在带阴影的弹出窗口显示之后,怎样获取对系统的控制权,使得当用户按下键盘任意键或鼠标时都将使带阴影的弹出窗口消失,这里采取的方法是,当弹出窗口创建和显示之后,立即进入一个消息循环,从应用程序队列中获取所有消息,并判断是否为鼠标消息或键盘消息,如是,则摧毁窗口结束,并将控制权归还给调用程序.实现片断如下: <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: #008080"> 1</span> <span style="COLOR: #000000">    MSG Msg;<br /></span><span style="COLOR: #008080"> 2</span> <span style="COLOR: #000000">    BOOL bDone;<br /></span><span style="COLOR: #008080"> 3</span> <span style="COLOR: #000000">    SetCapture();<br /></span><span style="COLOR: #008080"> 4</span> <span style="COLOR: #000000">    bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080"> 5</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">bDone)<br /></span><span style="COLOR: #008080"> 6</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080"> 7</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(PeekMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg,NULL,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,PM_REMOVE))<br /></span><span style="COLOR: #008080"> 8</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_KEYDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_SYSKEYDOWN</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 9</span> <span style="COLOR: #000000">                Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_LBUTTONDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_RBUTTONDOWN)<br /></span><span style="COLOR: #008080">10</span> <span style="COLOR: #000000">                bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> TRUE;<br /></span><span style="COLOR: #008080">11</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">12</span> <span style="COLOR: #000000">            {<br /></span><span style="COLOR: #008080">13</span> <span style="COLOR: #000000">                TranslateMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">14</span> <span style="COLOR: #000000">                DispatchMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">15</span> <span style="COLOR: #000000">            }<br /></span><span style="COLOR: #008080">16</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">17</span>  ReleaseCapture();<br />      DestroyWindow();<span style="COLOR: #000000"></span></div><br />. 带阴影的类 CShadowWnd 类的头文件及其实现文件的全部细节<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: #008080"> 1</span> <span style="COLOR: #000000">#pragma once<br /></span><span style="COLOR: #008080"> 2</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 3</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 4</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> CShadowWnd</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 5</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 6</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000"> CShadowWnd : </span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000"> CWnd<br /></span><span style="COLOR: #008080"> 7</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 8</span> <span style="COLOR: #000000">    DECLARE_DYNAMIC(CShadowWnd)<br /></span><span style="COLOR: #008080"> 9</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">10</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">11</span> <span style="COLOR: #000000">    CShadowWnd();<br /></span><span style="COLOR: #008080">12</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">~</span><span style="COLOR: #000000">CShadowWnd();<br /></span><span style="COLOR: #008080">13</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">14</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">protected</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">15</span> <span style="COLOR: #000000">    DECLARE_MESSAGE_MAP()<br /></span><span style="COLOR: #008080">16</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">17</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">18</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000"> BOOL Create(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000"> RECT</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000"> rect, CWnd</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000"> pParentWnd);<br /></span><span style="COLOR: #008080">19</span> <span style="COLOR: #000000">    CString m_sShowText;<br /></span><span style="COLOR: #008080">20</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> ShowReadOnlyText(CString sText);<br /></span><span style="COLOR: #008080">21</span> <span style="COLOR: #000000">    CBrush m_bmpBrush;<br /></span><span style="COLOR: #008080">22</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">protected</span><span style="COLOR: #000000">:<br /></span><span style="COLOR: #008080">23</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> OnNcPaint();<br /></span><span style="COLOR: #008080">24</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> OnPaint();<br /></span><span style="COLOR: #008080">25</span> <span style="COLOR: #000000">    afx_msg </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> OnCreate(LPCREATESTRUCT lpCreateStruct);<br /></span><span style="COLOR: #008080">26</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">27</span> <span style="COLOR: #000000">};<br /></span><span style="COLOR: #008080">28</span> <span style="COLOR: #000000"></span></div><br />// cpp<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: #008080">  1</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000"> ShadowWnd.cpp : 实现文件<br /></span><span style="COLOR: #008080">  2</span> <span style="COLOR: #008000"></span><span style="COLOR: #008000">//<br /></span><span style="COLOR: #008080">  3</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  4</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">stdafx.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  5</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">MFCApp.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  6</span> <span style="COLOR: #000000">#include </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">ShadowWnd.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  7</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">  8</span> <span style="COLOR: #000000"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影位图数组 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080">  9</span> <span style="COLOR: #008000"></span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> aPattern[]</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">{</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0xAA</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0x55</span><span style="COLOR: #000000">};<br /></span><span style="COLOR: #008080"> 10</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH 10    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影宽度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 11</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT 13    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">阴影高度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 12</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">    MAXWIDTH    400            </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">显示字符矩形的最大宽度 </span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 13</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 14</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 15</span> <span style="COLOR: #000000">IMPLEMENT_DYNAMIC(CShadowWnd, CWnd)<br /></span><span style="COLOR: #008080"> 16</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 17</span> <span style="COLOR: #000000">CShadowWnd::CShadowWnd()<br /></span><span style="COLOR: #008080"> 18</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 19</span> <span style="COLOR: #000000">    CBitmap bmp;<br /></span><span style="COLOR: #008080"> 20</span> <span style="COLOR: #000000">    bmp.CreateBitmap(</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)aPattern);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一个阴影位图 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 21</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    m_bmpBrush.CreatePatternBrush(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">bmp);        </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一把阴影刷 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 22</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 23</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 24</span> <span style="COLOR: #000000">CShadowWnd::</span><span style="COLOR: #000000">~</span><span style="COLOR: #000000">CShadowWnd()<br /></span><span style="COLOR: #008080"> 25</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 26</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 27</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 28</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 29</span> <span style="COLOR: #000000">BEGIN_MESSAGE_MAP(CShadowWnd, CWnd)<br /></span><span style="COLOR: #008080"> 30</span> <span style="COLOR: #000000">    ON_WM_NCPAINT()<br /></span><span style="COLOR: #008080"> 31</span> <span style="COLOR: #000000">    ON_WM_PAINT()<br /></span><span style="COLOR: #008080"> 32</span> <span style="COLOR: #000000">    ON_WM_CREATE()<br /></span><span style="COLOR: #008080"> 33</span> <span style="COLOR: #000000">END_MESSAGE_MAP()<br /></span><span style="COLOR: #008080"> 34</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 35</span> <span style="COLOR: #000000">BOOL CShadowWnd::Create(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000"> RECT</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000"> rect, CWnd</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000"> pParentWnd)<br /></span><span style="COLOR: #008080"> 36</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 37</span> <span style="COLOR: #000000">    LPCTSTR pClassName </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> AfxRegisterWndClass(CS_HREDRAW</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">CS_VREDRAW);<br /></span><span style="COLOR: #008080"> 38</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> CWnd::CreateEx(WS_EX_STATICEDGE,pClassName,L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Shadow window</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,WS_POPUP,<br /></span><span style="COLOR: #008080"> 39</span> <span style="COLOR: #000000">        rect.left,rect.top,rect.right,rect.bottom,<br /></span><span style="COLOR: #008080"> 40</span> <span style="COLOR: #000000">        pParentWnd</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">GetSafeHwnd(),</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,NULL);<br /></span><span style="COLOR: #008080"> 41</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 42</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::OnNcPaint()<br /></span><span style="COLOR: #008080"> 43</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 44</span> <span style="COLOR: #000000">    CWindowDC dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 45</span> <span style="COLOR: #000000">    CRect rc;<br /></span><span style="COLOR: #008080"> 46</span> <span style="COLOR: #000000">    GetWindowRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">rc);<br /></span><span style="COLOR: #008080"> 47</span> <span style="COLOR: #000000">    rc.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> rc.left;<br /></span><span style="COLOR: #008080"> 48</span> <span style="COLOR: #000000">    rc.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> rc.top;<br /></span><span style="COLOR: #008080"> 49</span> <span style="COLOR: #000000">    rc.top </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 50</span> <span style="COLOR: #000000">    rc.left </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 51</span> <span style="COLOR: #000000">    m_bmpBrush.UnrealizeObject();<br /></span><span style="COLOR: #008080"> 52</span> <span style="COLOR: #000000">    CBrush </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">OldBrush </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> dc.SelectObject(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">m_bmpBrush);<br /></span><span style="COLOR: #008080"> 53</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画底部阴影 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 54</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.PatBlt(rc.left</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,rc.bottom</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT,<br /></span><span style="COLOR: #008080"> 55</span> <span style="COLOR: #000000">        rc.right</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,SPOPUP_SHADOWHEIGHT,PATCOPY);<br /></span><span style="COLOR: #008080"> 56</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画右边阴影 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 57</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.PatBlt(rc.right</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH,rc.top</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT,<br /></span><span style="COLOR: #008080"> 58</span> <span style="COLOR: #000000">        SPOPUP_SHADOWWIDTH,rc.bottom,PATCOPY);<br /></span><span style="COLOR: #008080"> 59</span> <span style="COLOR: #000000">    dc.SelectObject(OldBrush);<br /></span><span style="COLOR: #008080"> 60</span> <span style="COLOR: #000000">    CBrush </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pBrush </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> CBrush::FromHandle(GetSysColorBrush(COLOR_WINDOWFRAME));<br /></span><span style="COLOR: #008080"> 61</span> <span style="COLOR: #000000">    rc.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 62</span> <span style="COLOR: #000000">    rc.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 63</span> <span style="COLOR: #000000">    dc.FrameRect(rc,pBrush);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">画边框 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 64</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 65</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 66</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::OnPaint()<br /></span><span style="COLOR: #008080"> 67</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 68</span> <span style="COLOR: #000000">    CPaintDC dc(</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"> device context for painting</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 69</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CRect rect;<br /></span><span style="COLOR: #008080"> 70</span> <span style="COLOR: #000000">    GetClientRect(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">rect);<br /></span><span style="COLOR: #008080"> 71</span> <span style="COLOR: #000000">    rect.left </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 72</span> <span style="COLOR: #000000">    rect.top </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080"> 73</span> <span style="COLOR: #000000">    rect.right </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 74</span> <span style="COLOR: #000000">    rect.bottom </span><span style="COLOR: #000000">-=</span><span style="COLOR: #000000"> SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 75</span> <span style="COLOR: #000000">    dc.SetTextColor(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">255</span><span style="COLOR: #000000">));<br /></span><span style="COLOR: #008080"> 76</span> <span style="COLOR: #000000">    dc.DrawText(m_sShowText,rect,DT_WORDBREAK</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_NOPREFIX);<br /></span><span style="COLOR: #008080"> 77</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080"> 78</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080"> 79</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> CShadowWnd::ShowReadOnlyText(CString sText)<br /></span><span style="COLOR: #008080"> 80</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080"> 81</span> <span style="COLOR: #000000">    m_sShowText </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> sText;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">存入显示字符串 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 82</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CDC dc;<br /></span><span style="COLOR: #008080"> 83</span> <span style="COLOR: #000000">    dc.CreateDC(L</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">DISPLAY</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,NULL,NULL,NULL);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建一个显示设备描述表 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 84</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.SelectObject(GetStockObject(SYSTEM_FONT));    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">选择字体到设备描述表 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 85</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    CRect rect(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,MAXWIDTH,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br /></span><span style="COLOR: #008080"> 86</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">获得待显示的字符串 sText 的实际高度和宽度 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 87</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    dc.DrawText(sText,rect,DT_WORDBREAK</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_CENTER</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_CALCRECT</span><span style="COLOR: #000000">|</span><span style="COLOR: #000000">DT_NOPREFIX);<br /></span><span style="COLOR: #008080"> 88</span> <span style="COLOR: #000000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">为矩形留些余量 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 89</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    rect.right </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">SPOPUP_SHADOWWIDTH;<br /></span><span style="COLOR: #008080"> 90</span> <span style="COLOR: #000000">    rect.bottom </span><span style="COLOR: #000000">+=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">SPOPUP_SHADOWHEIGHT;<br /></span><span style="COLOR: #008080"> 91</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Create(rect,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">创建窗口 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 92</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">ShowWindow(SW_SHOW);<br /></span><span style="COLOR: #008080"> 93</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">UpdateWindow();    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">立刻更新窗口 <br /></span><span style="COLOR: #008080"> 94</span> <span style="COLOR: #008000">    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">进入消息循环,获取全部消息,控制整个系统 </span><span style="COLOR: #008000"><br /></span><span style="COLOR: #008080"> 95</span> <span style="COLOR: #008000"></span><span style="COLOR: #000000">    MSG Msg;<br /></span><span style="COLOR: #008080"> 96</span> <span style="COLOR: #000000">    BOOL bDone;<br /></span><span style="COLOR: #008080"> 97</span> <span style="COLOR: #000000">    SetCapture();<br /></span><span style="COLOR: #008080"> 98</span> <span style="COLOR: #000000">    bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> FALSE;<br /></span><span style="COLOR: #008080"> 99</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">!</span><span style="COLOR: #000000">bDone)<br /></span><span style="COLOR: #008080">100</span> <span style="COLOR: #000000">    {<br /></span><span style="COLOR: #008080">101</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(PeekMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg,NULL,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,PM_REMOVE))<br /></span><span style="COLOR: #008080">102</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_KEYDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_SYSKEYDOWN</span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">103</span> <span style="COLOR: #000000">                Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_LBUTTONDOWN </span><span style="COLOR: #000000">||</span><span style="COLOR: #000000"> Msg.message </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> WM_RBUTTONDOWN)<br /></span><span style="COLOR: #008080">104</span> <span style="COLOR: #000000">                bDone </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> TRUE;<br /></span><span style="COLOR: #008080">105</span> <span style="COLOR: #000000">            </span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">106</span> <span style="COLOR: #000000">            {<br /></span><span style="COLOR: #008080">107</span> <span style="COLOR: #000000">                TranslateMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">108</span> <span style="COLOR: #000000">                DispatchMessage(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">Msg);<br /></span><span style="COLOR: #008080">109</span> <span style="COLOR: #000000">            }<br /></span><span style="COLOR: #008080">110</span> <span style="COLOR: #000000">    }<br /></span><span style="COLOR: #008080">111</span> <span style="COLOR: #000000">    ReleaseCapture();<br /></span><span style="COLOR: #008080">112</span> <span style="COLOR: #000000">    DestroyWindow();<br /></span><span style="COLOR: #008080">113</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">114</span> <span style="COLOR: #000000"><br /></span><span style="COLOR: #008080">115</span> <span style="COLOR: #000000"></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> CShadowWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)<br /></span><span style="COLOR: #008080">116</span> <span style="COLOR: #000000">{<br /></span><span style="COLOR: #008080">117</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000"> (CWnd::OnCreate(lpCreateStruct) </span><span style="COLOR: #000000">==</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">)<br /></span><span style="COLOR: #008080">118</span> <span style="COLOR: #000000">        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">119</span> <span style="COLOR: #000000">    CenterWindow();<br /></span><span style="COLOR: #008080">120</span> <span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #008080">121</span> <span style="COLOR: #000000">}<br /></span><span style="COLOR: #008080">122</span> <span style="COLOR: #000000"></span></div><br />四.使用方法: <br /><br />1.   将该类增加到一个项目文件中 <br /><br />2. 在你欲使用函数的类(一般为视类或框架窗口类)中增加一个成员变量(如:CShadowWnd m_ShadowWnd),当需要使用带阴影的弹出窗口显示信息时,调用成员函数(如: m_ShadowWnd.ShowReadOnlyText(String sText)即可,无须考虑其实现细节<img src ="http://www.cppblog.com/eday/aggbug/15709.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-28 01:05 <a href="http://www.cppblog.com/eday/archive/2006/11/28/15709.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在VC中改变控件的背景色</title><link>http://www.cppblog.com/eday/archive/2006/11/27/15706.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Mon, 27 Nov 2006 12:35:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/27/15706.html</guid><description><![CDATA[        在VC编程中要改变控件（诸如CView, CFrameWnd, or CWnd等）的背景色可通过处理特定的消息来实现。但如果想改变按钮的颜色,就只能使用自绘制的按钮(也可以用位图按钮，此处未做说明)而不能通过OnCtlColor()改变。 <br />　　 一、在一个MFC应用程序中，要改变控件的背景色可通过重载OnCtlColor()函数来实现。方法是在该函数中设置所需颜色后再返回一个画刷句柄便可重绘控件背景色。OnCtlColor()函数对于控件背景色的处理是通过捕捉相应的控件消息来实现的。常用的此类消息有： <br /><br />CTLCOLOR_DLG 对话框 <br /><br />CTLCOLOR_EDIT 编辑框 <br /><br />CTLCOLOR_LISTBOX 列表框 <br /><br />CTLCOLOR_MSGBOX 消息框 <br /><br />CTLCOLOR_SCROLLBAR 滑动条 <br /><br />CTLCOLOR_STATIC 静态文本框、矩形等。 <br /><br />以下示例代码说明如何更改以上控件的背景色： <br /><br />//CmyDialog.h定义 <br /><br />class CMyDialog : public Cdialog //派生自己的对话框类 <br /><br />{ <br /><br />…….. <br /><br />// Implementation <br /><br />protected: <br /><br />// Generated message map functions <br /><br />//{{AFX_MSG(CMyDialog) <br /><br />afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); <br /><br />……. <br /><br />//}}AFX_MSG <br /><br />DECLARE_MESSAGE_MAP() <br /><br />}; <br /><br />//CmyDialog.cpp 定义 <br /><br />…… <br /><br />HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) <br /><br />{ <br /><br />switch (nCtlColor) { <br /><br />case CTLCOLOR_EDIT: <br /><br />case CTLCOLOR_MSGBOX: <br /><br />case CTLCOLOR_DLG : <br /><br />case CTLCOLOR_EDIT : //在此加入你想要改变背景色的控件消息 <br /><br />pDC-&gt;SetBkMode(TRANSPARENT); <br /><br />HBRUSH B = CreateSolidBrush(COLOR); //COLOR是你想设置的颜色 <br /><br />return (HBRUSH) B; <br /><br />default: //其他控件设置自己默认的颜色和背景刷. <br /><br />return CDialog::OnCtlColor(pDC, pWnd, nCtlColor); <br /><br />}} <br /><br />说明：1、可分别处理以上消息以实现不同控件不同背景色。 <br /><br />2、此方法不适用于按纽控件。 <br /><br />二、通过定制来实现不同颜色按纽。 <br /><br />以下通过定制方形彩色按纽来说明： <br /><br />第一步：派生出自己的按纽类。 <br /><br />//CcolorButton.h <br /><br />class CColorButton : public CButton <br /><br />{ <br /><br />DECLARE_DYNAMIC(CColorButton) <br /><br />public: <br /><br />CColorButton(); <br /><br />virtual ~CColorButton(); <br /><br />BOOL Attach(const UINT nID, CWnd* pParent, <br /><br />const COLORREF BGColor = RGB(192, 123, 192), // 按纽的背景色 <br /><br />const COLORREF FGColor = RGB(1, 1, 1), // 文本颜色 <br /><br />); <br /><br />protected: <br /><br />virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS); //重定义虚拟函数DrawItem <br /><br />void DrawFrame(CDC *DC, CRect R); //绘制按纽框 <br /><br />void DrawFilledRect(CDC *DC, CRect R, COLORREF color); //填充按纽框 <br /><br />void DrawLine(CDC *DC, CRect EndPoints, COLORREF color); <br /><br />void DrawLine(CDC *DC, long left, long top, long right, long bottom, COLORREF color); <br /><br />void DrawButtonText(CDC *DC, CRect R, const char *Buf, COLORREF TextColor); <br /><br />//绘制按纽上的文本 <br /><br />COLORREF GetFGColor() { return m_fg; } <br /><br />COLORREF GetBGColor() { return m_bg; } <br /><br />private: <br /><br />COLORREF m_fg, m_bg; <br /><br />}; <br /><br />#endif <br /><br />第二步：定义各函数 <br /><br />//CcolorButton.cpp <br /><br />…… <br /><br />// CColorButton <br /><br />IMPLEMENT_DYNAMIC(CColorButton, CButton) <br /><br />CColorButton::CColorButton() <br /><br />{ } <br /><br />CColorButton::~CColorButton() <br /><br />{ <br /><br />} <br /><br />//定义Attach（）函数 <br /><br />BOOL CColorButton::Attach(const UINT nID, CWnd* pParent, const COLORREF BGColor, const COLORREF FGColor) <br /><br />{ <br /><br />if (!SubclassDlgItem(nID, pParent)) <br /><br />return FALSE; <br /><br />m_fg = FGColor; <br /><br />m_bg = BGColor; <br /><br />return TRUE; <br /><br />} <br /><br />//重载DrawItem（） <br /><br />void CColorButton::DrawItem(LPDRAWITEMSTRUCT lpDIS) <br /><br />{ <br /><br />CDC* pDC = CDC::FromHandle(lpDIS-&gt;hDC); <br /><br />UINT state = lpDIS-&gt;itemState; <br /><br />CRect focusRect, btnRect; <br /><br />focusRect.CopyRect(&amp;lpDIS-&gt;rcItem); //按纽的选中虚线框 <br /><br />btnRect.CopyRect(&amp;lpDIS-&gt;rcItem); <br /><br />// 设置表示按纽被选中的虚线框 <br /><br />focusRect.left += 4; <br /><br />focusRect.right -= 4; <br /><br />focusRect.top += 4; <br /><br />focusRect.bottom -= 4; <br /><br />// 按纽标题 <br /><br />const int bufSize = 512; <br /><br />TCHAR buffer[bufSize]; <br /><br />GetWindowText(buffer, bufSize); <br /><br />// 绘制并标志按纽 <br /><br />DrawFilledRect(pDC, btnRect, GetBGColor()); <br /><br />DrawFrame(pDC, btnRect); <br /><br />DrawButtonText(pDC, btnRect, buffer, GetFGColor()); <br /><br />// 如果按纽处于选中状态则在其上绘制选中虚线框 <br /><br />if (state &amp; ODS_FOCUS) { <br /><br />DrawFocusRect(lpDIS-&gt;hDC, (LPRECT)&amp;focusRect); <br /><br />} <br /><br />} <br /><br />void CColorButton::DrawFrame(CDC *DC, CRect R) <br /><br />{ //绘制按纽，用户通过定制该函数可实现不同形状的按纽。 <br /><br />DrawLine(DC, R.left, R.top, R.right, R.top, RGB(255, 255, 255)); <br /><br />DrawLine(DC, R.left, R.top, R.left, R.bottom, RGB(255, 255, 255)); <br /><br />//以下绘制按纽的外围框线以使按纽有立体感 <br /><br />DrawLine(DC, R.left + 1, R.bottom - 1, R.right, R.bottom - 1, RGB(1, 1, 1)); <br /><br />//绘制按纽左框线和上框线 <br /><br />DrawLine(DC, R.right - 1, R.top + 1, R.right - 1, R.bottom, RGB(1, 1, 1)); <br /><br />//绘制按纽右框线和下框线 <br /><br />} <br /><br />//用色彩填充按纽框 <br /><br />void CColorButton::DrawFilledRect(CDC *DC, CRect R, COLORREF color) <br /><br />{ <br /><br />CBrush B; <br /><br />B.CreateSolidBrush(color); <br /><br />DC-&gt;FillRect(R, &amp;B); <br /><br />} <br /><br />// DrawLine用于绘制按纽，其为多态函数 <br /><br />void CColorButton::DrawLine(CDC *DC, CRect EndPoints, COLORREF color) <br /><br />{ <br /><br />…… <br /><br />} <br /><br />void CColorButton::DrawLine(CDC *DC, long left, long top, long right, long bottom, COLORREF color) <br /><br />{ <br /><br />…… <br /><br />} <br /><br />//绘制按纽文本 <br /><br />void CColorButton::DrawButtonText(CDC *DC, CRect R, const char *Buf, COLORREF TextColor) <br /><br />{ <br /><br />COLORREF prevColor = DC-&gt;SetTextColor(TextColor); <br /><br />DC-&gt;SetBkMode(TRANSPARENT); <br /><br />DC-&gt;DrawText(Buf, strlen(Buf), R, DT_CENTER|DT_VCENTER|DT_SINGLELINE); <br /><br />DC-&gt;SetTextColor(prevColor); <br /><br />} <br /><br />第三步：引用定制类 <br /><br />定制任意对话框CColorDlg，在其上画一按键控件。ID为IDOK。 <br /><br />//CColorDlg.h <br /><br />class CColorDlg : public CDialog <br /><br />{ <br /><br />….. <br /><br />// Implementation <br /><br />protected: <br /><br />CColorButton m_btnOK; <br /><br />} <br /><br />//CColorDlg.cpp <br /><br />……. <br /><br />BOOL CColorBtnSampleDlg::OnInitDialog() <br /><br />{ <br /><br />CDialog::OnInitDialog(); <br /><br />……. <br /><br />VERIFY(m_btnOK.Attach(IDOK, this, RED, BLUE, YELLOW)); <br /><br />……. <br /><br />}<br /><img src ="http://www.cppblog.com/eday/aggbug/15706.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-27 20:35 <a href="http://www.cppblog.com/eday/archive/2006/11/27/15706.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows 中不规则窗体的编程实现</title><link>http://www.cppblog.com/eday/archive/2006/11/26/15668.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 25 Nov 2006 16:28:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/26/15668.html</guid><description><![CDATA[
		<img height="1" src="http://www.pcbookcn.com/images/ads_200.gif" width="1" align="left" />
		<span class="main">
				<font size="3">
						<strong>一、序言</strong>
						<br />
						<br />　　在绝大多数的Windows应用程序中，其窗体都是使用的正规正矩的矩形窗体,例如我们常用的，“记事本”，“扫雷”，等等。矩形窗体，具有编程实现简单，风格简洁的优点，所以在普通文档应用程序和简单小游戏中使用足矣。但在某些娱乐游戏程序中使用就略显呆板些了，这时若用不规则窗体替代原先的矩形窗体，将会使这类程序更添情趣。典型的例子有windows 自代的Media Player,新版本的Media Player有个控制面板的选项，选中这些面板，播放器就以选中的面板形状出现，这时的播放器比以前版本的Media Player的古老矩形界面要生动有趣的多了。 要实现不规则窗体不是太难，知道了基本原理后，你也可以创建各种有趣的不规则窗体。<br /><br />　　<b>二、实现原理</b><br /><br />　　所有的 Windows 窗体都位于一个称为“region”中，窗体的大小如果超出“region”的范围，windows 会自动裁剪超出"region"范围那部分的窗体，使其不可见。所以，要创建不规则窗体有两个步骤：第一步就是创建不规则"region".第二步就是将窗体放到创建的“region”中。<br /><br />　　其中第二步很简单就调用一条语句即可。在SDK中调用API函数SetWindowRgn，该函数原型如下：<br /><br /></font>
				<table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1">
						<tbody>
								<tr>
										<td>int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL bRedraw );</td>
								</tr>
						</tbody>
				</table>
				<br />　　其中hWnd为待设置的窗体句柄，hRgn为已经创建的"region"句柄，bRedraw代表是否要重绘窗体。在MFC 中使用窗口类CWnd的成员函数int CWnd::SetWindowRgn（HRGN hRgn, BOOL bRedraw );该函数的参数意义与API中同名函数相同。<br /><br />　　相对与第二步，创建不规则窗体的第一步要复杂许多，并且不规则窗体越复杂，创建其"region"的过程也越复杂。接下去我们将由浅入深地介绍各种创建”region”的方法。<br /><br />　　在MFC中"region"对象，由CRgn类实现。CRgn的几乎每个成员函数都有同名的SDK API函数对应。<br /><br />　　<b>三、简单“region”的创建</b><br /><br />　　类CRgn创建一个新的"region"的简单方法有以下几个成员函数： BOOL CRgn::CreateRectRgn( int x1, int y1, int x2, int y2 ); 创建矩形的“region”。 <br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CRgn::CreateEllipticRgn( int x1, int y1, int x2, int y2 ); 创建圆形或椭圆形“region”。 <br />BOOL CRgn::CreateRoundRectRgn( int x1, int y1, int x2, int y2, int x3, int y3 ); 创建圆角矩形“region”。 <br />BOOL CRgn::CreatePolygonRgn( LPPOINT lpPoints, int nCount, int nMode ); 创建多边形“region”。 </td></tr></tbody></table><br />　　这里以创建椭圆窗体为例，介绍椭圆窗体创建的方法。在创建椭圆“region”的CreateEllipticRgn函数中，x1,y1指椭圆所在矩形的左上角坐标，x2,y2指该矩形的右下角坐标。<br /><br />　　下面的代码加入到MFC对话框程序的OnInitDialog函数中，可将该对话框变成椭圆窗体：<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CTestDlg::OnInitDialog()<br />{<br />CDialog::OnInitDialog();<br />...<br />CRgn rgn;<br />rgn. CreateEllipticRgn(0,0,200,100);<br />SetWindowRgn(rgn,TRUE);<br />}</td></tr></tbody></table><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn1.gif" /><br />图一 椭圆窗体效果图</div></td></tr></tbody></table><br />　　<b>四、作图路径法创建”region”</b><br /><br />　　使用该方法创建”region”的过程如下：<br /><br />　　第一步绘制所要创建的窗体形状。<br />　 <br />　　该步骤中使用到CDC类中的一些成员函数如下：BOOL CDC::BeginPath( );<br /><br />　　调用该函数后当前设备环境(DC)开始追踪绘图的过程。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>int CDC::SetBkMode( int nBkMode );</td></tr></tbody></table><br />　　设置绘图时的背景模式，此应用中nBkMode必须取值为TRANSPARENT 。即设置绘图时背景不发生变化。<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>BOOL CDC::EndPath( );</td></tr></tbody></table><br />　　调用该函数后当前设备环境(DC)结束追踪绘图的过程。<br /><br />　　开始绘图前，先调用BeginPath，然后调用SetBkMode。接下去就可调用CDC的其他绘图函数作图，例如Arc,AngleArc,LineTo,MoveTo,RoundRect,,Textout等等。绘图完毕调用EndPath().<br /><br />　　第二步将绘制的结果转成”region”.<br /><br />　　此步骤中使用SDK API函数<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>HRGN PathToRegion( HDC hdc );</td></tr></tbody></table><br />　　Hdc为作图DC的句柄， CDC类中的m_hDC成员变量可做此参数传入。示例，将下面代码加入某个按钮单击事件中，可以将当前窗体变为字符串”hello”的形状<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>void CTestDlg::OnTest() <br />{<br />　HRGN wndRgn;<br />　CClientDC dc(this);<br />　CFont mFont;<br /><br />　if (dc.m_hDC!=NULL)<br />　{<br />　　VERIFY(mFont.CreateFont(200, 50, 0, 0, FW_HEAVY, TRUE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, <br />CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "宋体")); <br /><br />　　//开始记录窗体轮廓路径<br />　　dc.BeginPath(); <br /><br />　　//设置背景为透明模式,这句话是必须有的。<br />　　dc.SetBkMode(TRANSPARENT); <br /><br />　　CFont * pOldFont;<br />　　pOldFont = dc.SelectObject( &amp;mFont );<br />　　dc.TextOut(0, 0, "Hello");<br /><br />　　//结束记录窗体轮廓路径<br />　　dc.SelectObject( pOldFont );<br />　　dc.EndPath();<br /><br />　　//把所记录的路径转化为窗体轮廓句柄<br />　　wndRgn = ::PathToRegion(dc.m_hDC);<br /><br />　　//赋予窗体指定的轮廓形状 <br />　　this-&gt;SetWindowRgn(wndRgn, TRUE); <br />　}<br />}</td></tr></tbody></table><br />　　CClientDC是CDC的派生类，故此该类具有所有CDC类的成员变量和成员函数。 <br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn2.gif" /><br />图二 hello形状的窗体效果图</div></td></tr></tbody></table><br />　　<b>五、根据图像创建”region”</b><br /><br />　　此法创建不规则窗体比较复杂。首先准备一张含有目标窗体形状的图片，设置透明色即将图片中部不属于窗体形状的部分，标记成同一种颜色，例如蓝色RGB(0,0,255).程序运行后先装入图片。然后逐个扫描图片的每个像素，如这个像素不属于透明色，则在相应位置创建一个只含一个像素的“region”然后将这些小”region ”合并起来组成一个任意形状的”region”.这里将使用到CRgn的一个成员函数 ：int CRgn::CombineRgn( CRgn* pRgn1, CRgn* pRgn2, int nCombineMode );<br /><br />　　其中pRgn1,pRgn2为要合并的两个“region”，nCombineMode为合并的方式，此应用中取RGN_OR，即两”region”全部合并去处重复部分。代码实现如下：<br /><br /><table bordercolor="#ffcc66" width="90%" align="center" bgcolor="#dadacf" border="1"><tbody><tr><td>void SetupRegion(<br />　CDC *pDC, //窗体的DC指针<br />　CBitmap &amp;cBitmap, //含有窗体形状的位图对象<br />　COLORREF TransColor //透明色<br />)<br />{ <br />　CDC memDC;<br />　//创建与传入DC兼容的临时DC<br />　memDC.CreateCompatibleDC(pDC);<br /><br />　CBitmap *pOldMemBmp=NULL;<br />　//将位图选入临时DC<br />　pOldMemBmp=memDC.SelectObject(&amp;cBitmap);<br /><br />　CRgn wndRgn;<br />　//创建总的窗体区域，初始region为0<br />　wndRgn.CreateRectRgn(0,0,0,0);<br /><br />　BITMAP bit; <br />　cBitmap.GetBitmap (&amp;bit);//取得位图参数，这里要用到位图的长和宽 <br /><br />　int y;<br />　for(y=0;y&lt;=bit.bmHeight ;y++)<br />　{<br />　　CRgn rgnTemp; //保存临时region<br /><br />　　int iX = 0;<br />　　do<br />　　{<br />　　　//跳过透明色找到下一个非透明色的点.<br />　　　while (iX &lt;= bit.bmWidth &amp;&amp; memDC.GetPixel(iX, y) == TransColor)<br />　　　　iX++;<br />　　　　//记住这个起始点<br />　　　　int iLeftX = iX;<br /><br />　　　　//寻找下个透明色的点<br />　　　　while (iX &lt;= bit.bmWidth &amp;&amp; memDC.GetPixel(iX, y) != TransColor)<br />　　　　　++iX;<br /><br />　　　　//创建一个包含起点与重点间高为1像素的临时“region”<br />　　　　rgnTemp.CreateRectRgn(iLeftX, y, iX, y+1);<br /><br />　　　　//合并到主"region".<br />　　　　wndRgn.CombineRgn(&amp;wndRgn, &amp;rgnTemp, RGN_OR);<br /><br />　　　//删除临时"region",否则下次创建时和出错<br />　　　rgnTemp.DeleteObject();<br />　　}while(iX GetWindow();<br />　　pWnd-&gt;SetWindowRgn(wndRgn,TRUE); <br />　　pWnd-&gt;SetForegroundWindow(); <br />　}</td></tr></tbody></table><br />　　上述代码创建的不规则窗体中，在OnEraseBkgnd事件中绘制该位图，就可得到与该位图形状一模一样的窗体。 <br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://www.cppblog.com/images/cppblog_com/eday/2991/o_rgn3.gif" /><br />图三 根据位图和位图中的透明色创建的窗体效果图</div></td></tr></tbody></table><br />　　<b>六、小结</b><br /><br />　　三种创建“region”的方法，第一种最简单，如果所需的窗体形状是简单的几何图形，这种方法最合适；第二种稍微复杂些，但是创建的窗体形状更多些；第三种方法可以创建任何在图片中画出的窗体形状，但是实现的复杂度也最高。 <br /></span>
<img src ="http://www.cppblog.com/eday/aggbug/15668.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-26 00:28 <a href="http://www.cppblog.com/eday/archive/2006/11/26/15668.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>是使用_beginthread 还是 CreateThread  !</title><link>http://www.cppblog.com/eday/archive/2006/11/25/15648.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Fri, 24 Nov 2006 16:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/25/15648.html</guid><description><![CDATA[
		<div class="postText">
				<p>CreateThread 是一个Win 32API 函数,_beginthread 是一个CRT(C Run-Time)函数，他们都是实现多线城的创建的函数，而且他们拥有相同的使用方法,相同的参数列表。</p>
				<p>但是他们有什么区别呢？</p>
				<p>一般来说，从使用角度是没有多大的区别的，CRT函数中除了signal()函数不能在CreateThread创建的线城中使用外，其他的CRT函数都可一正常使用，但是如果在CreateThread创建的线城中使用CRT函数的话，会产生一些Memory Leak.</p>
				<p>下面是摘自KB的原话：</p>
				<div class="summary">
						<h2>SUMMARY</h2>All C Run-time functions except the signal() function work correctly when used in threads created by the CreateThread() function. However, depending on what CRT functions are called, there may be a small memory leak when threads are terminated. Calling strlen(), for example, does not trigger the allocation of the CRT thread data-block, and calling malloc(), fopen(), _open(), strtok(), ctime(), or localtime() causes allocation of a CRT per-thread data-block, which may cause a memory leak. </div>
				<div class="moreinformation">
						<h2>MORE INFORMATION</h2>The "Programming Techniques" manual supplied with Visual C++ 32-bit Edition states that using CreateThread() in a program that uses Libcmt.lib causes many CRT functions to fail. Actually, the only function that should not be used in a thread created with CreateThread() is the signal() function. <br /><br />There are two ways to create threads. One method involves using the CRT _beginthread() or _beginthreadex() (with Visual C++ 2.0 and later); the other method involves using the CreateThread() API. All CRT functions other than the signal() function work correctly in threads created with either _beginthread() or CreateThread(). However, there are some problems involved with using CRT functions in threads created with CreateThread(). <br /><br />Threads that are created and terminated with the CreateThread() and ExitThread() Win32 API functions do not have memory that is allocated by the CRT for static data and static buffers cleaned up when the thread terminates. Some examples of this type of memory are static data for errno and _doserrno and the static buffers used by functions such as asctime(), ctime(), localtime(), gmtime(), and mktime(). Using CreateThread() in a program that uses the CRT (for example, links with LIBCMT.LIB) may cause a memory leak of about 70-80 bytes each time a thread is terminated. <br /><br />To guarantee that all static data and static buffers allocated by the CRT are cleaned up when the thread terminates, _beginthreadex() and _endthreadex() should be used when creating a thread. The _beginthreadex() function includes the same parameters and functionality as CreateThread(). <br /></div>
				<div class="moreinformation">另外有个小小的测验：</div>
				<div class="moreinformation"> 用CreateThread 创建的线城能否被CRT函数 _endthreadex() 关闭？</div>
				<div class="moreinformation"> </div>
				<div class="moreinformation"> </div>
				<div class="moreinformation">CreateThread()和_beginthreadex()在Jeffrey的《Windows核心编程》中讲的很清楚，应当尽量避免使用CreateThread()。 <br />事实上，_beginthreadex()在内部先为线程创建一个线程特有的tiddata结构，然后调用CreateThread()。在某些非线程安全的CRT函数中会请求这个结构。如果直接使用CreateThread()的话，那些函数发现请求的tiddata为NULL，就会在现场为该线程创建该结构，此后调用EndThread()时会引起内存泄漏。_endthreadex()可以释放由CreateThread()创建的线程，实际上，在它的内部会先释放由_beginthreadex()创建的tiddata结构，然后调用EndThread()。 <br />因此，应当使用_beginthreadex()和_endthreadex()，而避免使用CreateThread()和EndThread()。当然，_beginthread()和_endthread()也是应当避免使用的。 <br /></div>
				<div class="moreinformation"> </div>
				<div class="moreinformation"> </div>
				<div class="moreinformation">程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程，一直有所争论。本文将从对CRT源代码出发探讨这个问题。 
<p><font face="courier new, courier, mono">I. 起因</font></p><p><font face="courier new, courier, mono">今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread，并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解，为了对朋友负责，专门翻阅了一下VC的运行库（CRT）源代码，终于找到了答案。</font></p><p><font face="courier new, courier, mono">II. CRT</font></p><p><font face="courier new, courier, mono">CRT(C/C++ Runtime Library)是支持C/C++运行的一系列函数和代码的总称。虽然没有一个很精确的定义，但是可以知道，你的main就是它负责调用的，你平时调用的诸如strlen、strtok、time、atoi之类的函数也是它提供的。我们以Microsoft Visual.NET 2003中所附带的CRT为例。假设你的.NET 2003安装在C:Program FilesMicrosoft Visual Studio .NET 2003中，那么CRT的源代码就在C:Program FilesMicrosoft Visual Studio .NET 2003Vc7crtsrc中。既然有了这些实现的源代码，我们就可以找到一切解释了。</font></p><p><font face="courier new, courier, mono">III. _beginthread/_endthread</font></p><p><font face="courier new, courier, mono">这个函数究竟做了什么呢？它的代码在thread.c中。阅读代码，可以看到它最终也是通过CreateThread来创建线程的，主要区别在于，它先分配了一个_tiddata，并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数_threadstart中，在那里会把这个指针作为一个TLS（Thread Local Storage）保存起来。然后_threadstart会调用我们传入的线程函数，并且在那个函数退出后调用_endthread。这里也可以看到，_threadstart用一个__try/__except块把我们的函数包了起来，并且在发生异常的时候，调用exit退出。（_threadstart和endthread的代码都在thread.c中）<br />这个_tiddata是一个什么样的结构呢？它在mtdll.h中定义，它的成员被很多CRT函数所用到，譬如int _terrno，这是这个线程中的错误标志；char* _token，strtok以来这个变量记录跨函数调用的信息，...。<br />那么_endthread又做了些什么呢？除了调用浮点的清除代码以外，它还调用了_freeptd来释放和这个线程相关的tiddata。也就是说，在_beginthread里面分配的这块内存，以及在线程运行过程中其它CRT函数中分配并且记录在这个内存结构中的内存，在这里被释放了。<br />通过上面的代码，我们可以看到，如果我使用_beginthread函数创建了线程，它会为我创建好CRT函数需要的一切，并且最后无需我操心，就可以把清除工作做得很好，可能唯一需要注意的就是，如果需要提前终止线程，最好是调用_endthread或者是返回，而不要调用ExitThread，因为这可能造成内存释放不完全。同时我们也可以看出，如果我们用CreateThread函数创建了线程，并且不对C运行库进行调用（包括任何间接调用），就不必担心什么问题了。</font></p><p><font face="courier new, courier, mono">IV. CreateThread和CRT</font></p><p><font face="courier new, courier, mono">或许有人会说，我用CreateThread创建线程以后，我也调用了C运行库函数，并且也使用ExitThread退出了，可是我的程序运行得好好的，既没有因为CRT没有初始化而崩溃，也没有因为忘记调用_endthread而发生内存泄漏，这是为什么呢，让我们继续我们的CRT之旅。<br />假设我用CreateThread创建了一个线程，我调用strtok函数来进行字符串处理，这个函数肯定是需要某些额外的运行时支持的。strtok的源代码在strtok.c中。从代码可见，在多线程情况下，strtok的第一句有效代码就是_ptiddata ptd = _getptd()，它通过这个来获得当前的ptd。可是我们并没有通过_beginthread来创建ptd，那么一定是_getptd捣鬼了。打开tidtable.c，可以看到_getptd的实现，果然，它先尝试获得当前的ptd，如果不能，就重新创建一个，因此，后续的CRT调用就安全了。可是这块ptd最终又是谁释放的呢？打开dllcrt0.c，可以看到一个DllMain函数。在VC中，CRT既可以作为一个动态链接库和主程序链接，也可以作为一个静态库和主程序链接，这个在Project Setting-&gt;Code Generations里面可以选。当CRT作为DLL链接到主程序时，DllMain就是CRT DLL的入口。Windows的DllMain可以由四种原因调用：Process Attach/Process Detach/Thread Attach/Thread Detach，最后一个，也就是当线程函数退出后但是线程还没有销毁前，会在这个线程的上下文中用Thread Detach调用DllMain，这里，CRT做了一个_freeptd(NULL)，也就是说，如果有ptd，就free掉。所以说，恰巧没有发生内存泄漏是因为你用的是动态链接的CRT。<br />于是我们得出了一个更精确的结论，如果我没有使用那些会使用_getptd的CRT函数，使用CreateThread就是安全的。</font></p><p><font face="courier new, courier, mono">V. 使用ptd的函数</font></p><p><font face="courier new, courier, mono">那么，究竟那些函数使用了_getptd呢？很多！在CRT目录下搜索_getptd，你会发觉很多意想不到的函数都用到了它，除了strtok、rand这类需要保持状态的，还有所有的字符串相关函数，因为它们要用到ptd中的locale信息；所有的mbcs函数，因为它们要用到ptd中的mbcs信息，...。</font></p><p><font face="courier new, courier, mono">VI. 测试代码</font></p><p><font face="courier new, courier, mono">下面是一段测试代码（leaker中用到了atoi，它需要ptd）：</font></p><p><font face="courier new, courier, mono">#include <windows.h><br />#include <process.h><br />#include <iostream><br />#include <crtdbg.h></crtdbg.h></iostream></process.h></windows.h></font></p><p><font face="courier new, courier, mono">volatile bool threadStarted = false;</font></p><p><font face="courier new, courier, mono">void leaker()<br />{<br />    std::cout &lt;&lt; atoi( "0" ) &lt;&lt; std::endl;<br />}</font></p><p><font face="courier new, courier, mono">DWORD __stdcall CreateThreadFunc( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />    _endthread();<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">void __cdecl beginThreadFunc( LPVOID )<br />{<br />    leaker();<br />    threadStarted = false;<br />}</font></p><p><font face="courier new, courier, mono">int main()<br />{<br />    for(;;)<br />    {<br />        while( threadStarted )<br />            Sleep( 5 );<br />        threadStarted = true;<br />//      _beginthread( beginThreadFunc, 0, 0 );//1<br />        CreateThread( NULL, 0, CreateThreadFunc, 0, 0, 0 );//2<br />//      CreateThread( NULL, 0, CreateThreadFuncWithEndThread, 0, 0, 0 );//3<br />    }<br />    return 0;<br />}</font></p><p><font face="courier new, courier, mono">如果你用VC的多线程+静态链接CRT选项去编译这个程序，并且尝试打开1、2、3之中的一行，你会发觉只有2打开的情况下，程序才会发生内存泄漏（可以在Task Manager里面明显的观察到）。3之所以不会出现内存泄漏是因为主动调用了_endthread。</font></p><p><font face="courier new, courier, mono">VII. 总结</font></p><p><font face="courier new, courier, mono">如果你使用了DLL方式链接的CRT库，或者你只是一次性创建少量的线程，那么你或许可以采取鸵鸟策略，忽视这个问题。上面一节代码中第3种方法基于对CRT库的了解，但是并不保证这是一个好的方法，因为每一个版本的VC的CRT可能都会有些改变。看来，除非你的头脑清晰到可以记住这一切，或者你可以不厌其烦的每调用一个C函数都查一下CRT代码，否则总是使用_beginthread（或者它的兄弟_beginthreadex）是一个不错的选择。</font></p><p><font face="Courier New"><strong>[后记]<br />网友condor指出本文的一个错误：在dllcrt0.c中，DllMain的Thread Detach所释放的ptd，其实是dllcrt0.c的DllMain中的Thread Attach所创建的。也就是说，当你用CRT DLL的时候，DllMain对线程做了一切初始化/清除工作。我查看源代码，thread.c中的_threadstart函数，在设置TLS之前做了检查，这其实就是为了避免重复设置导致的内存泄漏。</strong></font></p></div>
		</div>
<img src ="http://www.cppblog.com/eday/aggbug/15648.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-25 00:51 <a href="http://www.cppblog.com/eday/archive/2006/11/25/15648.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinAPI编程基础之消息分流器</title><link>http://www.cppblog.com/eday/archive/2006/11/22/15545.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Wed, 22 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/22/15545.html</guid><description><![CDATA[对于熟悉Win API编程的同志们来说，windowsx.h这个头文件应该不会太陌生吧，这次要讲的内容就来自这个windowsx.h头文件。<br /><br />经常能在msdn上查到这样一些函数，明明是个函数，而且模样长得和一般的api函数也一样一样的，可却叫做macro，为什么呢？留意一下函数使用的requirement，你会发现，它的声明正是在windowsx.h这个头文件里。<br /><br />Windowsx.h包含了这样一些内容：<br />宏API，窗口消息分流器，控件API；<br /><br />所有的这些宏定义，可以使你的程序更加安全，简洁，结构更清晰，大大提高程序的可读性；其中窗口消息分流器（message cracker）是我们今天要讨论的话题，它可以使我们的API程序变得更简洁。下面就进入我们的主题：（有关windowsx.h的更多内容，可以参考MS Knowledge Base Article #83456.）<br /><br />消息分流器是Windows提供的一组宏定义，它的两个最大的作用，用MS的话来说，就是：<br /><br />● 安全的数据类型，因为消息分流器完成了大量的类型转换的工作；<br />● 使程序向32位windows的转化更简单；<br /><br />当然，使用消息分流器会大大改变程序的面貌，你也可以选择不使用它。<br /><br />下面我们就以一个对话框窗口的消息处理过程为例，看看消息分流器到底是怎么运作的。<br /><br /><br />1．消息分流器的基本使用<br />先看一个普通的窗口消息处理函数，它可能需要处理一些窗口的初始化，无效客户区重绘等消息：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />  // ...<br />  return 0;<br />    <br />  case WM_PAINT:<br />  // ...<br />  return 0;<br />          <br />  case WM_DESTROY:<br />  //...<br />  return 0;<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />而通过使用消息分流器，我们可以把每个case都写到相应的消息处理函数中，就像下面这样：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />      return HANDLE_WM_CREATE(hwnd, wParam, lParam, Cls_OnCreate);<br />    <br />  case WM_PAINT:<br />      return HANDLE_WM_PAINT(hwnd, wParam, lParam, Cls_OnPaint);<br />          <br />  case WM_DESTROY:<br />  return HANDLE_WM_DESTROY(hwnd, wParam, lParam, Cls_OnDestroy);<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />这里用到了三个宏定义：HANDLE_WM_CREATE, HANDLE_WM_PAINT, HANDLE_WM_DESTROY；这三个宏定义就是我们的三个消息分流器（别看叫什么分流器，说穿了也不值几个钱，呵呵），它们在windowsx.h中的定义如下：<br /><br />#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \<br />((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)<br />#define HANDLE_WM_PAINT(hwnd, wParam, lParam, fn) \<br />((fn)(hwnd), 0L)<br />#define HANDLE_WM_DESTROYCLIPBOARD(hwnd, wParam, lParam, fn) \<br />  ((fn)(hwnd), 0L)<br /><br />把这三个宏定义替换回去，就变成：<br /><br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />  case WM_CREATE:<br />      return Cls_OnCreate(hwnd, (LPCREATESTRUCT)(lParam) ? 0L : (LRESULT)-1L;<br />    // 如果处理了消息，则Cls_OnCreate应返回TRUE，导致WndProc返回0，否则Cls_OnCreate返回FALSE，导致WndProc返回-1；<br />  case WM_PAINT:<br />      return Cls_OnPaint(hwnd), 0L;<br />    // 逗号表达式；Cls_OnPaint是void类型，这里返回0；     <br />  case WM_DESTROY:<br />  return Cls_OnDestroy(hwnd), 0L;         // 同Cls_OnPaint<br />}     <br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />之后我们就可以按照消息分流器的定义编写相应的消息处理函数了：<br /><br />BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct){…};<br />void Cls_OnPaint(HWND hwnd){…};<br />void Cls_OnDestroyClipboard(HWND hwnd){…};<br /><br />windowsx.h还提供了一个更加简化的方法：使用HANDLE_MSG宏，这个宏是这样定义的：<br /><br />#define HANDLE_MSG(hwnd, message, fn)   \<br />  case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))<br /><br />这个宏要做的就是根据不同的message（##用来连接前后的字符串），把自己“变成”相应的HANDLE_XXXXMESSAGE形式的宏，再通过相应的宏来执行消息处理代码；<br />比如实际代码中写入：<br /><br />HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate)<br /><br />则经过转换就变成：<br /><br />case (WM_CREATE): return HANDLE_WM_CREATE((hwnd), (wParam), (lParam), (Cls_OnCreate))<br /><br />这样，我们就可以直接把程序写为：<br />LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate);<br />HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);<br />HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);<br />}<br />return DefWindowProc(hwnd, msg, wParam, lParam);<br />}<br /><br />之后直接编写相应的消息处理过程就可以了。是不是简洁多了？而且把消息处理封装到函数里面，就可以使用VS直接跳转到这个函数，再也不用费劲去找那个case了。要注意的一点是，虽然windowsx.h里包括了所有消息对应的分流器，但它们的参数是宏定义显式说明的，在编写消息处理函数时，必须遵循宏定义中的参数类型，否则会导致错误；这么多消息分流器，我们每次新写一个消息处理函数时就得看看是否把参数设置正确了，整个过程繁琐冗长。好在已经有一个工具叫Message Cracker Wizard，可以帮助我们生成消息分流器和相关的处理过程，具体见：<a href="http://www.codeproject.com/win32/msgcrackwizard.asp" target="_blank">http://www.codeproject.com/win32/msgcrackwizard.asp</a>。<br /><br /><br />2．在对话框中使用消息分流器<br />在对话框消息处理中，窗口子类化是我们经常使用的手段，这也可以通过消息分流器实现，但是有点小问题 :&gt;<br />下面是一个使用了windowsx.h消息分流器的对话框及其处理过程：<br />……<br />int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) <br />{<br />DialogBoxParam(<br />        hinstExe, MAKEINTRESOURCE(IDD_PASSTHRU), NULL, (DLGPROC)Dlg_Proc, 0);<br /><br />  return(0);<br />}<br />……<br /><br />LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />HANDLE_MSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 不能直接使用HANDLE_MSG宏<br />HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand);     // 不能直接使用HANDLE_MSG宏<br />}<br /><br />return false;<br />}<br /><br />以上程序中直接使用HANDLE_MSG可能导致错误；为什么呢？问题出在子类化的消息处理过程的返回值上，msdn中对于对话框消息处理过程的返回值有如下说明：<br /><br />一般情况下，对话框过程函数应该在处理了消息的情况下返回TRUE，如果没有处理，则返回FALSE。如果对话框过程返回了FALSE，那么对话框管理器为这条消息准备默认的对话操作。<br /><br />如果对话框处理了一个需要特定返回值的消息，则对话框的返回值应该被设置为调用SetWindowLong后的返回值，并在返回TRUE之前立即返回这个值。注意你必须立即调用SetWindowLong（这个函数用于调用窗口子类化的过程），这会导致DWL_MSGRESULT值被一个嵌套的对话框消息改写。返回值为特定值的消息有：<br />•     WM_CHARTOITEM <br />•     WM_COMPAREITEM <br />•     WM_CTLCOLORBTN <br />•     WM_CTLCOLORDLG <br />•     WM_CTLCOLOREDIT <br />•     WM_CTLCOLORLISTBOX <br />•     WM_CTLCOLORSCROLLBAR <br />•     WM_CTLCOLORSTATIC <br />•     WM_INITDIALOG <br />•     WM_QUERYDRAGICON <br />•     WM_VKEYTOITEM<br />看到没有？ 我们的消息WM_INITDIALOG也在其中，对这个消息进行处理的过程不能简单的返回TRUE表示对消息进行了处理，而是另有其意；它将转化为：<br /><br />case (WM_INITDIALOG): return HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, Cls_OnInitDialog);<br /><br />宏HANDLE_WM_INITDIALOG定义如下：<br /><br />#define HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, fn) \<br />(LRESULT)(DWORD)(UINT)(BOOL)(fn)((hwnd), (HWND)(wParam), lParam)<br /><br />对WM_INITDIALOG的处理，如果返回TRUE，则表示设置键盘焦点到对话框的默认控件，否则返回FALSE；这时好像还看不出什么问题，而对于我们的另外一个消息WM_COMMAND，HANDLE_MSG简单的把它变成：<br /><br />case (WM_COMMAND): return HANDLE_WM_COMMAND(hwnd, wParam, lParam, Cls_OnCommand);<br /><br />宏HANDLE_WM_COMMAND定义如下：<br /><br />#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \<br />  ((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam), (UINT)HIWORD(wParam)), 0L)<br /><br />问题出来了，我们的Cls_OnCommand由于是个void型的函数，是没有返回值的，因此windows默认这种消息处理过程必须返回一个0值，而返回0值不就表示我们的消息过程不处理这个消息么？这个矛盾是HANDLE_MSG无法解决的。怎么办才能使消息过程在处理完WM_COMMAND消息之后正确的返回一个TRUE呢？ 答案是使用另一个windowsx.h中的宏：SetDlgMsgResult(hwnd, msg, result) <br /><br />这个宏定义如下：<br /><br />#define   SetDlgMsgResult(hwnd, msg, result) (( <br />    (msg) == WM_CTLCOLORMSGBOX     || \<br />    (msg) == WM_CTLCOLOREDIT     || \<br />    (msg) == WM_CTLCOLORLISTBOX   || \<br />    (msg) == WM_CTLCOLORBTN       || \<br />    (msg) == WM_CTLCOLORDLG       || \<br />    (msg) == WM_CTLCOLORSCROLLBAR   || \<br />    (msg) == WM_CTLCOLORSTATIC     || \<br />    (msg) == WM_COMPAREITEM       || \<br />    (msg) == WM_VKEYTOITEM       || \<br />    (msg) == WM_CHARTOITEM       || \<br />    (msg) == WM_QUERYDRAGICON     || \<br />    (msg) == WM_INITDIALOG         \<br />  ) ? (BOOL)(result) : (SetWindowLongPtr((hwnd), DWLP_MSGRESULT, (LPARAM)(LRESULT)(result)), TRUE))<br /><br />（有没有注意到，里面多了一个WM_CTLCOLORMSGBOX ？ 这个消息是16位WinAPI中的消息，一度被转换为Win32 API的一个消息；现在在最新的32位API中已经被删除了；保留它可能考虑到兼容性的问题，这里不做进一步讨论）<br />现在看到了，如果对话框过程处理的消息恰巧为返回特定值中的一个，则如实返回result；不要被前面的BOOL蒙蔽，BOOL在头文件中的定义实际上是一个int型，一旦需要返回非TRUE或FALSE的其他值，照样可以；这样，我们的Cls_OnInitDialog就能够正确的返回它的BOOL值了，而Cls_OnCommand在处理之后，也可以由后面的逗号表达式正确的返回一个TRUE表示消息已处理。<br /><br />在《Windows核心编程》一书中，大牛Jeffrey自己定义了一个宏，使SetDlgMsgResult宏的使用更加方便：<br /><br />#define chHANDLE_DLGMSG(hwnd, message, fn)           \<br />  case (message): return (SetDlgMsgResult(hwnd, uMsg,   \<br />    HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) <br /><br />可见这个宏只是简单的对SetDlgMsgRseult宏进行了封装。<br /><br />这样，我们最终的代码可以写成：<br /><br />LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, <br />WPARAM wParam, LPARAM lParam)<br />{<br />switch(msg)<br />{<br />chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 使用大牛的chHANDLE_DLGMSG宏<br />chHANDLE_DLGMSG(hwnd, WM_COMMAND, Cls_OnCommand);     <br />}<br /><br />return false;<br />}<br /><br /><br /><br />下面把原来程序整个框架列出来：<br /><br />LRESULT CALLBACK Dlg_Proc(HWND hwnd, UNIT umsg, WPARAM wparam, LPARAM lparam)<br />{<br />    switch(msg)     <br />    {<br />    case WM_COMMAND:           // 每个case都被一个message cracker代替，这里使用大牛同志的<br />        // do something;     // chHANDLE_DLGMSG宏；这个宏负责对消息筛选，处理并返回相应的值<br />        return true;<br /><br />    case WM_INITDIALOG: <br />        // do something;<br />        return xxxx;<br />}<br /><br />return false;     // 如果消息不在我们的DlgProc过程中被处理，则告诉调用这个DlgProc的消息，<br />}                       //告诉系统的对话框管理器，这个消息我们不处理，交给你了<br /><br />对比一下，消息分流器的作用不言自明。<br /><br />以上只是介绍了消息分流器的部分应用，更多创造性的用法还等你自己在实践中发掘。<br /><br />下面列出一些有用的参考资料：<br /><br /><a href="http://support.microsoft.com/default.aspx?scid=kb;en-us;83456" target="_blank">http://support.microsoft.com/default.aspx?scid=kb;en-us;83456</a> 介绍了STRICT宏定义以及windowsx.h<br /><a href="http://www.codeproject.com/win32/msgcrackwizard.asp" target="_blank">http://www.codeproject.com/win32/msgcrackwizard.asp</a>     提供message cracker wizard的下载，而且附有源代码<br />《windows核心编程》windows系统编程，就跟定大牛了 :&gt; 他在自己的sample中大量使用了message cracker<img src ="http://www.cppblog.com/eday/aggbug/15545.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-22 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/22/15545.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PE文件格式详解(上)</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15391.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 09:01:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15391.html</guid><description><![CDATA[
		<strong>摘要</strong>
		<br />
		<br />　　 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中（Specs and Strategy, Specifications, Windows NT File Format Specifications），但是它非常之晦涩。 <br />　　 然而这一的文档并未提供足够的信息，所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题，它会对整个的PE文件格式作一个十分彻底的解释，另外，本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。 <br />　　 为了获得PE文件中所包含的重要信息，我编写了一个名为PEFILE.DLL的动态链接库，本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中（译注：示例程序请在MSDN中寻找，本站恕不提供），你可以在你自己的应用程序中使用这个DLL；同样，你亦可以依你所愿地使用并构建它的源码。在本文末尾，你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。 <br /><br /><strong>介绍</strong><br /><br />　　 Windows操作系统家族最近增加的Windows NT为开发环境和应用程序本身带来了很大的改变，这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范，同时为了保证与旧版本MS-DOS及Windows操作系统的兼容，PE文件格式也保留了MS-DOS中那熟悉的MZ头部。 <br />　　 在本文之中，PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中，本文会详细讨论PE文件的每一个组成部分。 <br />　　 许多单独的文件成分定义都来自于Microsoft Win32 SDK开发包中的WINNT.H文件，在这个文件中你会发现用来描述文件头部和数据目录等各种成分的结构类型定义。但是，在WINNT.H中缺少对PE文件结构足够的定义，在这种情况下，我定义了自己的结构来存取文件数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义，整套的PEFILE.H开发文件包含在PEFile示例程序之中。 <br />　　 本文配套的示例程序除了PEFILE.DLL示例代码之外，还有一个单独的Win32示例应用程序，名为EXEVIEW.EXE。创建这一示例目的有二：首先，我需要测试PEFILE.DLL的函数，并且某些情况要求我同时查看多个文件；其次，很多解决PE文件格式的工作和直接观看数据有关。例如，要弄懂导入地址名称表是如何构成的，我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体，而EXEVIEW.EXE就是查看这些信息的最佳示例。 <br />　　 闲话少叙，让我们开始吧。 <br /><br /><strong>PE文件结构 </strong><br /><br />　　 PE文件格式被组织为一个线性的数据流，它由一个MS-DOS头部开始，接着是一个是模式的程序残余以及一个PE文件标志，这之后紧接着PE文件头和可选头部。这些之后是所有的段头部，段头部之后跟随着所有的段实体。文件的结束处是一些其它的区域，其中是一些混杂的信息，包括重分配信息、符号表信息、行号信息以及字串表数据。我将所有这些成分列于图1。<br /><img height="413" src="http://www.vckbase.com/document/journal/vckbase38/images/pe1.gif" width="104" /><br /><strong>图1.PE文件映像结构</strong><br />　　 从MS-DOS文件头结构开始，我将按照PE文件格式各成分的出现顺序依次对其进行讨论，并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文件。这些示例都利用了Windows NT最酷的特色之一——内存映射文件，这一特色允许用户使用一个简单的指针来存取文件中所包含的数据，因此所有的示例都使用了内存映射文件来存取PE文件中的数据。 <br />　　 注意：请查阅本文末尾关于如何使用PEFILE.DLL的那一段。 <br /><br /><strong>MS-DOS头部/实模式头部</strong><br /><br />　　 如上所述，PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中，它并非一个新概念，因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是，当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候，操作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说，当你在MS-DOS 6.0下运行一个Windows NT可执行文件时，你会得到这样一条消息：“This program cannot be run in DOS mode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话，操作系统装载文件的时候就会失败，并提供一些完全没用的信息，例如：“The name specified is not recognized as an internal or external command, operable program or batch file.” <br />　　 MS-DOS头部占据了PE文件的头64个字节，描述它内容的结构如下： <pre>//WINNT.H

typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
  USHORT e_magic; // 魔术数字
  USHORT e_cblp; // 文件最后页的字节数
  USHORT e_cp; // 文件页数
  USHORT e_crlc; // 重定义元素个数
  USHORT e_cparhdr; // 头部尺寸，以段落为单位
  USHORT e_minalloc; // 所需的最小附加段
  USHORT e_maxalloc; // 所需的最大附加段
  USHORT e_ss; // 初始的SS值（相对偏移量）
  USHORT e_sp; // 初始的SP值
  USHORT e_csum; // 校验和
  USHORT e_ip; // 初始的IP值
  USHORT e_cs; // 初始的CS值（相对偏移量）
  USHORT e_lfarlc; // 重分配表文件地址
  USHORT e_ovno; // 覆盖号
  USHORT e_res[4]; // 保留字
  USHORT e_oemid; // OEM标识符（相对e_oeminfo）
  USHORT e_oeminfo; // OEM信息
  USHORT e_res2[10]; // 保留字
  LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</pre>第一个域e_magic，被称为魔术数字，它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D，表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部，就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用，但是对于Windows NT来说，这个结构中只有一个有用的域——最后一个域e_lfnew，一个4字节的文件偏移量，PE文件头部就是由它定位的。对于Windows NT的PE文件来说，PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。 <br /><br /><strong>实模式残余程序</strong><br /><br />　　 实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。对于一个MS-DOS的可执行映像文件，应用程序就是从这里执行的。对于Windows、OS/2、Windows NT这些操作系统来说，MS-DOS残余程序就代替了主程序的位置被放在这里。这种残余程序通常什么也不做，而只是输出一行文本，例如：“This program requires Microsoft Windows v3.1 or greater.”当然，用户可以在此放入任何的残余程序，这就意味着你可能经常看到像这样的东西：“You can''t run a Windows NT application on OS/2, it''s simply not possible.” <br />　　 当为Windows 3.1构建一个应用程序的时候，链接器将向你的可执行文件中链接一个名为WINSTUB.EXE的默认残余程序。你可以用一个基于MS-DOS的有效程序取代WINSTUB，并且用STUB模块定义语句指示链接器，这样就能够取代链接器的默认行为。为Windows NT开发的应用程序可以通过使用-STUB:链接器选项来实现。 <br /><br /><strong>PE文件头部与标志</strong><br /><br />　　 PE文件头部是由MS-DOS头部的e_lfanew域定位的，这个域只是给出了文件的偏移量，所以要确定PE头部的实际内存映射地址，就需要添加文件的内存映射基地址。例如，以下的宏是包含在PEFILE.H源文件之中的： <br /><pre>//PEFILE.H

#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
                       ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))</pre>在处理PE文件信息的时候，我发现文件之中有些位置需要经常查阅。既然这些位置仅仅是对文件的偏移量，那么用宏来实现这些定位就比较容易，因为它们较之函数有更好的表现。 <br />　　 请注意这个宏所获得的是PE文件标志，而并非PE文件头部的偏移量。那是由于自Windows与OS/2的可执行文件开始，.EXE文件都被赋予了目标操作系统的标志。对于Windows NT的PE文件格式而言，这一标志在PE文件头部结构之前。在Windows和OS/2的某些版本中，这一标志是文件头的第一个字。同样，对于PE文件格式，Windows NT使用了一个DWORD值。 <br />　　 以上的宏返回了文件标志的偏移量，而不管它是哪种类型的可执行文件。所以，文件头部是在DWORD标志之后，还是在WORD标志处，是由这个标志是否Windows NT文件标志所决定的。要解决这个问题，我编写了ImageFileType函数（如下），它返回了映像文件的类型： <br /><pre>//PEFILE.C

DWORD WINAPI ImageFileType (LPVOID lpFile)
{
  /* 首先出现的是DOS文件标志 */
  if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
  {
    /* 由DOS头部决定PE文件头部的位置 */
    if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
        IMAGE_OS2_SIGNATURE ||
        LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
        IMAGE_OS2_SIGNATURE_LE)
      return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
    else if (*(DWORD *)NTSIGNATURE (lpFile) ==
      IMAGE_NT_SIGNATURE)
    return IMAGE_NT_SIGNATURE;
    else
      return IMAGE_DOS_SIGNATURE;
  }
  else
    /* 不明文件种类 */
    return 0;
}
</pre>以上列出的代码立即告诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型并且返回一个适当的文件种类来说，这个宏就会使这两件事变得非常简单。WINNT.H之中定义的四种不同文件类型有： <pre>//WINNT.H

#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
　　</pre>首先，Windows的可执行文件类型没有出现在这一列表中，这一点看起来很奇怪。但是，在稍微研究一下之后，就能得到原因了：除了操作系统版本规范的不同之外，Windows的可执行文件和OS/2的可执行文件实在没有什么区别。这两个操作系统拥有相同的可执行文件结构。 <br />　　 现在把我们的注意力转向Windows NT PE文件格式，我们会发现只要我们得到了文件标志的位置，PE文件之后就会有4个字节相跟随。下一个宏标识了PE文件的头部： <pre>//PEFILE.C

#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE))
　　</pre>这个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不幸的是，这个常量并未定义在WINNT.H之中，于是我将它定义在了PEFILE.H中，它是一个DWORD的大小。 <br />　　 既然我们知道了PE文件头的位置，那么就可以检查头部的数据了。我们只需要把这个位置赋值给一个结构，如下： <pre>PIMAGE_FILE_HEADER pfh;
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile);</pre>在这个例子中，lpFile表示一个指向可执行文件内存映像基地址的指针，这就显出了内存映射文件的好处：不需要执行文件的I/O，只需使用指针pfh就能存取文件中的信息。PE文件头结构被定义为： <pre>//WINNT.H

typedef struct _IMAGE_FILE_HEADER {
  USHORT Machine;
  USHORT NumberOfSections;
  ULONG TimeDateStamp;
  ULONG PointerToSymbolTable;
  ULONG NumberOfSymbols;
  USHORT SizeOfOptionalHeader;
  USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

#define IMAGE_SIZEOF_FILE_HEADER 20
</pre>请注意这个文件头部的大小已经定义在这个包含文件之中了，这样一来，想要得到这个结构的大小就很方便了。但是我觉得对结构本身使用sizeof运算符（译注：原文为“function”）更简单一些，因为这样的话我就不必记住这个常量的名字IMAGE_SIZEOF_FILE_HEADER，而只需要记住结构IMAGE_FILE_HEADER的名字就可以了。另一方面，记住所有结构的名字已经够有挑战性的了，尤其在是这些结构只有WINNT.H中才有的情况下。 <br />　　 PE文件中的信息基本上是一些高级信息，这些信息是被操作系统或者应用程序用来决定如何处理这个文件的。第一个域是用来表示这个可执行文件被构建的目标机器种类，例如DEC(R) Alpha、MIPS R4000、Intel(R) x86或一些其它处理器。系统使用这一信息来在读取这个文件的其它数据之前决定如何处理它。 <br />　　 Characteristics域表示了文件的一些特征。比如对于一个可执行文件而言，分离调试文件是如何操作的。调试器通常使用的方法是将调试信息从PE文件中分离，并保存到一个调试文件（.DBG）中。要这么做的话，调试器需要了解是否要在一个单独的文件中寻找调试信息，以及这个文件是否已经将调试信息分离了。我们可以通过深入可执行文件并寻找调试信息的方法来完成这一工作。要使调试器不在文件中查找的话，就需要用到IMAGE_FILE_DEBUG_STRIPPED这个特征，它表示文件的调试信息是否已经被分离了。这样一来，调试器可以通过快速查看PE文件的头部的方法来决定文件中是否存在着调试信息。 <br />　　 WINNT.H定义了若干其它表示文件头信息的标记，就和以上的例子差不多。我把研究这些标记的事情留给读者作为练习，由你们来看看它们是不是很有趣，这些标记位于WINNT.H中的IMAGE_FILE_HEADER结构之后。 <br />　　 PE文件头结构中另一个有用的入口是NumberOfSections域，它表示如果你要方便地提取文件信息的话，就需要了解多少个段——更明确一点来说，有多少个段头部和多少个段实体。每一个段头部和段实体都在文件中连续地排列着，所以要决定段头部和段实体在哪里结束的话，段的数目是必需的。以下的函数从PE文件头中提取了段的数目： <pre>PEFILE.C
int WINAPI NumOfSections(LPVOID lpFile)
{
  /* 文件头部中所表示出的段数目 */
  return (int)((PIMAGE_FILE_HEADER)
    PEFHDROFFSET (lpFile))-&gt;NumberOfSections);
}
</pre>如你所见，PEFHDROFFSET以及其它宏用起来非常方便。<br /><br /><strong>PE可选头部</strong><br /><br />　　 PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是“可选头部”，但是请确信：这个头部并非“可选”，而是“必需”的。OPTHDROFFSET宏可以获得指向可选头部的指针： <pre>//PEFILE.H

#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER)))
　　</pre>可选头部包含了很多关于可执行映像的重要信息，例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。IMAGE_OPTIONAL_HEADER结构如下： <pre>//WINNT.H

typedef struct _IMAGE_OPTIONAL_HEADER {
  //
  // 标准域
  //
  USHORT Magic;
  UCHAR MajorLinkerVersion;
  UCHAR MinorLinkerVersion;
  ULONG SizeOfCode;
  ULONG SizeOfInitializedData;
  ULONG SizeOfUninitializedData;
  ULONG AddressOfEntryPoint;
  ULONG BaseOfCode;
  ULONG BaseOfData;
  //
  // NT附加域
  //
  ULONG ImageBase;
  ULONG SectionAlignment;
  ULONG FileAlignment;
  USHORT MajorOperatingSystemVersion;
  USHORT MinorOperatingSystemVersion;
  USHORT MajorImageVersion;
  USHORT MinorImageVersion;
  USHORT MajorSubsystemVersion;
  USHORT MinorSubsystemVersion;
  ULONG Reserved1;
  ULONG SizeOfImage;
  ULONG SizeOfHeaders;
  ULONG CheckSum;
  USHORT Subsystem;
  USHORT DllCharacteristics;
  ULONG SizeOfStackReserve;
  ULONG SizeOfStackCommit;
  ULONG SizeOfHeapReserve;
  ULONG SizeOfHeapCommit;
  ULONG LoaderFlags;
  ULONG NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
</pre>如你所见，这个结构中所列出的域实在是冗长得过分。为了不让你对所有这些域感到厌烦，我会仅仅讨论有用的——就是说，对于探究PE文件格式而言有用的。 <br /><br /><strong>标准域</strong><br /><br />　　 首先，请注意这个结构被划分为“标准域”和“NT附加域”。所谓标准域，就是和UNIX可执行文件的COFF格式所公共的部分。虽然标准域保留了COFF中定义的名字，但是Windows NT仍然将它们用作了不同的目的——尽管换个名字更好一些。 <br />　　 ·Magic。我不知道这个域是干什么的，对于示例程序EXEVIEW.EXE示例程序而言，这个值是0x010B或267（译注：0x010B为.EXE，0x0107为ROM映像，这个信息我是从eXeScope上得来的）。 <br />　　 ·MajorLinkerVersion、MinorLinkerVersion。表示链接此映像的链接器版本。随Window NT build 438配套的Windows NT SDK包含的链接器版本是2.39（十六进制为2.27）。 <br />　　 ·SizeOfCode。可执行代码尺寸。 <br />　　 ·SizeOfInitializedData。已初始化的数据尺寸。 <br />　　 ·SizeOfUninitializedData。未初始化的数据尺寸。 <br />　　 ·AddressOfEntryPoint。在标准域中，AddressOfEntryPoint域是对PE文件格式来说最为有趣的了。这个域表示应用程序入口点的位置。并且，对于系统黑客来说，这个位置就是导入地址表（IAT）的末尾。以下的函数示范了如何从可选头部获得Windows NT可执行映像的入口点。 <pre>//PEFILE.C

LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
{
  PIMAGE_OPTIONAL_HEADER poh;
  poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
  if (poh != NULL)
    return (LPVOID)poh-&gt;AddressOfEntryPoint;
  else
    return NULL;
}
</pre>·BaseOfCode。已载入映像的代码（“.text”段）的相对偏移量。 <br />　　 ·BaseOfData。已载入映像的未初始化数据（“.bss”段）的相对偏移量。 <br /><br /><strong>Windows NT附加域</strong><br /><br />　　 添加到Windows NT PE文件格式中的附加域为Windows NT特定的进程行为提供了装载器的支持，以下为这些域的概述。 <br />　　 ·ImageBase。进程映像地址空间中的首选基地址。Windows NT的Microsoft Win32 SDK链接器将这个值默认设为0x00400000，但是你可以使用-BASE:linker开关改变这个值。 <br />　　 ·SectionAlignment。从ImageBase开始，每个段都被相继的装入进程的地址空间中。SectionAlignment则规定了装载时段能够占据的最小空间数量——就是说，段是关于SectionAlignment对齐的。 <br />　　 Windows NT虚拟内存管理器规定，段对齐不能少于页尺寸（当前的x86平台是4096字节），并且必须是成倍的页尺寸。4096字节是x86链接器的默认值，但是它可以通过-ALIGN: linker开关来设置。 <br />　　 ·FileAlignment。映像文件首先装载的最小的信息块间隔。例如，链接器将一个段实体（段的原始数据）加零扩展为文件中最接近的FileAlignment边界。早先提及的2.39版链接器将映像文件以0x200字节的边界对齐，这个值可以被强制改为512到65535这么多。 <br />　　 ·MajorOperatingSystemVersion。表示Windows NT操作系统的主版本号；通常对Windows NT 1.0而言，这个值被设为1。 <br />　　 ·MinorOperatingSystemVersion。表示Windows NT操作系统的次版本号；通常对Windows NT 1.0而言，这个值被设为0。 <br />　　 ·MajorImageVersion。用来表示应用程序的主版本号；对于Microsoft Excel 4.0而言，这个值是4。 <br />　　 ·MinorImageVersion。用来表示应用程序的次版本号；对于Microsoft Excel 4.0而言，这个值是0。 <br />　　 ·MajorSubsystemVersion。表示Windows NT Win32子系统的主版本号；通常对于Windows NT 3.10而言，这个值被设为3。 <br />　　 ·MinorSubsystemVersion。表示Windows NT Win32子系统的次版本号；通常对于Windows NT 3.10而言，这个值被设为10。 <br />　　 ·Reserved1。未知目的，通常不被系统使用，并被链接器设为0。 <br />　　 ·SizeOfImage。表示载入的可执行映像的地址空间中要保留的地址空间大小，这个数字很大程度上受SectionAlignment的影响。例如，考虑一个拥有固定页尺寸4096字节的系统，如果你有一个11个段的可执行文件，它的每个段都少于4096字节，并且关于65536字节边界对齐，那么SizeOfImage域将会被设为11 * 65536 = 720896（176页）。而如果一个相同的文件关于4096字节对齐的话，那么SizeOfImage域的结果将是11 * 4096 = 45056（11页）。这只是个简单的例子，它说明每个段需要少于一个页面的内存。在现实中，链接器通过个别地计算每个段的方法来决定SizeOfImage确切的值。它首先决定每个段需要多少字节，并且最后将页面总数向上取整至最接近的SectionAlignment边界，然后总数就是每个段个别需求之和了。 <br />　　 ·SizeOfHeaders。这个域表示文件中有多少空间用来保存所有的文件头部，包括MS-DOS头部、PE文件头部、PE可选头部以及PE段头部。文件中所有的段实体就开始于这个位置。 <br />　　 ·CheckSum。校验和是用来在装载时验证可执行文件的，它是由链接器设置并检验的。由于创建这些校验和的算法是私有信息，所以在此不进行讨论。 <br />　　 ·Subsystem。用于标识该可执行文件目标子系统的域。每个可能的子系统取值列于WINNT.H的IMAGE_OPTIONAL_HEADER结构之后。 <br />　　 ·DllCharacteristics。用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记。 <br />　　 ·SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit。这些域控制要保留的地址空间数量，并且负责栈和默认堆的申请。在默认情况下，栈和堆都拥有1个页面的申请值以及16个页面的保留值。这些值可以使用链接器开关-STACKSIZE:与-HEAPSIZE:来设置。 <br />　　 ·LoaderFlags。告知装载器是否在装载时中止和调试，或者默认地正常运行。 <br />　　 ·NumberOfRvaAndSizes。这个域标识了接下来的DataDirectory数组。请注意它被用来标识这个数组，而不是数组中的各个入口数字，这一点非常重要。 <br />　　 ·DataDirectory。数据目录表示文件中其它可执行信息重要组成部分的位置。它事实上就是一个IMAGE_DATA_DIRECTORY结构的数组，位于可选头部结构的末尾。当前的PE文件格式定义了16种可能的数据目录，这之中的11种现在在使用中。 <br /><br /><strong>数据目录</strong><br /><br />WINNT.H之中所定义的数据目录为：<pre>//WINNT.H
 
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器值（MIPS GP）
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
　　</pre>基本上，每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本身是相同的，但是每个特定的目录种类却是完全唯一的。每个数据目录的定义在本文的以后部分被描述为“预定义段”。 <pre>//WINNT.H

typedef struct _IMAGE_DATA_DIRECTORY {
  ULONG VirtualAddress;
  ULONG Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
</pre>每个数据目录入口指定了该目录的尺寸和相对虚拟地址。如果你要定义一个特定的目录的话，就需要从可选头部中的数据目录数组中决定相对的地址，然后使用虚拟地址来决定该目录位于哪个段中。一旦你决定了哪个段包含了该目录，该段的段头部就会被用于查找数据目录的精确文件偏移量位置。 <br />　　 所以要获得一个数据目录的话，那么首先你需要了解段的概念。我在下面会对其进行描述，这个讨论之后还有一个有关如何定位数据目录的示例。 <br /><br /><strong>PE文件段</strong><br /><br />　　 PE文件规范由目前为止定义的那些头部以及一个名为“段”的一般对象组成。段包含了文件的内容，包括代码、数据、资源以及其它可执行信息，每个段都有一个头部和一个实体（原始数据）。我将在下面描述段头部的有关信息，但是段实体则缺少一个严格的文件结构。因此，它们几乎可以被链接器按任何的方法组织，只要它的头部填充了足够能够解释数据的信息。 <br /><br /><strong>段头部</strong><br /><br />　　 PE文件格式中，所有的段头部位于可选头部之后。每个段头部为40个字节长，并且没有任何的填充信息。段头部被定义为以下的结构： <pre>//WINNT.H

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    ULONG PhysicalAddress;
    ULONG VirtualSize;
  } Misc;
  ULONG VirtualAddress;
  ULONG SizeOfRawData;
  ULONG PointerToRawData;
  ULONG PointerToRelocations;
  ULONG PointerToLinenumbers;
  USHORT NumberOfRelocations;
  USHORT NumberOfLinenumbers;
  ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
　　</pre>你如何才能获得一个特定段的段头部信息？既然段头部是被连续的组织起来的，而且没有一个特定的顺序，那么段头部必须由名称来定位。以下的函数示范了如何从一个给定了段名称的PE映像文件中获得一个段头部： <pre>//PEFILE.C

BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
  PIMAGE_SECTION_HEADER psh;
  int nSections = NumOfSections (lpFile);
  int i;
  if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
      != NULL)
  {
    /* 由名称查找段 */
    for (i = 0; i &lt; nSections; i++)
    {
      if (!strcmp(psh-&gt;Name, szSection))
      {
        /* 向头部复制数据 */
        CopyMemory((LPVOID)sh, (LPVOID)psh,
            sizeof(IMAGE_SECTION_HEADER));
        return TRUE;
      }
      else
        psh++;
    }
  }
  return FALSE;
}</pre>这个函数通过SECHDROFFSET宏将第一个段头部定位，然后它开始在所有段中循环，并将要寻找的段名称和每个段的名称相比较，直到找到了正确的那一个为止。当找到了段的时候，函数将内存映像文件的数据复制到传入函数的结构中，然后IMAGE_SECTION_HEADER结构的各域就能够被直接存取了。 <br /><br /><strong>段头部的域</strong><br /><br />　　 ·Name。每个段都有一个8字符长的名称域，并且第一个字符必须是一个句点。 <br />　　 ·PhysicalAddress或VirtualSize。第二个域是一个union域，现在已不使用了。 <br />　　 ·VirtualAddress。这个域标识了进程地址空间中要装载这个段的虚拟地址。实际的地址由将这个域的值加上可选头部结构中的ImageBase虚拟地址得到。切记，如果这个映像文件是一个DLL，那么这个DLL就不一定会装载到ImageBase要求的位置。所以一旦这个文件被装载进入了一个进程，实际的ImageBase值应该通过使用GetModuleHandle来检验。 <br />　　 ·SizeOfRawData。这个域表示了相对FileAlignment的段实体尺寸。文件中实际的段实体尺寸将少于或等于FileAlignment的整倍数。一旦映像被装载进入了一个进程的地址空间，段实体的尺寸将会变得少于或等于FileAlignment的整倍数。 <br />　　 ·PointerToRawData。这是一个文件中段实体位置的偏移量。 <br />　　 ·PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。这些域在PE格式中不使用。 <br />　　 ·Characteristics。定义了段的特征。这些值可以在WINNT.H及本光盘（译注：MSDN的光盘）的PE格式规范中找到。 <br /><br />值         定义 <br />0x00000020 代码段 <br />0x00000040 已初始化数据段 <br />0x00000080 未初始化数据段 <br />0x04000000 该段数据不能被缓存 <br />0x08000000 该段不能被分页 <br />0x10000000 共享段 <br />0x20000000 可执行段 <br />0x40000000 可读段 <br />0x80000000 可写段 <br /><br /><strong>定位数据目录</strong><br /><br />　　 数据目录存在于它们相应的数据段中。典型地来说，数据目录是段实体中的第一个结构，但不是必需的。由于这个缘故，如果你需要定位一个指定的数据目录的话，就需要从段头部和可选头部中获得信息。 <br />　　 为了让这个过程简单一点，我编写了以下的函数来定位任何一个在WINNT.H之中定义的数据目录。 <pre>// PEFILE.C

LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile,
    DWORD dwIMAGE_DIRECTORY)
{
  PIMAGE_OPTIONAL_HEADER poh;
  PIMAGE_SECTION_HEADER psh;
  int nSections = NumOfSections(lpFile);
  int i = 0;
  LPVOID VAImageDir;
  /* 必须为0到(NumberOfRvaAndSizes-1)之间 */
  if (dwIMAGE_DIRECTORY &gt;= poh-&gt;NumberOfRvaAndSizes)
    return NULL;
  /* 获得可选头部和段头部的偏移量 */
  poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
  psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile);
  /* 定位映像目录的相对虚拟地址 */
  VAImageDir = (LPVOID)poh-&gt;DataDirectory
      [dwIMAGE_DIRECTORY].VirtualAddress;
  /* 定位包含映像目录的段 */
  while (i++ &lt; nSections)
  {
    if (psh-&gt;VirtualAddress &lt;= (DWORD)VAImageDir &amp;&amp;
        psh-&gt;VirtualAddress + 
        psh-&gt;SizeOfRawData &gt; (DWORD)VAImageDir)
      break;
    psh++;
  }
  if (i &gt; nSections)
    return NULL;
  /* 返回映像导入目录的偏移量 */
  return (LPVOID)(((int)lpFile + 
    (int)VAImageDir. psh-&gt;VirtualAddress) +
    (int)psh-&gt;PointerToRawData);
}
　　</pre>该函数首先确认被请求的数据目录入口数字，然后它分别获取指向可选头部和第一个段头部的两个指针。它从可选头部决定数据目录的虚拟地址，然后它使用这个值来决定数据目录定位在哪个段实体之中。如果适当的段实体已经被标识了，那么数据目录特定的位置就可以通过将它的相对虚拟地址转换为文件中地址的方法来找到。 <img src ="http://www.cppblog.com/eday/aggbug/15391.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 17:01 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15391.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PE文件格式详解(下)</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15392.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 09:01:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15392.html</guid><description><![CDATA[
		<strong>预定义段</strong>
		<br />
		<br />　　 一个Windows NT的应用程序典型地拥有9个预定义段，它们是.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。一些应用程序不需要所有的这些段，同样还有一些应用程序为了自己特殊的需要而定义了更多的段。这种做法与MS-DOS和Windows 3.1中的代码段和数据段相似。事实上，应用程序定义一个独特的段的方法是使用标准编译器来指示对代码段和数据段的命名，或者使用名称段编译器选项-NT——就和Windows 3.1中应用程序定义独特的代码段和数据段一样。 <br />　　 以下是一个关于Windows NT PE文件之中一些有趣的公共段的讨论。 <br /><br /><strong>可执行代码段，.text</strong><br /><br />　　 Windows 3.1和Windows NT之间的一个区别就是Windows NT默认的做法是将所有的代码段（正如它们在Windows 3.1中所提到的那样）组成了一个单独的段，名为“.text”。既然Windows NT使用了基于页面的虚拟内存管理系统，那么将分开的代码放入不同的段之中的做法就不太明智了。因此，拥有一个大的代码段对于操作系统和应用程序开发者来说，都是十分方便的。 <br />　　 .text段也包含了早先提到过的入口点。IAT亦存在于.text段之中的模块入口点之前。（IAT在.text段之中的存在非常有意义，因为这个表事实上是一系列的跳转指令，并且它们的跳转目标位置是已固定的地址。）当Windows NT的可执行映像装载入进程的地址空间时，IAT就和每一个导入函数的物理地址一同确定了。要在.text段之中查找IAT，装载器只用将模块的入口点定位，而IAT恰恰出现于入口点之前。既然每个入口拥有相同的尺寸，那么向后退查找这个表的起始位置就很容易了。 <br /><br /><strong>数据段，.bss、.rdata、.data</strong><br /><br />　　 .bss段表示应用程序的未初始化数据，包括所有函数或源模块中声明为static的变量。 <br />　　 .rdata段表示只读的数据，比如字符串文字量、常量和调试目录信息。 <br />　　 所有其它变量（除了出现在栈上的自动变量）存储在.data段之中。基本上，这些是应用程序或模块的全局变量。 <br /><br /><strong>资源段，.rsrc</strong><br /><br />　　 .rsrc段包含了模块的资源信息。它起始于一个资源目录结构，这个结构就像其它大多数结构一样，但是它的数据被更进一步地组织在了一棵资源树之中。以下的IMAGE_RESOURCE_DIRECTORY结构形成了这棵树的根和各个结点。 <pre>//WINNT.H

typedef struct _IMAGE_RESOURCE_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  USHORT NumberOfNamedEntries;
  USHORT NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
　　</pre>请看这个目录结构，你将会发现其中竟然没有指向下一个结点的指针。但是，在这个结构中有两个域NumberOfNamedEntries和NumberOfIdEntries代替了指针，它们被用来表示这个目录附有多少入口。附带说一句，我的意思是目录入口就在段数据之中的目录后边。有名称的入口按字母升序出现，再往后是按数值升序排列的ID入口。 <br />　　 一个目录入口由两个域组成，正如下面IMAGE_RESOURCE_DIRECTORY_ENTRY结构所描述的那样： <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
  ULONG Name;
  ULONG OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
</pre>根据树的层级不同，这两个域也就有着不同的用途。Name域被用于标识一个资源种类，或者一种资源名称，或者一个资源的语言ID。OffsetToData与常常被用来在树之中指向兄弟结点——即一个目录结点或一个叶子结点。 <br />　　 叶子结点是资源树之中最底层的结点，它们定义了当前资源数据的尺寸和位置。IMAGE_RESOURCE_DATA_ENTRY结构被用于描述每个叶子结点： <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
  ULONG OffsetToData;
  ULONG Size;
  ULONG CodePage;
  ULONG Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
</pre>OffsetToData和Size这两个域表示了当前资源数据的位置和尺寸。既然这一信息主要是在应用程序装载以后由函数使用的，那么将OffsetToData作为一个相对虚拟的地址会更有意义一些。——幸甚，恰好是这样没错。非常有趣的是，所有其它的偏移量，比如从目录入口到其它目录的指针，都是相对于根结点位置的偏移量。 <br />　　 要更清楚地了解这些内容，请参考图2。 <br /><img height="423" src="http://www.vckbase.com/document/journal/vckbase38/images/pe2.gif" width="491" /><br /><strong>图2.一个简单的资源树结构</strong><br />　　 图2描述了一个非常简单的资源树，它包含了仅仅两个资源对象：一个菜单和一个字串表。更深一层地来说，它们各自都有一个子项。然而，你仍然可以看到资源树有多么复杂——即使它像这个一样只有一点点资源。 <br />　　 在树的根部，第一个目录有一个文件中包含的所有资源种类的入口，而不管资源种类有多少。在图2中，有两个由树根标识的入口，一个是菜单的，另一个是字串表的。如果文件中拥有一个或多个对话框资源，那么根结点会再拥有一个入口，因此，就有了对话框资源的另一个分支。 <br />　　 WINUSER.H中标识了基本的资源种类，我将它们列到了下面：<pre>//WINUSER.H

/*
* 预定义的资源种类
*/
#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)
　　</pre>在树的第一层级，以上列出的MAKEINTRESOURCE值被放置在每个种类入口的Name处，它标识了不同的资源种类。 <br />　　 每个根目录的入口都指向了树中第二层级的一个兄弟结点，这些结点也是目录，并且每个都拥有它们自己的入口。在这一层级，目录被用来以给定的种类标识每一个资源种类。如果你的应用程序中有多个菜单，那么树中的第二层级会为每个菜单都准备一个入口。 <br />　　 你可能意识到了，资源可以由名称或整数标识。在这一层级，它们是通过目录结构的Name域来分辨的。如果如果Name域最重要的位被设置了，那么其它的31个位就会被用作一个到IMAGE_RESOURCE_DIR_STRING_U结构的偏移量。 <pre>// WINNT.H

typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
  USHORT Length;
  WCHAR NameString[1];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
　　</pre>这个结构仅仅是由一个2字节长的Length域和一个UNICODE字符Length组成的。 <br />　　 另一方面，如果Name域最重要的位被清空，那么它的低31位就被用于表示资源的整数ID。图2示范的就是菜单资源作为一个命名的资源，以及字串表作为一个ID资源。 <br />　　 如果有两个菜单资源，一个由名称标识，另一个由资源标识，那么它们二者就会在菜单资源目录之后拥有两个入口。有名称的资源入口在第一位，之后是由整数标识的资源。目录域NumberOfNamedEntries和NumberOfIdEntries将各自包含值1，表示当前的1个入口。 <br />　　 在第二层级的下面，资源树就不再更深一步地扩展分支了。第一层级分支至表示每个资源种类的目录中，第二层级分支至由标识符表示的每个资源的目录中，第三层级是被个别标识的资源与它们各自的语言ID之间一对一的映射。要表示一个资源的语言ID，目录入口结构的Name域就被用来表示资源的主语言ID和子语言ID了。Windows NT的Win32 SDK开发包中列出了默认的值资源，例如对于0x0409这个值来说，0x09表示主语言LANG_ENGLISH，0x04则被定义为子语言的SUBLANG_ENGLISH_CAN。所有的语言ID值都定义于Windows NT Win32 SDK开发包的文件WINNT.H中。 <br />　　 既然语言ID结点是树中最后的目录结点，那么入口结构的OffsetToData域就是到一个叶子结点（即前面提到过的IMAGE_RESOURCE_DATA_ENTRY结构）的偏移量。 <br />　　 再回过头来参考图2，你会发现每个语言目录入口都对应着一个数据入口。这个结点仅仅表示了资源数据的尺寸以及资源数据的相对虚拟地址。 <br />　　 在资源数据段（.rsrc）之中拥有这么多结构有一个好处，就是你可以不存取资源本身而直接可以从这个段收集很多信息。例如，你可以获得有多少种资源、哪些资源（如果有的话）使用了特别的语言ID、特定的资源是否存在以及单独种类资源的尺寸。为了示范如何利用这一信息，以下的函数说明了如何决定一个文件中包含的不同种类的资源： <pre>// PEFILE.C

int WINAPI GetListOfResourceTypes(LPVOID lpFile, HANDLE hHeap, char **pszResTypes)
{
  PIMAGE_RESOURCE_DIRECTORY prdRoot;
  PIMAGE_RESOURCE_DIRECTORY_ENTRY prde;
  char *pMem;
  int nCnt, i;
  /* 获得资源树的根目录 */
  if ((prdRoot = (PIMAGE_RESOURCE_DIRECTORY)ImageDirectoryOffset
      (lpFile, IMAGE_DIRECTORY_ENTRY_RESOURCE)) == NULL)
    return 0;
  /* 在堆上分配足够的空间来包括所有类型 */
  nCnt = prdRoot-&gt;NumberOfIdEntries * (MAXRESOURCENAME + 1);
  *pszResTypes = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
      nCnt);
  if ((pMem = *pszResTypes) == NULL)
    return 0;
  /* 将指针指向第一个资源种类的入口 */
  prde = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)prdRoot +
      sizeof (IMAGE_RESOURCE_DIRECTORY));
  /* 在所有的资源目录入口类型中循环 */
  for (i = 0; i &lt; prdRoot-&gt;NumberOfIdEntries; i++)
  {
    if (LoadString(hDll, prde-&gt;Name, pMem, MAXRESOURCENAME))
      pMem += strlen(pMem) + 1;
    prde++;
  }
  return nCnt;
}
　　</pre>这个函数将一个资源种类名称的列表写入了由pszResTypes标识的变量中。请注意，在这个函数的核心部分，LoadString是使用各自资源种类目录入口的Name域来作为字符串ID的。如果你查看PEFILE.RC，你会发现我定义了一系列的资源种类的字符串，并且它们的ID与它们在目录入口中的定义完全相同。PEFILE.DLL还有有一个函数，它返回了.rsrc段中的资源对象总数。这样一来，从这个段中提取其它的信息，借助这些函数或另外编写函数就方便多了。 <br /><br /><strong>导出数据段，.edata</strong><br /><br />　　 .edata段包含了应用程序或DLL的导出数据。在这个段出现的时候，它会包含一个到达导出信息的导出目录。 <pre>// WINNT.H

typedef struct _IMAGE_EXPORT_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  ULONG Name;
  ULONG Base;
  ULONG NumberOfFunctions;
  ULONG NumberOfNames;
  PULONG *AddressOfFunctions;
  PULONG *AddressOfNames;
  PUSHORT *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
　　</pre>导出目录中的Name域标识了可执行模块的名称。NumberOfFunctions域和NumberOfNames域表示模块中有多少导出的函数以及这些函数的名称。 <br />　　 AddressOfFunctions域是一个到导出函数入口列表的偏移量。AddressOfNames域是到一个导出函数名称列表起始处偏移量的地址，这个列表是由null分隔的。AddressOfNameOrdinals是一个到相同导出函数顺序值（每个值2字节长）列表的偏移量。 <br />　　 三个AddressOf...域是当模块装载时进程地址空间中的相对虚拟地址。一旦模块被装载，那么要获得进程地质空间中的确切地址的话，就应该在相对虚拟地址上加上模块的基地址。可是，在文件被装载前，仍然可以决定这一地址：只要从给定的域地址中减去段头部的虚拟地址（VirtualAddress），再加上段实体的偏移量（PointerToRawData），这个结果就是映像文件中的偏移量了。以下的例子解说了这一技术： <br /><pre>// PEFILE.C

int WINAPI GetExportFunctionNames(LPVOID lpFile, HANDLE hHeap, char **pszFunctions)
{
  IMAGE_SECTION_HEADER sh;
  PIMAGE_EXPORT_DIRECTORY ped;
  char *pNames, *pCnt;
  int i, nCnt;
  /* 获得.edata域中的段头部和指向数据目录的指针 */
  if ((ped = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryOffset
      (lpFile, IMAGE_DIRECTORY_ENTRY_EXPORT)) == NULL)
    return 0;
  GetSectionHdrByName (lpFile, &amp;sh, ".edata");
  /* 决定导出函数名称的偏移量 */
  pNames = (char *)(*(int *)((int)ped-&gt;AddressOfNames -
    (int)sh.VirtualAddress + (int)sh.PointerToRawData +
    (int)lpFile) - (int)sh.VirtualAddress +
    (int)sh.PointerToRawData + (int)lpFile);
  /* 计算出要为所有的字符串分配多少内存 */
  pCnt = pNames;
  for (i = 0; i &lt; (int)ped-&gt;NumberOfNames; i++)
    while (*pCnt++);
  nCnt = (int)(pCnt.pNames);
  /* 在堆上为函数名称分配内存 */
  *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nCnt);
  /* 将所有字符串复制到缓冲区 */
  CopyMemory((LPVOID)*pszFunctions, (LPVOID)pNames, nCnt);
  return nCnt;
}</pre>请注意，在这个函数之中，变量pNames是由决定偏移量地址和当前偏移量位置的方法来赋值的。偏移量的地址和偏移量本身都是相对虚拟地址，因此在使用之前必须进行转换——函数之中体现了这一点。虽然你可以编写一个类似的函数来决定顺序值或函数入口点，但是我为什么不为你做好呢？——GetNumberOfExportedFunctions、GetExportFunctionEntryPoints和GetExportFunctionOrdinals已经存在于PEFILE.DLL之中了。 <br /><br /><strong>导入数据段，.idata</strong><br /><br />　　 .idata段是导入数据，包括导入库和导入地址名称表。虽然定义了IMAGE_DIRECTORY_ENTRY_IMPORT，但是WINNT.H之中并无相应的导入目录结构。作为代替，其中有若干其它的结构，名为IMAGE_IMPORT_BY_NAME、IMAGE_THUNK_DATA与IMAGE_IMPORT_DESCRIPTOR。在我个人看来，我实在不知道这些结构是如何和.idata段发生关联的，所以我花了若干个小时来破译.idata段实体并且得到了一个更简单的结构，我名之为IMAGE_IMPORT_MODULE_DIRECTORY。 <pre>// PEFILE.H

typedef struct tagImportDirectory
{
  DWORD dwRVAFunctionNameList;
  DWORD dwUseless1;
  DWORD dwUseless2;
  DWORD dwRVAModuleName;
  DWORD dwRVAFunctionAddressList;
} IMAGE_IMPORT_MODULE_DIRECTORY, *PIMAGE_IMPORT_MODULE_DIRECTORY;
</pre>和其它段的数据目录不同的是，这个是作为文件中的每个导入模块重复出现的。你可以将它看作模块数据目录列表中的一个入口，而不是一个整个数据段的数据目录。每个入口都是一个指向特定模块导入信息的目录。 <br />　　 IMAGE_IMPORT_MODULE_DIRECTORY结构中的一个域dwRVAModuleName是一个相对虚拟地址，它指向模块的名称。结构中还有两个dwUseless参数，它们是为了保持段的对齐。PE文件格式规范提到了一些东西，关于导入标记、时间/日期标志以及主/次版本，但是在我的实验中，这两个域自始而终都是空的，所以我仍然认为它们没有什么用处。 <br />　　 基于这个结构的定义，你便可以获得可执行文件中导入的所有模块和函数名称了。以下的函数示范了如何获得特定的PE文件中的所有导入函数名称： <pre>//PEFILE.C

int WINAPI GetImportModuleNames(LPVOID lpFile, HANDLE hHeap, char **pszModules)
{
  PIMAGE_IMPORT_MODULE_DIRECTORY pid;
  IMAGE_SECTION_HEADER idsh;
  BYTE *pData;
  int nCnt = 0, nSize = 0, i;
  char *pModule[1024];
  char *psz;
  pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset 
      (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
  pData = (BYTE *)pid;
  /* 定位.idata段头部 */
  if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
    return 0;
  /* 提取所有导入模块 */
  while (pid-&gt;dwRVAModuleName)
  {
    /* 为绝对字符串偏移量分配缓冲区 */
    pModule[nCnt] = (char *)(pData + 
        (pid-&gt;dwRVAModuleName-idsh.VirtualAddress));
    nSize += strlen(pModule[nCnt]) + 1;
    /* 增至下一个导入目录入口 */
    pid++;
    nCnt++;
  }
  /* 将所有字符串赋值到一大块的堆内存中 */
  *pszModules = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nSize);
  psz = *pszModules;
  for (i = 0; i &lt; nCnt; i++)
  {
    strcpy(psz, pModule[i]);
    psz += strlen (psz) + 1;
  }
  return nCnt;
}
  </pre>这个函数非常好懂，然而有一点值得指出——注意while循环。这个循环当pid-&gt;dwRVAModuleName为0的时候终止，这就暗示了在IMAGE_IMPORT_MODULE_DIRECTORY结构列表的末尾有一个空的结构，这个结构拥有一个0值，至少dwRVAModuleName域为0。这便是我在对文件的实验中以及之后在PE文件格式中研究的行为。 <br />　　 这个结构中的第一个域dwRVAFunctionNameList是一个相对虚拟地址，这个地址指向一个相对虚拟地址的列表，这些地址是文件中的一些文件名。如下面的数据所示，所有导入模块的模块和函数名称都列于.idata段数据中了： <pre>E6A7 0000 F6A7 0000 08A8 0000 1AA8 0000 ................
28A8 0000 3CA8 0000 4CA8 0000 0000 0000 (...&lt;...L.......
0000 4765 744F 7065 6E46 696C 654E 616D ..GetOpenFileNam
6541 0000 636F 6D64 6C67 3332 2E64 6C6C eA..comdlg32.dll
0000 2500 4372 6561 7465 466F 6E74 496E ..%.CreateFontIn
6469 7265 6374 4100 4744 4933 322E 646C directA.GDI32.dl
6C00 A000 4765 7444 6576 6963 6543 6170 l...GetDeviceCap
7300 C600 4765 7453 746F 636B 4F62 6A65 s...GetStockObje
6374 0000 D500 4765 7454 6578 744D 6574 ct....GetTextMet
7269 6373 4100 1001 5365 6C65 6374 4F62 ricsA...SelectOb
6A65 6374 0000 1601 5365 7442 6B43 6F6C ject....SetBkCol
6F72 0000 3501 5365 7454 6578 7443 6F6C or..5.SetTextCol
6F72 0000 4501 5465 7874 4F75 7441 0000 or..E.TextOutA..
</pre>以上的数据是EXEVIEW.EXE示例程序.idata段的一部分。这个特别的段表示了导入模块列表和函数名称列表的起始处。如果你开始检查数据中的这个段，你应该认出一些熟悉的Win32 API函数以及模块名称。从上往下读的话，你可以找到GetOpenFileNameA，紧接着是COMDLG32.DLL。然后你能发现CreateFontIndirectA，紧接着是模块GDI32.DLL，以及之后的GetDeviceCaps、GetStockObject、GetTextMetrics等等。 <br />　　 这样的式样会在.idata段中重复出现。第一个模块是COMDLG32.DLL，第二个是GDI32.DLL。请注意第一个模块只导出了一个函数，而第二个模块导出了很多函数。在这两种情况下，函数和模块的排列的方法是首先出现一个函数名，之后是模块名，然后是其它的函数名（如果有的话）。 <br />　　 以下的函数示范了如何获得指定模块的所有函数名。 <pre>// PEFILE.C

int WINAPI GetImportFunctionNamesByModule(LPVOID lpFile, HANDLE hHeap,
    char *pszModule, char **pszFunctions)
{
  PIMAGE_IMPORT_MODULE_DIRECTORY pid;
  IMAGE_SECTION_HEADER idsh;
  DWORD dwBase;
  int nCnt = 0, nSize = 0;
  DWORD dwFunction;
  char *psz;
  /* 定位.idata段的头部 */
  if (!GetSectionHdrByName(lpFile, &amp;idsh, ".idata"))
    return 0;
  pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset 
      (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
  dwBase = ((DWORD)pid. idsh.VirtualAddress);
  /* 查找模块的pid */
  while (pid-&gt;dwRVAModuleName &amp;&amp; strcmp (pszModule, 
      (char *)(pid-&gt;dwRVAModuleName+dwBase)))
    pid++;
  /* 如果模块未找到，就退出 */
  if (!pid-&gt;dwRVAModuleName)
    return 0;
  /* 函数的总数和字符串长度 */
  dwFunction = pid-&gt;dwRVAFunctionNameList;
  while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
      *(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
  {
    nSize += strlen ((char *)((*(DWORD *)(dwFunction +
      dwBase)) + dwBase+2)) + 1;
    dwFunction += 4;
    nCnt++;
  }
  /* 在堆上分配函数名称的空间 */
  *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
  psz = *pszFunctions;
  /* 向内存指针复制函数名称 */
  dwFunction = pid-&gt;dwRVAFunctionNameList;
  while (dwFunction &amp;&amp; *(DWORD *)(dwFunction + dwBase) &amp;&amp;
    *((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)))
  {
    strcpy (psz, (char *)((*(DWORD *)(dwFunction + dwBase)) +
        dwBase+2));
    psz += strlen((char *)((*(DWORD *)(dwFunction + dwBase))+
        dwBase+2)) + 1;
    dwFunction += 4;
  }
  return nCnt;
}
　　</pre>就像GetImportModuleNames函数一样，这一函数依靠每个信息列表的末端来获得一个置零的入口。这在种情况下，函数名称列表就是以零结尾的。 <br />　　 最后一个域dwRVAFunctionAddressList是一个相对虚拟地址，它指向一个虚拟地址表。在文件装载的时候，这个虚拟地址表会被装载器置于段数据之中。但是在文件装载前，这些虚拟地址会被一些严密符合函数名称列表的虚拟地址替换。所以在文件装载之前，有两个同样的虚拟地址列表，它们指向导入函数列表。 <br /><br /><strong>调试信息段，.debug </strong><br /><br />　　 调试信息位于.debug段之中，同时PE文件格式也支持单独的调试文件（通常由.DBG扩展名标识）作为一种将调试信息集中的方法。调试段包含了调试信息，但是调试目录却位于早先提到的.rdata段之中。这其中每个目录都涉及了.debug段之中的调试信息。调试目录的结构IMAGE_DEBUG_DIRECTORY被定义为： <pre>// WINNT.H

typedef struct _IMAGE_DEBUG_DIRECTORY {
  ULONG Characteristics;
  ULONG TimeDateStamp;
  USHORT MajorVersion;
  USHORT MinorVersion;
  ULONG Type;
  ULONG SizeOfData;
  ULONG AddressOfRawData;
  ULONG PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
</pre>这个段被分为单独的部分，每个部分为不同种类的调试信息数据。对于每个部分来说都是一个像上边一样的调试目录。不同的调试信息种类如下： <pre>// WINNT.H

#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4
　　</pre>每个目录之中的Type域表示该目录的调试信息种类。如你所见，在上边的表中，PE文件格式支持很多不同的调试信息种类，以及一些其它的信息域。对于那些来说，IMAGE_DEBUG_TYPE_MISC信息是唯一的。这一信息被添加到描述可执行映像的混杂信息之中，这些混杂信息不能被添加到PE文件格式任何结构化的数据段之中。这就是映像文件中最合适的位置，映像名称则肯定会出现在这里。如果映像导出了信息，那么导出数据段也会包含这一映像名称。 <br />　　 每种调试信息都拥有自己的头部结构，该结构定义了它自己的数据。这些结构都列于WINNT.H之中。关于IMAGE_DEBUG_DIRECTORY一件有趣的事就是它包括了两个标识调试信息的域。第一个是AddressOfRawData，为相对文件装载的数据虚拟地址；另一个是PointerToRawData，为数据所在PE文件之中的实际偏移量。这就使得定位指定的调试信息相当容易了。 <br />　　 作为最后的例子，请你考虑以下的函数代码，它从IMAGE_DEBUG_MISC结构中提取了映像名称。 <pre>//PEFILE.C

int WINAPI RetrieveModuleName(LPVOID lpFile, HANDLE hHeap, char **pszModule)
{
  PIMAGE_DEBUG_DIRECTORY pdd;
  PIMAGE_DEBUG_MISC pdm = NULL;
  int nCnt;
  if (!(pdd = (PIMAGE_DEBUG_DIRECTORY)ImageDirectoryOffset(lpFile, 
      IMAGE_DIRECTORY_ENTRY_DEBUG)))
  return 0;
  while (pdd-&gt;SizeOfData)
  {
    if (pdd-&gt;Type == IMAGE_DEBUG_TYPE_MISC)
    {
      pdm = (PIMAGE_DEBUG_MISC)((DWORD)pdd-&gt;PointerToRawData + (DWORD)lpFile);
      nCnt = lstrlen(pdm-&gt;Data) * (pdm-&gt;Unicode ? 2 : 1);
      *pszModule = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nCnt+1);
      CopyMemory(*pszModule, pdm-&gt;Data, nCnt);
      break;
    }
    pdd ++;
  }
  if (pdm != NULL)
    return nCnt;
  else
    return 0;
}
</pre>你看到了，调试目录结构使得定位一个特定种类的调试信息变得相对容易了些。只要定位了IMAGE_DEBUG_MISC结构，提取映像名称就如同调用CopyMemory函数一样简单。 <br />　　 如上所述，调试信息可以被剥离到单独的.DBG文件中。Windows NT SDK包含了一个名为REBASE.EXE的程序可以实现这一目的。例如，以下的语句可以将一个名为TEST.EXE的调试信息剥离： <br />　　 rebase -b 40000 -x c:\samples\testdir test.exe <br />　　 调试信息被置于一个新的文件中，这个文件名为TEST.DBG，位于c:\samples\testdir之中。这个文件起始于一个单独的IMAGE_SEPARATE_DEBUG_HEADER结构，接着是存在于原可执行映像之中的段头部的一份拷贝。在段头部之后，是.debug段的数据。也就是说，在段头部之后，就是一系列的IMAGE_DEBUG_DIRECTORY结构及其相关的数据了。调试信息本身保留了如上所描述的常规映像文件调试信息。 <br /><br /><strong>PE文件格式总结</strong><br /><br />　　 Windows NT的PE文件格式向熟悉Windows和MS-DOS环境的开发者引入了一种全新的结构。然而熟悉UNIX环境的开发者会发现PE文件格式与COFF规范很相像（如果它不是以COFF为基础的话）。 <br />　　 整个格式的组成：一个MS-DOS的MZ头部，之后是一个实模式的残余程序、PE文件标志、PE文件头部、PE可选头部、所有的段头部，最后是所有的段实体。 <br />　　 可选头部的末尾是一个数据目录入口的数组，这些相对虚拟地址指向段实体之中的数据目录。每个数据目录都表示了一个特定的段实体数据是如何组织的。 <br />　　 PE文件格式有11个预定义段，这是对Windows NT应用程序所通用的，但是每个应用程序可以为它自己的代码以及数据定义它自己独特的段。 <br />　　 .debug预定义段也可以分离为一个单独的调试文件。如果这样的话，就会有一个特定的调试头部来用于解析这个调试文件，PE文件中也会有一个标志来表示调试数据被分离了出去。 <br /><br /><strong>PEFILE.DLL函数描述</strong><br /><br />　　 PEFILE.DLL主要由一些函数组成，这些函数或者被用来获得一个给定的PE文件中的偏移量，或者被用来把文件中的一些数据复制到一个特定的结构中去。每个函数都有一个需求——第一个参数是一个指针，这个指针指向PE文件的起始处。也就是说，这个文件必须首先被映射到你进程的地址空间中，然后映射文件的位置就可以作为每个函数第一个参数的lpFile的值来传入了。 <br />　　 我意在使函数的名称使你能够一见而知其意，并且每个函数都随一个详细描述其目的的注释而列出。如果在读完函数列表之后，你仍然不明白某个函数的功能，那么请参考EXEVIEW.EXE示例来查明这个函数是如何使用的。以下的函数原型列表可以在PEFILE.H中找到： <pre>// PEFILE.H

/* 获得指向MS-DOS MZ头部的指针 */
BOOL WINAPI GetDosHeader(LPVOID, PIMAGE_DOS_HEADER);

/* 决定.EXE文件的类型 */
DWORD WINAPI ImageFileType(LPVOID);

/* 获得指向PE文件头部的指针 */
BOOL WINAPI GetPEFileHeader(LPVOID, PIMAGE_FILE_HEADER);

/* 获得指向PE可选头部的指针 */
BOOL WINAPI GetPEOptionalHeader(LPVOID, PIMAGE_OPTIONAL_HEADER);

/* 返回模块入口点的地址 */
LPVOID WINAPI GetModuleEntryPoint(LPVOID);

/* 返回文件中段的总数 */
int WINAPI NumOfSections(LPVOID);

/* 返回当可执行文件被装载入进程地址空间时的首选基地址 */
LPVOID WINAPI GetImageBase(LPVOID);

/* 决定文件中一个特定的映像数据目录的位置 */
LPVOID WINAPI ImageDirectoryOffset(LPVOID, DWORD);

/* 获得文件中所有段的名称 */
int WINAPI GetSectionNames(LPVOID, HANDLE, char **);

/* 复制一个特定段的头部信息 */
BOOL WINAPI GetSectionHdrByName(LPVOID, PIMAGE_SECTION_HEADER, char *);

/* 获得由空字符分隔的导入模块名称列表 */
int WINAPI GetImportModuleNames(LPVOID, HANDLE, char **);

/* 获得一个模块由空字符分隔的导入函数列表 */
int WINAPI GetImportFunctionNamesByModule(LPVOID, HANDLE, char *, char **);

/* 获得由空字符分隔的导出函数列表 */
int WINAPI GetExportFunctionNames(LPVOID, HANDLE, char **);

/* 获得导出函数总数 */
int WINAPI GetNumberOfExportedFunctions(LPVOID);

/* 获得导出函数的虚拟地址入口点列表 */
LPVOID WINAPI GetExportFunctionEntryPoints(LPVOID);

/* 获得导出函数顺序值列表 */
LPVOID WINAPI GetExportFunctionOrdinals(LPVOID);

/* 决定资源对象的种类 */
int WINAPI GetNumberOfResources (LPVOID);

/* 返回文件中所使用的所有资源对象的种类 */
int WINAPI GetListOfResourceTypes(LPVOID, HANDLE, char **);

/* 决定调试信息是否已从文件中分离 */
BOOL WINAPI IsDebugInfoStripped(LPVOID);

/* 获得映像文件名称 */
int WINAPI RetrieveModuleName(LPVOID, HANDLE, char **);

/* 决定文件是否是一个有效的调试文件 */
BOOL WINAPI IsDebugFile(LPVOID);

/* 从调试文件中返回调试头部 */
BOOL WINAPI GetSeparateDebugHeader(LPVOID, PIMAGE_SEPARATE_DEBUG_HEADER);
　　除了以上所列的函数之外，本文中早先提到的宏也定义在了PEFILE.H中，完整的列表如下：
/* PE文件标志的偏移量 */
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
                       ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew))

/* MS操作系统头部标识了双字的NT PE文件标志；PE文件头部就紧跟在这个双字之后 */
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE))

/* PE可选头部紧跟在PE文件头部之后 */
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER)))

/* 段头部紧跟在PE可选头部之后 */
#define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + \
                        ((PIMAGE_DOS_HEADER)a)-&gt;e_lfanew + \
                        SIZE_OF_NT_SIGNATURE + \
                        sizeof(IMAGE_FILE_HEADER) + \
                        sizeof(IMAGE_OPTIONAL_HEADER)))
　　</pre>要使用PEFILE.DLL，你只用包含PEFILE.H文件并在应用程序中链接到这个DLL即可。所有的这些函数都是互斥性的函数，但是有些函数的功能可以相互支持以获得文件信息。例如，GetSectionNames可以用于获得所有段的名称，这样一来，为了获得一个拥有独特段名称（在编译期由应用程序开发者定义的）的段头部，你就需要首先获得所有名称的列表，然后再对那个准确的段名称调用函数GetSectionHeaderByName了。现在，你可以享受我为你带来的这一切了！ <img src ="http://www.cppblog.com/eday/aggbug/15392.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 17:01 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15392.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC++中使用内存映射文件处理大文件</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15389.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:59:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15389.html</guid><description><![CDATA[
		<div class="item-content">
				<div>　　<b>摘要：</b> 本文给出了一种方便实用的解决大文件的读取、存储等处理的方法，并结合相关程序代码对具体的实现过程进行了介绍。<br /><br />　　<b><font color="#ac0000">引言</font></b><br /><br />　　文件操作是应用程序最为基本的功能之一，Win32 API和MFC均提供有支持文件处理的函数和类，常用的有Win32 API的CreateFile()、WriteFile()、ReadFile()和MFC提供的CFile类等。一般来说，以上这些函数可以满足大多数场合的要求，但是对于某些特殊应用领域所需要的动辄几十GB、几百GB、乃至几TB的海量存储，再以通常的文件处理方法进行处理显然是行不通的。目前，对于上述这种大文件的操作一般是以内存映射文件的方式来加以处理的，本文下面将针对这种Windows核心编程技术展开讨论。<br /><br />　　<b><font color="#ac0000">内存映射文件</font></b><br /><br />　　内存映射文件与虚拟内存有些类似，通过内存映射文件可以保留一个地址空间的区域，同时将物理存储器提交给此区域，只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件，而非系统的页文件，而且在对该文件进行操作之前必须首先对文件进行映射，就如同将整个文件从磁盘加载到内存。由此可以看出，使用内存映射文件处理存储于磁盘上的文件时，将不必再对文件执行I/O操作，这意味着在对文件进行处理时将不必再为文件申请并分配缓存，所有的文件缓存操作均由系统直接管理，由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤，使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外，实际工程中的系统往往需要在多个进程之间共享数据，如果数据量小，处理方法是灵活多变的，如果共享数据容量巨大，那么就需要借助于内存映射文件来进行。实际上，内存映射文件正是解决本地多个进程间数据共享的最有效方法。<br /><br />　　内存映射文件并不是简单的文件I/O操作，实际用到了Windows的核心编程技术--内存管理。所以，如果想对内存映射文件有更深刻的认识，必须对Windows操作系统的内存管理机制有清楚的认识，内存管理的相关知识非常复杂，超出了本文的讨论范畴，在此就不再赘述，感兴趣的读者可以参阅其他相关书籍。下面给出使用内存映射文件的一般方法：<br /><br />　　首先要通过CreateFile()函数来创建或打开一个文件内核对象，这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后，只指定了映像文件的路径，映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后，还必须为文件数据保留一个地址空间区域，并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时，对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样，在完成了对内存映射文件的使用时，还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单，可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。<br /><br />　　<b><font color="#ac0000">内存映射文件相关函数</font></b><br /><br />　　在使用内存映射文件时，所使用的API函数主要就是前面提到过的那几个函数，下面分别对其进行介绍：<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>HANDLE CreateFile(LPCTSTR lpFileName,<br />DWORD dwDesiredAccess,<br />DWORD dwShareMode,<br />LPSECURITY_ATTRIBUTES lpSecurityAttributes,<br />DWORD dwCreationDisposition,<br />DWORD dwFlagsAndAttributes, <br />HANDLE hTemplateFile); </td></tr></tbody></table><br />　　函数CreateFile()即使是在普通的文件操作时也经常用来创建、打开文件，在处理内存映射文件时，该函数来创建/打开一个文件内核对象，并将其句柄返回，在调用该函数时需要根据是否需要数据读写和文件的共享方式来设置参数dwDesiredAccess和dwShareMode，错误的参数设置将会导致相应操作时的失败。<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>HANDLE CreateFileMapping(HANDLE hFile,<br />LPSECURITY_ATTRIBUTES lpFileMappingAttributes,<br />DWORD flProtect,<br />DWORD dwMaximumSizeHigh,<br />DWORD dwMaximumSizeLow,<br />LPCTSTR lpName); </td></tr></tbody></table><br />　　CreateFileMapping()函数创建一个文件映射内核对象，通过参数hFile指定待映射到进程地址空间的文件句柄（该句柄由CreateFile()函数的返回值获取）。由于内存映射文件的物理存储器实际是存储于磁盘上的一个文件，而不是从系统的页文件中分配的内存，所以系统不会主动为其保留地址空间区域，也不会自动将文件的存储空间映射到该区域，为了让系统能够确定对页面采取何种保护属性，需要通过参数flProtect来设定，保护属性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分别表示文件映射对象被映射后，可以读取、读写文件数据。在使用PAGE_READONLY时，必须确保CreateFile()采用的是GENERIC_READ参数；PAGE_READWRITE则要求CreateFile()采用的是GENERIC_READ|GENERIC_WRITE参数；至于属性PAGE_WRITECOPY则只需要确保CreateFile()采用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的参数dwMaximumSizeHigh和dwMaximumSizeLow也是相当重要的，指定了文件的最大字节数，由于这两个参数共64位，因此所支持的最大文件长度为16EB，几乎可以满足任何大数据量文件处理场合的要求。<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>LPVOID MapViewOfFile(HANDLE hFileMappingObject,<br />DWORD dwDesiredAccess,<br />DWORD dwFileOffsetHigh,<br />DWORD dwFileOffsetLow,<br />DWORD dwNumberOfBytesToMap);</td></tr></tbody></table><br />　　MapViewOfFile()函数负责把文件数据映射到进程的地址空间，参数hFileMappingObject为CreateFileMapping()返回的文件映像对象句柄。参数dwDesiredAccess则再次指定了对文件数据的访问方式，而且同样要与CreateFileMapping()函数所设置的保护属性相匹配。虽然这里一再对保护属性进行重复设置看似多余，但却可以使应用程序能更多的对数据的保护属性实行有效控制。MapViewOfFile()函数允许全部或部分映射文件，在映射时，需要指定数据文件的偏移地址以及待映射的长度。其中，文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定，而且必须是操作系统的分配粒度的整数倍，对于Windows操作系统，分配粒度固定为64KB。当然，也可以通过如下代码来动态获取当前操作系统的分配粒度：<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>SYSTEM_INFO sinf;<br />GetSystemInfo(&amp;sinf);<br />DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;</td></tr></tbody></table><br />　　参数dwNumberOfBytesToMap指定了数据文件的映射长度，这里需要特别指出的是，对于Windows 9x操作系统，如果MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象，将返回空值（NULL）；但是在Windows 2000下，MapViewOfFile()只需要为必要的视图找到足够大的一个区域即可，而无须考虑整个文件映射对象的大小。<br /><br />　　在完成对映射到进程地址空间区域的文件处理后，需要通过函数UnmapViewOfFile()完成对文件数据映像的释放，该函数原型声明如下：<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);</td></tr></tbody></table><br />　　唯一的参数lpBaseAddress指定了返回区域的基地址，必须将其设定为MapViewOfFile()的返回值。在使用了函数MapViewOfFile()之后，必须要有对应的UnmapViewOfFile()调用，否则在进程终止之前，保留的区域将无法释放。除此之外，前面还曾由CreateFile()和CreateFileMapping()函数创建过文件内核对象和文件映射内核对象，在进程终止之前有必要通过CloseHandle()将其释放，否则将会出现资源泄漏的问题。<br /><br />　　除了前面这些必须的API函数之外，在使用内存映射文件时还要根据情况来选用其他一些辅助函数。例如，在使用内存映射文件时，为了提高速度，系统将文件的数据页面进行高速缓存，而且在处理文件映射视图时不立即更新文件的磁盘映像。为解决这个问题可以考虑使用FlushViewOfFile()函数，该函数强制系统将修改过的数据部分或全部重新写入磁盘映像，从而可以确保所有的数据更新能及时保存到磁盘。</div>
				<div> </div>
				<div>
						<span>　　<b><font color="#ac0000">使用内存映射文件处理大文件应用示例</font></b><br /><br />　　下面结合一个具体的实例来进一步讲述内存映射文件的使用方法。该实例从端口接收数据，并实时将其存放于磁盘，由于数据量大（几十GB），在此选用内存映射文件进行处理。下面给出的是位于工作线程MainProc中的部分主要代码，该线程自程序运行时启动，当端口有数据到达时将会发出事件hEvent[0]，WaitForMultipleObjects()函数等待到该事件发生后将接收到的数据保存到磁盘，如果终止接收将发出事件hEvent[1]，事件处理过程将负责完成资源的释放和文件的关闭等工作。下面给出此线程处理函数的具体实现过程：<br /><br /><table bgcolor="#ffffff"><tbody><tr><td><p>……<br />// 创建文件内核对象，其句柄保存于hFile<br />HANDLE hFile = CreateFile("Recv1.zip",<br />GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ, <br />NULL,CREATE_ALWAYS, <br />FILE_FLAG_SEQUENTIAL_SCAN, NULL);<br /><br />// 创建文件映射内核对象，句柄保存于hFileMapping<br />HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE, <br />0, 0x4000000, NULL);<br />// 释放文件内核对象<br />CloseHandle(hFile);<br /><br />// 设定大小、偏移量等参数<br />__int64 qwFileSize = 0x4000000;<br />__int64 qwFileOffset = 0;<br />__int64 T = 600 * sinf.dwAllocationGranularity;<br />DWORD dwBytesInBlock = 1000 * sinf.dwAllocationGranularity;<br /><br />// 将文件数据映射到进程的地址空间<br />PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,<br />FILE_MAP_ALL_ACCESS,<br />(DWORD)(qwFileOffset&gt;&gt;32), (DWORD)(qwFileOffset&amp;0xFFFFFFFF), dwBytesInBlock);<br />while(bLoop) <br />{<br />  // 捕获事件hEvent[0]和事件hEvent[1]<br />  DWORD ret = WaitForMultipleObjects(2, hEvent, FALSE, INFINITE); <br />  ret -= WAIT_OBJECT_0;<br />  switch (ret)<br />  {<br />    // 接收数据事件触发<br />    case 0:<br />    // 从端口接收数据并保存到内存映射文件<br />      nReadLen=syio_Read(port[1], pbFile + qwFileOffset, QueueLen);<br />      qwFileOffset += nReadLen;<br /><br />    // 当数据写满60%时，为防数据溢出，需要在其后开辟一新的映射视图<br />if (qwFileOffset &gt; T)<br />{<br />T = qwFileOffset + 600 * sinf.dwAllocationGranularity;<br />UnmapViewOfFile(pbFile);<br />pbFile = (PBYTE)MapViewOfFile(hFileMapping,<br />FILE_MAP_ALL_ACCESS,<br />(DWORD)(qwFileOffset&gt;&gt;32), (DWORD)(qwFileOffset&amp;0xFFFFFFFF), dwBytesInBlock);<br />}<br />break;<br />        // 终止事件触发<br />    case 1:<br />      bLoop = FALSE;<br /><br />      // 从进程的地址空间撤消文件数据映像<br />      UnmapViewOfFile(pbFile);<br /><br />      // 关闭文件映射对象<br />      CloseHandle(hFileMapping);<br />      break;<br />  }<br />}<br />…</p></td></tr></tbody></table><br />　　在终止事件触发处理过程中如果只简单的执行UnmapViewOfFile()和CloseHandle()函数将无法正确标识文件的实际大小，即如果开辟的内存映射文件为30GB，而接收的数据只有14GB，那么上述程序执行完后，保存的文件长度仍是30GB。也就是说，在处理完成后还要再次通过内存映射文件的形式将文件恢复到实际大小，下面是实现此要求的主要代码：<br /><br /><table bgcolor="#ffffff"><tbody><tr><td>// 创建另外一个文件内核对象<br />hFile2 = CreateFile("Recv.zip", <br />GENERIC_WRITE | GENERIC_READ,<br />FILE_SHARE_READ, <br />NULL,<br />CREATE_ALWAYS, <br />FILE_FLAG_SEQUENTIAL_SCAN,<br />NULL);<br /><br />// 以实际数据长度创建另外一个文件映射内核对象<br />hFileMapping2 = CreateFileMapping(hFile2,<br />NULL, PAGE_READWRITE,0,(DWORD)(qwFileOffset&amp;0xFFFFFFFF),NULL);<br /><br />// 关闭文件内核对象<br />CloseHandle(hFile2);<br /><br />// 将文件数据映射到进程的地址空间<br />pbFile2 = (PBYTE)MapViewOfFile(hFileMapping2, <br />FILE_MAP_ALL_ACCESS, 0, 0, qwFileOffset);<br /><br />// 将数据从原来的内存映射文件复制到此内存映射文件<br />memcpy(pbFile2, pbFile, qwFileOffset); <br /><br />file://从进程的地址空间撤消文件数据映像<br />UnmapViewOfFile(pbFile);<br />UnmapViewOfFile(pbFile2);<br /><br />// 关闭文件映射对象<br />CloseHandle(hFileMapping);<br />CloseHandle(hFileMapping2);<br /><br />// 删除临时文件<br />DeleteFile("Recv1.zip");</td></tr></tbody></table><br />　　<b><font color="#ac0000">结论</font></b><br /><br />　　经实际测试，内存映射文件在处理大数据量文件时表现出了良好的性能，比通常使用CFile类和ReadFile()和WriteFile()等函数的文件处理方式具有明显的优势。本文所述代码在Windows 98下由Microsoft Visual C++ 6.0编译通过。</span>
				</div>
				<div class="clear">
				</div>
		</div>
<img src ="http://www.cppblog.com/eday/aggbug/15389.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:59 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15389.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在MFC中调用COM组件中的方法</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15385.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:56:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15385.html</guid><description><![CDATA[通过ClassWizard利用类型库生成包装类（图一），不过有个前提就是com组件的接口必须是派生自IDispatch 
<p><img style="DISPLAY: block; MARGIN: 0px auto 10px; TEXT-ALIGN: center" alt="10b3f9a0487.jpg" src="http://img3.pp.sohu.com/images/2006/5/2/17/19/10b3f9a0487.jpg" border="0" /></p><p>    这时就会生成你熟悉的 .h 声明文件，可以象使用其他类一样来使用这个COM组件中的方法了。</p><p>有时 COM 组件中的方法使用的是 VARIANT 类型。你可以用 COleVariant来代替</p><p>//VARIANT Send(VARIANT * pszServer, VARIANT * pszMsg, VARIANT * pszOut)</p><p> COleVariant ipaddress = "ipaddress";<br /> COleVariant pszMsg = "pszMsg";<br /> COleVariant pszOut ;</p><p> CoInitialize(NULL);</p><p> CSocket0 soc;<br /> if (soc.CreateDispatch("L2ATL.Socket") != 0)<br /> {<br />  soc.Send( &amp;ipaddress , &amp;pszMsg , &amp;pszOut );<br />  soc.ReleaseDispatch();<br /> }</p><p> CoUninitialize(); </p><img src ="http://www.cppblog.com/eday/aggbug/15385.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:56 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15385.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-四</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15383.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:55:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15383.html</guid><description><![CDATA[
		<div class="item-content">1）我们要起动WSA，这时个要用到的WSAStartup函数，用法如下： 
<p><br />　　 2）使用socket函数得到socket句柄，m<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>hSocket:=Socket(AF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>INET, SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>RAW, IPPROTO<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>IP); 用法如下：</p><p><br />　　在程序里m<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>hSocket为socket句柄，AF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>INET，SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>RAW，IPPROTO<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>IP均为常量。</p><p>　　3)定义SOCK<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ADDR类型，跟据我们的网卡IP给Sock<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ADDR类型附值，然后我们使用bind函数来绑定我们的网卡，Bind函</p><p>数用法如下：</p><p>4)用WSAIoctl来注册WSA的输入输出组件，其用法如下：</p><p><br />　　5)下面做死循环，在死循环块里，来实现数据的接收。但是徇环中间要用Sleep()做延时，不然程序会出错。</p><p>　　6)在循环块里，用recv函数来接收数据，recv函数用法如下：</p><p><br />　　7)在buffer里就是我们接收回来的数据了，如果我们想要知道数据是什么地方发来的，那么，我们要定义一定IP包结</p><p>构，用CopyMemory()把IP信息从buffer里面读出来就可以了，不过读出来的是十六进制的数据需要转换一下。</p><p>　　看了封包捕获的全过程序，对你是不是有点起发，然而在这里要告诉大家的是封包的获得是很容易的，但是许多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a></p><p>的封包都是加密的，如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。 </p><p><br />四种网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的设计方法</p><p>[文章导读] <br /> <br />在几年前我看到别人玩网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用上了外挂，做为程序员的我心里实在是不爽 </p><p><br /> </p><p>在几年前我看到别人玩网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用上了外挂，做为程序员的我心里实在是不爽，想搞清楚这到底是怎么回事。就拿了一些</p><p>来<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>，小有心得，拿出来与大家共享，外挂无非就是分几种罢了（依制作难度）：</p><p><br />　1、动作式，所谓动作式，就是指用API发命令给窗口或API控制鼠标、键盘等，使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>里的<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>进行流动或者<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>，最</p><p>早以前的“石器”外挂就是这种方式。（这种外挂完全是垃圾，TMD，只要会一点点API的人都知道该怎么做，不过这种外</p><p>挂也是<a href="http://hackbase.com/hacker/hacker" target="_blank"><font color="#000000">入门</font></a>级的好东东，虽然不能提高你的战斗力，但是可以提高你的士气）</p><p>　　2、本地修改式，这种外挂跟传统上的一些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>修改器没有两样，做这种外挂在<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>只需要对内存地址有一点认识并</p><p>且掌握API就可以实现，“精灵”的外挂这是这种方式写成的，它的难点在于找到那些地址码，找地址一般地要借助于别</p><p>人的<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>，有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>还有双码校验，正正找起来会比较困难。（这种外挂，比上一种有一点点难度，但是这种外挂做起来</p><p>能够用，也是有一定难度的啦~~，这种外挂可以很快提升你对内存地址的理解及应用，是你<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a><a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>提高的好东东）</p><p>　　3、<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式，这种外挂的目的是帮外挂制作者偷到用户的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>（TMD，“烂”就一个字，不过要知已知彼所以还是要谈</p><p>一下啦~~），做这种外挂有一定的难度，需要HOOK或键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>做底子，才可以完成，它的原理是先首截了用户的帐号</p><p>或<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>，然后发到指定邮箱。（我以前写过这样的东东，但是从来没有用过，我知道这种东东很不道德，所以以后千万别</p><p>用呀！）</p><p>　　4、加速式，这种外挂可以加快<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的速度……（对不起大家，这种东东我没有实际做过，所以不能妄自评，惭愧）</p><p>　　这几种外挂之中，前三种可以用VB，Delphi等语言比较好实现，后两种则要用VC等底层支持比较好的<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a><a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>才好实</p><p>现。 </p><p>　　动作式外挂</p><p>　　首先，先来谈一下动作式的外挂，这也是我第一次写外挂时做的最简单的一种。</p><p>　　记得还在“石器”时代的时候，我看到别人挂着一种<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>（外挂）<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>就可以四外游走（当时我还不知道外挂怎么回</p><p>事），于是找了这种<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>过来<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>（拿来后才听别人说这叫外挂），发现这种东东其实实现起来并不难，仔佃看其实<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a></p><p>的行走无非就是鼠标在不同的地方点来点去而已，看后就有实现这功能的冲动，随后跑到MSDN上看了一些资料，发现这种</p><p>实现这几个功能，只需要几个简单的API函数就可以搞定：</p><p>　　1、首先我们要知道现在鼠标的位置（为了好还原现在鼠标的位置）所以我们就要用到API函数GetCursorPos，它的使</p><p>用方法如下：</p><p><br />　　 2、我们把鼠标的位置移到要到<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>走到的地方，我们就要用到SetCursorPos函数来移动鼠标位置，它的使用方法如</p><p>下：</p><p><br />　　3、模拟鼠标发出按下和放开的动作，我们要用到mouse<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>event函数来实现，具休使用方法用下：</p><p><br />　　在它的dwFlags处，可用的事件很多如移动MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>MOVE，左键按下MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN，左键放开</p><p>MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP，具体的东东还是查一下MSDN吧~~~~~</p><p>　　 好了，有了前面的<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>，我们就可以来看看<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>移走是怎么实现的了：</p><p><br />　　看了以上的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，是不是觉得<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>的游走很简单啦~~，举一仿三，还有好多好东东可以用这个<a href="http://hackbase.com/network/network" target="_blank"><font color="#000000">技巧</font></a>实现（我早就说过</p><p>，TMD，这是垃圾外挂的做法，相信了吧~~~），接下来，再看看<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>里面自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>的做法吧（必需<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>支持快捷键</p><p>的），道理还是一样的，只是用的API不同罢了~~~，这回我们要用到的是keybd<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>event函数，其用法如下：</p><p> </p><p>　　我们还要知道扫描码不可以直接使用，要用函数MapVirtualKey把键值转成扫描码，MapVirtualKey的具体使用方法如</p><p>下：</p><p><br />　　好了，比说此快接键是CTRL+A，接下来让我们看看实际<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>是怎么写的：</p><p><br />　　首先模拟按下了CTRL键，再模拟按下A键，再模拟放开A键，最后放开CTRL键，这就是一个模拟按快捷键的周期。</p><p>　　（看到这里，差不多对简易外挂有了一定的了解了吧~~~~做一个试试？如果你举一仿三还能有更好的东东出来，这就</p><p>要看你的领悟能力了~~，不过不要高兴太早这只是才开始，以后还有更复杂的东东等着你呢~~）</p><p><br />本地修改式外挂</p><p>　　现在我们来看看，比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。</p><p>　　具我所知，本地修改式外挂最典型的应用就是在“精灵”<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>上面，因为我在近一年前（“精灵”还在测试阶段），</p><p>我所在的公司里有很多同事玩“精灵”，于是我看了一下<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的数据处理方式，发现它所发送到<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器上的信息是存在于</p><p>内存当中（我看后第一个感受是：修改这种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>和修改单机版的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>没有多大分别，换句话说就是在他向<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器提交信息</p><p>之前修改了内存地址就可以了），当时我找到了地址于是修改了内存地址，果然，按我的想法修改了地址，让系统自动提</p><p>交后，果然成功了~~~~~，后来“精灵”又改成了双地址校检，内存校检等等，在这里我就不废话了~~~~，OK，我们就来</p><p>看看这类外挂是如何制作的：</p><p>　　在做外挂之前我们要对Windows的内存有个具体的认识，而在这里我们所指的内存是指系统的内存偏移量，也就是相</p><p>对内存，而我们所要对其进行修改，那么我们要对几个Windows API进行了解，OK，跟着例子让我们看清楚这种外挂的制</p><p>作和API的应用（为了保证网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的正常运行，我就不把找内存地址的方法详细解说了）：</p><p>　　1、首先我们要用FindWindow,知道<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>窗口的句柄，因为我们要通过它来得知<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的运行后所在进程的ID，下面就是</p><p>FindWindow的用法：</p><p><br />　　2、我们GetWindowThreadProcessId来得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>窗口相对应进程的进程ID，函数用法如下：</p><p>DWORD GetWindowThreadProcessId(</p><p>HWND hWnd, // handle of window<br />LPDWORD lpdwProcessId // address of variable for process identifier<br />);</p><p>　　3、得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程ID后，接下来的事是要以最高权限打开进程，所用到的函数OpenProcess的具体使用方法如下：</p><p><br />　　在dwDesiredAccess之处就是设存取方式的地方，它可设的权限很多，我们在这里使用只要使用PROCESS<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ALL<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>ACCESS </p><p>来打开进程就可以，其他的方式我们可以查一下MSDN。</p><p>　　4、打开进程后，我们就可以用函数对存内进行操作，在这里我们只要用到WriteProcessMemory来对内存地址写入数</p><p>据即可（其他的操作方式比如说：ReadProcessMemory等，我在这里就不一一介绍了），我们看一下WriteProcessMemory</p><p>的用法：</p><p><br />　　5、下面用CloseHandle关闭进程句柄就完成了。</p><p>　　这就是这类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的程序实现部份的方法，好了，有了此方法，我们就有了理性的认识，我们看看实际例子，提升</p><p>一下我们的感性认识吧，下面就是XX<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，我们照上面的方法对应去<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下吧：</p><p><br />　　这个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>是用了多地址对所要提交的数据进行了校验，所以说这类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂制作并不是很难，最难的是要找到这些地</p><p>址。</p><p><br /><a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式外挂</p><p>　　<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>式外挂，可能大多像<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>吧，是帮助做外挂的人偷取别人<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的帐号及<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">密码</font></a>的东东。因为网络上有此类外挂的存</p><p>在，所以今天不得不说一下（我个人是非常讨厌这类外挂的，请看过本文的朋友不要到处乱用此<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，谢谢合作）。要做</p><p>此类外挂的程序实现方法很多（比如HOOK，键盘监视等<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>），因为HOOK<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>对程序员的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>要求比较高并且在实际应用</p><p>上需要多带一个<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库，所以在文中我会以键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现此类<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">木马</font></a>的制作。键盘监视<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>只需要一个.exe文件</p><p>就能实现做到后台键盘监视，这个程序用这种<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现比较适合。</p><p>　　在做程序之前我们必需要了解一下程序的思路：</p><p>　　1、我们首先知道你想记录<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的登录窗口名称。</p><p>　　2、判断登录窗口是否出现。</p><p>　　3、如果登录窗口出现，就记录键盘。</p><p>　　4、当窗口关闭时，把记录信息，通过邮件发送到程序设计者的邮箱。</p><p>　　第一点我就不具体分析了，因为你们比我还要了解你们玩的是什么<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>，登录窗口名称是什么。从第二点开始，我们</p><p>就开始这类外挂的程序实现之旅：</p><p>　　那么我们要怎么样判断登录窗口虽否出现呢？其实这个很简单，我们用FindWindow函数就可以很轻松的实现了：</p><p><br />　　实际程序实现中，我们要找到'xx'窗口，就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现，那么</p><p>我们就可以对键盘信息进行记录了。</p><p>　　先首我们用SetWindowsHookEx设置监视日志，而该函数的用法如下：</p><p><br />　　在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数，来实现而HINSTANCE这里我们直接</p><p>用本程序的HINSTANCE就可以了，具体实现方法为：</p><p><br />　　而HOOKPROC里的函数就要复杂一点点：</p><p><br />　　以上就是记录键盘的整个过程，简单吧，如果记录完可不要忘记释放呀，UnHookWindowsHookEx(hHook)，而hHOOK,就</p><p>是创建setwindowshookex后所返回的句柄。</p><p>　　我们已经得到了键盘的记录，那么现在最后只要把记录的这些信息发送回来，我们就大功造成了。其他发送这块并不</p><p>是很难，只要把记录从文本文件里边读出来，用DELPHI自带的电子邮件组件发一下就万事OK了。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p><br />　　这个程序全部功能已经实现，编编试试。</p><p><br />加速型外挂</p><p>　　原本我一直以为加速外挂是针对某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>而写的，后来发现我这种概念是不对的，所谓加速外挂其实是修改时钟频率</p><p>达到加速的目的。</p><p>　　以前DOS时代玩过<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>的人就会马上想到，这很简单嘛不就是直接修改一下8253寄存器嘛，这在以前DOS时代可能可以</p><p>行得通，但是windows则不然。windows是一个32位的操作系统，并不是你想改哪就改哪的（微软的东东就是如此霸气，说</p><p>不给你改就不给你改），但要改也不是不可能，我们可以通过两种方法来实现：第一是写一个硬件驱动来完成，第二是用</p><p>Ring0来实现（这种方法是CIH的作者陈盈豪首用的，它的原理是修改一下IDE表-&gt;创建一个中断门-&gt;进入Ring0-&gt;调用中断</p><p>修改向量，但是没有办法只能用ASM汇编来实现这一切*<a href="http://hackbase.com/game/game/2005022010070_3.html#" target="_blank"><font color="#000000">_</font></a>*，做为高级语言使用者惨啦！），用第一种方法用点麻烦，所以</p><p>我们在这里就用第二种方法实现吧~~~</p><p>　　在实现之前我们来理一下思路吧：</p><p>　　1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门，修改向量等工作</p><p>　　2、调用这个过程来实现加速功能</p><p>　　好了，现在思路有了，我们就边看<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>边讲解吧：</p><p>　　首先我们建立一个过程，这个过程就是本程序的核心部份：</p><p><br />　　最核心的东西已经写完了，大部份读者是知其然不知其所以然吧，呵呵，不过不知其所以然也然。下面我们就试着用</p><p>一下这个过程来做一个类似于“变速齿轮”的一个东东吧！</p><p>　　先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的</p><p>Change事件里写上：</p><p><br />　　因为windows默认的值为$1742，所以我们把1742做为基数，又因为值越小越快，反之越慢的原理，所以写了这样一个</p><p>公式，好了，这就是“变速齿轮”的一个Delphi＋ASM版了（只适用于win9X），呵呵，试一下吧，这对你帮助会很大的，</p><p>呵呵。</p><p>　　在win2000里，我们不可能实现在直接对端口进行操作，Ring0也失了效，有的人就会想到，我们可以写驱动程序来完</p><p>成呀，但在这里我告诉你，windows2000的驱动不是一个VxD就能实现的，像我这样的低手是写不出windows所用的驱动WDM</p><p>的，没办法，我只有借助外力实现了，ProtTalk就是一个很好的设备驱动，他很方便的来实现对低层端口的操作，从而实</p><p>现加速外挂。</p><p>　　1、我们首先要下一个PortTalk驱动，他的官方网站是<a href="http://www.beyondlogic.org/"><font color="#000000">http://www.beyondlogic.org/</font></a></p><p>　　2、我们要把里面的prottalk.sys拷贝出来。</p><p>　　3、建立一个Protalk.sys的接口（我想省略了，大家可以上<a href="http://www.freewebs.com/liuyue/porttalk.pas"><font color="#000000">http://www.freewebs.com/liuyue/porttalk.pas</font></a>下个pas </p><p>文件自己看吧）</p><p>　　4、实现加速外挂。</p><p>　　下面就讲一下这程序的实现方法吧，如果说用ProtTalk来操作端口就容易多了，比win98下用ring权限操作方便。</p><p>　　1、新建一个工程，把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。</p><p>　　2、我们在我们新建的工程加入我们的接口文件</p><p><br />　　3、我们建立一个过程</p><p><br />　　4、先加一个窗口，在窗口上放上一个trackbar控件把其Max设为20，Min设为1，把Position设为10，在这个控件的</p><p>Change事件里写上：</p><p><br />　　就这么容易。</p><p><br />在内存中修改数据的网游外挂</p><p>[文章导读] <br /> <br />现在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都是把一些信息存入内存单元的，那么我们只需要修改具体内存值就能修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中的属性，很多网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>也</p><p>不外于此 </p><p><br /> </p><p>　　现在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都是把一些信息存入内存单元的，那么我们只需要修改具体内存值就能修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中的属性，很多网络游</p><p>戏也不外于此。</p><p><br />　　曾几何时，一些网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>也是可以用内存外挂进行修改的，后来被发现后，这些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>就把单一内存地址改成多内存地</p><p>址校验，加大了修改难度，不过仍然可以通过内存分析器可以破解的。诸如“FPE”这样的<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>便提供了一定的内存分析</p><p>功能。</p><p>　　“FPE”是基于内存外挂的佼佼者，是家喻户晓的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>修改<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>。很多同类的<a href="http://down.hackbase.com/" target="_blank"><font color="#000000">软件</font></a>都是模仿“FPE”而得到玩家的认可</p><p>。而“FPE”实现的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>到现在都没有公开，很多人只能够通过猜测“FPE”的实现方法，实现同类外挂。笔者也曾经模仿</p><p>过“FPE”实现相应的功能，如“内存修改”、“内存查询”等<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。稍后会对此<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>进行剖析。</p><p>　　既然要做内存外挂，那么就必须对Windows的内存机制有所了解。<a href="http://hackbase.com/skill" target="_blank"><font color="#000000">计算机</font></a>的内存(RAM)总是不够用的，在操作系统中内</p><p>存就有物理内存和虚拟内存之分，因为程序创建放入物理内存的地址都是在变化的，所以在得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性时并不能够直接</p><p>访问物理内存地址。在v86模式下，段寄存器使用方法与实模式相同，那么可以通过段寄存器的值左移4位加上地址偏移量</p><p>就可以得到线性地址，而程序创建时在线性地址的中保留4MB-2GB的一段地址，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中属性便放于此。在windows中把虚拟</p><p>内存块称之为页，而每页为4KB，在访问内存时读取<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性时，为了不破坏数据完整性的快速浏览内存地址值，最好一</p><p>次访问一页。</p><p>　　在操作进程内存时，不需要再使用汇编语言，Windows中提供了一些访问进程内存空间的API，便可以直接对进程内存</p><p>进行操作。但初学者一般掌握不了这一项<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，为了使初学者也能够对内存进行操作，做出基于内存控制的外挂，笔者把</p><p>一些内存操作及一些内存操作逻辑进行了封装，以控件形式提供给初学者。控件名为：MpMemCtl。</p><p>　　初学者在使用此控件时，要先安装外挂引擎控件包（在此后的每篇文章中外挂引擎控件包仅提供与该文章相应的控制</p><p>控件），具体控件安装方式，请参阅《Delphi指南》，由于篇幅所限，恕不能详细提供。</p><p>　　在引擎安装完成后，便可以在Delphi中的组件栏内，找到[MP GameControls]控件组，其中可以找到[MpMemCtl]控件</p><p>。初学者可以使用此控件可以对内存进行控制。</p><p>　　一、 得到进程句柄</p><p>　　需要操作<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>内存,那么首先必须确认要操作的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>,而<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序在运行时所产生的每一个进程都有一个唯一的句柄。</p><p>　　使用控件得到句柄有三种方法：</p><p>　　1、 通过控件打开程序得到句柄。</p><p>　　在控件中，提供了startProgram方法，通过该方法，可以打开程序得到进程句柄，并且可以返回进程信息。</p><p><br />　　该方法提供了两个参数，第一个参数为要打开的程序路径，第二个参数为打开程序后所创建进程的进程信息。使用这</p><p>个方法在得到进程信息的同时，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使用控件直接对内存进程</p><p>读写操作。其应用实例如下：</p><p><br />　　2、通过控件根据程序名称得到句柄。</p><p>　　在控件中，对系统运行进程也有了相应的描述，控件提供了两个方法，用于根据程序名称得到相应的进程句柄。</p><p>getProcIDs()可以得到系统现在所运行的所有程序的名称列表。getProcID()可以通过所运行程序名称，得到相应进程的</p><p>句柄。</p><p><br />　　其应用实例如下：</p><p>　　首先可以通过getProcIDs()并把参数列表返回ComboBox1.Items里：</p><p><br />　　接着可以通过getProcID()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这时可以使</p><p>用控件直接对内存进程读写操作。</p><p><br />　　3、通过控件根据窗口名称得到句柄。</p><p>　　在控件中，控件提供了两个方法，用于根据窗口名称得到相应的进程句柄。可以通过getALLWindow()得到所有在进程</p><p>中运行的窗口。getWinProcHandle()可以通过相应的窗口名称，得到相应的进程的句柄。</p><p><br />　　其应用实例如下：</p><p>　　首先可以通过getALLWindow ()并把参数列表返回ComboBox1.Items里：</p><p><br />　　接着可以通过getWinProcHandle ()得到相应的进程句柄，并给控件的ProcHandle（进程句柄）属性进行了附值，这</p><p>时可以使用控件直接对内存进程读写操作。</p><p><br />　　二、使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停</p><p>　　在程序中，为了便于更好的得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的当前属性。在控件中提供了<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停方法。只需要调用该方法，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>便可以自</p><p>由的暂停或启动。该方法为：pauseProc()</p><p><br />　　控制类型只能够传入参数0或1，0代表使<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>暂停，1代表取消暂停。其应用实例如下：</p><p><br />　　三、读写内存值</p><p>　　<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性其实寄存在内存地址值里，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中要了解或修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性，可以通过对内存地值的读出或写入完成。</p><p>　　通过控件，要读写内存地址值很容易。可以通过调用控件提供的getAddressValue（）及setAddressValue（）两个方</p><p>法即可，在使用方法之前，要确认的是要给ProcHandle属性进行附值，因为对内存的操作必须基于进程。给ProcHandle属</p><p>性附值的方法，在上文中已经介绍。无论是对内存值进行读还是进行写，都要明确所要操作的内存地址。</p><p><br />　　要注意的是，传入内存地址时，内存地址必须为Pointer型。其应用实例如下：</p><p>　　读取地址值（如果“主角”等级所存放的地址为4549632）：</p><p><br />　　这时aValue变量里的值为内存地址[4549632]的值。</p><p>　　写入地址值：</p><p><br />　　通过该方法可以把要修改的内存地址值改为87，即把“主角”等级改为87。</p><p>　　四、内存地址值分析</p><p>　　在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中要想要到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性存放的内存地址，那么就对相应内存地址进行内存分析，经过分析以后才可得到<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性</p><p>存放的人存地址。</p><p>　　控件提供两种基于内存地址的分析方法。一种是按精确地址值进行搜索分析，另一种是按内存变化增减量进行搜索分</p><p>析。</p><p>　　1、 如果很明确的知道当前想要修改的地址值，那么就用精确地址值进行搜索分析</p><p>　　在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中，需要修改<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>的经验值，那么首先要从<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>画面上获得经验值信息，如<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>当前经验值为9800，需要</p><p>把经验值调高，那么这时候就需要对<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>经验值在内存中搜索得到相应的内存地址，当然很可能在内存中地址值为9800的</p><p>很多，第一次很可能搜索出若干个地址值为9800的地址。等待经验值再有所变化，如从9800变为了20000时，再次进行搜</p><p>索，那么从刚刚所搜索到的地址中，便可以进一步获得范围更少的内存地址，以此类推，那么最后可得到经验值具体存放</p><p>的地址。</p><p>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的Search（）方法即可。但是在搜索之前要</p><p>确认搜索的范围，正如前文中所说：“而程序创建时在线性地址的中保留4MB-2GB的一段地址”，所以要搜索的地址应该</p><p>是4MB-2GB之间，所以要把控件的MaxAddress属性设为2GB，把控件的MinAddress属性设为4MB。还有一个需要确认的是需</p><p>要搜索的值，那么应该把SearchValue属性设置为当前搜索的值。如果需要显示搜索进度那么可以把ShowGauge属性挂上一</p><p>个相应的TGauge控件（该控件为进度条控件）。</p><p><br />　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。其应用</p><p>实例如下：</p><p><br />　　2、 如果不明确当前想要修改的地址值，只知道想要修改的值变大或变小，那么就按内存变化增减量进行搜索分析。</p><p>　　如有些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值不显示出来，但要对<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值进行修改，那么只有借助于内存量增减变化而进行搜索分析出该</p><p><a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值存放的地址。如果<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>被怪物打了一下，那么<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值就会减少，那么这时候就用减量进行搜索分析，如果<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a></p><p>吃了“血”<a href="http://hackbase.com/gest/hackbase" target="_blank"><font color="#000000">人物</font></a>血值就会增加，那么这时候就用增量进行搜索分析。经过不断搜索，最后会把范围放血值的内存地址给搜</p><p>索出来。</p><p>　　如要用控件来实现内存值精确搜索，其实方法很简单，只需要调用该控件的compare()方法即可。MaxAddress、</p><p>MinAddress属性设置上面章节中有详细介绍，在此不再重提。在此分析中不需要再指定SearchValue属性。如果需要显示</p><p>搜索进度那么可以把ShowGauge属性挂上一个相应的TGauge控件。</p><p><br />　　在搜索分析时为了提高搜索效率、实现业务逻辑，那么需要传入一个参数，从而确认是否是第一次进行内存。搜索分</p><p>析类型有两种：如果参数值为0，那么就代表增量搜索。如果参数值为1，那么就代表减量搜索。其应用实例如下：</p><p><br />　　五、得到内存地址值</p><p>　　在控件中，提供获得分析后内存地址列表的方法，只需要调用getAddressList()方法，便可以获得分析过程中或分析</p><p>结果地址列表。但如果使用的是按内存变化增减量进行搜索分析的方法，那么第一次可能会搜索出来很多的地址，致使返</p><p>回速度过长，那么建议使用getAddressCount（）方法确定返回列表为一定长度后才给予返回。</p><p><br />　　其应用实例如下：</p><p><br />　　通过以上五个步骤，便可以整合成一个功能比较完备的，基于内存控制方法的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂。有了“FPE”的关键部份功</p><p>能。利用此<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>，通过一些方法，不仅仅可以分析出来<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性单内存地址，而且可以分析出一部份多内存<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>属性存放</p><p>地址。</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15383.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:55 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15383.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-二</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15381.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:54:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15381.html</guid><description><![CDATA[
		<div class="item-content">(8).编译项目ActiveKey，生成ActiveKey.DLL和ActiveKey.lib。 
<p>　　接着，我们还需要创建一个外壳程序将全局钩子安装了Windows系统中，这个外壳程序编写步骤如下：</p><p>　　(1).创建一个对话框模式的应用程序，项目名为Simulate。</p><p>　　(2).在主对话框中加入一个按钮，使用ClassWizard为其创建CLICK事件。</p><p>　　(3).将ActiveKey项目Debug目录下的ActiveKey.DLL和ActiveKey.lib拷贝到Simulate项目目录下。</p><p>　　(4).从“工程”菜单中选择“设置”，弹出Project Setting对话框，选择Link标签，在“对象/库模块”中输入</p><p>ActiveKey.lib。</p><p>　　(5).将ActiveKey项目中的ActiveKey.h头文件加入到Simulate项目中，并在Stdafx.h中加入#include ActiveKey.h。</p><p>　　(6).在按钮单击事件函数输入如下<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　　void CSimulateDlg::OnButton1() <br />　　　{<br />// TODO: Add your control notification handler code here<br />if( !bSetup )<br />{<br />m<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>hook.Start();//激活全局钩子。<br />}<br />else<br />{<br />m<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>hook.Stop();//撤消全局钩子。<br />}<br />bSetup = !bSetup;</p><p>　　　}  </p><p>　　(7).编译项目，并运行程序，单击按钮激活外挂。</p><p>　　(8).启<a href="http://hackbase.com/flash" target="_blank"><font color="#000000">动画</font></a>笔程序，选择文本<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>并将笔的颜色设置为红色，将鼠标放在任意位置后，按F10键，画笔程序自动移动</p><p>鼠标并写下一个红色的大写R。图一展示了按F10键前的画笔程序的状态，图二展示了按F10键后的画笔程序的状态。</p><p><br />图一：按F10前状态(001.jpg)</p><p><br />图二：按F10后状态(002.jpg) </p><p><br /> </p><p>　五、封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p><p>　　通过对动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的介绍，我们对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂有了一定程度上的认识，也学会了使用动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现简单的动作</p><p>模拟型<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的制作。这种动作模拟型<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂有一定的局限性，它仅仅只能解决使用<a href="http://hackbase.com/skill" target="_blank"><font color="#000000">计算机</font></a>代替人力完成那么有规律</p><p>、繁琐而无聊的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作。但是，随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的盛行和复杂度的增加，很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>要求将客户端动作信息及时反馈回<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a></p><p>器，通过<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器对这些动作信息进行有效认证后，再向客户端发送下一步<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作信息，这样动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>将失去原有的</p><p>效应。为了更好地“外挂”这些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序也进行了升级换代，它们将以前针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>用户界面层的模拟推进到数</p><p>据通讯层，通过封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>在客户端挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器发送来的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>控制数据包，分析数据包并修改数据包；同时还需按照游</p><p>戏数据包结构创建数据包，再模拟客户端发送给<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器，这个过程其实就是一个封包的过程。</p><p>　　封包的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是实现第二类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的最核心的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>涉及的<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>很广泛，实现方法也很多，如挡截WinSock</p><p>、挡截API函数、挡截消息、VxD驱动程序等。在此我们也不可能在此文中将所有的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>都进行详细介绍，故选择两种</p><p>在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序中最常用的两种方法：挡截WinSock和挡截API函数。</p><p>　　1． 挡截WinSock</p><p>　　众所周知，Winsock是Windows网络<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，它工作于Windows应用层，它提供与底层传输<a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>无关的高层数据传输</p><p><a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口。在Windows系统中，使用WinSock接口为应用程序提供基于<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">TCP/IP</font></a><a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>的网络访问<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>，这些<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>是由</p><p>Wsock32.DLL<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库提供的函数库来完成的。</p><p>　　由上说明可知，任何Windows基于<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">TCP/IP</font></a>的应用程序都必须通过WinSock接口访问网络，当然网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序也不例外。</p><p>由此我们可以想象一下，如果我们可以控制WinSock接口的话，那么控制<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>客户端程序与<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器之间的数据包也将易如</p><p>反掌。按着这个思路，下面的工作就是如何完成控制WinSock接口了。由上面的介绍可知，WinSock接口其实是由一个<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a></p><p>链接库提供的一系列函数，由这些函数实现对网络的访问。有了这层的认识，问题就好办多了，我们可以制作一个类似的</p><p><a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>链接库来代替原WinSock接口库，在其中实现WinSock32.dll中实现的所有函数，并保证所有函数的参数个数和顺序、</p><p>返回值类型都应与原库相同。在这个自制作的<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>库中，可以对我们感兴趣的函数（如发送、接收等函数）进行挡截，放</p><p>入外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，最后还继续调用原WinSock库中提供的相应功能函数，这样就可以实现对网络数据包的挡截、修改和发</p><p>送等封包功能。</p><p>　　下面重点介绍创建挡截WinSock外挂程序的基本步骤：</p><p>　　(1) 创建DLL项目，选择Win32 Dynamic-Link Library，再选择An empty DLL project。</p><p>　　(2) 新建文件wsock32.h，按如下步骤输入<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　① 加入相关变量声明：</p><p>　　　HMODULE hModule=NULL; //模块句柄<br />　　　char buffer[1000]; //缓冲区<br />　　　FARPROC proc; //函数入口指针  </p><p>　　② 定义指向原WinSock库中的所有函数地址的指针变量，因WinSock库共提供70多个函数，限于篇幅，在此就只选择</p><p>几个常用的函数列出，有关这些库函数的说明可参考MSDN相关内容。</p><p>　　　//定义指向原WinSock库函数地址的指针变量。<br />　　　SOCKET (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *socket1)(int ,int,int);//创建Sock函数。<br />　　　int　(<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *WSAStartup1)(WORD,LPWSADATA);//初始化WinSock库函数。<br />　　　int　(<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *WSACleanup1)();//清除WinSock库函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *recv1)(SOCKET ,char FAR * ,int ,int );//接收数据函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *send1)(SOCKET ,const char * ,int ,int);//发送数据函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *connect1)(SOCKET,const struct sockaddr *,int);//创建连接函数。<br />　　　int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *bind1)(SOCKET ,const struct sockaddr *,int );//绑定函数。<br />　　　......其它函数地址指针的定义略。  </p><p>　　(3) 新建wsock32.cpp文件，按如下步骤输入<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p><p>　　① 加入相关头文件声明：</p><p>　　　#include <br />　　　#include <br />　　　#include "wsock32.h"  </p><p>　　② 添加DllMain函数，在此函数中首先需要加载原WinSock库，并获取此库中所有函数的地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　BOOL WINAPI DllMain (HANDLE hInst,ULONG ul<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>reason<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>for<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>call,LPVOID lpReserved)<br />　　　{<br />　　　　if(hModule==NULL){<br />　　　　　//加载原WinSock库，原WinSock库已复制为wsock32.001。<br />　　　hModule=LoadLibrary("wsock32.001"); <br />　　}<br />　　　　else return 1;<br />//获取原WinSock库中的所有函数的地址并保存，下面仅列出部分<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />if(hModule!=NULL){<br />　　　　　//获取原WinSock库初始化函数的地址，并保存到WSAStartup1中。<br />proc=GetProcAddress(hModule,"WSAStartup");<br />　　　WSAStartup1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(WORD,LPWSADATA))proc;<br />　　　　　//获取原WinSock库消除函数的地址，并保存到WSACleanup1中。<br />　　　　proc=GetProcAddress(hModule i,"WSACleanup");<br />　　　　WSACleanup1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)())proc;<br />　　　　　//获取<a href="http://hackbase.com/News/hk" target="_blank"><font color="#000000">原创</font></a>建Sock函数的地址，并保存到socket1中。<br />　　　　proc=GetProcAddress(hModule,"socket");<br />　　　　　socket1=(SOCKET (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(int ,int,int))proc;<br />　　　　　//获取<a href="http://hackbase.com/News/hk" target="_blank"><font color="#000000">原创</font></a>建连接函数的地址，并保存到connect1中。<br />　　　　　proc=GetProcAddress(hModule,"connect");<br />　　　　　connect1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,const struct sockaddr *,int ))proc;<br />　　　　　//获取原发送函数的地址，并保存到send1中。<br />　　　　　proc=GetProcAddress(hModule,"send");<br />　　　　　send1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,const char * ,int ,int ))proc;<br />　　　　　//获取原接收函数的地址，并保存到recv1中。<br />　　　　　proc=GetProcAddress(hModule,"recv");<br />　　　　　recv1=(int (<a href="http://hackbase.com/game/game/2005022010070_1.html#" target="_blank"><font color="#000000">_</font></a>stdcall *)(SOCKET ,char FAR * ,int ,int ))proc;<br />　　　　　......其它获取函数地址<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。<br />　　　}<br />　　　else return 0;<br />　　　return 1;<br />} </p><p>　　③ 定义库输出函数，在此可以对我们感兴趣的函数中添加外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，在所有的输出函数的最后一步都调用原</p><p>WinSock库的同名函数。部分输出函数定义<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>//库输出函数定义。<br />//WinSock初始化函数。<br />　　　　int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)<br />　　　　{<br />　　　　　//调用原WinSock库初始化函数<br />　　　　　return WSAStartup1(wVersionRequired,lpWSAData);<br />　　　　}<br />　　　　//WinSock结束清除函数。<br />　　　　int PASCAL FAR WSACleanup(void)<br />　　　　{<br />　　　　　return WSACleanup1(); //调用原WinSock库结束清除函数。<br />　　　　}<br />　　　　//创建Socket函数。<br />　　　　SOCKET PASCAL FAR socket (int af, int type, int protocol)<br />　　　　{<br />　　　　　//调用原WinSock库创建Socket函数。<br />　　　　　return socket1(af,type,protocol);<br />　　　　}<br />　　　　//发送数据包函数<br />　　　　int PASCAL FAR send(SOCKET s,const char * buf,int len,int flags)<br />　　　　{<br />　　　//在此可以对发送的缓冲buf的内容进行修改，以实现欺骗<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器。<br />　　　外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>......<br />　　　//调用原WinSock库发送数据包函数。<br />　　　　　return send1(s,buf,len,flags);<br />　　　　}<br />//接收数据包函数。<br />　　　　int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags)<br />　　　　{<br />　　　//在此可以挡截到<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器端发送到客户端的数据包，先将其保存到buffer中。<br />　　　strcpy(buffer,buf);<br />　　　//对buffer数据包数据进行分析后，对其按照玩家的指令进行相关修改。<br />　　　外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>......<br />　　　//最后调用原WinSock中的接收数据包函数。<br />　　　　　return recv1(s, buffer, len, flags);<br />　　　　　}<br />　　　　.......其它函数定义<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15381.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:54 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15381.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨-三</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15382.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:54:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15382.html</guid><description><![CDATA[
		<div class="item-content">(4)、新建wsock32.def配置文件，在其中加入所有库输出函数的声明，部分声明<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下： 
<p>　　　LIBRARY "wsock32"<br />　　　E<a href="http://hackbase.com/skill/XP" target="_blank"><font color="#000000">XP</font></a>ORTS <br />　　　　WSAStartup @1<br />　　　WSACleanup @2<br />　　　　recv @3<br />　　　　send @4<br />　　　　socket @5<br />　　　bind @6<br />　　　closesocket @7<br />　　　connect @8 </p><p>　　　......其它输出函数声明<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>略。</p><p>　　(5)、从“工程”菜单中选择“设置”，弹出Project Setting对话框，选择Link标签，在“对象/库模块”中输入</p><p>Ws2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32.lib。</p><p>　　(6)、编译项目，产生wsock32.dll库文件。</p><p>　　(7)、将系统目录下原wsock32.dll库文件拷贝到被外挂程序的目录下，并将其改名为wsock.001；再将上面产生的</p><p>wsock32.dll文件同样拷贝到被外挂程序的目录下。重新启动<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序，此时<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序将先加载我们自己制作的</p><p>wsock32.dll文件，再通过该库文件间接调用原WinSock接口函数来实现访问网络。上面我们仅仅介绍了挡载WinSock的实</p><p>现过程，至于如何加入外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，还需要外挂开发人员对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数据包结构、内容、加密算法等方面的仔细分析（这个</p><p>过程将是一个艰辛的过程），再生成外挂控制<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。关于数据包分析方法和<a href="http://hackbase.com/network/network" target="_blank"><font color="#000000">技巧</font></a>，不是本文讲解的范围，如您感兴趣可以</p><p>到网上查查相关资料。</p><p><br />(3)、注入外挂<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>进入被挂<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程中</p><p>　　完成了定位和修改程序中调用API函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>后，我们就可以随意设计自定义的API函数的替代函数了。做完这一切后，</p><p>还需要将这些<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>注入到被外挂<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>程序进程内存空间中，不然<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进程根本不会访问到替代函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。注入方法有很多</p><p>，如利用全局钩子注入、利用<a href="http://hackbase.com/skill/regedit" target="_blank"><font color="#000000">注册表</font></a>注入挡截User32库中的API函数、利用CreateRemoteThread注入（仅限于NT/2000）、</p><p>利用BHO注入等。因为我们在动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>一节已经接触过全局钩子，我相信聪明的读者已经完全掌握了全局钩子的制作</p><p>过程，所以我们在后面的实例中，将继续利用这个全局钩子。至于其它几种注入方法，如果感兴趣可参阅MSDN有关内容。</p><p>　　有了以上理论基础，我们下面就开始制作一个挡截MessageBoxA和recv函数的实例，在开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序 时，可以此</p><p>实例为框架，加入相应的替代函数和处理<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>即可。此实例的开发过程如下：</p><p>　　(1) 打开前面创建的ActiveKey项目。</p><p>　　(2) 在ActiveKey.h文件中加入HOOKAPI结构，此结构用来存储被挡截API函数名称、原API函数地址和替代函数地址。</p><p>　　　typedef struct tag<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HOOKAPI <br />　　　{ <br />　　　LPCSTR szFunc;//被HOOK的API函数名称。<br />　　　PROC pNewProc;//替代函数地址。<br />　　　PROC pOldProc;//原API函数地址。<br />　　　}HOOKAPI, *LPHOOKAPI;  </p><p>　　(3) 打开ActiveKey.cpp文件，首先加入一个函数，用于定位输入库在输入数据段中的IAT地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　extern "C" <a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>declspec(dllexport)PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR <br />　　　LocationIAT(HMODULE hModule, LPCSTR szImportMod) <br />　　　//其中，hModule为进程模块句柄；szImportMod为输入库名称。<br />　　　{ <br />　　　//检查是否为DOS程序，如是返回NULL，因DOS程序没有IAT。<br />　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADER pDOSHeader = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADER) hModule; <br />　　　if(pDOSHeader-&gt;e<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>magic != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DOS<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SIGNATURE) return NULL; <br />　　　　//检查是否为NT标志，否则返回NULL。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADERS pNTHeader = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader-</p><p>&gt;e<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>lfanew)); <br />　　　　if(pNTHeader-&gt;Signature != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SIGNATURE) return NULL; <br />　　　　//没有IAT表则返回NULL。<br />　　　　if(pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DIRECTORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ENTRY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT].VirtualAddress == 0) </p><p>return NULL; <br />　　　　//定位第一个IAT位置。 <br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR pImportDesc = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)</p><p>(pNTHeader-&gt;OptionalHeader.DataDirectory[IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DIRECTORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ENTRY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT].VirtualAddress)); <br />　　　　//根据输入库名称循环检查所有的IAT，如匹配则返回该IAT地址，否则检测下一个IAT。<br />　　　　while (pImportDesc-&gt;Name) <br />　　　　{ <br />　　　　　//获取该IAT描述的输入库名称。<br />　　　PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc-&gt;Name)); <br />　　　if (stricmp(szCurrMod, szImportMod) == 0) break; <br />　　　pImportDesc++; <br />　　　　} <br />　　　　if(pImportDesc-&gt;Name == NULL) return NULL; <br />　　　return pImportDesc; <br />　　　} </p><p>　　再加入一个函数，用来定位被挡截API函数的IAT项并修改其内容为替代函数地址。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　extern "C" <a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a><a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>declspec(dllexport) <br />　　　HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi) <br />　　　//其中，hModule为进程模块句柄；szImportMod为输入库名称；pHookAPI为HOOKAPI结构指针。<br />　　　{ <br />　　　　//定位szImportMod输入库在输入数据段中的IAT地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod); <br />　　if (pImportDesc == NULL) return FALSE; <br />　　　　//第一个Thunk地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA pOrigThunk = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA)((DWORD)hModule + (DWORD)(pImportDesc-</p><p>&gt;OriginalFirstThunk)); <br />　　 //第一个IAT项的Thunk地址。<br />　　　　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA pRealThunk = (PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>THUNK<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA)((DWORD)hModule + (DWORD)(pImportDesc-</p><p>&gt;FirstThunk)); <br />　　　　//循环查找被截API函数的IAT项，并使用替代函数地址修改其值。<br />　　　while(pOrigThunk-&gt;u1.Function) <br />{ <br />　//检测此Thunk是否为IAT项。<br />if((pOrigThunk-&gt;u1.Ordinal &amp; IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ORDINAL<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>FLAG) != IMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>ORDINAL<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>FLAG) <br />{<br />　 //获取此IAT项所描述的函数名称。<br />　PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NAME pByName =(PIMAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>IMPORT<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>NAME)((DWORD)hModule+(DWORD)(pOrigThunk-</p><p>&gt;u1.AddressOfData)); <br />　if(pByName-&gt;Name[0] == '\0') return FALSE; <br />　　//检测是否为挡截函数。<br />if(strcmpi(pHookApi-&gt;szFunc, (char*)pByName-&gt;Name) == 0) <br />　 { <br />　　　　　　　MEMORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BASIC<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>INFORMATION mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk;<br />　　　　　　　//查询修改页的信息。<br />　　　　　　　VirtualQuery(pRealThunk, &amp;mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk, sizeof(MEMORY<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>BASIC<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>INFORMATION)); <br />//改变修改页保护属性为PAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>READWRITE。<br />　　　　　　　VirtualProtect(mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.BaseAddress,mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.RegionSize, PAGE<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>READWRITE, </p><p>&amp;mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.Protect); <br />//保存原来的API函数地址。<br />　　　 　　if(pHookApi-&gt;pOldProc == NULL) <br />pHookApi-&gt;pOldProc = (PROC)pRealThunk-&gt;u1.Function; <br />　 //修改API函数IAT项内容为替代函数地址。<br />pRealThunk-&gt;u1.Function = (PDWORD)pHookApi-&gt;pNewProc; <br />//恢复修改页保护属性。<br />DWORD dwOldProtect; <br />　　　　　　　VirtualProtect(mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.BaseAddress, mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.RegionSize, mbi<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>thunk.Protect, </p><p>&amp;dwOldProtect); <br />　　　　　 } <br />} <br />　 pOrigThunk++; <br />　 pRealThunk++; <br />} <br />　　SetLastError(ERROR<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SUCCESS); //设置错误为ERROR<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>SUCCESS，表示成功。<br />　　return TRUE; <br />　　　}  </p><p>　　(4) 定义替代函数，此实例中只给MessageBoxA和recv两个API进行挡截。<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p><p>　　　static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)<br />　　　{<br />　　　　//过滤掉原MessageBoxA的正文和标题内容，只显示如下内容, 。<br />return MessageBox(hWnd, "Hook API OK!", "Hook API", uType); <br />　　　} <br />　　　static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags )<br />　　　{<br />　　　//此处可以挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器发送来的网络数据包，可以加入分析和处理数据<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />　　　return recv(s,buf,len,flags);<br />　　　}  </p><p>　　(5) 在KeyboardProc函数中加入激活挡截API<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>，在if( wParam == 0X79 )语句中后面加入如下else if语句：</p><p>　　　......<br />　　　//当激活F11键时，启动挡截API函数功能。<br />　　　else if( wParam == 0x7A )<br />　　　{ <br />　　　　HOOKAPI api[2];<br />api[0].szFunc ="MessageBoxA";//设置被挡截函数的名称。<br />api[0].pNewProc = (PROC)MessageBoxA1;//设置替代函数的地址。<br />api[1].szFunc ="recv";//设置被挡截函数的名称。<br />api[1].pNewProc = (PROC)recv1; //设置替代函数的地址。<br />//设置挡截User32.dll库中的MessageBoxA函数。<br />HookAPIByName(GetModuleHandle(NULL),"User32.dll",&amp;api[0]);<br />//设置挡截Wsock32.dll库中的recv函数。<br />HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&amp;api[1]);<br />　　　}<br />　　　......  </p><p>　　(6) 在ActiveKey.cpp中加入头文件声明 "#include "wsock32.h"。 从“工程”菜单中选择“设置”，弹出Project </p><p>Setting对话框，选择Link标签，在“对象/库模块”中输入Ws2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32..lib。</p><p>　　(7) 重新编译ActiveKey项目，产生ActiveKey.dll文件，将其拷贝到Simulate.exe目录下。运行Simulate.exe并启动</p><p>全局钩子。激活任意应用程序，按F11键后，运行此程序中可能调用MessageBoxA函数的操作，看看信息框是不是有所变化</p><p>。同样，如此程序正在接收网络数据包，就可以实现封包功能了。</p><p>　　六、结束语</p><p>　　除了以上介绍的几种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序常用的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>以外，在一些外挂程序中还使用了<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数据修改<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>、<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>加速<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>等</p><p>。在这篇文章里，就不逐一介绍了。</p><p>网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂核心封包揭密</p><p>[文章导读] <br /> <br />网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是大多数<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>爱好者都比较关注的关注的问题之一，在这里就让我们一起<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下这一个问题吧 <br />　　网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>是大多数<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>爱好者都比较关注的关注的问题之一，在这里就让我们一起<a href="http://hackbase.com/hacker/leak" target="_blank"><font color="#000000">研究</font></a>一下这一个问题吧</p><p>。</p><p>　　别看这是封包这一问题，但是涉及的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>范围很广范，实现的方式也很多（比如说APIHOOK,VXD,Winsock2都可以实现</p><p>），在这里我们不可能每种<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和方法都涉及，所以我在这里以Winsock2<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>作详细讲解，就算作抛砖引玉。</p><p>　　由于大多数读者对封包类<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>不是很了解，我在这里就简单介绍一下相关<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>：</p><p>　　APIHooK：</p><p>　　由于Windows的把内核提供的功能都封装到API里面，所以大家要实现功能就必须通过API，换句话说就是我们要想捕</p><p>获数据封包，就必须先要得知道并且捕获这个API，从API里面得到封包信息。</p><p>　　VXD：</p><p>　　直接通过控制VXD驱动程序来实现封包信息的捕获，不过VXD只能用于win9X。</p><p>　　winsock2：</p><p>　　winsock是Windows网络<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，winsock工作在应用层，它提供与底层传输<a href="http://hackbase.com/network/protocol" target="_blank"><font color="#000000">协议</font></a>无关的高层数据传输<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>接口，</p><p>winsock2是winsock2.0提供的<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>提供者接口，但只能在win2000下用。</p><p>　　好了，我们开始进入winsock2封包式<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>吧。</p><p>　　在封包<a href="http://hackbase.com/hacker/program" target="_blank"><font color="#000000">编程</font></a>里面我准备分两个步骤对大家进行讲解：1、封包的捕获，2、封包的发送。</p><p>　　首先我们要实现的是封包的捕获：</p><p>　　Delphi的封装的winsock是1.0版的，很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面</p><p>做一个接口，才可以使用winsock2。</p><p>　　1、如何做winsock2的接口？</p><p>　　1）我们要先定义winsock2.0所用得到的类型，在这里我们以WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA类型做示范，大家可以举一仿三的来实现</p><p>winsock2其他类型的封装。</p><p>　　我们要知道WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;，大家</p><p>会发现WSData是引用参数，在传入参数时传的是变量的地址，所以我们对WSA<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>DATA做以下封装：</p><p><br />　　2）我们要从WS2<a href="http://hackbase.com/game/game/2005022010070_2.html#" target="_blank"><font color="#000000">_</font></a>32.DLL引入winsock2的函数，在此我们也是以WSAStartup为例做函数引入：</p><p><br />　　通过以上方法，我们便可以对winsock2做接口，下面我们就可以用winsock2做封包捕获了，不过首先要有一块网卡。</p><p>因为涉及到正在运作的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>安全问题，所以我们在这里以IP数据包为例做封包捕获，如果下面的某些数据类型您不是</p><p>很清楚，请您查阅MSDN：</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:54 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DLL的远程注入技术</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15379.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15379.html</guid><description><![CDATA[DLL的远程注入技术是目前Win32病毒广泛使用的一种技术。使用这种技术的病毒体通常位于一个DLL中，在系统启动的时候，一个EXE程序会将这个DLL加载至某些系统进程（如Explorer.exe）中运行。这样一来，普通的进程管理器就很难发现这种病毒了，而且即使发现了也很难清除，因为只要病毒寄生的进程不终止运行，那么这个DLL就不会在内存中卸载，用户也就无法在资源管理器中删除这个DLL文件，真可谓一箭双雕哉。<br />记得2003年QQ尾巴病毒肆虐的时候，就已经有些尾巴病毒的变种在使用这种技术了。到了2004年初，我曾经尝试着仿真了一个QQ尾巴病毒，但独是跳过了DLL的远程加载技术。直到最近在学校论坛上看到了几位朋友在探讨这一技术，便忍不住将这一尘封已久的技术从我的记忆中拣了出来，以满足广大的技术爱好者们。<br /><br />必备知识<br /><br />在阅读本文之前，你需要了解以下几个API函数：<br /><br />OpenProcess - 用于打开要寄生的目标进程。<br />VirtualAllocEx/VirtualFreeEx - 用于在目标进程中分配/释放内存空间。<br />WriteProcessMemory - 用于在目标进程中写入要加载的DLL名称。<br />CreateRemoteThread - 远程加载DLL的核心内容，用于控制目标进程调用API函数。<br />LoadLibrary - 目标进程通过调用此函数来加载病毒DLL。<br /><br />在此我只给出了简要的函数说明，关于函数的详细功能和介绍请参阅MSDN。<br /><br />示例程序<br /><br />我将在以下的篇幅中用一个简单的示例Virus.exe来实现这一技术。这个示例的界面如下图：<br /><img src="http://home.nuc.edu.cn/~titilima/images/remote1.GIF" border="0" /><br /><br />首先运行Target.exe，这个文件是一个用Win32 Application向导生成的“Hello, World”程序，用来作为寄生的目标进程。<br />然后在界面的编辑控件中输入进程的名称“Target.exe”，单击“注入DLL”按钮，这时候Virus.exe就会将当前目录下的DLL.dll注入至Target.exe进程中。<br />在注入DLL.dll之后，你也可以单击“卸载DLL”来将已经注入的DLL卸载。<br />点这里下载示例程序<br /><br />模拟的病毒体DLL.dll<br /><br />这是一个简单的Win32 DLL程序，它仅由一个入口函数DllMain组成：<br /><br />BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )<br />{<br />  switch ( fdwReason )<br />  {<br />  case DLL_PROCESS_ATTACH:<br />    {<br />        MessageBox( NULL, _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION );<br />    }<br />    break;<br />  case DLL_PROCESS_DETACH:<br />    {<br />        MessageBox( NULL, _T("DLL已从目标进程卸载。"), _T("信息"), MB_ICONINFORMATION );<br />    }<br />    break;<br />  }<br />  return TRUE;<br />}<br /><br />如你所见，这里我在DLL被加载和卸载的时候调用了MessageBox，这是用来显示我的远程注入/卸载工作是否成功完成。而对于一个真正的病毒体来说，它往往就是处理DLL_PROCESS_ATTACH事件，在其中加入了启动病毒代码的部分：<br /><br />  case DLL_PROCESS_ATTACH:<br />    {<br />        StartVirus();<br />    }<br />    break;<br /><br />注入！<br /><br />现在要开始我们的注入工作了。首先，我们需要找到目标进程：<br /><br />DWORD FindTarget( LPCTSTR lpszProcess )<br />{<br />  DWORD dwRet = 0;<br />  HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );<br />  PROCESSENTRY32 pe32;<br />  pe32.dwSize = sizeof( PROCESSENTRY32 );<br />  Process32First( hSnapshot, &amp;pe32 );<br />  do<br />  {<br />    if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 )<br />    {<br />        dwRet = pe32.th32ProcessID;<br />        break;<br />    }<br />  } while ( Process32Next( hSnapshot, &amp;pe32 ) );<br />  CloseHandle( hSnapshot );<br />  return dwRet;<br />}<br /><br />这里我使用了Tool Help函数库，当然如果你是NT系统的话，也可以选择PSAPI函数库。这段代码的目的就是通过给定的进程名称来在当前系统中查找相应的进程，并返回该进程的ID。得到进程ID后，就可以调用OpenProcess来打开目标进程了：<br /><br />// 打开目标进程<br />HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessID );<br /><br />现在有必要说一下OpenProcess第一个参数所指定的三种权限。在Win32系统下，每个进程都拥有自己的4G虚拟地址空间，各个进程之间都相互独立。如果一个进程需要完成跨进程的工作的话，那么它必须拥有目标进程的相应操作权限。在这里，PROCESS_CREATE_THREAD表示我可以通过返回的进程句柄在该进程中创建新的线程，也就是调用CreateRemoteThread的权限；同理，PROCESS_VM_OPERATION则表示在该进程中分配/释放内存的权限，也就是调用VirtualAllocEx/VirtualFreeEx的权限；PROCESS_VM_WRITE表示可以向该进程的地址空间写入数据，也就是调用WriteProcessMemory的权限。<br />至此目标进程已经打开，那么我们该如何来将DLL注入其中呢？在这之前，我请你看一行代码，是如何在本进程内显式加载DLL的：<br /><br />HMODULE hDll = LoadLibrary( "DLL.dll" );<br /><br />那么，如果能控制目标进程调用LoadLibrary，不就可以完成DLL的远程注入了么？的确是这样，我们可以通过CreateRemoteThread将LoadLibrary作为目标进程的一个线程来启动，这样就可以完成“控制目标进程调用LoadLibrary”的工作了。到这里，也许你会想当然地写下类似这样的代码：<br /><br />DWORD dwID;<br />LPVOID pFunc = LoadLibraryA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)"DLL.dll", 0, &amp;dwID );<br /><br />不过结果肯定会让你大失所望——注入DLL失败！<br />嗯嗯，那么现在让我们来分析一下失败的原因吧。我是前说过，在Win32系统下，每个进程都拥有自己的4G虚拟地址空间，各个进程之间都是相互独立的。在这里，我们当作参数传入的字符串"DLL.dll"其实是一个数值，它表示这个字符串位于Virus.exe地址空间之中的地址，而这个地址在传给Target.exe之后，它指向的东西就失去了有效性。举个例子来说，譬如A、B两栋大楼，我住在A楼的401；那么B楼的401住的是谁我当然不能确定——也就是401这个门牌号在B楼失去了有效性，而且如果我想要入住B楼的话，我就必须请B楼的楼长为我在B楼中安排新的住处（当然这个新的住处是否401也就不一定了）。<br />由此看来，我就需要做这么一系列略显繁杂的手续——首先在Target.exe目标进程中分配一段内存空间，然后向这段空间写入我要加载的DLL名称，最后再调用CreateRemoteThread。这段代码就成了这样：<br /><br />// 向目标进程地址空间写入DLL名称<br />DWORD dwSize, dwWritten;<br />dwSize = lstrlenA( lpszDll ) + 1;<br />LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );<br />if ( NULL == lpBuf )<br />{<br />  CloseHandle( hProcess );<br />  // 失败处理<br />}<br />if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &amp;dwWritten ) )<br />{<br />  // 要写入字节数与实际写入字节数不相等，仍属失败<br />  if ( dwWritten != dwSize )<br />  {<br />    VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />    CloseHandle( hProcess );<br />    // 失败处理<br />  }<br />}<br />else<br />{<br />  CloseHandle( hProcess );<br />  // 失败处理<br />}<br />// 使目标进程调用LoadLibrary，加载DLL<br />DWORD dwID;<br />LPVOID pFunc = LoadLibraryA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &amp;dwID );<br /><br />需要说的有两点，一是由于我要在目标进程中为ANSI字符串来分配内存空间，所以这里凡是和目标进程相关的部分，都明确使用了后缀为“A”的API函数——当然，如果要使用Unicode字符串的话，可以换作后缀是“W”的API；第二，在这里LoadLibrary的指针我是取的本进程的LoadLibraryA的地址，这是因为LoadLibraryA/LoadLibraryW位于kernel32.dll之中，而Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址，所以这里的函数地址在Target.exe中也是有效的。<br />在调用LoadLibrary完毕之后，我们就可以做收尾工作了：<br /><br />// 等待LoadLibrary加载完毕<br />WaitForSingleObject( hThread, INFINITE );<br />// 释放目标进程中申请的空间<br />VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />CloseHandle( hThread );<br />CloseHandle( hProcess );<br /><br />在此解释一下WaitForSingleObject一句。由于我们是通过CreateRemoteThread在目标进程中另外开辟了一个LoadLibrary的线程，所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。<br />好了，现在你可以尝试着整理这些代码并编译运行。运行Target.exe，然后开启一个有模块查看功能的进程查看工具（在这里我使用我的July）来查看Target.exe的模块，你会发现在注入DLL之前，Target.exe中并没有DLL.dll的存在：<br /><br /><img src="http://home.nuc.edu.cn/~titilima/images/remote2.GIF" border="0" /><br /><img src="http://home.nuc.edu.cn/~titilima/images/remote3.GIF" border="0" /><br /><br />矛盾相生<br /><br />记得2004年初我将QQ尾巴病毒成功仿真后，有很多网友询问我如何才能杀毒，不过我都没有回答——因为当时我研究的重点并非病毒的寄生特性。这一寄生特性直到今天可以说我才仿真完毕，那么，我就将解毒的方法也一并公开吧。<br />和DLL的注入过程类似，只不过在这里使用了两个API：GetModuleHandle和FreeLibrary。出于篇幅考虑，我略去了与注入部分相似或相同的代码：<br /><br />// 使目标进程调用GetModuleHandle，获得DLL在目标进程中的句柄<br />DWORD dwHandle, dwID;<br />LPVOID pFunc = GetModuleHandleA;<br />HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &amp;dwID );<br />// 等待GetModuleHandle运行完毕<br />WaitForSingleObject( hThread, INFINITE );<br />// 获得GetModuleHandle的返回值<br />GetExitCodeThread( hThread, &amp;dwHandle );<br />// 释放目标进程中申请的空间<br />VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );<br />CloseHandle( hThread );<br />// 使目标进程调用FreeLibrary，卸载DLL<br />pFunc = FreeLibrary;<br />hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)dwHandle, 0, &amp;dwID );<br />// 等待FreeLibrary卸载完毕<br />WaitForSingleObject( hThread, INFINITE );<br />CloseHandle( hThread );<br />CloseHandle( hProcess );<br /><br />用这个方法可以卸载一个进程中的DLL模块，当然包括那些非病毒体的DLL。所以，这段代码还是谨慎使用为好。<br />在完成卸载之后，如果没有别的程序加载这个DLL，你就可以将它删除了。<br /><img src ="http://www.cppblog.com/eday/aggbug/15379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏外挂设计技术探讨 一</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15380.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15380.html</guid><description><![CDATA[
		<p>网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>盛行催生了各种外挂，让商家头痛不已，那么外挂是怎么设计的呢？看看本文就知道了 </p>
		<p> </p>
		<p>一、 前言 </p>
		<p>　　所谓<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂，其实是一种<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外辅程序，它可以协助玩家自动产生<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作、修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>网络数据包以及修改<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>内</p>
		<p>存数据等，以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然，现在对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序的“合法”身份众说</p>
		<p>纷纭，在这里我不想对此发表任何个人意见，让时间去说明一切吧。</p>
		<p>
				<br />　　随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的时代的来临，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂在原有的功能之上进行了新的发展，它变得更加多种多样，功能更加强大，操</p>
		<p>作更加简单，以至有些<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的外挂已经成为一个体系，比如《石器时代》，外挂品种达到了几十种，自动战斗、自动行走</p>
		<p>、自动练级、自动补血、加速、不遇敌、原地遇敌、快速增加经验值、按键精灵……几乎无所不包。</p>
		<p>　　<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的设计主要是针对于某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>开发的，我们可以根据它针对的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的类型可大致可将外挂分为两种大类。</p>
		<p>　　一类是将<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中大量繁琐和无聊的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>动作使用外挂自动完成，以帮助玩家轻松搞定<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象并可以快速的增加玩家</p>
		<p>的经验值。比如在《龙族》中有一种工作的设定，玩家的工作等级越高，就可以驾驭越好的装备。但是增加工作等级却不</p>
		<p>是一件有趣的事情，毋宁说是重复枯燥的机械劳动。如果你想做法师用的杖，首先需要做基本工作--?砍树。砍树的方法</p>
		<p>很简单，在一棵大树前不停的点鼠标就可以了，每10000的经验升一级。这就意味着玩家要在大树前不停的点击鼠标，这</p>
		<p>种无聊的事情通过"按键精灵"就可以解决。外挂的"按键精灵"功能可以让玩家摆脱无趣的点击鼠标的工作。</p>
		<p>　　另一类是由外挂程序产生欺骗性的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>封包，并将这些封包发送到网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器，利用这些虚假信息欺骗<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a></p>
		<p>器进行<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>数值的修改，达到修改角色能力数值的目的。这类外挂程序针对性很强，一般在设计时都是针对某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>某个</p>
		<p>版本来做的，因为每个网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器与客户端交流的数据包各不相同，外挂程序必须要对欺骗的网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器的数据</p>
		<p>包进行分析，才能产生<a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器识别的数据包。这类外挂程序也是当前最流利的一类<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序。</p>
		<p>　　另外，现在很多外挂程序功能强大，不仅实现了自动动作代理和封包功能，而且还提供了对网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的客户端程序的</p>
		<p>数据进行修改，以达到欺骗网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器的目的。我相信，随着网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>商家的反外挂<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的进展，<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂将会产生</p>
		<p>更多更优秀的<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，让我们期待着看场<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>大战吧......</p>
		<p>　　三、外挂<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>综述</p>
		<p>　　可以将开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序的过程大体上划分为两个部分：</p>
		<p>　　前期部分工作是对外挂的主体<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>进行分析，不同类型的外挂分析主体<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的内容也不相同。如外挂为上述谈到的外</p>
		<p>挂类型中的第一类时，其分析过程常是针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的场景中的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象的位置和分布情况进行分析，以实现外挂自动进行攻</p>
		<p>击以及位置移动。如外挂为外挂类型中的第二类时，其分析过程常是针对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器与客户端之间通讯包数据的结构、内</p>
		<p>容以及加密算法的分析。因网络<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>公司一般都不会公布其<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>产品的通讯包数据的结构、内容和加密算法的信息，所以</p>
		<p>对于开发第二类外挂成功的关键在于是否能正确分析<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>包数据的结构、内容以及加密算法，虽然可以使用一些<a href="http://hackbase.com/hacker/tool" target="_blank"><font color="#000000">工具</font></a>辅助</p>
		<p>分析，但是这还是一种坚苦而复杂的工作。</p>
		<p>　　后期部分工作主要是根据前期对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的分析结果，使用大量的程序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>编写外挂程序以实现对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的控制或修改</p>
		<p>。如外挂程序为第一类外挂时，通常会使用到鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的自动位置移动，使用键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>来实现游</p>
		<p>戏角色的自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>。如外挂程序为第二类外挂时，通常会使用到挡截Sock和挡截API函数<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>，以挡截<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器传来的</p>
		<p>网络数据包并将数据包修改后封包后传给<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a><a href="http://vip.hackbase.com/" target="_blank"><font color="#000000">服务</font></a>器。另外，还有许多外挂使用对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>客户端程序内存数据修改<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>以及</p>
		<p>
				<a href="http://hackbase.com/game" target="_blank">
						<font color="#000000">游戏</font>
				</a>加速<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>　　本文主要是针对开发<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂程序后期使用的程序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>进行探讨，重点介绍的如下几种在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂中常使用的程</p>
		<p>序开发<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：</p>
		<p>　　● 动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：主要包括键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>　　● 封包<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>：主要包括挡截Sock<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和挡截API<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。</p>
		<p>四、动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p>
		<p>　　我们在前面介绍过，几乎所有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>都有大量繁琐和无聊的<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>动作以增加玩家的功力，还有那些数不完的迷宫，这</p>
		<p>些好像已经成为了角色<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>的代名词。现在，外挂可以帮助玩家从这些繁琐而无聊的工作中摆脱出来，专注于<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>情节的</p>
		<p>进展。外挂程序为了实现自动角色位置移动和自动<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>等功能，需要使用到键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>和鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。下面我们将重</p>
		<p>点介绍这些<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>并编写一个简单的实例帮助读者理解动作模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>的实现过程。</p>
		<p>　　１． 鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a><br />　　<br />　　几乎所有的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中都使用了鼠标来改变角色的位置和方向，玩家仅用一个小小的鼠标，就可以使角色畅游天下。那么</p>
		<p>，我们如何实现在没有玩家的参与下角色也可以自动行走呢。其实实现这个并不难，仅仅几个Windows API函数就可以搞</p>
		<p>定，让我们先来认识认识这些API函数。</p>
		<p>　　(1) 模拟鼠标动作API函数mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event，它可以实现模拟鼠标按下和放开等动作。</p>
		<p>　　　　VOID mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(<br />　　　　　　DWORD dwFlags, // 鼠标动作标识。<br />　　　　　　DWORD dx, // 鼠标水平方向位置。<br />　　　　　　DWORD dy, // 鼠标垂直方向位置。<br />　　　　　　DWORD dwData, // 鼠标轮子转动的数量。<br />　　　　　　DWORD dwExtraInfo // 一个关联鼠标动作辅加信息。<br />　　　　); </p>
		<p>　　其中，dwFlags表示了各种各样的鼠标动作和点击活动，它的常用取值如下：</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MOVE　表示模拟鼠标移动事件。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN 表示模拟按下鼠标左键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP 表示模拟放开鼠标左键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTDOWN 表示模拟按下鼠标右键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTUP 表示模拟放开鼠标右键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MIDDLEDOWN 表示模拟按下鼠标中键。</p>
		<p>　　　MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>MIDDLEUP 表示模拟放开鼠标中键。</p>
		<p>　　(2)、设置和获取当前鼠标位置的API函数。获取当前鼠标位置使用GetCursorPos()函数，设置当前鼠标位置使用</p>
		<p>SetCursorPos()函数。</p>
		<p>　　　　BOOL GetCursorPos(<br />　　　　　LPPOINT　lpPoint // 返回鼠标的当前位置。<br />　　　　);<br />　　　　BOOL SetCursorPos(<br />　　　　int X, // 鼠标的水平方向位置。<br />　　　　　　int Y //鼠标的垂直方向位置。<br />　　　　);  </p>
		<p>　　通常<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的行走都是通过鼠标移动至目的地，然后按一下鼠标的按钮就搞定了。下面我们使用上面介绍的API函</p>
		<p>数来模拟角色行走过程。</p>
		<p>　　　CPoint oldPoint,newPoint;<br />　　　GetCursorPos(&amp;oldPoint); //保存当前鼠标位置。<br />　　　newPoint.x = oldPoint.x+40;<br />　　　newPoint.y = oldPoint.y+10;<br />　　　SetCursorPos(newPoint.x,newPoint.y); //设置目的地位置。<br />　　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTDOWN,0,0,0,0);//模拟按下鼠标右键。<br />　　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>RIGHTUP,0,0,0,0);//模拟放开鼠标右键。  </p>
		<p>　　2． 键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a></p>
		<p>　　在很多<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中，不仅提供了鼠标的操作，而且还提供了键盘的操作，在对<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象进行<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>时还可以使用快捷键。为</p>
		<p>了使这些<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>过程能够自动进行，外挂程序需要使用键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>。像鼠标模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>一样，Windows API也提供了一系列</p>
		<p>API函数来完成对键盘动作的模拟。</p>
		<p>　　模拟键盘动作API函数keydb<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event，它可以模拟对键盘上的某个或某些键进行按下或放开的动作。</p>
		<p>　　　VOID keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(<br />　　　　　BYTE bVk, // 虚拟键值。<br />　　　　　BYTE bScan, // 硬件扫描码。<br />　　　　　DWORD dwFlags, // 动作标识。<br />　　　　　DWORD dwExtraInfo // 与键盘动作关联的辅加信息。<br />　　　); </p>
		<p>　　其中，bVk表示虚拟键值，其实它是一个BYTE类型值的宏，其取值范围为1-254。有关虚拟键值表请在MSDN上使用关键</p>
		<p>字“Virtual-Key Codes”查找相关资料。bScan表示当键盘上某键被按下和放开时，键盘系统硬件产生的扫描码，我们可</p>
		<p>以MapVirtualKey()函数在虚拟键值与扫描码之间进行转换。dwFlags表示各种各样的键盘动作，它有两种取值：</p>
		<p>KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>EXTENDEDKEY和KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP。</p>
		<p>　　下面我们使用一段<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>实现在<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>中按下Shift+R快捷键对<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>对象进行<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">攻击</font></a>。</p>
		<p>　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,0),0,0); //按下CTRL键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),0,0);//键下R键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0), KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开R键。<br />　　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CONTROL,0), <br />　　　KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开CTRL键。 </p>
		<p>　　3． 激活外挂</p>
		<p>　　上面介绍的鼠标和键盘模拟<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>实现了对<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>角色的动作部分的模拟，但要想外挂能工作于<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>之上，还需要将其与</p>
		<p>
				<a href="http://hackbase.com/game" target="_blank">
						<font color="#000000">游戏</font>
				</a>的场景窗口联系起来或者使用一个激活键，就象按键精灵的那个激活键一样。我们可以用GetWindow函数来枚举窗口</p>
		<p>，也可以用Findwindow函数来查找特定的窗口。另外还有一个FindWindowEx函数可以找到窗口的子窗口，当<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>切换场景</p>
		<p>的时候我们可以用FindWindowEx来确定一些当前窗口的特征，从而判断是否还在这个场景，方法很多了，比如可以</p>
		<p>GetWindowInfo来确定一些东西，比如当查找不到某个按钮的时候就说明<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>场景已经切换了等等办法。当使用激活键进</p>
		<p>行关联，需要使用Hook<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>开发一个全局键盘钩子，在这里就不具体介绍全局钩子的开发过程了，在后面的实例中我们将</p>
		<p>会使用到全局钩子，到时将学习到全局钩子的相关<a href="http://hackbase.com/network/zs" target="_blank"><font color="#000000">知识</font></a>。</p>
		<p>
				<br />　　4． 实例实现</p>
		<p>　　通过上面的学习，我们已经基本具备了编写动作式<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂的能力了。下面我们将创建一个画笔程序外挂，它实现自</p>
		<p>动移<a href="http://hackbase.com/flash" target="_blank"><font color="#000000">动画</font></a>笔字光标的位置并写下一个红色的“R”字。以这个实例为基础，加入相应的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>动作规则，就可以实现一个完</p>
		<p>整的<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>外挂。这里作者不想使用某个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>作为例子来开发外挂（因没有<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>商家的授权啊！），如读者感兴趣的话可以</p>
		<p>找一个<a href="http://hackbase.com/game" target="_blank"><font color="#000000">游戏</font></a>试试，最好仅做测试<a href="http://hackbase.com/network" target="_blank"><font color="#000000">技术</font></a>用。</p>
		<p>　　首先，我们需要编写一个全局钩子，使用它来激活外挂，激活键为F10。创建全局钩子步骤如下：</p>
		<p>　　(1)．选择MFC AppWizard(DLL)创建项目ActiveKey，并选择MFC Extension DLL（共享MFC拷贝）类型。</p>
		<p>　　(2).插入新文件ActiveKey.h，在其中输入如下<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p>
		<p>　　　#ifndef <a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYDLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>H<br />　　　#define <a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYDLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>H</p>
		<p>　　　class AFX<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>EXT<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>CLASS CKeyHook:public CObject<br />　　　{<br />　　　　public:<br />　CKeyHook();<br />　~CKeyHook();<br />　HHOOK Start();　//安装钩子<br />　BOOL Stop(); //卸载钩子<br />　　　};<br />　　　#endif  </p>
		<p>　　(3).在ActiveKey.cpp文件中加入声明＂#include ActiveKey.h＂。</p>
		<p>　　(4).在ActiveKey.cpp文件中加入共享数据段，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　//Shared data section<br />　　　#pragma data<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>seg("sharedata")<br />　　　HHOOK glhHook=NULL; //钩子句柄。<br />　　　HINSTANCE glhInstance=NULL; //DLL实例句柄。<br />　　　#pragma data<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>seg()  </p>
		<p>　　(5).在ActiveKey.def文件中设置共享数据段属性，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　SETCTIONS<br />　　　shareddata READ WRITE SHARED </p>
		<p>　　(6).在ActiveKey.cpp文件中加入CkeyHook类的实现<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>和钩子函数<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>：</p>
		<p>　　　//键盘钩子处理函数。<br />　　　extern "C" LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)<br />　　　{<br />　　　if( nCode &gt;= 0 )<br />　　　{<br />　　　if( wParam == 0X79 )//当按下F10键时，激活外挂。<br />　{<br />　　//外挂实现<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>。<br />CPoint newPoint,oldPoint;<br />　　 GetCursorPos(&amp;oldPoint);<br />　　 newPoint.x = oldPoint.x+40;<br />　　 newPoint.y = oldPoint.y+10;<br />　　 SetCursorPos(newPoint.x,newPoint.y);<br />　　 mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTDOWN,0,0,0,0);//模拟按下鼠标左键。<br />　　mouse<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(MOUSEEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>LEFTUP,0,0,0,0);//模拟放开鼠标左键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,0),0,0); //按下SHIFT键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),0,0);//按下R键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(0x52,MapVirtualKey(0x52,0),KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开R键。<br />　　keybd<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>event(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,MapVirtualKey(VK<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>SHIFT,0),KEYEVENTF<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYUP,0);//放开SHIFT键。<br />　　　　　　SetCursorPos(oldPoint.x,oldPoint.y);<br />　}<br />　　　}<br />　　　return CallNextHookEx(glhHook,nCode,wParam,lParam);<br />　　　}</p>
		<p>　　　CKeyHook::CKeyHook(){}<br />　　　CKeyHook::~CKeyHook()<br />　　　{　<br />　　　if( glhHook )<br />Stop();<br />　　　}<br />　　　//安装全局钩子。<br />　　　HHOOK CKeyHook::Start()<br />　　　{ <br />glhHook = SetWindowsHookEx(WH<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>KEYBOARD,KeyboardProc,glhInstance,0);//设置键盘钩子。<br />return glhHook;<br />}<br />　　　//卸载全局钩子。<br />　　　BOOL CKeyHook::Stop()<br />　　　{<br />　　　BOOL bResult = TRUE;<br />　if( glhHook )<br />　　　bResult = UnhookWindowsHookEx(glhHook);//卸载键盘钩子。<br />　　　return bResult;<br />　　　}  </p>
		<p>　　(7).修改DllMain函数，<a href="http://hackbase.com/hacker" target="_blank"><font color="#000000">代码</font></a>如下：</p>
		<p>　　　extern "C" int APIENTRY<br />　　　DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)<br />　　　{<br />//如果使用lpReserved参数则删除下面这行 <br />UNREFERENCED<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PARAMETER(lpReserved);</p>
		<p>if (dwReason == DLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PROCESS<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>ATTACH)<br />{<br />　　TRACE0("NOtePadHOOK.DLL Initializing!\n");<br />　　 //扩展DLL仅初始化一次 <br />　　if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance))<br />return 0;<br />　　new CDynLinkLibrary(ActiveKeyDLL);<br />　　　　　　//把DLL加入<a href="http://hackbase.com/News/industry" target="_blank"><font color="#000000">动态</font></a>MFC类库中 <br />　　glhInstance = hInstance;<br />　　//插入保存DLL实例句柄 <br />}<br />else if (dwReason == DLL<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>PROCESS<a href="http://hackbase.com/game/game/2005022010070.html#" target="_blank"><font color="#000000">_</font></a>DETACH)<br />{<br />　　TRACE0("NotePadHOOK.DLL Terminating!\n");<br />　　//终止这个链接库前调用它 <br />　　AfxTermExtensionModule(ActiveKeyDLL);<br />}<br />return 1; <br />　　　} </p>
<img src ="http://www.cppblog.com/eday/aggbug/15380.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:53 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15380.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户层下拦截系统 API 的原理与实现 二</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15378.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:52:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15378.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1 //////////////////////////////////////////////////////////////////////   2 附录：一个拦截CreateFile函数的简单实现   3 //////////////////////////////////////////////////////////////////////   4 #include &lt;stdio....&nbsp;&nbsp;<a href='http://www.cppblog.com/eday/archive/2006/11/18/15378.html'>阅读全文</a><img src ="http://www.cppblog.com/eday/aggbug/15378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:52 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用户层下拦截系统 API 的原理与实现 一</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15377.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:51:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15377.html</guid><description><![CDATA[　　拦截api的技术有很多种，大体分为用户层和内核层的拦截．这里只说说用户层的拦截．而用户层也分为许多种：修改PE文件导入表，直接修改要拦截的api的内存（从开始到最后，使程序跳转到指定的地址执行）．不过大部分原理都是修改程序流程，使之跳转到你要执行的地方，然后再返回到原地址．原来api的功能必须还能实现．否则拦截就失去作用了．修改文件导入表的方法的缺点是如果用户程序动态加载（使用LoadLibrary和GetProcAddress函数），拦截将变得复杂一些．所以这里介绍一下第二种方法，直接修改api，当然不是全局的．（后面会说到） <br /><br />　　需要了解的一些知识： <br /><br />　　１．windows内存的结构属性和进程地址空间 <br /><br />　　２．函数堆栈的一些知识 <br /><br /><br /><br />一：win2000和xp的内存结构和进程地址空间 <br /><br />  windows采用4GB平坦虚拟地址空间的做法。即每个进程单独拥有4GB的地址空间。每个进程只能访问自己的这4GB的虚拟空间，而对于其他进程的地址空间则是不可见的。这样保证了进程的安全性和稳定性。但是，这4GB的空间是一个虚拟空间，在使用之前，我们必须先保留一段虚拟地址，然后再为这段虚拟地址提交物理存储器。可是我们的内存大部分都还没有1GB，那么这4GB的地址空间是如何实现的呢？事实上windows采用的内存映射这种方法，即把物理磁盘当作内存来使用，比如我们打开一个可执行文件的时候，<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>会为我们开辟这个4GB的地址空间：0x00000000--0xffffffff。其中0x00000000--0x7fffffff是属于用户层的空间.0x80000000--0xffffffff则属于共享内核方式分区，主要是<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>的线程调度，内存管理，文件系统支持，网络支持和所有设备驱动程序。对于用户层的进程，这些地址空间是不可访问的。任何访问都将导致一个错误。开辟这4GB的虚拟地址空间之后，系统会把磁盘上的执行文件映射到进程的地址空间中去(一般是在地址0x00400000，可以通过修改编译选项来修改这个地址)而一个进程运行所需要的动态库文件则一般从0x10000000开始加载。但是如果所有的动态库都加载到这个位置肯定会引起冲突。因此必须对一些可能引起冲突的dll编译时重新修改基地址。但是对于所有的<b><a href="http://www.baidu.com/s?tn=piglet&amp;ct=&amp;lm=&amp;z=&amp;rn=&amp;word=操作系统" target="_blank"><font color="#ff0000">操作系统</font></a></b>所提供的动态库windows已经定义好了映射在指定的位置。这个位置会随着版本的不同而会有所改变，不过对于同一台机器上的映射地址来说都是一样的。即在a进程里映射的kernel32.dll的地址和在进程b里的kernel32.dll的地址是一样的。对于文件映射是一种特殊的方式，使得程序不需要进行磁盘i/o就能对磁盘文件进行操作，而且支持多种保护属性。对于一个被映射的文件，主要是使用CreateFileMapping函数，利用他我们可以设定一些读写属性:PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY.第一参数指定只能对该映射文件进行读操作。任何写操作将导致内存访问错误。第二个参数则指明可以对映射文件进行读写。这时候，任何对文件的读写都是直接操作文件的。而对于第三个参数PAGE_WRITECOPY顾名思义就是写入时拷贝，任何向这段内存写入的操作(因为文件是映射到进程地址空间的，对这段空间的读写就相当于对文件进行的直接读写)都将被系统捕获，并重新在你的虚拟地址空间重新保留并分配一段内存，你所写入的一切东西都将在这里，而且你原先的指向映射文件的内存地址也会实际指向这段重新分配的内存，于是在进程结束后，映射文件内容并没有改变，只是在运行期间在那段私有拷贝的内存里面存在着你修改的内容。windows进程运行所需要映射的一些系统dll就是以这种方式映射的，比如常用的ntdll.dll,kernel32.dll,gdi32.dll.几乎所有的进程都会加载这三个动态库。如果你在一个进程里修改这个映射文件的内容，并不会影响到其他的进程使用他们。你所修改的只是在本进程的地址空间之内的。事实上原始文件并没有被改变。 <br />这样，在后面的修改系统api的时候，实际就是修改这些动态库地址内的内容。前面说到这不是修改全局api就是这个原因，因为他们都是以写入时拷贝的方式来映射的。不过这已经足够了，windows提供了2个强大的内存操作函数ReadProcessMemory和WriteProcessMemory.利用这两个函数我们就可以随便对任意进程的任意用户地址空间进行读写了。但是，现在有一个问题，我们该写什么，说了半天，怎么实现跳转呢？现在来看一个简单的例子： <br />MessageBox(NULL, "World", "Hello", 0); <br />我们在执行这条语句的时候，调用了系统api MessageBox，实际上在程序中我没有定义UNICODE宏，系统调用的是MessageBox的ANSI版本MessageBoxA,这个函数是由user32.dll导出的。下面是执行这条语句的汇编代码： <br />0040102A   push         0 <br />0040102C   push         offset string "Hello" (0041f024) <br />00401031   push         offset string "World" (0041f01c) <br />00401036   push         0 <br />00401038   call         dword ptr [__imp__MessageBoxA@16 (0042428c)] <br />前面四条指令分别为参数压栈，因为MessageBoxA是__stdcall调用约定，所以参数是从右往左压栈的。最后再CALL 0x0042428c <br /><br />看看0042428c这段内存的值： <br />0042428C   0B 05 D5 77 00 00 00 <br />可以看到这个值0x77d5050b,正是user32.dll导出函数MessageBoxA的入口地址。 <br /><br />这是0x77D5050B处的内容，   <br />77D5050B 8B FF                 mov         edi,edi <br />77D5050D 55                   push         ebp <br />77D5050E 8B EC                 mov         ebp,esp <br />理论上只要改变api入口和出口的任何机器码，都可以拦截该api。这里我选择最简单的修改方法，直接修改api入口的前十个字节来实现跳转。为什么是十字节呢？其实修改多少字节都没有关系，只要实现了函数的跳转之后，你能把他们恢复并让他继续运行才是最重要的。在CPU的指令里，有几条指令可以改变程序的流程：JMP，CALL，INT，RET，RETF，IRET等指令。这里我选择CALL指令，因为他是以函数调用的方式来实现跳转的，这样可以带一些你需要的参数。到这里，我该说说函数的堆栈了。 <br /><br />总结：windows进程所需要的动态库文件都是以写入时拷贝的方式映射到进程地址空间中的。这样，我们只能拦截指定的进程。修改目标进程地址空间中的指定api的入口和出口地址之间的任意数据，使之跳转到我们的拦截代码中去，然后再恢复这些字节，使之能顺利工作。 <br /><br /><br /><br /><br />二：函数堆栈的一些知识 <br /><br />  正如前面所看到MessageBoxA函数执行之前的汇编代码，首先将四个参数压栈，然后CALL MessageBoxA，这时候我们的线程堆栈看起来应该是这样的： <br /><br />|     |     &lt;---ESP <br />|返回地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|..   | <br /><br />我们再看MessageBoxA的汇编代码， <br />77D5050B 8B FF                 mov         edi,edi <br />77D5050D 55                   push         ebp <br />77D5050E 8B EC                 mov         ebp,esp <br />注意到堆栈的操作有PUSH ebp,这是保存当前的基址指针，以便一会儿恢复堆栈后返回调用线程时使用，然后再有mov ebp,esp就是把当前esp的值赋给ebp，这时候我们就可以使用 ebp+偏移 来表示堆栈中的数据，比如参数1就可以表示成[ebp+8]，返回地址就可以表示成[ebp+4]..如果我们在拦截的时候要对这些参数和返回地址做任何处理，就可以使用这种方法。如果这个时候函数有局部变量的话，就通过减小ESP的值的方式来为之分配空间。接下来就是保存一些寄存器：EDI,ESI,EBX.要注意的是，函数堆栈是反方向生长的。这时候堆栈的样子： <br />|....| <br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量| <br />|EBP     |   <br />|返回地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|..   | <br /><br />在函数返回的时候，由函数自身来进行堆栈的清理，这时候清理的顺序和开始入栈的顺序恰恰相反，类似的汇编代码可能是这样的： <br /><br />pop edi <br />pop esi <br />pop ebx <br />add esp, 4 <br />pop ebp <br />ret 0010 <br />先恢复那些寄存器的值，然后通过增加ESP的值的方式来释放局部变量。这里可以用mov esp, ebp来实现清空所有局部变量和其他一些空闲分配空间。接着函数会恢复EBP的值，利用指令POP EBP来恢复该寄存器的值。接着函数运行ret 0010这个指令。该指令的意思是，函数把控制权交给当前栈顶的地址的指令，同时清理堆栈的16字节的参数。如果函数有返回值的话，那在EAX寄存器中保存着当前函数的返回值。如果是__cdecl调用方式，则执行ret指令，对于堆栈参数的处理交给调用线程去做。如wsprintf函数。 <br /><br />这个时候堆栈又恢复了原来的样子。线程得以继续往下执行... <br />在拦截api的过程之中一个重要的任务就是保证堆栈的正确性。你要理清每一步堆栈中发生了什么。 <br /><br /><br /><br />三：形成思路 <br />   <br />  呵呵，不知道你现在脑海是不是有什么想法。怎么去实现拦截一个api？ <br />  这里给出一个思路，事实上拦截的方法真的很多，理清了一个，其他的也就容易了。而且上面所说的2个关键知识，也可以以另外的形式来利用。 <br />  我以拦截CreateFile这个api为例子来简单说下这个思路吧： <br />   <br />  首先，既然我们要拦截这个api就应该知道这个函数在内存中的位置吧，至少需要知道从哪儿入口。CreateFile这个函数是由kernel32.dll这个动态库导出的。我们可以使用下面的方法来获取他映射到内存中的地址： <br />  HMODULE hkernel32 = LoadLibrary("Kernel32.dll"); <br />  PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA"); <br />这就可以得到createfile的地址了，注意这里是获取的createfile的ansic版本。对于UNICODE版本的则获取CreateFileW。这时dwCreateFile的值就是他的地址了。对于其他进程中的createfile函数也是这个地址，前面说过windows指定了他提供的所有的dll文件的加载地址。 <br />   <br />  接下来，我们该想办法实现跳转了。最简单的方法就是修改这个api入口处的代码了。但是我们该修改多少呢？修改的内容为什么呢？前面说过我们可以使用CALL的方式来实现跳转，这种方法的好处是可以为你的拦截函数提供一个或者多个参数。这里只要一个参数就足够了。带参数的函数调用的汇编代码是什么样子呢，前面也已经说了，类似与调用MessageBoxA时的代码： <br /><br />PUSH   参数地址 <br />CALL   函数入口地址(这里为一个偏移地址) <br /><br />执行这2条指令就能跳转到你要拦截的函数了，但是我们该修改成什么呢。首先，我们需要知道这2条指令的长度和具体的机器代码的值。其中PUSH对应0x68，而CALL指令对应的机器码为0xE8,而后面的则分别对应拦截函数的参数地址和函数的地址。注意第一个是一个直接的地址，而第二个则是一个相对地址。当然你也可以使用0xFF0x15这个CALL指令来进行直接地址的跳转。 <br />下面就是计算这2个地址的值了， <br />对于参数和函数体的地址，要分情况而定，对于对本进程中api的拦截，则直接取地址就可以了。对于参数，可以先定义一个参数变量，然后取变量地址就ok了。 <br />如果是想拦截其他进程中的api，则必须使用其他一些方法，最典型的方法是利用VirtualAllocEx函数来在其他进程中申请和提交内存空间。然后用WriteProcessMemory来分别把函数体和参数分别写入申请和分配的内存空间中去。然后再生成要修改的数据，最后用WriteProcessMemory来修改api入口，把入口的前10字节修改为刚刚生成的跳转数据。比如在远程进程中你写入的参数和函数体的内存地址分别为0x00010000和0x00011000,则生成的跳转数据为 68 00 00 01 00 E8 00 10 01 00(PUSH 00010000   CALL 00011000),这样程序运行createfile函数的时候将会先运行PUSH 00010000   CALL 00011000，这样就达到了跳转的目的。此刻我们应该时刻注意堆栈的状态，对于CreateFile有 <br />HANDLE CreateFile( <br />LPCTSTR lpFileName, <br />DWORD dwDesiredAccess, <br />DWORD dwShareMode, <br />LPSECURITY_ATTRIBUTES lpSecurityAttributes, <br />DWORD dwCreationDisposition, <br />DWORD dwFlagsAndAttributes, <br />HANDLE hTemplateFile <br />); <br />可以看到其有7个参数，于是在调用之前，堆栈应该已经被压入了这7个参数，堆栈的样子： <br />|....|     &lt;---ESP <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />这是执行到我们的跳转语句：PUSH 00010000,于是堆栈又变了： <br /><br />|....|     &lt;---ESP <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />接着执行CALL 00011000,堆栈变为： <br />|...|   &lt;---ESP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />接下来就到了我们的拦截函数中拉，当然，函数肯定也会做一些类似动作，把EBP压栈，为局部变量分配空间等。这时候堆栈的样子又变了： <br /><br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量| <br />|EBP|     &lt;---EBP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />这时候，你想做什么就尽情地做吧，获取参数信息，延缓执行CreateFile函数等等。拿获取打开文件句柄的名字来说吧，文件名是第一个参数，前面说过我们可以用[EBP+8]来获取参数，但是对照上面的堆栈形状，中间又加了另外一些数据，所以我们用[EBP+16]来获取第一个参数的地址。比如： <br />char* PFileName = NULL; <br />__asm{ <br />MOV EAX,[EBP+16] <br />MOV [szFileName], EAX <br />} <br /><br />比如我们用一个messagebox来弹出一个信息，说明该程序即将打开一个某谋路径的文件句柄。但是有一个要注意的是，如果你想拦截远程进程的话，对于那个拦截函数中所使用到的任何函数或者以任何形式的相对地址的调用都要停止。因为每个进程中的地址分配都是独立的，比如上面的CALL MessageBoxA改成直接地址的调用。对于使用messagebox，我们应该定义一个函数指针，然后把这个指针的值赋值为user32.dll中导出该函数的直接地址。然后利用这个指针来进行函数调用。对于messagebox函数的调用可以这样，在源程序中定义一个参数结构体，参数中包含一个导出函数的地址,把这个地址设为MessageBoxA的直接地址，获取地址的方法就不说了。然后把这个参数传给拦截函数，就可以使用拉。这也是利用一个参数的原因。类似代码如下： <br /><br /><br />typedef struct _RemoteParam { <br />  DWORD dwMessageBox; <br />} RemoteParam, * PRemoteParam; <br /><br />typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);//定义一个函数指针 <br /><br />//拦截函数 <br />void HookCreateFile(LPVOID lParam) <br />{ <br />RemoteParam* pRP = (RemoteParam*)lParam;//获取参数地址 <br />char* PFileName = NULL;//定义一个指针 <br />__asm{ <br />MOV EAX,[EBP+16] <br />MOV [szFileName], EAX //把CreateFile第一个参数的值，文件的路径的地址传                       //给szFileName <br />} <br /><br />//定 义一个函数指针 <br />PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP-&gt;dwMessageBox; <br /><br />pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINformATION |MB_OK); <br />//输出要打开的文件的路径 <br />//..... <br />} <br /><br />对于你要使用的其他函数，都是使用同样的方式，利用这个参数来传递我们要传递的函数的绝对地址，然后定义这个函数指针，就可以使用了。 <br /><br /><br />好了，接下来我们该让被拦截的api正常工作了，这个不难，把他原来的数据恢复一下就可以了。那入口的10个字节。我们在改写他们的时候应该保存一下，然后也把他放在参数中传递给拦截函数，呵呵，参数的作用可多了。接着我们就可以用WriteProcessMemory函数来恢复这个api的入口了，代码如下： <br />PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP-&gt;dwGetCurrentProcess; <br />PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP-&gt;dwWriteProcessMemory; <br />   <br />if(!pfnWriteProcessMemory(pfnGetCurrentProcess(), <br />                                          (LPVOID)pfnConnect, <br />                                          (LPCVOID)pRP-&gt;szOldCode, <br />                                          10, <br />                                          NULL)) <br />pfnMessageBox(NULL, pRP-&gt;szModuleName1, pRP-&gt;szModuleName2,   MB_ICONINformATION | MB_OK); <br />其中这些函数指针的定义和上面的类似。 <br />而参数中的szoldcode则是在源程序中在修改api之前保存好，然后传给拦截函数，在源程序中是用ReadProcessMemory函数来获取他的前10个字节的： <br />ReadProcessMemory(GetCurrentProcess(), <br />                                (LPCVOID)RParam.dwCreateFile, <br />                                oldcode, <br />                                10, <br />                                &amp;dwPid) <br />strcat((char*)RParam.szOldCode, (char*)oldcode); <br /><br /><br />接下来如果你还继续保持对该api的拦截，则又该用WriteProcessMemory 来修改入口了，跟前面的恢复入口是一样的，只不过把szOldCode换成了szNewCode了而已。这样你又能对CreateFile继续拦截了。 <br /><br />好了，接下来该进行堆栈的清理了，也许你还要做点其他事情，尽管做去。但是清理堆栈是必须要做的，在函数结束的时候，因为在我们放任api恢复执行之后，他又return 到我们的函数中来了，这个时候的堆栈是什么样子呢？ <br />|EDI|   &lt;---ESP <br />|ESI| <br />|EBX| <br />|局部变量|   <br />|EBP|     &lt;---EBP <br />|api入口之后的第11个字节的指令的地址|     <br />|00010000| <br />|createfile执行后的下一条指令地址| <br />|参数1| <br />|参数2| <br />|参数3| <br />|参数4| <br />|参数5| <br />|参数6| <br />|参数7| <br />|..| <br /><br />我们的目标是把返回值记录下来放到EAX寄存器中去，把返回地址记录下来，同时把堆栈恢复成原来的样子。 <br />首先我们恢复那些寄存器的值，接着释放局部变量，可以用mov esp, ebp.因为我们不清楚具体的局部变量分配了多少空间。所以使用这个方法。 <br /><br /><br />__asm <br />{POP EDI <br />POP ESI <br />POP EBX   //恢复那些寄存器 <br />MOV EDX, [NextIpAddr]//把返回地址放到EDX中，因为待会儿                   //EBX被恢复后，线程中的所有局部变量就不能正常使用了。 <br />         <br />MOV EAX, [Retvalue]//返回值放到EAX中，当然也可以修改这个返回值 <br />MOV ESP, EBP//清理局部变量 <br />POP EBP//恢复EBP的值 <br />ADD ESP, 28H   //清理参数和返回地址，注意一共(7+1+1+1)*4 <br />PUSH EDX //把返回地址压栈，这样栈中就只有这一个返回地址了，返回之后栈           //就空了 <br />RET <br />} <br /><br />这样，一切就完成了，堆栈恢复了应该有的状态，而你想拦截的也拦截到了。 <br /><br /><br />四：后记 <br />  拦截的方式多种多样，不过大体的思路却都相同。要时刻注意你要拦截的函数的堆栈状态以及在拦截函数中的对数据的引用和函数的调用（地址问题）。 <img src ="http://www.cppblog.com/eday/aggbug/15377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:51 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MFC 的一些常见问题</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15375.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:50:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15375.html</guid><description><![CDATA[
		<div class="item-content">
				<p>
						<b>如何抛出（throw）由CUserException派生的异常？</b>
				</p>
				<p>当我试图捕获（catch）一个派生类异常时，我得到以下错误"error C2039:'classCMyException': is not a member of 'CMyException' 'classCMyException': undeclared identifier 'IsKindOf': cannot convert parameter 1 from 'int*' to 'const struct CRuntimeClass*" <br /><br />你必需通过使用DECLARE_DYNAMIC（）和IMPLEMENT_DYNAMIC()宏来使你的CMyException类可以动态地创建。CATCH宏希望能够得到关于被抛出类的运行时刻信息。 </p>
				<p>
						<b>异常类一定要从CUserException中派生出来吗？</b>
				</p>
				<p>不，CUserException中的"User"仅仅指用户产生的异常。而把它当作你所能派生的唯一异常是种常见的误解。 </p>
				<p>
						<b>如何从HDC建立一个CDC类？</b>
				</p>
				<p>有时Windows API将会给你一个DC句柄，你可以通过它建立一个CDC类。例如：下拉式列表、组合框和按钮。通过hDC你将接收到绘制消息。下面是将HDC转换成你更熟悉的CDC的程序段。你也可以将该技巧用在其他任何MFC类和Windows句柄的转换中。 </p>
				<pre>void MyODList::DrawItem(LPDRAWITEMSTRUCT lpDrawItem)
{
    CDC myDC;
    myDC.Attach(lpDrawItem-&gt;hDC);
    //在此插入其他需要的代码。

    //如果你不将句柄分离，它将被删除，从而导致问题。
    myDC.Detach();
}

</pre>
				<pre>另一个方法是调用CDC类的FromHandle方法：
            CDC * pDC = CDC:FromHandle(lpDrawItem-&gt;hDC);
</pre>目前还不清楚哪种方法更优越―使用FromHandle()的错误也许会更少些，因为它不要求你分离（detach）句柄。 
<p><b>如何从磁盘上读取256色位图文件？</b></p><p>当前，MFC并不支持直接读取和显示DIB文件和BMP文件。然而，有很多样例应用程序能够说明如何完成该项任务。第一个例子是MFC样例程序DIBLOOK。样例MULTDOCS用DIBLOOK提供的相同源代码来读取并显示DIB文件和BMP文件。其他两个VC++中附带的例子是SDK软件包中的DIBVIEW程序和SHOWDIB程序。 </p><p><b>如何改变一个视图的大小？</b></p><p>通常，你可以调用函数MoveWindow（）来改变窗口的大小。在用MFC库开发的应用程序中， 视图是被框架窗口所围绕的一个子窗口。为了改变一个视图的大小，你可以通过调用函数GetParentFrame()来得到框架窗口的指针，然后调用函数MoveWindow()来改变父窗口的大小。当父框架窗口改变大小时，视图也会自动地改变大小来适应父窗口。 </p><p><b>如何改变一个CFormView的大小？</b></p><p>要想详细了解的话，你可以看有关Visual C＋＋基础知识的文章Q98598 《Using CFormView in SDI and MDI Applications》。基本上，在从CFormView类派生出来的类中，你必须覆盖函数OnInitialUpdate()。其他有关建立CFormView的细节问题，可以从该文章中获得。 </p><pre>在类ClikethisView中声明如下函数：
  virtual void OnInitialUpdate();

在ClikethisView的代码中，函数如下：

  void ClikethisView::OnInitialUpdate()
  {
      //使窗口与主对话框同样大小
      CFormView::OnInitialUpdate();
      GetParentFrame()-&gt;RecalcLayout();
      ResizeParentToFit( /*FALSE*/ );
  }
  
 </pre><p><b>如何使用一个文档模板的新视图？</b></p><p>在用AppWizard创建的应用程序中，你有两种选择：改变当前视图的派生关系或者建立一个新视图并且在你的MDI程序中同时利用新视图和原先的视图。<br /><br /><br />为了创建一个新视图，你可以用ClassWizard由CView派生一个新的类。当新类创建以后，利用新视图或修改由AppWizard提供的视图，两者的步骤是相同的。<br /><br /><br />修改视类的头文件，从而将所有对CView类的引用改名为你所想要的名称。本例中的类由CScrollView派生而来。通常，这个步骤包括对类的改变，视类将由如下方式派生而来：<br />    class CMyView : public CScrollView<br /><br /><br />修改视类的实现文件，从而将所有对CView的引用改名为你所想要的名称。这包括将IMPLEMENT_DYNCREATE那一行的语句改为：<br />    IMPLEMENT_DYNCREATE(CMyView, CScrollView)<br /><br /><br />将BEGIN_MESSAGE_MAP那一行的语句改为：<br />    BEGIN_MESSAGE_MAP(CMyView, CScrollView)<br /><br /><br />并且将其他所有的CView改成CScrollView.<br /><br /><br />假如你修改的视图是由AppWizard生成的，那么就不需要作更多的修改了。而如果你在创建一个新视图，先在CWinApp::InitInstance()函数中找到对AddDocTemplate()函数的调用。AddDocTemplate()函数的第三个参数是RUNTIME_CLASS(CSomeView)，用CMyView来代替CSomeView，就可以将当前视图改为新视图。在MDI应用程序中，你可以增加第二个AddDocTemplate()函数调用来使用多视图类型，将RUNTIME_CLASS(CSomeView)改为RUNTIME_CLASS (CMyView)。<br /><br />要想获得更多的信息请参阅Q99562中相关文章《Switching Views in a Single Document Interface Program》 。 </p><p><b>如何改变视图的背景色？</b></p><p>你可以通过处理WM_ERASEBKGND消息来改变CView、CFrameWnd或CWnd对象的背景色。请看如下的程序段： </p><pre>  BOOL CSampleView::OnEraseBkgnd(CDC* pDC)
  {
      // 设置所要求背景色的刷子
      CBrush backBrush(RGB(255, 128, 128));
      // 保存旧刷子
      CBrush* pOldBrush = pDC-&gt;SelectObject(&amp;backBrush);
      CRect rect;
      pDC-&gt;GetClipBox(&amp;rect);     // 擦除所需的区域
      pDC-&gt;PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
      pDC-&gt;SelectObject(pOldBrush);
      return TRUE;
  }
  
</pre>而我则用如下方法解决这个问题： <pre>  HBRUSH dlgtest::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
  {
      switch (nCtlColor)
      {
          case CTLCOLOR_BTN:
          case CTLCOLOR_STATIC:
          {
              pDC-&gt;SetBkMode(TRANSPARENT);
          }
          case CTLCOLOR_DLG:
          {
              CBrush*     back_brush;
              COLORREF    color;
              color = (COLORREF) GetSysColor(COLOR_BTNFACE);
              back_brush = new CBrush(color);
              return (HBRUSH) (back_brush-&gt;m_hObject);
          }
      }
      return(CFormView::OnCtlColor(pDC, pWnd, nCtlColor));
  }
</pre><p><b>如何得到当前视图？</b></p><p>最佳方法是将视图当作一个参数来传递。如果不能这样做，但你确信它是当前激活文档和当前激活视图的话，你也可以得到该视图。具体细节见Visual C++文章Q108587《Get Current CDocument or CView from Anywhere》。 </p><pre>简单说来，用：

      ((CFrameWnd*) AfxGetApp()-&gt;m_pMainWnd))-&gt;GetActiveDocument()

和：

      ((CFrameWnd*)(AfxGetApp()-&gt;m_pMainWnd))-&gt;GetActiveView()

</pre>来得到文档和视图。一个好的方法是将它们封装在你的CMyDoc和CMyView类的静态函数中，并且核对它们是否属于正确的RUNTIME_CLASS。然而，假如这个视图不是当前激活视图或者你在运行OLE本地激活，这样将不成功。 
<p><b>如何在一个文档中建立多个视图？</b></p><p>CDocTemplate::CreateNewFrame()函数创建MFC MDI应用程序中的文档的附加视图。为了调用该函数，要指定一个指向CDocument对象（指将为之建立视图的文档）的指针和一个指向可从中复制属性的框架窗口的指针。一般情形下，该函数的第二个参数为NULL。 <br /><br />当应用程序调用函数CreateNewFrame()时，该函数就创建一个框架窗口和在该窗口内的视图。框架窗口和它的视图的类型由与CreateNewFrame()函数调用指定的文档相关的文档摸板(CDocTemplate)决定。 <br /><br />Visual C++中的CHKBOOK MFC样例程序也演示了如何为文档建立附加的框架和视图。检查CHKBOOK.CPP文件中的CChkBookApp::OpenDocumentfile()函数。 <br /><br />另一个用函数CreateNewFrame()的例子是MULTVIEW样本程序。 <br /><br />CreateNewFrame()函数建立了一个框架和一个视图，而不仅仅是一个视图。假如CreateNewFrame()函数不能完全符合你的需要，可参考CreateNewFrame()函数的源程序来了解对建立结构和视图所必须的步骤。 </p><p><b>如何在MDI程序中得到所有的视图？</b></p><p>你必须用一些文档中没有记载的函数： </p><pre>  CDocument::GetFirstViewPosition(); // DOCCORE.CPP
  CDocument::GetNextView(); // DOCCORE.CPP
  CMultiDocTemplate::GetFirstDocPosition(); // DOCMULTI.CPP
  CMultiDocTemplate::GetNextDoc(); // DOCMULTI.CPP 
  
</pre><p>你还需要与CWinApp的成员m_templateList打交道。<br />注意：在MFC 版本4.0中已改变。现在已经有一个叫CDocManager的类可以帮助你显示所有的视图和文档。请参考《MFC Internals》获得更详细的信息。 </p><p><b>如何建立一个可用鼠标拉动的CScrollView类</b></p><p>在CIS上从MSMFC库下载AUTOSV.LZH。这个程序告诉你如何实现一个辅助消息循环来管理鼠标的活动，并提供了钩挂来对代码进行定制。这是一个免费软件。 </p><p><b>一定要用视图/文档结构吗？</b></p><p>MFC并不一定要求你使用文档/视图结构。查看HELLO、 MDI和HELLOAPP例子―它们就没有用那种结构。大多数MFC特性都可以在非文档/视图应用程序中得到运用。但是当你不用文档 / 视图结构时，你确实会失去一些特性，例如打印预览和许多OLE特性。 </p><p><b>如何得到当前文档？</b></p><p>请详细参阅"如何得到当前视图？"章节。 </p><p><b>文档何时被析构？</b></p><p>在SDI程序中，程序退出后文档就被删除。在MDI程序中，与该文档相关的最后一个视图关闭时文档就被删除。为了在SDI和MDI中同时用这个文档，你应该在虚函数DeleteContents()函数中删除该文档的数据，而不是在析构器中。 </p><p><b>如何建立多文档？</b></p><p>为了加入对附加文档类型的支持，你可以在CWinApp派生类中创建和注册附加CmultiDocTemplate对象。这种方法已经在MULTDOCS样例程序中得以说明。将一个附加文档类型加入到MFC程序的一般步骤如下： </p><p>用AppWizard来创建一个新的文档类和视图类。 <br />用资源编辑器增加新的资源字串来支持新的文档类。要想知道关于文档样板字符串格式的更多内容，请参阅"如何理解文档样板字符串"。<br /><br />用资源编辑器增加附加的应用程序图标和菜单资源。注意，这些资源中每一个的ID都必须与在步骤2中创建的文档模板字符串的ID是相同的。这个ID被CmultiDocTemplate类用来识别与附加文档类型相关的资源。<br /><br />在应用程序的InitInstance（）函数中，创建了另一个CMultiDocTemplate对象并且用CWinApp::AddDocTemplate()函数来注册。例如： </p><pre>CMultiDocTemplate* pDocTemplate2 = new CMultiDocTemplate(
  IDR_DOC2TYPE, RUNTIME_CLASS(CDoc2),
  RUNTIME_CLASS(CMDIChildWnd),RUNTIME_CLASS(CView2));
  AddDocTemplate(pDocTemplate2);
</pre>最后，将定制的序列化和绘图代码加入到你的新文档和视图类中。 
<p><b>如何得到一个打开文档的列表？</b></p><p>下面的程序段指明如何得到用CDocTemplate对象建立的所有文档的指针列表。<br />下面的程序段中，CMyApp由CWinApp派生而来。变量m_templateList是一个CPtrList对象，它是CwinApp的成员变量，包含一个所有文档模板指针的列表。文档模板函数GetFirstDocPosition()和GetNextDoc()被用来在文档模板列表中进行迭代来得到每一个文档模板。 </p><pre>  void CMyApp::GetDocumentList(CObList * pDocList)
  {
      ASSERT(pDocList-&gt;IsEmpty());
      POSITION pos = m_templateList.GetHeadPosition();
      while (pos)
      {
          CDocTemplate* pTemplate = 
              (CDocTemplate*)m_templateList.GetNext(pos);
          POSITION pos2 = pTemplate-&gt;GetFirstDocPosition();
          while (pos2) 
          {
              CDocument * pDocument;
              if ((pDocument=pTemplate-&gt;GetNextDoc(pos2)) != NULL)
                  pDocList-&gt;AddHead(pDocument);
          }
      }
  }
  
</pre><p>在参考手册或在线帮助中，有两个CdocTemplate类的公共成员函数没有被说明。然而， 这些公共成员函数在CDocTemplate类中被定义，并且为在打开文档的列表中前后搜索提供了简单的支持。 </p><p>这些函数如下：<br /><br /><br />Function virtual POSITION GetFirstDocPosition() const；<br />调用该函数得到在打开的文档列表中与模板相关联的第一个文档的位置。返回的POSITION的值能够被GetNextDoc成员函数反复使用。 <br /><br />Function Virtual CDocument* GetNextDoc(POSITION&amp; rPosition) const；<br />rPostion是前面调用GetNextDoc 或GetFirstDocPosition成员函数返回的POSITION值。这个值不能是NULL。调用该函数来在所有打开的文档中进行迭代。该函数返回被rPosition所标识的文档并将rPosition设置为列表中的下一个文档的POSITION值。假如所检索的是列表中的最后一个文档，rPosition将被设为空值。 </p><p><font color="#0000ff">注意，这仅对MFC3.2版本或更低版本有效，对MFC4.0版本请参考下面：</font></p><pre>  void CMyApp::DoSomethingToAllDocs()
  {
      CObList  pDocList;
      POSITION pos = GetFirstDocTemplatePosition();
      while(pos)
      {
          CDocTemplate* pTemplate = GetNextDocTemplate(pos); 
          POSITION pos2 = pTemplate-&gt;GetFirstDocPosition();
          while(pos2)
          {
              CDocument* pDocument;
              if(pDocument = pTemplate-&gt;GetNextDoc(pos2))
                  pDocList.AddHead(pDocument);
          }
      }
      if(!pDocList.IsEmpty()){
          pos = pDocList.GetHeadPosition();
      while(pos)
      {  
          //为每一个文档调用CDocument函数
          ( (CDocument*)pDocList.GetNext(pos) )
              -&gt;UpdateAllViews(NULL);
      }
  }
</pre><p><b>如何使我的程序在启动时不创建一个新文档？</b></p><p>在程序的InitInstance中的ProcessShellCommand函数之前加入： cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing </p><p></p><p>（－）. 下面是常见的Afx全局函数： </p><p></p><p>AfxFormatString1：类似printf一般地将字符串格式化 </p><p></p><p>AfxFormatString2：类似printf一般地将字符串格式化 </p><p></p><p>AfxMessageBox：类似Windows API 函数 MessageBox </p><p></p><p>AfxOuputDebugString：将字符串输往除错装置 </p><p></p><p>AfxGetApp：获得application object (CwinApp派生对象)的指针 </p><p></p><p>AfxGetMainWnd：获得程序主窗口的指针 </p><p></p><p>AfxGetInstance：获得程序的instance handle </p><p></p><p></p><p></p><p>（二）. CString 与char []之间的转换.  </p><p></p><p>在VC中，恐怕这两个是经常要进行转换的吧 </p><p></p><p>char str[10] = ”str”; </p><p></p><p>CString sstr = “sstr”; </p><p></p><p>sstr.Format(“%s”,str); </p><p></p><p>strcpy(str,(LPCTSTR)sstr); </p><p></p><p></p><p></p><p>（三）. 关闭程序: </p><p></p><p>PostQuitMessage(WM_CLOSE);  或者PostQuitMessage(WM_DESTROY); </p><p></p><p>    更绝的是关闭所有的程序：::ExitWindows (); </p><p></p><p></p><p></p><p>（四）. 在关闭窗口时，当要对文件进行保存时，可在这里添加函数： </p><p></p><p>    1.）在CMainFrame里的OnClose（）里，用MessageBox("内容","标题",组合形式);组合形式可以查看MSDN的MESSAGEBOX( ) Function </p><p></p><p>    2.）在CXXXDoc::SaveModified() 里，只能用AfxMessageBox(""); </p><p></p><p>不能用MessageBox（）函数 </p><p></p><p></p><p></p><p>（五）. 如何修改窗体的标题： </p><p></p><p>    1.）修改主窗口的标题：m_pMainWnd-&gt;SetWindowText("你的标题"); </p><p></p><p>    2.）如果在你的document类中进行改，则直接调用SetTitle("...")，如果在你的view类中改，则GetDocument()-&gt;SetTitle("...") </p><p></p><p>    3.）如果想使窗口的标题全部替换，则用：AfxGetMainWnd()-&gt;SetWindowText("你的标题"); </p><p></p><p></p><p></p><p>（六）. 得到窗体的标题： </p><p></p><p>    1.）AfxGetMainWnd()-&gt;GetWindowText();  </p><p></p><p>   2.）先FindWindow()找到窗口的HWND,在GetWindowText(); </p><p></p><p></p><p></p><p>（七）. 在多文档/视图中： </p><p></p><p>    1.）子窗口的最大化： </p><p></p><p>      void CChildFrame::ActivateFrame(int nCmdShow) </p><p></p><p>      { </p><p></p><p>        // TODO: Add your specialized code here and/or call the base class </p><p></p><p>        nCmdShow=SW_MAXIMIZE; </p><p></p><p>        CMDIChildWnd::ActivateFrame(nCmdShow); </p><p></p><p>      } </p><p></p><p></p><p></p><p>2.）屏蔽子对话框：在APP类里把这两句话屏蔽掉 </p><p></p><p>      if (!ProcessShellCommand(cmdInfo)) </p><p></p><p>        return FALSE; </p><p></p><p>3.）关闭子窗口： </p><p></p><p>::SendMessage(::AfxGetMainWnd()-&gt;m_hWnd, WM_COMMAND,ID_FILE_CLOSE,0); </p><p></p><p></p><p></p><p>（八）. 在装进自定义的光标后，在移动的过程中，鼠标的形状总是在自定义和默认的光标之间晃动，可以这样解决，在视中的PreCreateWindow（）中加入如下几句： </p><p></p><p>     BOOL CXXXXView::PreCreateWindow(CREATESTRUCT&amp; cs) </p><p></p><p>{ </p><p></p><p>       // TODO: Modify the Window class or styles here by modifying </p><p></p><p>       // the CREATESTRUCT cs </p><p></p><p>       cs.lpszClass =AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,0, </p><p></p><p>                   (HBRUSH)::GetStockObject (WHITE_BRUSH),0); </p><p></p><p>       return CView::PreCreateWindow(cs); </p><p></p><p>} </p><p></p><p></p><p></p><p>（九）. 怎样禁止改变窗口的大小和不能移动的窗口： </p><p></p><p>         再 CMainFrame的OnCreate函数中加入： </p><p></p><p>       CMenu *pTopMenu=GetSystemMenu(false); </p><p></p><p>         pTopMenu-&gt;RemoveMenu(4,MF_BYPOSITION);//最大化窗口不可用 </p><p></p><p>       pTopMenu-&gt;RemoveMenu(2,MF_BYPOSITION);//size </p><p></p><p>       pTopMenu-&gt;RemoveMenu(1,MF_BYPOSITION);//使不可移动 </p><p></p><p></p><p></p><p>（十）.使窗口始终在最前方： </p><p></p><p>只要在App类中的InitInstance()函数中加入以下代码就可以了: </p><p></p><p>BOOL CwindowOnTopApp:: InitInstance() </p><p></p><p>{ </p><p></p><p>   //此处略去了VC自动生成的代码 </p><p></p><p>   m_pMainWnd-&gt;showWindow(SW_SHOW); </p><p></p><p>   m_pMainWnd-&gt;UpdateWindow(); </p><p></p><p>   m_pMainWnd-&gt;SetWindowPos(&amp;CWnd::WndTopMost,0,0,0,0, </p><p></p><p>SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); </p><p></p><p>      Return true; </p><p></p><p>}</p><div class="clear"></div></div>
<img src ="http://www.cppblog.com/eday/aggbug/15375.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:50 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15375.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Double Buffering With GDI+</title><link>http://www.cppblog.com/eday/archive/2006/11/18/15369.html</link><dc:creator>松*</dc:creator><author>松*</author><pubDate>Sat, 18 Nov 2006 08:43:00 GMT</pubDate><guid>http://www.cppblog.com/eday/archive/2006/11/18/15369.html</guid><description><![CDATA[
		<h2>Introduction</h2>
		<p>This is a simple example about GDI+ which I used in my first GDI+ project.This article is focused on <b>Double Buffering</b>. First if you want to add GDI+ supports to your project,you can refer to <a href="http://www.codeproject.com/dotnet/startinggdiplus.asp"><font color="#000000">Starting with GDI+</font></a> By <a href="http://www.codeproject.com/script/profile/whos_who.asp?id=6556"><font color="#000000">Christian Graus</font></a>.<br />The main part of my example is in <code><font face="新宋体">OnPaint</font></code> function that you can see everything there,Image,Font,Brush and etc.They are very clear so I don't discuss about them.I only explain "Double Buffering" here.<br /><br /><br />As you know you can draw images into your windows and create a brush or pen from TextureBrush and draw shapes or line, using the image.Even you can draw a text using the images by supplying TextureBrush.(you can see the examples in this article or Christian Grause articles)<br /><br />I think one of the most important use of images is <b>Double Buffernig</b> technique.This technique is used when the drawing we wish to create has many details,and it is time consuming to draw even with a fast computer.In this situation it seems the image creeps on to the screen while being drawn.For example in mapping applications or CAD/CAM applications we would encounter this problem. In this technique instead of drawing in the screen,first we draw into an image and then we draw the iamge into the window. Here is my example in double buffering:<br /></p>
		<pre>
				<font style="BACKGROUND-COLOR: #ffffff">   srand( (<span>unsigned</span>)time( NULL ) );
   <span>int</span> number = rand();
   number /= RAND_MAX + <span>1</span>; 
   number *= <span>254</span>;

   Rect rc(rect.left,rect.top,rect.right,rect.bottom);
      Bitmap bmp(rect.right,rect.bottom);

   <span>// Create a Graphics object that is associated with the image.</span>
   Graphics* graph = Graphics::FromImage(&amp;bmp);
 
   <span>for</span>(<span>int</span> x=<span>0</span>;x&lt;rect.right;x++)
   {
   	<span>for</span>(<span>int</span> y=<span>0</span>; y&lt;rect.bottom; y++)
   	{
   		<span>double</span> number = rand();
  		number /= RAND_MAX + <span>1</span>; 
		number *= <span>254</span>;
		Pen pen(Color(number,number,number,number));
		graph-&gt;DrawLine(&amp;pen,<span>0</span>,<span>0</span>,x,y);
        	}
	}
   <span>// Draw the altered image.</span>
   graphics.DrawImage(&amp;bmp,rect.left,rect.top,rect.right,rect.bottom);
</font>
		</pre>
		<p>It takes 36 seconds to paint the screen on my computer(AMD 1.33GHtz and 256Mb RAM) without double buffering but only 5 seconds with this technique.You know,it is not interesting to wait 36 seconds each time that your window need to repaint!<br /><br />Finally I want to thank <a href="http://www.codeproject.com/script/profile/whos_who.asp?id=6556"><font color="#000000">Christian Graus</font></a> for his supports and good articles about GDI+ which I used some of his codes and ideas.<br /></p>
<img src ="http://www.cppblog.com/eday/aggbug/15369.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eday/" target="_blank">松*</a> 2006-11-18 16:43 <a href="http://www.cppblog.com/eday/archive/2006/11/18/15369.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>