把C++當指令碼語言寫!

來源:互聯網
上載者:User

  提到指令碼,腦海裡馬上閃過一大堆:Python,Perl,Ruby,PHP,JS,VBS,LUA。。。 不過你有沒聽說過,用經典的C++做指令碼語言嗎?先不多說,上個圖。(先別糾結那個function,那僅僅是個宏而已,待會你就明白了)

  

  

  

  或許你在想這一定是瘋了,用世界上最複雜的語言做指令碼,寫的人累不說,指令碼引擎先累壞了。各種複雜的模板庫,要邊解釋邊運行,得有多強大的虛擬機器才撐得住。

  好吧,那麼我們退一步,不強求解釋執行,迴歸到原始的編譯後執行。———— 不過那還算指令碼嗎?

  

編譯速度

  事實上如今高效能的指令碼都是先編譯後啟動並執行,大名鼎鼎的JavaScript V8引擎,號稱速度最快的LUA-Jit,以及眾所周知的ActionScript。。。預先編譯不僅能大幅提高運行速度,更重要的是能夠提前發現指令碼中顯式的錯誤。

  

  但指令碼中所謂的編譯,和傳統語言的編譯,還是很大區別的。指令碼的編譯,不過是代碼上的深度最佳化,很快就可以完成。相比複雜了多的C++來說,似乎是望塵莫及的。提到C++的編譯速度,大家的映象莫過於在VC裡按下F5之後,看著輸出框內一條一條的“Compiling...”緩緩出現。有時僅僅測試一個微小的修改,也要等上好幾秒的時間。緩慢的編譯速度備受煎熬,以至於簡單的程式往往選擇VB或C#這樣可以快速調試的語言。

  

  對於龐大的MFC程式來說,緩慢的編譯是理所當然的。但簡單的小程式出現過長的編譯時間,那一定是標頭檔引用的不合理了。事實上,使用預先處理標頭檔的小程式,編譯僅僅是一瞬間的,之後的各種停頓往往是IDE引起的。

  

  那麼我們就來測試下,不用IDE,僅用純命令編譯個C++小程式。我們使用VC6.0的編譯器:CL.exe

  為了確保純淨的編譯環境,我們把CL.exe必須依賴的檔案複製到建立的檔案夾裡。對於VC6的版本,只要有如下5個檔案,就可以完成.cpp到.exe的編譯了。

CL.exe
  C1XX.DLL
  C2.DLL
  MSPDB60.DLL
Link.exe

  開啟cmd,設定好環境變數,對應到VC6的頭目錄和庫目錄

SET INCLUDE=C:\Program Files (x86)\Microsoft Visual Studio\VC98\Include

SET LIB=C:\Program Files (x86)\Microsoft Visual Studio\VC98\Lib

  就可以調用命令編譯了:

cl test.cpp

  一眨眼的工夫,編譯和連結完成,產生了test.exe,一切正常。而這還是在沒有使用先行編譯頭的情況下編譯的。

  

  由此可見,即使語言本身很複雜,但只要用它寫的代碼不複雜,編譯還是非常快的。

  仔細想想也應如此。以如今的硬體設定,運行98年的編譯器,編譯一個才幾行代碼的程式,自然是一瞬間的。

  命令列編譯簡單的C++程式是如此的快速,利用這個優勢,繼續我們的指令碼探索。。。

  

運行環境

  如果要寫一個產生100個隨機序號的小程式,你會使用哪類語言?

  相比傳統語言要先建立一個工程項目,我們直接在案頭建立個文字檔就可以寫指令碼了。

  雖然用文字編輯器寫代碼沒任何優勢,但對於簡單的程式足矣。之後程式交給其他人使用時,指令碼優勢就淋漓盡致的體現出來了:當他們自己想簡單修改一些邏輯規則時,只需用記事本開啟就可以,而記事本每台電腦上都有。

  相反,傳統語言寫的程式,即使有原始碼,使用者想簡單的修改下也無法生效,還需安裝並配置好相應的開發環境才行,這對不熟悉的人來說頗費周折。

  

  所以指令碼必須足夠簡單 —— 簡單到使用者只管修改和運行就可以,其他步驟都交給指令碼宿主自動完成。

  

  如果想用C++寫指令碼,那麼代碼的編譯和連結當然必須是全自動的,這並不複雜。

  但僅僅依靠CL.exe等幾個命令還是不夠的,因為在其他的電腦上並沒有相應的開發環境 —— Include和Lib檔案夾,因此就無法通過編譯和連結了。

  而這些標頭檔庫檔案,一共多達上千個,全都帶上則有近百兆!顯然,我們的指令碼只用到幾個準系統就可以了,那些複雜的windows標頭檔就沒必要了。

  

  事實上,程式的標頭檔只是函數和結構的定義,僅僅用來給編譯器分析而已,最終並不產生實際的指令。所以,我們把常用的標頭檔,事先產生一個.pch先行編譯頭檔案就可以。以後編譯時間,將他對應到某個標頭檔就可以了,例如stdafx.h。這樣就無需使用任何標頭檔了。即使stdafx.h也不在,編譯仍然能通過,因為這一切都打包在.pch裡面了。並且大量的標頭檔經過事先的分析,編譯時間就無需再編譯它們了,速度大幅提升。

  

  至於Lib檔案,裡面都是庫函數的內容。除非整個程式不使用任何C執行階段程式庫,那麼我們可以不帶上任何lib,但那樣只能寫最基本的代碼了。對於一般的簡單指令碼程式,只需幾個必要的lib即可:KERNEL32.LIB,LIBCMT.LIB,LIBCPMT.LIB,OLDNAMES.LIB。總共才1M多。

  

  我們把這幾個lib檔案以及.pch檔案,放在cl.exe同個目錄下,這樣就無需指定INCLUDE和LIB環境變數。

  

  

  至此,我們有了一個精簡版的VC6編譯器。通過上述10個檔案,我們可以不依賴任何環境,獨立編譯C++程式了。

cl /Yu"stdafx.h" /Fp"MyDLL.pch" test.cpp

實際運行

  現在,我們可以動態產生C++代碼檔案,並且自動編譯的能力了。但是如何將最終的二進位檔案與指令碼宿主互動呢?

  由於exe只能運行在獨立的進程裡,資料互動只能通過匿名管道,要實現回調什麼的非常困難。

  但若換成dll就可以大顯身手了,不僅運行在同一進程空間內,更重要的是dll是可以動態載入卸載的,這一點太符合指令碼程式的特性了。並且當某個模組更新了之後,就可以把先前的模組釋放掉,載入最新的。而這一切都是動態,無需重啟宿主即可完成!

  而且dll可以匯出內部的函數,宿主用GetProcAddress()就可以輕鬆獲得某個函數地址;至於回調,傳遞一個宿主的函數指標給指令碼就可以了。只要約定好函式宣告,雙方都可以用最簡單原始的方法互相調用,甚至共用同一塊記憶體空間。

  

  為了讓函數匯出更簡潔,本例中定義了個叫function的宏:

#define function extern "C" __declspec(dllexport) void

  於是就可以簡單的定義一個匯出函數了:

function Test(){      // some code here}

  是不是很有指令碼的感覺呢:)

  

語法檢查

  一個用文字編輯器敲出來的代碼,拼字錯誤是難免的。所以一個好的指令碼引擎,會在運行前做一次全面的語法檢查,事先排除明顯的錯誤,而不是邊解釋邊運行。

  C++就是將其做到了極限,不僅能查出致命的錯誤,甚至不規範的代碼也會有警告提示。這是非常值得的,一個小bug浪費的時間,足夠幾萬次編譯了。

  想要在我們的C++指令碼裡實現這個功能,其實是非常簡單的。因為在調用cl.exe編譯時間,要是有編譯錯誤就會反饋出來。我們根據對應的錯誤行號,提示使用者就可以了。

  

  

  

 
調試環境

  一個強大的指令碼引擎,往往帶有調試器。雖然編譯器能夠預先排除一些錯誤,但是邏輯上的錯誤只有在運行時才能出現。

  對於簡單的指令碼程式,這項功能似乎不那麼重要。畢竟在調試狀態下運行,效能會有所影響。

  在C++指令碼裡,我們可以通過宏來擴充調試功能,決定是否輸出調試資訊。不過對於異常錯誤,處理就比較講究了。

  由於我們最終啟動並執行是二進位dll模組,這和普通的指令碼有著天壤之別。dll模組是和宿主共用一個進程的,所以一旦當dll內異常觸發時,整個進程包括宿主一塊進入調試狀態了(系統裝有開發環境的話)。如果錯誤過於嚴重,會導致整個進程的崩潰。這是個非常值得注意的地方,也是C++作指令碼在許可權上的隱患。所以儘可能少用指標特性,使用更安全的代碼,讓代碼風險降到最少。

  對於致命的錯誤,宿主記錄下dump檔案是非常重要的,方便調試。

  

  不過出於簡單,本例的宿主是用VB寫的,也就無法在調用前使用__try{}進行SEH捕捉。如果宿主也是C++實現的話,則儘可能捕捉dll內的異常。

 

開發環境

  有別於指令碼語言,C++本身就是用於大型程式的開發,所以開發環境是非常完善的。

  但作為一個指令碼,往往都是單個的文字檔,而不是一個項目組。任何版本的VC編輯單個cpp檔案,和編輯純文字檔案幾乎沒有區別。因此我們事先得建立一個模板項目,將需要編輯的cpp移到此項目內開發,這樣才會有下拉框智能提示等功能。

  不過既然選擇它作為指令碼來使用,那就應該用來處理一些簡單的,經常變更的邏輯事務。對於複雜的指令碼程式,還不如直接寫在宿主裡面了。

  

  事實上,“程式”和“指令碼”之間從沒一條固定的界限。用純粹的程式也可以寫一個複雜的遊戲故事情節,用純粹的指令碼也可以開發一個大型項目。只不過太過死板,或太過靈活,都會增加額外的工作量。

 

總結

  與其稱之為C++指令碼,倒不如說是外掛程式———可以根據需求,動態產生指令的外掛程式。

  雖然可以玩轉出一些指令碼的特徵,然而C++終究是門嚴格的語言。相比指令碼的靈活性,C++固然更為嚴謹和死板。當然,憑藉強大的宏、模版、運算子多載,我們可以充分擴充,為指令碼提供豐富多樣的特徵和文法糖。

  當然,它的優勢也是顯而易見的:效能超高,互動簡單。並且完全支援C++的特性。

  

  

  事實上,不僅僅是C++,任何一門進階語言都可以當“指令碼”使用,只要調用它們的編譯器即可。如果喜歡C#,或者Java風格,只需稍作修改就可以。

 

  為了簡單示範,本例使用VB寫了個簡單的宿主程式,包括基本的編譯,連結,載入,語法檢查功能。

  宿主提供了一個叫“Print”的介面,可以輸出字串。要實現更多介面和擴充功能,修改cl檔案夾內的T.h即可。

  

  源碼可以在這裡下載:http://files.cnblogs.com/index-html/CppScript.rar

  

  其中有一個DLLTmpl的工程,沒有任何用處,僅僅為了產生一個.pch先行編譯標頭檔而已。如果想在指令碼裡使用更多的標頭檔,就得在StdAfx.h內添加。編譯之後的release/MyDll.pch複製到cl檔案夾,覆蓋原有的即可。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.