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();
}