Cint(C/C++ interpreter, C-int)是一個C++解譯器,顧名思義,和GCC、VC等編譯器不同,它是解釋執行C++代碼的。它具有的能力有:
•支援 K&R-C, ANSI-C, ANSI-C++
Cint 覆蓋了80-90%的K&R-C、ANSI-C和C++語言特性。包括多繼承、虛函數重載、操作符重載、預設參數、模板(這個猛)等等。 Cint的目標不是成為100%相容ANSI/ISO C++語言的處理機,而是一個可移植的足以解析大部分標準C++的指令碼環境。
•可處理大規模C/C++源碼
Cint可以處理大規模的C/C++源碼,這可不是所有C++解譯器都能做到的。Cint可以快速載入入源檔案並解析超過6萬行的代碼。
•可與編譯代碼混合使用
基於速度和互動的需要,你可以混合使用編譯代碼和指令碼代碼。"makecint"工具可以把任何C/C++對象作為先行編譯庫嵌入到指令碼中去,先行編譯庫可以 配置成動態連結程式庫。指令碼代碼和先行編譯代碼可以雙向無縫調用。
•動態C++
Cint是動態。它可以從命令列處理C++聲明,動態地定義/刪除類和函數的聲明、載入/卸載源檔案和動態庫,以及一個擴充的 RTTI機制,這些足以讓你開發出不可思議的C++用法。
•內建的Debugger和Class Browser
Cint有一個內建的debugger,可以用於調試複雜的C++執行流程。基於的文本Class Browser是Debugger功能的一部分。
•可移植性
Cint可以在不少作業系統上工作:HP-UX, Linux, SunOS, Solaris, AIX, Alpha-OSF, IRIX, FreeBSD, NetBSD, NEC EWS4800, NewsOS, BeBox, Windows-NT ,Windows-9x, MS-DOS, MacOS, VMS, NextStep, Convex。
移植比較簡單,你可以參考platform/README文檔。
編譯Cint
Cint的首頁是[url]http://root.cern.ch/root/Cint.html[/url]
當前的最新穩定版本是cint-5.16.19, 有先行編譯好的版本和源碼版本,建議下載源碼版本。
用各種編譯器編譯Cint的方法都寫在Readme文檔裡,不過我用VC2005編譯時間發現並沒那麼簡單,下面說說我的編譯方法 吧:
準備工作
1.msys和mingw, Cint使用Unix系的編譯風格,configure和make需要GNU的Shell。在Windows下,可以使用msys(首頁: [url]www.mingw.org[/url], 個人認為,作為C++編程人員,msys應該是必備的)。
2.VC,這是費話,呵呵,我用的是VC2005 Express。當然,如果你用的是其它編譯器也行。
3.安裝好msys後,在msys/bin裡建一個文字檔:cygpath,裡面的內容為:
#!/bin/sh
# A simple cygpath replacement
until [ -z "$1" ]
do
if [ "${1:0:1}" != "-" ]; then
echo $1 | sed -e 's///////g' -e 's/^/([a-zA-Z]/):////1/g'
fi
shift
doneCInt的make檔案裡使用了cygpath命令,這個命令是把Windows風格的路徑改成Unix風格。由於msys兩種風格都能支援,所以就沒有 提供這個命令,只能自己寫一個湊數啦。
開始編譯
1.進入VC命令列環境
2.在VC命令列環境下執行msys.bat進入msys,這樣進入的msys才能正確調用VC編譯器。
3.輸入./configure --arch=msvc8,後面的msvc8根據你的編譯器決定,可選的有:linux linuxicc macgcc djgpp cygwin mingw mwerks hpux aix msvc7 msvc8 solaris solarisgcc
4.很快執行後會產生一個叫makefile.conf的文字檔,如果願意,可以把makefile.conf裡的編譯選項-MD 改成-MT。
5.輸入make編譯,最終產生libcint.dll、libcint.lib、cint.exe、makecint.exe四個 東東,以及指令碼專用頭文 件。
6.到這裡,我們Cint的編譯工作已經完成。
編譯指令碼先行編譯庫
現在我們已經可以用Cint編寫可解析C++語言的程式了,當然裡面用到的函數都得自己寫。另外,Cint已經提供了一部分庫代碼,只 要編譯好這些代碼並把相應的 DLL放到Include目錄裡就可以在指令碼中當作普通標頭檔來用了。
Cint所有庫代碼都在lib目錄裡,這裡以win32api為例,這個庫提供了一小部分WinAPI函數供指令碼使用
還是在msys中
1.首先把/include/windows.h和/include/winsock.h改名,比如改成_windows.h和 _winsock.h。這是為 了防止編譯時間引用錯標頭檔(現在要的是VC內建的windows.h)
2.進入lib/win32api
3.輸入makecint -mk Makewin -dl win32api.dll -h +P cintwin.h -P winfunc.h -cint -Z0,產生一個名為Makewin檔案
4.修改Makewin。(唉,都是Unix和Win不同造成的)
# CINT := $(shell whereis cint.exe)
# 改成
# CINT := 絕對路徑cint.exe,比如我的CINT := D:/Code/libs/cint-5.16.19/cint.exe
# LIBS := -LIBPATH:$(CINTSYSDIRW) $(subst @imp@,cint,lib@imp@.lib) kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib
# 改成
# LIBS := $(CINTSYSDIRW)$(subst @imp@,cint,lib@imp@.lib) kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib
# CINTSYSDIRW := $(shell cygpath -m $(CINTSYSDIRU) )
# 改成
# CINTSYSDIRW := $(CINTSYSDIRU)5.如果願意,可以把-MD選項改成-MT
6.搞定後輸入 make -f Makewin
7.如果沒出錯,會產生一個win32api.dll檔案,把它複製到/include裡
8.最後別忘了把之前改名的/include/windows.h和/include/winsock.h改回來
9.為了驗證是否正確編譯,可以用VC開啟/demo/Win32App/graph01目錄下的專案檔編譯試試。(注意要修改一 下項目的路徑設定)
先從一段代碼開始,我們有這麼一段C++代碼:
#include <iostream>
using namespace std;
int main(){
for(int i=0; i<10; i++)
cout << "Hello World " << i << endl;
return 0;
}
如果要用Cint來解釋執行它(而不是你的編譯器),我們要做的工作是:
1.建立控制台項目
2.把libcint.dll放到可執行檔能找到的地方(放PATH環境變數指向的路徑或者和你的可執行檔放在一起)
3.設定項目附加元件封裝含路徑為[CINT]/inc
4.把libcint.lib加入項目
5.編寫代碼
1.#include <G__ci.h>
2.
3.const char * szHello=
4."#include <iostream> "
5."using namespace std; "
6."int main() "
7."{ "
8." for(int i=0; i<10; i++) "
9." cout << /"Hello World /" << i << endl; "
10."} ";
11.
12.int main(int argc, char* argv[])
13.{
14. G__init_cint("cint");
15.
16. G__load_text(szHello);
17. G__exec_text("main()");
18. G__scratch_all();
19.
20. system("pause"); //暫停
21. return 0;
22.}
編譯執行,運行後你可能會看到Cint報告說找不到標頭檔iostream。它要的iostream這個標頭檔在[CINT]/include裡,你只要把[CINT]/include複製到你的可執行檔路徑裡就行了(我說的是整個include檔案夾)。
如果指令碼代碼位於磁碟檔案中,那就更簡單了
假設helloworld.cxx檔案為C++語言指令碼代碼,那麼:
1.#include <G__ci.h>
2.
3.int main(int argc, char* argv[])
4.{
5. G__init_cint("cint helloworld.cxx");
6. G__scratch_all();
7.
8. system("pause");
9. return 0;
10.}
代碼裡用到的東東和作用
G__ci.h Cint的主要標頭檔,這裡定義了所有的Cint方法。
int G__init_cint(const char* command); 初始化Cint環境
command參數:指定cint要執行的命令,比如"cint helloworld.cxx"表示載入helloworld.cxx,如果有main函數就執行之。它還可以指定命令選項,比如你可以試試"cint -T helloworld.cxx",
在控制台下執行cint --help瞭解更多的命令選項。
傳回值:可能的傳回值有:
• G__INIT_CINT_SUCCESS 初始化成功,沒有發現main函數
• G__INIT_CINT_SUCCESS_MAIN 初始化成功,有main函數並已成功執行
• G__INIT_CINT_FAILURE 初始化失敗
void G__scratch_all(void); 終止cint並清除所使用的資源
char* G__load_text(char *namedmacro);
int G__loadfile(const char *filename);
int G__unloadfile(const char *filename); 讀入一段C++程式碼片段(也可以讀入DLL檔案,這個以後再說)。G__load_text內部調用了G__loadfile,它首先產生一個臨時檔案,然 後用G__loadfile載入它,它的傳回值是臨時檔案標識,使用完後應該用G__unloadfile卸載(好吧,我上面的例子寫錯了,嘗試加上 G__unloadfile吧)。
G__value G__exec_text(G__CONST char *unnamedmacro);
char* G__exec_text_str(const char* unnamedmacro,char* result);
G__value G__calc(const char* expression); 執行一段C++代碼,返回執行結果。
1.G__calc只能執行簡單的C++運算式,不能執行含有聲明和條件的程式碼片段。
2.G__exec_text可以執行含有聲明和條件的程式碼片段,不過由於它使用臨時檔案,所以速度要慢得多。
3.G__exec_text_str是G__exec_text的另一種形式,用於返回字串。
為C++指令碼加入新的函數、資料類型等元素
編寫指令碼程式當然要提供一些東西給指令碼調用,和前面一樣,還是直接看例子比較好(主要是文采不好&&比較懶~~)。
在這個例子裡,我們給C++指令碼加入一個建立Windows視窗功能。
假設我們有一個CWin類,可以建立Windows表單,它有setBound和setTitle方法,用於設定位置大小和標題。另外還有一個CMsgLoop類負責訊息迴圈。
先放上它們的代碼(這個代碼不必深究,現在我們關心的不是怎樣顯示表單,而是怎樣把CWin類嵌入到指令碼裡去):
1.// addin.h檔案
2.#include <windows.h>
3.class CWin{
4.private:
5. static int gm_count; //CWin總數,最後一個CWin負責退出訊息迴圈
6. HWND m_hWnd;
7. static void RegisterWindow(); //註冊CWin表單
8. void Clear();
9. static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
10.public:
11. CWin(const CWin* parent); //建立表單,parent為NULL時為頂層表單,否則為子表單
12. ~CWin();
13. void setBound(int x, int y, int width, int height);
14. void setTitle(const char* title);
15.};
16.
17.class CMsgLoop{
18.private:
19. MSG m_msg;
20.public:
21. int run();
22.};
1.// addin.cpp檔案
2.#include "addin.h"
3.int CWin::gm_count = 0;
4.CWin::CWin(const CWin* parent)
5.{
6. DWORD dwStyle = WS_VISIBLE;
7. HWND hWndParent = NULL;
8.
9. if(parent == NULL)
10. {
11. dwStyle |= WS_OVERLAPPEDWINDOW;
12. }
13. else
14. {
15. dwStyle |= WS_CHILD;
16. hWndParent = parent->m_hWnd;
17. }
18.
19. RegisterWindow();
20. m_hWnd = CreateWindow(
21. "CInt.CWin",
22. NULL,
23. dwStyle,
24. 0,0,100,100,
25. hWndParent,
26. 0,0,0);
27.
28. if(m_hWnd){
29. gm_count++;
30. ::SetProp(m_hWnd, "CWin", this);
31. }
32.}
33.
34.CWin::~CWin()
35.{
36. Clear();
37.}
38.
39.void CWin::Clear()
40.{
41. if(m_hWnd){
42. ::RemoveProp(m_hWnd, "CWin");
43. ::DestroyWindow(m_hWnd);
44. if(--gm_count<=0) ::PostQuitMessage(0);
45. }
46.}
47.
48.void CWin::RegisterWindow()
49.{
50. static bool fRegistered = false;
51. if(!fRegistered)
52. {
53. WNDCLASS wc={
54. 0,WndProc,
55. 0,0,
56. ::GetModuleHandle(NULL),
57. NULL,LoadCursor(NULL, IDC_ARROW),
58. (HBRUSH)(COLOR_BTNFACE+1),
59. 0,"CInt.CWin"
60. };
61. if(::RegisterClass(&wc) != 0)
62. fRegistered = true;
63. }
64.}
65.LRESULT CALLBACK CWin::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
66.{
67. if(uMsg == WM_DESTROY)
68. {
69. CWin *pWin = (CWin*)::GetProp(hwnd, "CWin");
70. if(pWin)pWin->Clear();
71. }
72. else if(uMsg == WM_PAINT)
73. {
74. RECT rc;
75. ::GetClientRect(hwnd, &rc);
76.
77. PAINTSTRUCT ps;
78. HDC hdc = ::BeginPaint(hwnd, &ps);
79. char text[100];
80. ::GetWindowText(hwnd, text, 100);
81. ::DrawText(hdc, text, -1, &rc, DT_CENTER|DT_SINGLELINE|DT_VCENTER);
82. ::DrawEdge(hdc, &rc, BDR_RAISEDINNER,BF_RECT);
83. ::EndPaint(hwnd, &ps);
84. }
85. return ::DefWindowProc(hwnd,uMsg,wParam,lParam);
86.}
87.
88.void CWin::setBound(int x, int y, int width, int height)
89.{
90. if(m_hWnd)
91. {
92. ::MoveWindow(m_hWnd,x,y,width,height,TRUE);
93. }
94.}
95.
96.void CWin::setTitle(const char *title)
97.{
98. if(m_hWnd)
99. {
100. ::SetWindowText(m_hWnd, title);
101. }
102.}
103.
104.int CMsgLoop::run()
105.{
106. while(::GetMessage(&m_msg,NULL,0,0))
107. {
108. TranslateMessage(&m_msg);
109. DispatchMessage(&m_msg);
110. }
111. return (int)m_msg.wParam;
112.}
在程式裡,我們可以這樣使用它:
1.#include "addin.h"
2.
3.int main(int argc, char* argv[])
4.{
5. CWin frame(NULL); //建立表單
6. frame.setBound(100,100,300,200);
7. frame.setTitle("hello");
8.
9. CWin child(&frame); //建立子表單
10. child.setBound(5,5,60,25);
11. child.setTitle("child1");
12.
13. CWin child2(&frame); //建立子表單
14. child2.setBound(5,40,60,25);
15. child2.setTitle("child2");
16.
17. CWin frame2(NULL); //建立另一表單
18.
19. CMsgLoop ml; //訊息迴圈
20.
21. return ml.run();
22.}
不過現在的目的可不是讓它在我們的程式裡執行,而是作為指令碼給Cint來執行。現在開始吧:
建立一個addin_script.h檔案,輸入CWin和CMsgLoop的公用成員:
1.#ifndef __CINT__
2. #include "addin.h"
3.#else
4. class CWin{
5. public:
6. CWin(const CWin* parent);
7. ~CWin();
8. void setBound(int x, int y, int width, int height);
9. void setTitle(const char* title);
10. };
11. class CMsgLoop{
12. public:
13. int run();
14. };
15.#endif
完成後在命令列執行:
cint -c-1 addin_script.h這時會產生G__cpplink.C和G__cpplink.h兩個檔案,這兩個檔案是我們的程式和指令碼之間的橋樑。把這兩個檔案以及前面的addin.cpp加入到我們的項目中,注意要設定G__cpplink.C成"編譯為C++代碼"(或者乾脆副檔名改成.cpp),否則不能通過編譯。
我們主程式的代碼為:
1.#include "G__cpplink.h"
2.int main(int argc, char* argv[])
3.{
4. G__init_cint("cint");
5. G__cpp_setup(); //G__cpp_setup定義在G__cpplink.h裡
6. G__loadfile("script.cxx");//載入script.cxx,內容見後
7. G__calc("main()");
8. G__scratch_all();
9. return 0;
10.}
C++指令碼代碼script.cxx為(和我們的主程式放一起):
int main(int argc, char* argv[])
{
CWin frame(NULL); //建立表單
frame.setBound(100,100,300,200);
frame.setTitle("hello");
CWin child(&frame); //建立子表單
child.setBound(5,5,60,25);
child.setTitle("child1");
CWin child2(&frame); //建立子表單
child2.setBound(5,40,60,25);
child2.setTitle("child2");
CWin frame2(NULL); //建立另一表單
CMsgLoop ml; //訊息迴圈
return ml.run();
}
程式運行結果
你可以試著修改script.cxx看看效果。
總結一下上面的步驟:
1.首先,編寫好你要嵌入指令碼的類、函數或類型定義等東東
2.然後,把公開給指令碼的部分(如類的public部分、指定的類型定義等)重新聲明一下儲存為新的標頭檔。
3.再然後用“cint -c-1 標頭檔”或"cint -c-2 標頭檔"(分別對應C++和C代碼)產生指令碼嵌入函數。用cint讀取標頭檔時會帶有__CINT__宏定義,我們可以利用這個特性把指令碼用的聲明和程式用的聲明放到一個檔案裡(就象這個例子裡做的一樣)
4.最後,調用嵌入函數G__cpp_setup()或G__c_setup()把這我們寫好的東東嵌入到指令碼中供指令碼使用。
把CWin作為指令碼先行編譯庫
通過前面的介紹,我們已經知道怎樣把自訂的類嵌入到指令碼中供指令碼使用了。不過實際情況下大多不是像上面的樣本一樣直接整合在主程式中的,而是以“先行編譯庫”的形式作為單獨模組供C++指令碼使用,這樣更便於管理,而且靈活性更強。
我們現在就開始動手把前面的CWin類做成單獨的“先行編譯庫”,方法很簡單,只要把之前產生的G__cpplink.C和CWin實現addin.cpp編譯成dll檔案就行了:
cl -LD -TP -MT -O2 G__cpplink.c addin.cpp ^
-ID:/Code/libs/cint-5.16.19/inc ^
-link kernel32.lib user32.lib libcint.lib ^
-def:G__lib.def -OUT:cwin.dll我用的是MSVC的編譯器,簡單介紹一下這裡用到的cl參數:
•-LD 編譯成DLL
•-TP 強制按C++編譯
•-MT 多線程
•-O2 最佳化
•G__cpplink.c 是我們前面用cint弄出來的原始碼
•addin.cpp 是我們寫的CWin類實現
•-I 包含路徑,這裡指定包含cint庫標頭檔路徑
•-link 表示後面的參數轉給link
•kernel32.lib user32.lib 沒什麼好說的,WinAPI要用
•libcint.lib 我們編譯出來的cint庫檔案
•-def 指定def檔案,這裡的G__lib.def是用cint產生G__cpplink.c時一起產生的
•-out 指定輸出檔案名
如果沒有錯誤(應該沒錯,我試過的^_^),你應該已經得到了一個cwin.dll檔案。這個檔案就是Cint中所謂的“先行編譯庫”了。
使用先行編譯庫
Cint裡使用先行編譯庫很簡單,這裡有兩個方法:
方法一,使用G__loadfile函數
G__loadfile函數不僅可以載入C++原始碼,也可以載入dll檔案,即“先行編譯庫”。
1.#include "G__ci.h"
2.int main(int argc, char* argv[])
3.{
4. G__init_cint("cint");
5. G__loadfile("cwin.dll"); //載入我們的先行編譯庫
6. G__loadfile("script.cxx");
7. G__calc("main()");
8. G__scratch_all();
9. return 0;
10.}
方法二,在指令碼中載入
Cint有一個形式為#pragma include "DLL或h檔案"的先行編譯命令。我們可以用它在指令碼中動態載入先行編譯庫。
主程式碼:
1.#include "G__ci.h"
2.int main(int argc, char* argv[])
3.{
4. G__init_cint("cint");
5. G__loadfile("script.cxx");
6. G__calc("main()");
7. G__scratch_all();
8. return 0;
9.}
指令碼代碼(script.cxx):
#pragma include "cwin.dll" //注意這裡
int main(int argc, char* argv[])
{
CWin frame(NULL); //建立表單
frame.setBound(100,100,300,200);
frame.setTitle("hello");
CWin child(&frame); //建立子表單
child.setBound(5,5,60,25);
child.setTitle("child1");
CWin child2(&frame); //建立子表單
child2.setBound(5,40,60,25);
child2.setTitle("child2");
CWin child3(&frame); //建立子表單
child3.setBound(5,70,200,100);
child3.setTitle("child3");
CWin child4(&child3); //建立子表單
child4.setBound(5,40,60,25);
child4.setTitle("child2");
CWin frame2(NULL); //建立另一表單
CMsgLoop ml; //訊息迴圈
return ml.run();
}