但就其語言本身來看,JAVA和.Net更多的象是在C++之上減減增增。我感覺C/C++能夠稱的上編程語言史上最有影響力的語言,而它的成功完全是在其自身的優秀特證所決定的,后面的JAVA和C#的成功是完全在C/C++的基礎上,它們站在了C/C++這個巨人的肩膀上。
C是Combined的縮寫,產生于1972年,C在DOS時代以其語句簡潔,代碼執行速度快,既可編寫底層代碼,又可輕松實現GUI的繪制,脫穎而出,成為最適合于系統底層程序和游戲開發的語言,被譽為中級語言,是表示它兼有高級語言和低級語言的特證。C的出現讓人們看到了一種新的語言風格,感覺C語法最大的特點是簡潔,靈活,比如分號代表語句的結束,可以讓程序的一條語句不拘于寫在一行中,這比BASIC和Fortran好,用大括號來代替Begin和End要比Pascal在書寫上省下很多。同樣一段程序,用C可以做到代碼比別的語言短,而運行速度通常要比別的語言快。C語言中更可以調用一些DOS系統中斷功能,Bios中斷功能,直接訪問硬件,讀寫端口,Dos時代的C是無所不能的,比如把Dos系統中斷改了,讓Dos在處理例行事務時,作些程序指定的操作,那是挺容易的,所以Dos下面病毒很多,不過那個時代寫底層代碼時的限制少,Windows下很多直接對硬件的訪問都限制了,Web時代更是以虛擬機和跨平臺為榮了。C/C++傾向于系統開發,還有游戲設計,只要這兩個領域還在,它就是永遠的日不落帝國。
DOS時代最流行的開發工具是Turbo C,Borland公司的優秀產品,Turbo C是DOS時代C開發工具的霸主,我個人上是非常喜歡Turbo C,至今電腦上還裝著Turbo C2.0,也就2M左右,有時懷念起當年在大學機房里用Turbo C運行代碼的時光,還可以打開看下那熟悉的藍色界面,相信很多朋友都用過它。微軟推出的是Quick C(Windows 3.x上還有個Quick C For windows,其實也真挺好用的),但總還是比Turbo C用起來感覺差一些。從Simula,Smalltalk等面向對象的語言的成功中吸取了經驗,加入了面向對象的元素后,C++面世了。
C++工具的早期的主流是Borland C/C++和Microsoft C/C++,在開發平臺從DOS平臺轉換到Window平臺的背景下,各大C/C++開發工具廠商紛紛推出Windows應用的開發工具,出現了兩大類庫OWL和MFC,把面向對象技術在Windows應用開發中充分利用,正是面向對象技術簡化了Windows應用的開發,在Windows應用開發過程中展現了面向對象技術的威力后,面向對象技術也得以更加的流行和推廣,編程領域進入OO和Windows的時代。這時的開發工具更是相當的豐富的,Borland C/C++3.0版可以開發DOS和windows兩個平臺的程序,因為使用Win API寫Windows程序很麻煩,Borland推出了OWL(object windows library),用類庫封裝了windows API,與其相似的是微軟推出的MFC(Microsoft Function class)。
編程平臺開始向Windows平臺轉移的時候,想成為Windows平臺上C/C++開發工具的霸主的卻不只Borland和微軟兩家,李維的《Borland 傳奇》中描述了一場壯觀的開發工具的“圣戰”(也可以稱為四國大戰),它們是Borland C/C++, Microsoft Visual C/C++, Watcom C/C++(是否記得DOS 4GW 保護模式嗎,其實DOS時代很多游戲都是Watcom C/C++開發的,而它的運行必須要在DOS 4GW 保護模式下,這樣解決了DOS系統中直接訪問的內存不同超過1M的問題,當年幾乎所有的DOS版的游戲都是它開發的,比如古墓麗影,仙劍奇俠傳等), Symantec C/C++(著名的殺毒軟件諾頓的公司Symantec啊,其實它也是一家資深編程工具開發公司呢,后來的JAVA開發工具中Symantec也不甘寂寞的)四大工具閃亮登場,而最終勝出的是Borland C++ 3.1,在Window早期,Borland C++ 3.x是最好的windows應用的開發工具了,Borland C++ 開發出的Windows應用是很有特色的,因為按鈕和圖標都有點和microsoft的風格不太一樣的,當年用Borland C++開發的應用,現在一看仍能分辨出來,不過現在已經難見到這種應用程序了。Visual C++的統治開始了,當微軟在這個領域異軍突起時,Borland趕緊推出Borland C++4.0時,已經無法改變形勢了,后來的Borland C++5.0仍是優秀的作品(記不得4.0,4.5還是5.0的Borland C++了,安裝它時,屏幕上卻是開賽車時的第一視角的情景,用戶看到車內的行程指示計指示從0%升到100%后安裝就結束了,挺有創意的,不過那象是一條下坡路了)。
Visual C++是后來的王者,現在能見到的Visual C++都是5.0而6.0的,喜歡Visual C++的Wizard,點幾下鼠標一個Windows Application就產生了,Microsoft提供各種SDK,為用戶服務,其中最喜歡的SDK當然是Direct X,夢想用C++開發個SuperMario出來,至少也要俄羅斯方塊什么的,不過入IT行業工作后,整天項目做的累夠嗆,沒有時間去實現夢想了,入行IT后沒有機會參加C/C++項目,很大的遺憾了。當年在機械行業,最美好的回憶是02年為公司的閥門為一個控制系統,兩米多高的閥門,研究生畢業的胡老師組織好硬件線路,我用一臺奔II來控制閥門的開啟和運作,程序上由我一人開發,主界面上點擊按鈕來讓閥門運動(也可在自動模式自動運行),屏幕中間的最大區域是閥門當前的狀態圖。當系統接好,我第一個上來操作,一點鼠標看到那么巨大的機械被我的程序控制運作,真是很有成就感呢。我是用Win API+Direct X技術做的,DOS下全屏幕的800x600的分辨率,背景圖、按鈕、狀態圖都是我用Corel PhotoPaint , AutoCAD等工具制作的,剛啟動時閥門的照片以一個向下縱伸展開的動畫方式出現的創意還真有點意思,當時伊拉克客戶看閥門時,把我的程序(不與硬件連接的演示版)考了一份回去了,我感覺是我的程序出國了。
C/C++的魅力在于它的長盛不衰,除了Visual C++,Borland的C++ Builder是和VB,Delphi一樣的好用的屬于C++的RAD工具,Microsoft的.Net中Visual C++.Net是其不可缺少的一部分,Linux、Unix的Shell中C Shell是重要的一個,Mainframe應用開發中C應用廣泛。相信C/C++做為IT編程領域永遠的傳奇會繼續展現它的魅力的。
下篇介紹當今Web應用開發的主流----Java。雖然JAVA最早不是為Web開發而產生的,但它在語言方面的特色,與Web時代的驅勢不謀而合了,于是成就了一個屬于JAVA的時代。Java對應排行第二的上官金虹的子母龍鳳環,雖說Java就一種語言,可如今的Java 1.5(tiger版),1.6(mustang版),語言上已經和原來的Java 1.2~1.4不一樣了,它添加了對泛型,枚舉,元數據等的支持,流行很久的1.2版為基礎的Java2(母環)和吸取了C#.Net的一些優點后產生的Java5(子環)構成了Java的雙環。
]]>
1 引言
Visual C++ 是當今最流行的軟件開發工具之一,它可以實現可視化編程和支持面向對象的編程技術。通常在軟件開發的過程中,大部分程序采用高級語言編寫,以提高程序的開發效率,但在某些部分,例如程序的關鍵部分、運行次數很多的部分、運行速度要求很高的部分或直接訪問硬件的部分等利用匯編語言編寫,以提高程序的運行效率。為了滿足兩方面的要求,人們在開發的過程中將兩種語言進行混合編程,這種方法使兩種語言相互調用,進行參數傳遞,共享數據結構和數據信息,充分發揮了各種語言的特點和優勢,大大提高了應用軟件的效率。因此,正確掌握Visual C++與匯編語言的接口技術對軟件開發是十分必要的。
2 Visual C++調用匯編語言的常用方法
通常有兩種方法可以實現Visual C++調用匯編語言。一種方法是在從C++語言中直接使用匯編語句,即嵌入式匯編;另一種方法是用兩種語言分別編寫獨立的程序模塊,匯編語言編寫的源代碼匯編產生目標代碼OBJ文件,將C++源程序和OBJ文件組建工程文件,然后進行編譯和連接,生成可執行文件.EXE。
2.1 VC++中嵌入匯編語句的方法
嵌入式匯編又稱行內匯編,Visual C++提供了嵌入式匯編功能,允許在C++源程序中直接插入匯編語言指令的語句,可以直接訪問C++語言程序中定義的常量、變量和函數,而不用考慮二者之間的接口,從而避免了匯編語言和C++語言之間復雜的接口問題,提高了程序設計效率。
嵌入匯編語言指令采用__asm關鍵字,嵌入匯編格式:__asm{ 指令 },采用花括號的匯編語言程序段形式。具體應用通常采用兩種方式,第一種方式:__asm { 匯編程序段 }, 如下所示:__asm
{
mov eax,5h
mov ecx,7h
add eax,ecx
}
另一種方式:每一條匯編語句前添加“__asm”標記,格式:__asm 匯編語句,如下所示:
__asm mov eax,5h
__asm mov ecx,7h
__asm add eax,ecx
在Turbo C環境中C語言程序含有嵌入式匯編語言語句時,C編譯器首先將C代碼的源程序(.c)編譯成匯編語言源程序(.asm)。然后激活匯編程序Turbo Assembler將產生的匯編語言源文件編譯成目標文件(.obj),最后激活Tlink將目標文件鏈接成可執行文件(.exe)。Visual C++ 中嵌入匯編語句的編譯沒有Turbo C那樣復雜,它直接支持嵌入匯編方式,不需要獨立的匯編系統和另外的連接步驟。因此Visual C++中嵌入匯編比Turbo C中嵌入匯編進行編譯連接更為簡單方便。
2.2 采用模塊調用的方法
采用模塊調用方式,要協調命名、調用、參數傳遞和返回等進行約定。
(1) 采用一致的調用協議
Visual C++語言具有三種調用協議:_cdecl、_stdcall和_fastcall。MASM匯編語言利用“語言類型”確定調用協議和命名約定,支持的語言類型有:C、SYSCALL、STDCALL、PASCAL、BASIC和FORTRAN。
Visual C++與匯編語言混合編程通常利用堆棧進行參數傳遞,調用協議決定利用堆棧的方法和命名約定,兩者要一致,通常Visual C++采用_cdecl調用協議,MASN匯編語言采用C語言調用協議。
(2) 入口參數和返回參數的約定
不論何種整數類型進行參數傳遞時都擴展成32位,Visual C++中沒有遠、近調用之分,所有調用都是32位的偏移地址,所有的地址參數也都是32位偏移地址,在堆棧中占4個字節。圖1給出了采用C++語言調用協議的堆棧示意圖。參數返回時,對于小于等于32位的數據擴展為32位,存放在EAX寄存器中返回;4-8個字節的返回值存放在EDX、.EAX寄存器中返回;更大字節數據則將它們的地址指針存放在EAX中返回。
(3) 聲明公用函數名和變量名
對Visual C++和匯編語言使用的公用函數和變量應該進行聲明,并且標識符應該一致,C++語言對標識符區分字母的大小寫,而匯編不區分大小寫。在Visual C++語言程序中,采用extern “C”{ }對所調用的函數和變量給予說明。說明形式如下:
對函數的說明:extern “C” { 返回值類型 調用協議 函數名稱(參數類型表);}
對變量的說明:extern “C” { 變量類型 變量名;}
匯編語言程序中供外部使用的標識符應該標識PUBLIC屬性,使用外部標識符應該用extern說明。
2.3 模塊調用混合編程的實現步驟
采用模塊調用方式進行混合編程一般執行的步驟如下:(1)建立C++源程序(.cpp);(2)建立匯編語言源程序,并把匯編語言匯編成.obj文件;(3)建立工程文件.prj,將C++源程序和.obj文件放入該工程項目;(4)對工程文件進行編譯、連接,生成可執行文件.exe。
在與Visual C++混合編程的匯編語言過程中,編程環境是32位的,應該注意與16位MS-DOS環境的區別,在這種環境下的寄存器是32位的,因此匯編語言過程存取堆棧應該使用32位寄存器EBP進行相對尋址,而不是采用BP。匯編語言簡化段定義的格式應該采用flat模式,并且匯編時采用選項/coff,ML命令的選項/coff使得產生的.obj文件采用32位的格式。
3 在Visual C++中調用匯編語言的第三種方法
通常以上兩種方法就能夠實現C++與匯編語言混合編程,但是在一些特殊的情況下,用這兩種方法卻不能滿足功能的需要,我們提出了一種新的方法實現二者的混合編程:通過數組借助指針實現二者的混合編程。下面結合我們開發的課題――數控系統邏輯控制系統軟件開發,來進行具體說明。
該課題在Visual C++ 6.0的環境下進行開發的,上層采用C++語言,最底層采用了匯編語言,在C++語言中要調用匯編語言的編譯的結果,并進行回填,如果用通用的混合編程方法無法實現二者的調用,因為底層匯編語言是把所有的邏輯運算功能指令匯編在一起,而在C++語言中根據需要在需要的地方調用匯編語言中的某一功能模塊,因此對匯編語言編譯后的.OBJ文件無法進行控制。具體實現方法如下:
(1)把包括所有的邏輯指令的匯編語句編成一個匯編模塊程序,在匯編編譯器(如masm 6.x)中將匯編程序編譯成.OBJ文件。
(2)將匯編生成的機代碼放在一個數組中,
例如定義一個數組變量unsigned char OBJMOD[1241]。
(3)定義多個指針類型變量指向OBJMOD數組元素的地址,該地址對應每個匯編功能模塊的首地址,如定義一個指針變量unsigned char *LIBC21=&OBJMOD[869]。
(4)通過函數COPILE(*pModal)模塊,例如編譯匯編LIBC21功能模塊時,通過調用COPILE(LIBC21) 函數,把匯編編譯生成的機代碼分別傳遞到工作區域WKAREA中,通過WKAREA[POSIRR]=BUFRIS[PTRIS]來實現二次填充,把匯編機代碼中改寫的內容改寫成需要的地址或值,最后通過調用一系列函數,把結果保存到文件中。
本課題采用這種方法實現了C++和匯編語言的混合編程,從而實現C++語言與匯編語言的無縫結合。
4 結束語
Visual C++和匯編語言混合編程可以實現優勢互補,尤其用在高級語言開發底層軟件方面,例如用Visual C++6.0環境開發數控軟件PLC的控制功能,這種優勢更為明顯,具有很好的實際應用價值。
VC++ 6.0是Visual Studio 6.0中的一種重要編程語言,也是中國C++開發人員使用最多的開發工具之一。 Visual C++是構建Windows平臺下應用程序功能最強大而又最復雜的工具,是目前世界上使用最多的開發工具之一,應用極度廣泛,從桌面應用程序到服務器程序,從系統軟件到應用軟件,圖形圖像,語音技術,設備驅動,網絡安全,加密解密等等幾乎無處不在。主流的3 種操作系統Windows,Linux,Unix內核都是用C語言和匯編語言寫的,而上層的高級特性都是用C++編寫。
直到今天,Visual C++6.0仍然占據著C++開發工具的絕對優勢的地位。自從微軟從2000年開始推行.NET戰略以來,其核心的編程語言是C#,從那個時候,C++的支持力度就開始不斷的下滑,雖然Visual C++也有幾次更新,但都不是很令人滿意,更靈人焦慮的是,有些開發者開始懷疑Visual C++的前途。盡管Visual C++的一些快捷開發被其他開發語言所取代,由于C++可以與操作系統的天生的曖昧關系,能夠與C語言無縫結合,所以Visual C++不可能被淘汰,尤其是對于底層開發。盡管Visual C++ 6.0很強大,是編程語言里一再受寵的幸運兒。但是Visual C++6.0已經成了昨日黃花,尤其讓所有Visual C++6.0開發人員郁悶的是Visual C++6.0開發的界面還停留在早期的水平上,界面粗糙,操作復雜,比起現在華麗而又奪人眼目的界面設計,更是遜色不少。而且Visual C++6.0也不完全符合C++標準。
正是由于Visual C++6.0的界面設計還停留在Windows 98時期的水平,操作復雜,再加上它不是完全符合C++標準,在代碼和BUG跟蹤調試上異常復雜,這也提高了踏入VC++開發的門欖。但是隨著Visual C++2010的推出,全面支持最新的C++標準、改善了IDE的效率,并且引入了很多新的特性,這些功能的增強,大大提高程序員們的開發效率,帶來更高的代碼生產力與開發的便捷。
Visual C++2010 新特性
Visual C++2010為所有的VC++開發人員做了貼心而又周全的考慮,下面我們從可視化設計、人性化使用、安全可靠、支持C++新標準和部署簡潔等方面介紹Visual C++2010的新特性。
(1)Ribbion界面設計可視化
在Visual C++6.0開發里面,界面簡單丑陋是其中一個不爽的地方。盡管在Visual C++ 2008里面對Ribbion功能有所改善,但是仍不能進行可視化設計,Visual C++ 2010新增了Windows7的風格,同時最大的革新就是實現了可視化設計,很容易設計出漂亮的程序界面。 下面我們體驗一下Visual C++ 2010界面可視化設計的特性。具體操作步驟如下:
1. 打開Microsoft Visual Studio 2010,點擊“文件->新建->項目”,打開“新建項目”窗體
2. 在“新建項目”窗體中,選擇“Visual C++”節點中的“CLR”,然后在“模板”窗格中選擇“Windows 窗體應用程序”。名稱輸入“WinFormDemo”。
3. 隨即打開Windows 窗體設計器,出現Form1 窗體
4.設置窗體屬性,Text為WinFormDemo;添加一個Label,一個Button,一個DateTimePicker,具體如下:
控件類型 | 控件名稱 | Text屬性 |
Label | labMessage | 請選擇日期 |
Button | dtpSelected | |
DateTimePicker | btnExit | 退出 |
對話框
現在我們使用進度控件來創建一個對話框。我們首先添加一個菜單項和一個消息句柄來調用該對話框。以下是詳細的操作步驟:
1. 在 IDR_MAINFRAME 菜單資源中添加一個新的彈出菜單,并命名為 Tools;
2. 在 Tools 彈出菜單中添加一個菜單項,命名為 Run WCE First Dialog;
3. 使用 ClassWizard 給新創建的 Run WCE First Dialog 菜單項添加一個命令句柄;
下面我們來添加一個對話框,將控件放入其中,并綁定該對話框到一個類。
4. 插入一個新的對話框資源,并設置其標題為 WCE First Dialog,資源標號為 IDD_FIRST_DLG;
5. 將 Ok 按鈕的標題(caption)改為 Close;
6. 將 Cancel 按鈕的標題改為 Start,并將其資源標號改為 IDB_START_BTN;
7. 從控件面板上拉下一個進度控件并放置到對話框中。不要修改該進度控件的風格(styles)、標題(caption)和資源標號(resource ids);
8. 根據掌上電腦的屏幕面積調整該對話框的大小,使之能在掌上電腦中正確顯示。建議可以設為 196 x 47 象素大小;
9. 使用 ClassWizard 綁定該對話框資源到一個新的類:CWCEFirstDialog;
10. 創建完對話框類后,再使用 ClassWizard 來給進度控件添加一個成員變量,叫做 m_progressCtrl;
11. 為 Start 按鈕添加一個命令按鈕句柄;
現在你已經將所有的預備設置都添加到程序中去了。雖然我們所使用的進度顯示控件和對話框并沒有什么實際意義,但也能夠滿足我們的這個短小的入門教程的要求了。當我們選中 Run WCE First Dialog 菜單項時,對話框將顯示出來;然后點擊 Start 按鈕,進度控件就會從0增長到30000,最后顯示一個標準的 Windows 消息框;點擊 Close 按鈕將關閉該對話框。
為了設置進度指示器,你必須在對話框的 WM_INITDIALOG 消息句柄中設置進度值范圍和步長。首先使用 ClassWizard 為 Windows 消息 WM_INITDIALOG 添加一個消息句柄。在 OnInitDialog() 函數中添加下列代碼來設置進度指示器的范圍和步長:
BOOL CWCEFirstDialog::OnInitDialog() |
下一步,當用戶點擊 Start 按鈕時,進度控件的值要開始增長。在增長結束時,還要顯示一個消息框。下列代碼完成上述任務:
void CWCEFirstDialog::OnStartBtn() { for(int i = 0; i <=30000; i++) { m_progressCtrl.StepIt(); } AfxMessageBox(_T("Progress Complete!!!"), MB_OK); } |
完成以上代碼后,你就可以運行你的程序看看效果了。
當運行環境中包含垃圾回收機制時,區別開內存管理和資源管理,就非常重要了。典型地來說,垃圾回收器只對包含對象的內存之分配與釋放感興趣,它可不關心你的對象是否擁有其他的資源,如數據庫連接或核心對象的句柄。
內存管理
本地C++為程序員提供了超越內存管理的直接控制能力,在堆棧上分配一個對象,意味著只有在進入特定函數時,才會為對象分配內存,而當函數返回或堆棧展開時,內存被釋放。可使用操作符new來動態地為對象分配內存,此時內存分配在CRT堆中,并且需要程序員顯存地對對象指針使用操作符delete,才能釋放它。這種對內存的精確控制,也是C++可用于編寫極度高效的程序的原因之一,但如果程序員不小心,這也是內存泄漏的原因。另一方面,你不需要求助于垃圾回收器來避免內存泄漏--實際上這是CLR所采取的方法,而且是一個非常有效的方法,當然,對于垃圾回收堆,也有其他一些好處,如改進的分配效率及引用位置相關的優勢。所有這一切,都可以在C++中通過庫支持來實現,但除此之處,CLR還提供了一個單一的內存管理編程模型,其對所有的編程語言都是通用的,想一想與C++中COM自動化對象相交互和調度數據類型所需做的一切工作,就會發現其重要意義所在--橫跨數種編程語言的垃圾回收器,作用是非常巨大的。
為了效率,CLR也保留了堆棧的概念,以便值類型可在其上分配,但CLR也提供了一個newobj中間語言指令,以在托管堆中分配一個對象,但此指令只在C#中對引用對象使用操作符new時提供。在CLR中,沒有與C++中的delete操作符對應的函數,當應用程序不再引用某對象時,分配的內存最后將由垃圾回收器回收。
當操作符new應用于引用類型時,托管C++也會生成newobj指令,當然,對此使用delete操作符是不合法的。這確實是一個矛盾,但同時也證明了為什么用C++指針概念來表示一個引用類型不是一個好的做法。
在內存管理方面,除了上述在對象構造一節討論過的內容,C++/CLI沒有提供任何新的東西;資源管理,才是C++/CLI的拿手好戲。
資源管理
CLR只有在資源管理方面,才能勝過本地C++。Bjarne Stroustrup的"資源獲取即初始化"的技術觀點,基本定義了資源類型的模式,即類的構造函數獲取資源,析構函數釋放資源。這些類型是被當作堆棧上的局部對象,或復雜類型中的成員,其析構函數自動釋放先前分配的資源。一如Stroustrup所言"對垃圾回收機制來說,C++是最好的語言,主要是因為它生成很少的垃圾。"
也許有一點令人驚訝,CLR并沒有對資源管理提供任何顯式運行時支持,CLR不支持類似析構函數的C++概念,而是在 .NET Framework中,把資源管理這種模式,提升到一個IDisposable核心接口類型的中心位置。這種想法源自包裝資源的類型,理應實現此接口的單一Dispose方法,以便調用者在不再使用資源時,可調用該方法。不必說,C++程序員會認為這是時代的倒退,因為他們習慣于編寫那些缺省狀態下清理就是正確的代碼。
因為必須要調用一個方法來釋放資源,由此帶來的問題是,現在更難編寫"全無異常"的代碼了。因為異常隨時都可能發生,你不可能只是簡單地在一段代碼后,放置一個對對象的Dispose方法的調用,這樣做的話,就必須要冒資源泄漏的風險。在C#中解決這個問題的辦法是,使用try-finally塊和using語句,在面對異常時,可提供一個更可靠的辦法來調用Dispose方法。有時,構造函數也會使用這種方法,但一般的情況是,你必須要記住手工編寫它們,如果忘記了,生成的代碼可能會存在一個悄無聲息的錯誤。對缺乏真正析構函數的語言來說,是否需要try-finally塊和using語句,還有待論證。
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->using (SqlConnection connection = new SqlConnection("Database=master; Integrated Security=sspi")) ...{ SqlCommand command = connection.CreateCommand(); command.CommandText = "sp_databases"; command.CommandType = CommandType.StoredProcedure; connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) ...{ while (reader.Read()) ...{ Console.WriteLine(reader.GetString(0)); } } }
對托管C++來說,情節也非常類似,也需要使用一個try-finally語句,但其是Microsoft對C++的擴展。雖然很容易編寫一個簡單的Using模板類來包裝GCHandle,并在模板類的析構函數中調用托管對象的Dispose方法,但托管C++中依然沒有C# using語句的對等物。
]]><!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->Using<SqlConnection> connection(new SqlConnection(S"Database=master; Integrated Security=sspi")); SqlCommand* command = connection->CreateCommand(); command->set_CommandText(S"sp_databases"); command->set_CommandType(CommandType::StoredProcedure); connection->Open(); Using<SqlDataReader> reader(command->ExecuteReader()); while (reader->Read()) ...{ Console::WriteLine(reader->GetString(0)); }
Visual C++的自動化機制
在使用VC來進行開發的時候,在VC的集成開發環境里可以手工進行很多操作,例如:使用菜單命令來打開一個項目、修改源文件、保存文件,然后編譯連接得到可執行的文件等等。這些操作大部分都是例行的、重復多次進行的。
VC提供了一種Automation自動化(來自以前的OLE自動化)的擴展機制。VC的開發者可以通過開發VBScript宏或者插件,讓IDE來自動執行一些操作。好處是顯然的:可以大大縮短這些操作的時間,而且還可以避免在手工操作可能出現的一些錯誤,如遺漏、鍵入錯誤等等。VBScript宏是使用VBScript語言寫的一些程序,而插件則是使用VC或者Visual Basic開發的COM構件。
我們還可以使用VBScript宏和插件來對VC本身的界面進行美化、改造,例如為VC的窗口加上頁簽(與Delphi的界面類似)等等。
在宏或者插件里,VC的集成開發環境本身以及它的各個部件都可以作為對象來進行操作。例如,通過操作與項目的文件對應的對象,可以對某個文件進行打開、編輯和關閉等操作。類似的,操作與IDE中的窗口對應的對象,可以激活一個窗口、改變窗口的大小等等。
一個對象可以是集成開發環境的一個部件,或者一組相關的部件。例如,一個Document文件對象表示一個打開的文件,而Documents對象則表示所有打開的文件。類似,一個Window窗口對象表示一個打開的窗口,而Windows對象表示所有打開的窗口。Documents和Windows對象稱為集合對象,它們包含了相關的一組對象。
每一個IDE環境的對象都實現了一個雙向的接口,提供了大量的方法、屬性和事件,在開發VBScript宏和插件的時候需要使用這些方法、屬性和事件來操作這些對象。同時,每一個對象為Automation機制實現了一個IDispatch接口和一個COM接口,來支持對對象成員(屬性、方法和事件)的訪問。
為了更好地操作集成開發環境的對象,必須知道這些對象之間的關系。對象之間有一個層次包含的關系。Application對象是最頂層的,其它的對象都是從屬于它的。有了這個關系,就可以使用Application對象的屬性和方法來直接訪問它的所有下屬對象啦!VC集成開發環境的對象以及它們之間的包含關系如下:
Application
Application(Application也可以包含自己)
Projects
Project
Configurations
Configuration
Configurations(循環)
Documents
Document
Window(一般窗口)
TextDocument
TextSelection
TextWindow
Window(文本窗口)
TextEditor
Windows
Debugger
Breakpoints
Breakpoint
例如,Breakpoints對象有一個Breakpoint對象作為它的屬性之一,也就是說Breakpoints對象包含了一個或者多個Breakpoint對象。這樣就可以通過一個對象的屬性來訪問它包含的所有下屬對象。反過來,也可以通過一個對象的Parent屬性來訪問它所屬的對象。
VC這種環境的對象封裝機制為我們開發VBScript宏和插件來擴展VC集成開發環境的功能提供了極大的方便。下面就讓我們具體看看怎樣來開發VBScript宏和插件,利用VC的Automation機制,增強VC開發環境的功能,以方便自己日常的開發工作。
VBScript宏
VBScript宏是使用VBScript語言寫的不帶參數的過程。VBScript宏不單單可以用在VC里,在微軟的Office工具里也可以使用。我們可以使用宏來極大地簡化VC里的一些工作,例如組合一些命令、進行一些快速編輯或者自動進行一些復雜的處理等等。VBScript宏以Sub語句開始,然后是執行一些操作的VBScript語句,以End Sub語句結束。
我們先來看一個具體的宏。下面是快速創建一個C/C++文件的VBScript宏。
Sub CreateCPPFile
Set CPPDoc = Document.Add("Text")
CPPDoc.Language = dsCPP
End Sub
從這個簡單的宏,我們可以看到:
* Sub語句開始一個宏的定義,這個宏的名字是CreateCPPFile。宏的名字是任意的,可以選擇易于記憶的名字。
* 宏的第一行使用Documents對象的Add方法,往Documents對象里增加了一個Document對象,從而創建了一個新文件。
* 宏的第二行通過設置Document對象的Language屬性為常量dsCPP(代表C/C++),指定該文件的類型是C/C++文件。
* End Sub語句結束這個宏。
得到一個VBScript宏有兩個途徑:記錄或者手工書寫。最簡單、最容易的方法當然是記錄啦!在你記錄一個宏的時候,宏記錄器跟蹤你的動作,把這些動作轉換成VBScript語句,然后把這些語句插入到宏里面。
VC提供了兩種宏記錄的方法:正常宏記錄和快速宏記錄。
快速宏記錄則可以快速記錄你的一些動作,而不需要命名、描述或者編輯這個宏,但是只可以保存一個,重新記錄將刪掉原來的快速宏。記錄快速宏的簡單過程如下:(1)選擇Tools菜單里的Record Quick Macro命令;(2)執行需要記錄的動作,在執行動作的過程中可以使用宏記錄器工具條上的Pause暫停按鈕來臨時中斷記錄或者繼續記錄;(3)完成所有動作后,點擊宏記錄器工具條上的Stop按鈕即可。需要使用這個快速宏,選擇Tools菜單的Play Quick Macro命令即可。
如果你想要記錄一個復雜的宏,而且要進行一些編輯,那就要使用正常的宏記錄了。錄制正常宏的過程如下:(1)選擇Tools菜單的Macro命令打開宏管理器,如圖1所示;(2)如果需要把宏加到一個新的宏文件里,點擊Options按鈕,點擊New File按鈕,然后在Macro File框里填入文件名字;如果需要把宏加到一個已有的文件里,則從Macro File的下拉框里選擇文件;(3)在Macro Name框里寫入宏的名字;(4)點擊Record按鈕;(5)在Add Macro對話框里寫入宏的描述,點擊OK按鈕;(5)執行需要記錄的動作,中間可以點擊Pause按鈕暫停或者繼續;(6)點擊Stop按鈕完成錄制,這時候VC將打開宏文件,并且把光標停在宏的起始點,你可以對宏進行檢查或者編輯。
圖1
也可以往一個新的或者已有的宏文件里添加代碼,直接手工寫一個宏。步驟如下:(1)選擇Tools菜單的Macro命令打開宏管理器;(2)選擇宏文件或者新建宏文件;(3) 在Macro Name框里寫入宏的名字;(4)點擊Edit按鈕;(5)在Add Macro對話框里寫入宏的描述,點擊OK按鈕,VC將打開宏文件,在文件的尾部為新的宏創建一個架子-Sub塊,如下所示(假設宏的名字為MyMacro):
Sub MyMacro()
'Description: The macro description goes here
'TODO: Insert the macro code here.
//這一段是自己編寫的,為選中的文本加C語言的注釋符號
ActiveDocument.Selection = "/*" + ActiveDocument.Selection + "*/"
End Sub
你就可以往這個架子里填寫入具體操作了。
在記錄或者編寫好宏以后,宏將被保存在一個文本文件-宏文件里。在你開始記錄或者編寫新的宏的時候,需要選擇宏文件。宏文件的擴展名為.DSM,包含了一個或者多個VBScript宏,數量看需要而定。宏文件的一般格式是:開始是對這個文件的一些描述,然后就是每個宏的具體定義。
在VC里使用宏文件需要先安裝。一旦安裝了以后,每次啟動VC的時候都會自動裝載這些宏,可以在所有的項目里使用,不依賴于項目。安裝宏文件的方法如下:(1)選擇Tools菜單的Customize命令;(2)選擇Add-ins and Macro Files頁簽,如圖2所示,如果宏文件的名字沒有出現在窗口里,則點擊Browse按鈕去定位。(3)在窗口里打勾選上需要安裝的宏文件即可;如果取消選擇某個宏文件,則會卸載這個宏文件。
圖2
有了VBScript宏,只要運行它即可以自動幫你完成所需要的操作啦!如果需要經常運行某個宏,那么可以為這個宏分配一個工具條按鈕或者熱鍵,這樣就可以通過點擊按鈕或者按熱鍵方便地運行這個宏了。分配按鈕的具體設置是:(1)選擇Tools按鈕的Customize命令;(2)選擇Commands頁簽;(3)在Category框里選擇Macros;(4)在Commands框里,把要分配工具條按鈕的宏直接拖放到工具條上;(5)這時會彈出一個Button Appearance對話框,如圖3所示,為這個按鈕選擇一個合適的位圖,點擊OK即可,VC將在工具條上添加上這個宏的一個按鈕。分配熱鍵的方法是:(1)選擇Tools按鈕的Customize命令;(2)選擇Keyboard頁簽;(3)在Category框里選擇Macros;(4)在Commands框里,選擇要分配熱鍵的宏;(5)點擊Press new shortcut key框,按下熱鍵,然后點擊Assign按鈕就可以了。
圖3
VC插件
前面提到,VBScript宏和VC插件都可以用來擴展VC集成開發環境的功能,美觀、改造VC的界面,但是它們是有區別的。主要的區別當然在于:VBScript宏是使用VBScript語言寫的過程,而VC插件則是使用VC或者Visual Basic開發的COM構件。
開發VBScript宏比開發插件容易多了。生成一個VBScript宏只需要記錄宏、增加一些代碼(需要的話),然后運行就可以了。而生成一個插件需要VC來編寫代碼,編譯成DLL,然后與VC的集成開發環境連接(安裝插件),才能使用它提供的功能。
VBScript宏在功能上弱于插件。在宏里面,只能夠使用VBScript語言,只能訪問VC集成開發環境的對象;而插件則可以使用更多的語言(VC、VB),不僅能使用VC集成開發環境的對象,還可以使用整個計算機系統的資源。例如,宏里面與用戶交互只能使用VBScript提供的兩個對話框InputBox和MsgBox,而插件則可以使用任何形式的對話框。
從VBScript宏與插件的這些區別可以知道:如果使用VBScript宏可以完成的任務,建議盡量使用VBScript宏,因為開發一個VBScript宏比開發一個插件要簡單得多、快得多。VC插件更適合于用來提供復雜的功能,例如:全局查找替換、保存所有的文件并編譯、把VC的窗口變成頁簽式界面(如圖4所示)等等。
圖4
開發插件的基本步驟如下:
首先,需要設計插件。要確定:插件的功能、是否可以采用VBScript宏來實現(如果可以,就不開發這個插件而選擇宏)、插件提供哪些功能、需要使用哪些集成開發環境的對象、需要使用哪些屬性和方法、需要幾個插件、是否需要使用對話框、使用什么對話框、是否需要使用別的插件、是否需要使用別的應用等等。
其次,需要確定使用什么語言來開發插件。現在Visual C++、Visual Basic的4.0以上的版本都支持VC插件的開發。VC專門為插件開發提供了一個向導,可以幫你完成大部分的工作。如果使用Visual Basic的話,則需要使用一些類型庫來訪問VC集成開發環境的對象。因此,建議你使用VC來開發比較方便。
接著,需要創建一個插件的項目。使用VC的插件向導,向導將自動創建一個項目。如果使用VB來開發插件,最好以一個例子為基礎。
項目創建了以后,就需要編寫代碼了。如果使用了VC的插件向導或者以VB的一個例子為基礎的話,那么大部分代碼已經有了,你只需要加入具體處理的一些代碼即可。
然后,把項目編譯連接成DLL,可以把多個插件合并在一個DLL里。最后,在VC里安裝這個插件就可以使用了。運行命令行命令、點擊插件提供的工具條按鈕或者按插件提供的熱鍵就可以使用插件了。
VC 5.0以上的版本專門為插件的開發提供了一個插件向導。可以使用這個向導輕松、快速地生成插件的基本代碼,然后根據自己的需要進行修改。使用VC的插件向導進行插件開發的步驟如下:
首先,設計插件。接著,使用插件向導生成新的插件項目:(1)選擇File菜單的New命令;(2)在New對話框里,選擇Projects頁簽;(3)在窗口里選擇Developer Studio Add-in Wizard,在Project Name框里填入項目的名字,然后點擊OK;(4)這時進入插件向導(見圖5),為插件填寫名字和描述;如果插件需要工具條按鈕,則選上Provides a toolbar選項;如果插件需要處理VC集成開發環境對象的事件,則選上Responds to Developer Studio events選項,向導將為每個事件添加一個代碼框架,你必須為需要的事件提供具體的處理;點擊OK;(5)在彈出的New Project Information對話框里檢查向導即將生成的文件及項目目錄,確認正確后點擊OK就生成插件項目了。
圖5
然后,定制插件的功能或者增加新的功能。插件向導自動生成了添加一個功能到VC集成開發環境的代碼,但是如果你需要定制或者添加更多的功能的話,則要修改代碼,這個與普通應用開發是一樣的。修改需要使用到Application對象的三個方法:使用AddCommand方法添加一個功能;使用AddCommandBarButton方法添加一個工具條按鈕;使用AddKeyBinding方法來添加一個熱鍵。具體的做法請參照VC插件開發的幫助。
這里需要簡單說明一下VC集成開發環境與插件之間的關系,如圖6所示。每一個插件都向外提供了兩個對象DSAddIn和Commands。VC的集成開發環境使用DSAddIn對象來裝載或者卸載一個插件,使用Commands對象來執行插件提供的功能。具體來說,VC是調用插件的DSAddIn對象的OnConnection方法來裝載插件的,這個方法還發布了插件向外提供的功能接口,如果插件包含了事件的處理,還把連接到VC集成環境的事件上。VC調用DSAddIn對象的OnDisconnection方法來卸載插件。Commands對象則包含了AddCommand所加入的每一個功能接口,編寫插件的時候必須為每一個功能接口編寫代碼。
圖6
最后,把插件編譯連接成DLL。安裝插件的方法是:(1)選擇Tools菜單的Customize命令;(2)選擇Add-ins and Macro Files頁簽,如果插件的名字沒有出現在窗口里,則點擊Browse按鈕去定位。(3)在窗口里打勾選上需要安裝的插件即可;如果取消選擇某個插件,則會卸載這個插件。插件安裝了以后,VC每次啟動都會自動裝載這個插件,你就可以使用這個插件提供的功能來為自己的應用開發服務了。
]]>關于平臺之間的差異,主要是Windows平臺和Unix平臺之間的差異,這里著重介紹一下這兩個平臺在C/C++開發中存在的差異,其間會穿插介紹一些Unix不同分支之間的差異。
語言特性的差異,指的是不同操作系統平臺中,實現C++/C時的一些細微的差異,忽略這些差異可能會帶來一些特別隱蔽的錯誤。而且可能是致命的錯誤。所以,了解語言特性的差異,對于在Unix移植來說非常重要。如果考慮系統多多個平臺支持,就必須了解在不同平臺下語言特性的差異,從開發一開始就把這些因素考慮進去,這樣才能最低限度的降低移植的過程中工作量。
字節順序指的主要是整型變量在內存中的存儲方式。在計算機中,數據都是以二進制方式存儲的,包括在內存和硬盤中。而計算機又以8位二進制作為一個存儲單元。在32位系統中,一個整型的存儲需要四個存儲單元。也就是說要把一個32位的整數分割成位四段分別進行存儲,而每一段的存儲位置就是字節順序的差異。為了清楚的表示每段存儲的先后位置,我們用16進制來表示一段的值,下表列出了在Unix系統和Windows系統中整數20000在內存中的情況。
十六進制表示 |
0x00004E20 |
Windows內存表示 |
20 4E 00 00 |
Unix內存表示 |
00 00 4E 20 |
如表中所示,Windows中存儲方式和該整數的16進制表示是相反,是一種低位在前高位在后的存儲順序。而Unix下的存儲順序和正常的16進制表示的順序相同,稱為高位在前低位在后的順序。這種差異帶來的問題,主要體現在以下幾個方面:
? 網絡通信時
當Windows和Unix之間發生網絡數據傳輸,傳輸一個整型數據(如一個數據包的長度)的時候,如果不經處理直接把內存中的數據傳輸過去,那么在對方看來完全是另一個數據,這樣就會造成問題。如Windows下面發送過去一個20000(0x00004E20),在Unix下面收到的數據就會被理解成541982720(0x204E0000),這簡直是天壤之別。
? 文件存儲和讀取時
跟網絡傳輸類似,如果在Windows下面把某個整數寫到了文件中,然后在Unix下面打開這個文件讀取該數據,就會出現跟上面類似的問題。
這個問題主要體現在不同平臺之間互操作時,在多平臺開發過程中,尤其時在網絡應用開發的時候,兩個平臺之間數據交互是非常普遍的,所以這個問題也就顯的很普遍。解決這個問題的方法就是交互的雙方采用一種相同的數據編碼標準,就是數據在傳輸和存儲的時候采用什么方法進行編碼,具體的做法有一下幾種:
1. 數字轉換成字符傳進行交互
2. 協商一個同意的字節順序,根據自己平臺的字節順序還原數據
3. 采用其他標準的編碼方式,如ASN1編碼
跟這個問題類似,32位系統和64位系統的差異也會出現這樣的問題,解決方法跟這個問題的解決方法相同。在32位系統和64位系統中,長整型(long)分別用32位和64位表示,這樣,在不同系統之間交互的時候必然會出現整型數據表示方式不同的問題。目前大多數Windows系統都是32位的系統,而Unix中很多都是64位的,尤其是大型的服務器,所以這個問題必須引起重視。
在不同的系統下,由于編譯器的不同,對變量作用域的實現機制也有所不同,這里以Windows下的VC和Solaris下的CC這兩個編譯器為例做一個簡單的比較說明。
在C++的開發過程中,我們經常會有這樣的用法:
for(int i=0;i<num;i++)
{
…
}
這是一種最常用的for循環的用法,因為其中i主要使用來控制循環,所以一般沒有必要拿出來單獨進行聲明,只是放在for語句中一起聲明。這里i、j等簡單的變量就成了我們常用的變量,一般不按照編程規范那樣為他們命名。就是這種聲明方法,在Windows下和Solaris下有了不同的理解,i的作用域不同。我們先把作用域進行劃分,如下:
{
…
for(int i=0;i<num;i++)
II |
I |
}
…
…
}
我們劃分出I和II兩個作用域,其中作用域II包含在作用域I當中。在Windows下,變量i的作用域是I的整個范圍,而Solaris下的i的作用域只是II的范圍。其實標準的C++語法應該是Solaris的做法,但是微軟在實現的時候沒有按照這個標準實現,這就引發了我們討論的這個問題。由于這個差異,就引發了一些微妙而隱蔽的問題。先看一下下面兩端代碼。
A:
for(int i=0;i<num;i++)
{
…
}
…
for(i=0;i<num;i++)
{
…
}
B:
for(int i=0;i<num;i++)
{
…
}
…
for(int i=0;i<num;i++)
{
…
}
代碼A在Windows下面可以正常編譯,而在Solaris下面確編不過去,提示第二個for循環中變量i沒有定義。相反代碼B在Solaris下可以正常編譯,而在Windows下面編不過去,提示第二個for循環中變量i重復定義。
在通常的情況下,我們會按照B的方法書寫代碼,而在Windows編譯是出現錯誤,然后改成A的那種形式。這樣,在Windows下就沒有問題了,程序也可以編譯過去了,但是到Solaris下時,有會出現問題,這是就不得不把i的聲明拿到所有for循環的外面。當i的聲明拿到for循環的外面時,真正的問題來了。首先提示一下,這樣的一段代碼是沒有問題的:
C:
int i = 0;
if(cond)
{
…
for(int i=0;i<num;i++)
{
…
}
…
}
這是一段正確的代碼,雖然在外面已經定義了i,但是在for里面重新定義一個i也沒有問題,這是C++的語法所允許的(java里面不允許這樣做)。但就是因為這種C++語言的靈活機制,引發了問題的產生。
問題產生源于程序中出現了A_B那樣的代碼,然后把i的聲明拿到了外面。在后期維護的過程中,又在后面增加了一個循環,但是卻是按照C的那種方式增加的,這樣就產生了問題。請看如下代碼:
int i=0;
char str1[10];
char str2[10];
strcpy(str1,”hello”);
…
for(i=0;i<20;i++)
{
…
I |
…
if(cond)
{
for(int i=0;i<10;i++)
III |
II |
}
memcpy(str2,str1,i);
str2[i]=0;
}
…
在上述代碼,為了分析方便,我們把整段代碼分成I、II和III三個作用域。其中作用域II就是整個if語句,實現的相當于一個strcpy函數的功能。II中的內容就好是我們上面說的后期維護中加入的,當然,實際情況并不像我們例子中這么明前,i的聲明可能離我們的if語句很遠,所以加入這段代碼是不知道上面是否聲明了i變量。而且,這段代碼編譯的時候也不回出錯,不管是Windows還是Solaris(單獨的一段II中的代碼在Solaris下面編不過去)。在Windows下面,這段代碼可以正常的運行,不回出現任何問題,因為II中的代碼完全是根據Windows下的習慣編寫的。但是在Solaris下面,這段代碼就會出現內存越界的錯誤,雖然編譯可以正常通過,但是實現的卻不是程序員預期的目的。在執行memcpy的時候,那個i其實是外層聲明的那個i,值是20,而str2和str1的大小之后10,所以就發生了讀寫內存越界。而程序員預想的,這個i是for循環算出來的str1字符串的長度,應該是5。
要解決這類問題,就得加強編程規范,杜絕這種錯誤代碼的生成。從開始的時候就要意識到可能產生的問題,從而避免問題的發生。
]]>以下內容含腳本,或可能導致頁面不正常的代碼 |
---|
說明:上面顯示的是代碼內容。您可以先檢查過代碼沒問題,或修改之后再運行. |
微軟在其Visual C++產品中包含了一套C語言運行時庫,它的其它庫產品大多基于這一套庫(比如MFC)。在特殊的場合,我們可能需要使用自己的運行時庫來替代它。比如,某一些對于注重系統綜合性能的游戲。那時,我們只需要實現運行時庫中的某一些功能,甚至可以不按照標準來命名(因為那是你自己的運行庫,并且你不打算發布她)。比方說C語言運行時的內存分配函數,常用的不外乎malloc,calloc,free,realloc這幾個,我們實現的時候就沒有必要遵照以上的名字命名我們的相應功能的函數。
在替代運行庫以前必須認識到的是,許多基于運行庫的函數庫將不能再使用,比如剛才提到的(MFC)庫,而你在以前編寫的許多庫可能不能再使用,這意味著你可能要白手起家。(需要說明的是:ATL庫基本沒有使用C語言運行時庫,所以可以繼續使用,前提是使用時不要連接MFC)。
1. 基本概念
我們平時接觸VC++的時候,第一個接觸到的恐怕是WinMain和main,對應于Win32子系統的Windows窗口系統和控制臺兩個部分,最多是某些書籍上談到了對應多字節字符集的幾個變種。其實,這幾個入口點函數是VC++帶有的C運行庫要求的入口點。真正的vc程序的入口點函數是在使用VC++的C編譯器編譯程序時指定的。它可以是符合下面形式的任何名稱的函數:
void __cdecl Your_Entry (void);
如果你喜歡,你可以起一個更加藝術的名字。
說到這里,給出一個樣例程序可以更好的理解這個入口點函數和我們平時接觸的C運行時入口點函數之間有些什么。這是一個什么都不做的程序
// VC++ Entry point
void MyEntry (void);
{
{
將這些個字符敲在一個文本文件中,保存為:d:\test0.c
然后在VC++命令提示符環境中鍵入下面的步驟來編譯、連接這個程序(在上一個版本中,我把這個部分漏了,這可能使得不少人看了這篇文章卻不知道如何實現):
l 進入VC++的bin目錄,缺省安裝下,它應該在如下的目錄中:
C:\Program files\Microsoft Visual Studio\VC98\Bin
然后運行vcvars32.bat批處理文件,如下圖所示:
注意:我的機子上的目錄可能和你的不一樣。
屏幕會提示順利設置了vc的環境變量。
l 然后用下面的命令編譯上面的代碼文件
d:\
cl /c test0.c /nologo
如果沒有什么提示而很快的出現命令提示符,則表示編譯成功。
l 然后用下面的命令連接
link /ENTRY:”MyEntry” /OUT:test0.exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB test0.obj /nologo
不出什么意外的話,在D分區上應該有一個test0.exe文件,雙擊它發現什么也沒有出現。但是,其實它是一個不折不扣的Win32應用程序。你可以用相應工具來測試它,可以發現在入口點處是幾個符合C函數調用規則的幾個壓棧、數據轉移、和出棧指令。
上面用到的一些cl和link程序開關選項的意義請參考MSDN。
值得提一提的是:缺省情況下,link程序連接了4個C運行時庫中的某一個,并且將函數mainCRTStartup、wmainCRTStartup、WinMainCRTStartup、wWinMainCRTStartup中的一個作為缺省的入口點(我們這里只討論非動態連接庫,也就是一般的可執行印象)。具體使用哪個,是根據link命令行中指定的子系統。可以參考MSDN獲取更詳細的說明。
2. Microsoft C/C++ Runtime Library
有了上面這些基礎,我們接著再看一看Microsoft C/C++ Runtime Library在入口點處都作了些什么。我這里給出的代碼是經過篩選的,只是為了說明問題,這些代碼在VC安裝目錄中CRT\SRC下面的crt0.c中,缺省沒有安裝。
#undef _UNCODE
void WinMainCRTStartup (void)
{
int mainret;
STARTUPINFO StartupInfo;
_osver = GetVersion ();
_winminor = (osver >>8) & 0x00FF;
_winmajor = _osver & 0x00FF;
_winver = (winmajor << 8) + _winminor;
osver = (osver >> 16) & 0x00FFFF;
if (!_heap_init (1))
fast_error_exit (_RT_HEAPINIT);
_acmdln = (char*) GetCommandLineA ();
_aenvptr = (char*) __crtGetEnvironmentStringsA ();
_setargv ();
_setenvp ();
_cinit ();
StartupInfo.dwFlags = 0;
GetStartupInfo (&StartupInfo);
mainret = WinMain (GetModuleHandleA (NULL),
NULL,
;pszCommandLine,
StartupInfo.dwFlags & STARTF_USESHOWWINDOWS ?
StartupInfo.wShowWindow : SW_SHOWDEFAULT);
exit (mainret);
}
上面的代碼經過篩選,它用于多線程下,普通的多字符集C運行時。我稍微解釋一下代碼的含義,它完成以下任務:
l 獲取操作系統的版本信息,用于以后的操作;
l 然后初始化進程堆棧;
l 獲取命令行,獲取和設置環境變量;
l C運行時內部變量的初始化;
l 調用標準Win32窗口程序入口點函數(它應該是在你的應用程序中被定義和實現的);
l 調用ExitProcess函數退出應用程序,退出代碼是WinMain的返回值。
具體的代碼請參見運行庫的源代碼。
3. 不使用運行庫編寫自己的應用邏輯
接著,我們來試試看,不使用C運行庫,并且使得我們的應用程序做些個事情。請看下面的代碼:
// 程序init.c
#pragma once
#include <windows.h>
void entry (void)
{
char** p;
char* pAlloc;
char* pszNames[] = {
"SNK",
"Capcom",
"Nintindo",
"EA",
"3DO",
NULL
};
for (p = pszNames; *p != NULL; p ++)
{
MessageBox (0, *p, 0, MB_OK);
}
pAlloc = VirtualAlloc (0, 4096, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);
if (pAlloc)
{
const char* pText = "Hello, world!";
char* pTemp = (char*) pText, *pstr = pAlloc;
for (;*pTemp != '\0';) *pstr++ = *pTemp++;
*pstr = *pTemp;
MessageBox (0, pAlloc, 0, MB_OK);
VirtualFree (pAlloc, 4096, MEM_RELEASE);
}
}
使用下面的命令行來編譯連接它
cl init.c /c
link init.obj /SUBSYSTEM:WINDOWS /OUT:init.exe /ENTRY:”entry” /NODEFAULTLIB kernel32.lib user32.lib
生成的init.exe程序的運行中界面如下:
。
]]>0 簡介 C語言及其典型實現被設計為能被專家們容易地使用。這門語言簡潔并附有表達力。但有一些限制可以保護那些浮躁的人。一個浮躁的人可以從這些條款中獲得一些幫助。 在本文中,我們將會看一看這些未可知的益處。這是由于它的未可知,我們無法為其進行完全的分類。不過,我們仍然通過研究為了一個C程序的運行所需要做的事來做到這些。我們假設讀者對C語言至少有個粗淺的了解。 第一部分研究了當程序被劃分為記號時會發生的問題。第二部分繼續研究了當程序的記號被編譯器組合為聲明、表達式和語句時會出現的問題。第三部分研究了由多個部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當一個程序具體執行時會發生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關系。在第六部分中,我們注意到了我們所寫的程序也不并不是我們所運行的程序;預處理器將首先運行。最后,第七部分討論了可移植性問題:一個能在一個實現中運行的程序無法在另一個實現中運行的原因。 1 詞法缺陷 編譯器的第一個部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(token)一個記號是一個有一個或多個字符的序列,它在語言被編譯時具有一個(相關地)統一的意義。在C中, 例如,記號->的意義和組成它的每個獨立的字符具有明顯的區別,而且其意義獨立于->出現的上下文環境。 另外一個例子,考慮下面的語句: if(x > big) big = x; 該語句中的每一個分離的字符都被劃分為一個記號,除了關鍵字if和標識符big的兩個實例。 事實上,C程序被兩次劃分為記號。首先是預處理器讀取程序。它必須對程序進行記號劃分以發現標識宏的標識符。它必須通過對每個宏進行求值來替換宏調用。最后,經過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個流劃分為記號。 在這一節中,我們將探索對記號的意義的普遍的誤解以及記號和組成它們的字符之間的關系。稍后我們將談到預處理器。 1.1 = 不是 == 從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因為賦值的頻率要高于比較,因此為其分配更短的符號。 此外,C還將賦值視為一個運算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個大的表達式中。 這種便捷導致了一個潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等于y: if(x = y) 而實際上是將x設置為y的值并檢查結果是否非零。在考慮下面的一個希望跳過空格、制表符和換行符的循環: while(c == ' ' || c = '\t' || c == '\n') 在與'\t'進行比較的地方程序員錯誤地使用=代替了==。這個“比較”實際上是將'\t'賦給c,然后判斷c的(新的)值是否為零。因為'\t'不為零,這個“比較”將一直為真,因此這個循環會吃盡整個文件。這之后會發生什么取決于特定的實現是否允許一個程序讀取超過文件尾部的部分。如果允許,這個循環會一直運行。 一些C編譯器會對形如e1 = e2的條件給出一個警告以提醒用戶。當你趨勢需要先對一個變量進行賦值之后再檢查變量是否非零時,為了在這種編譯器中避免警告信息,應考慮顯式給出比較符。換句話說,將: if(x = y) 改寫為: if((x = y) != 0) 這樣可以清晰地表示你的意圖。 1.2 & 和 | 不是 && 和 || 容易將==錯寫為=是因為很多其他語言使用=表示比較運算。 其他容易寫錯的運算符還有&和&&,或|和||,這主要是因為C語言中的&和|運算符于其他語言中具有類似功能的運算符大為不同。我們將在第4節中貼近地觀察這些運算符。 1.3 多字符記號 一些C記號,如/、*和=只有一個字符。而其他一些C記號,如/*和==,以及標識符,具有多個字符。當C編譯器遇到緊連在一起的/和*時,它必須能夠決定是將這兩個字符識別為兩個分離的記號還是一個單獨的記號。C語言參考手冊說明了如何決定:“如果輸入流到一個給定的字符串為止已經被識別為記號,則應該包含下一個字符以組成能夠構成記號的最長的字符串”。因此,如果/是一個記號的第一個字符,并且/后面緊隨了一個*,則這兩個字符構成了注釋的開始,不管其他上下文環境。 下面的語句看起來像是將y的值設置為x的值除以p所指向的值: y = x/*p /* p 指向除數 */; 實際上,/*開始了一個注釋,因此編譯器簡單地吞噬程序文本,直到*/的出現。換句話說,這條語句僅僅把y的值設置為x的值,而根本沒有看到p。將這條語句重寫為: y = x / *p /* p 指向除數 */; 或者干脆是 y = x / (*p) /* p指向除數 */; 它就可以做注釋所暗示的除法了。 這種模棱兩可的寫法在其他環境中就會引起麻煩。例如,老版本的C使用=+表示現在版本中的+=。這樣的編譯器會將 a=-1; 視為 a =- 1; 或 a = a - 1; 這會讓打算寫 a = -1; 的程序員感到吃驚。 另一方面,這種老版本的C編譯器會將 a=/*b; 斷句為 a =/ *b; 盡管/*看起來像一個注釋。 1.4 例外 組合賦值運算符如+=實際上是兩個記號。因此, a + /* strange */ = 1 和 a += 1 是一個意思。看起來像一個單獨的記號而實際上是多個記號的只有這一個特例。特別地, p - > a 是不合法的。它和 p -> a 不是同義詞。 另一方面,有些老式編譯器還是將=+視為一個單獨的記號并且和+=是同義詞。 1.5 字符串和字符 單引號和雙引號在C中的意義完全不同,在一些混亂的上下文中它們會導致奇怪的結果而不是錯誤消息。 包圍在單引號中的一個字符只是書寫整數的另一種方法。這個整數是給定的字符在實現的對照序列中的一個對應的值。因此,在一個ASCII實現中,'a'和0141或97表示完全相同的東西。而一個包圍在雙引號中的字符串,只是書寫一個有雙引號之間的字符和一個附加的二進制值為零的字符所初始化的一個無名數組的指針的一種簡短方法。 線面的兩個程序片斷是等價的: printf("Hello world\n"); char hello[] = { 使用一個指針來代替一個整數通常會得到一個警告消息(反之亦然),使用雙引號來代替單引號也會得到一個警告消息(反之亦然)。但對于不檢查參數類型的編譯器卻除外。因此,用 printf('\n'); 來代替 printf("\n"); 通常會在運行時得到奇怪的結果。 由于一個整數通常足夠大,以至于能夠放下多個字符,一些C編譯器允許在一個字符常量中存放多個字符。這意味著用'yes'代替"yes"將不會被發現。后者意味著“分別包含y、e、s和一個空字符的四個連續存貯器區域中的第一個的地址”,而前者意味著“在一些實現定義的樣式中表示由字符y、e、s聯合構成的一個整數”。這兩者之間的任何一致性都純屬巧合 |
“C# Java是解釋型語言?Oh my god”
“引用vls:C# Java是解釋型語言?Oh my god
呵呵 我也很詫異“
“引用DiggingDeeply:@vlsC#不是解釋行語言,那JIT是干啥用的?
無知并不可憐,無知還不知道自己無知才叫可憐”
“。。。但如果因為它是"一邊變換一邊執行", 就將其當作解釋語言……只能說你看到表象卻沒抓住本質。”
很是無語,既然您認為別人無知那您的有知拿出來給無知的人看看,行不?
拋開這些不談,誰能給編譯型和解釋型給下個解釋。無一例外,要么就是某人的博客,要么就是搜索來的網頁,再要么就是放個闕詞就消散,這些作為論據充分不?
昨天翻了翻龍書,也沒有給這兩個概念下細致的解釋,可見兩個概念是很難下個精確的解釋的。
在<<programming language="" Pragmatics>>(by Michaei L. Scott)這本書里面,我找到了有關的解釋和說明,特摘錄如下
Compilation and Interpretation
高級語言里一個程序的編譯和執行大概是 下面的情況:
編譯器將高級語言從源代碼翻譯成與之等價的目標程序(就相當于從中文翻譯成中文),而后就隱退了。在隨后的某個時刻,用戶啟動目標程序由操作系統執行。實現高級語言的另外一種形式為解釋:
與編譯不同的是,解釋器在目標程序(其實根本就沒有目標程序,只是與編譯來比較)執行期間,解釋器一直隨之運行。這種執行流程完全由解釋器控制的。從效果上看,解釋器實現了一臺“虛擬計算機”,其“機器語言”就是高級語言,解釋器一次讀入一條或多條語句,按照其自身規定的形式去執行相應的操作。一般說來,解釋比編譯有著很好的靈活性;編譯一般有著較好的性能。但是有些語言確是采用了兩者的混合形式:
書中的原文:”如果原始階段的翻譯器比較基本,我們就說這個語言是“解釋的”。如果翻譯器很復雜,我們就說這一語言是“編譯的”。現在兩者的區分變得有些模糊了,因為“基本 ”和“復雜”都是修飾性術語,也因為完全可能出現用一個編譯器(復雜的翻譯流程)生成代碼,而后又由一個復雜的虛擬機(解釋器)執行。對于最后這種情況,如果翻譯器對程序做了徹底的分析(而不是做某種“機械的”變換),而且有關的中間語言程序與源程序并沒有很強的相似性,我們還是說這個語言是編譯的。這兩種特征 ----徹底的分析和非平凡的變換-----是刻畫編譯形式的標志性特征。“
根據以上標準,首先CSC只是對C#到IL做”機械“的翻譯,而且C#和IL之間有很強的相似性,因為兩者的程序代碼幾乎可以100%相互轉換(比如reflector可以將C#反編成IL,也可以將IL反編為C#)。您認為呢?
PS:再者某些人認為的ngen和cache程序集其實也是支持C#是解釋語言的結論,因為編譯型語言因為性能的天然因素是不須要這些手段的。也許我是錯的,請您不吝賜教,感激涕零.本人禁止了不負責任的匿名評論,請大家海涵。
]]>