好長時(shí)間以來,一直都想在網(wǎng)上找一個(gè)C風(fēng)格并且可以與C/C++配合良好的腳本語言。可是無奈,用過很多的腳本,lua,Python、 ruby等等,但總是感覺讓我不是很滿意(自己水平差,沒辦法),除了LUA一直在使用以外,其它腳本都是一看就放棄了。
越來越感覺用lua不順手,它的書寫風(fēng)格讓我感覺不舒服,可是,功夫不負(fù)有心人,7月底的時(shí)候,讓我找到了ChaiScript,簡單的看了介紹就趕緊的用了下試試……
由于本人菜鳥,有點(diǎn)發(fā)現(xiàn)總是不敢獨(dú)享,于是鑒于chaiScript沒有現(xiàn)成的使用文檔,所以就寫了這個(gè)破爛文章,僅為幫助像我一樣菜的朋友,故高手略過……
本人也只是簡單的測試和使用了一下,因此很多的東西摸得不是很透,所以,倘若我寫的內(nèi)容有錯(cuò)誤或者理解不多的地方,還請各位朋友幫忙指正,以免誤人子弟。
besterChen
2009年7月29日
首先,ChaiScript是一個(gè)開源項(xiàng)目,使用的是Google的SVN,如果大家有興趣參與或者獲取源代碼可以瀏覽網(wǎng)址:http://code.google.com/p/chaiscript/
直接引用下官網(wǎng)中對(duì)ChaiScript的評(píng)價(jià)和簡介:
ChaiScript是第一個(gè)(也許是唯一)直接針對(duì)C + +設(shè)計(jì)的嵌入式腳本語言。作為一個(gè)本地C + +應(yīng)用程序,,在現(xiàn)有的嵌入式腳本語言中,它也有自己的優(yōu)勢:
1. 它使用一個(gè)頭,唯一辦法,這使得它很容易與現(xiàn)有的項(xiàng)目。
2. 保持類型安全性之間的C + +應(yīng)用程序和用戶腳本。
3. 它支持多種C + +的技術(shù),包括回調(diào),重載函數(shù),類方法,和STL容器。
它是09年7月13日才公布了第一個(gè)發(fā)行版本,7月底公布的1.1的發(fā)布版本,比較幸運(yùn)的是我們可以很早的體驗(yàn)到這個(gè)腳本的最新特性,關(guān)于更多的介紹,請大家到下述網(wǎng)址自己的查找,這里就不多浪費(fèi)大家時(shí)間了。
它的官方網(wǎng)站是: http://www.chaiscript.com/
最新的下載地址在: http://code.google.com/p/chaiscript/downloads/list
下面讓我們開始吧。
ChaiScript的正常運(yùn)行需要有Boost的支持,所以,我們需要先在配置ChaiScript環(huán)境之前,安裝和配置好boost庫,下面我將分開介紹boost和ChaiScript的配置。
1、Boost庫的安裝和使用
大家可以官網(wǎng)(http://www.boost.org/)來獲取最新的boost庫代碼及Jam編譯器。
將bjam.exe復(fù)制到boost根目錄下:
將命令行也定位boost根目錄下。
編譯時(shí)通過設(shè)置參數(shù)可以指定編譯器和編譯版本以及存放路徑這里沒喲編譯python.
bjam --toolset=msvc-8.0 --prefix=x:\boost stage
這個(gè)命令將會(huì)生成release版的lib和dll文件
bjam --toolset=msvc-8.0 --prefix=x:\boost debug stage
這個(gè)命令將會(huì)生成帶gd字符的debug版的lib和dll文件
bjam --tooset=msvc-8.0 --prefix=x:\boost debug runtime-link=static stage
這個(gè)命令將會(huì)生成帶sgd字符的靜態(tài)debug版的lib和dll文件
如果不是用--prefix參數(shù)將默認(rèn)生成在c:\boost目錄下
編譯完成后可以建立一個(gè)bat文件自動(dòng)將生成的lib和dll文件拷貝到lib目錄下面
cplib.bat
dir /W/S/B *vc80*.lib,*vc80*.dll >liblist
if exist lib (echo exist lib fold) else (mkdir lib)
for /f %%x in (liblist) do @copy "%%x" lib\
拷貝之后可以做一下清理工作,同個(gè)建立一個(gè)bat文件來自動(dòng)清理編譯過程產(chǎn)生的.obj文件
delobj.bat
del /s/q *.obj
將這兩個(gè)文件放在x:\boost目錄下執(zhí)行就可以了。
配置環(huán)境變量
Tools -> Options -> Projects and Solutions -> VC++ Directories 在Library files加上x:\boost\lib
在Include files加上x:\boos
測試程序:
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string_regex.hpp>
using namespace std;
using namespace boost;
int main()
{
string s = " Hello boost!! ";
trim(s);
cout << s << endl;
getchar();
std::string regstr = "a+";
boost::regex expression(regstr);
std::string testString = "aaa";
// 匹配至少一個(gè)a
if( boost::regex_match(testString, expression) )
{
std::cout<< "Match" << std::endl;
}
else
{
std::cout<< "Not Match" << std::endl;
}
}
說明:本庫的配置方法引用與http://paul216.blog.hexun.com/29507251_d.html
2、ChaiScript運(yùn)行環(huán)境的配置。
ChaiScript的使用方法比較簡單,只要在IDE中配置一下包含目錄就可以了。
首先,我們從我們下載回來的文件中把Include拷貝到指定的一個(gè)庫文件夾中,比如我的目錄是:D:\OtherLib\ChaiScript,如下圖:
然后在VC環(huán)境中點(diǎn)擊:
Tools -> Options -> Projects and Solutions -> VC++ Directories 在Include files加上D:\OtherLib\ChaiScript\Include
這樣我們就可以開始寫可以嵌ChaiScript的程序了。
關(guān)于ChaiScript的基礎(chǔ)語法講解并不像如何使用ChaiScript那樣模糊,在官網(wǎng)上已經(jīng)講的很詳盡清楚了,只不過是英文版本,我就簡單的翻譯一下,我盡量建議大家看英文版本,因?yàn)槲业挠⑽乃轿易约憾疾环判?#8230;…
1、 條件模塊(if)
ChaiScript支持If語句,但是需要說明:"else if"是一個(gè)單獨(dú)的關(guān)鍵字而不是兩個(gè)分開的關(guān)鍵字。大概的用法格式如下:
var i = 2
if (i < 2)
{
print("less than 2")
}
else if (i == 2)
{
print("equal to 2")
}
else
{
print("more than 2")
}
2、 循環(huán)語句
ChaiScript跟C + +的循環(huán)風(fēng)格差不多有兩種:for循環(huán)和while循環(huán)。大概的用法如下:
1) For 循環(huán):
for (var i = 0; i < 10; ++i)
{
print("i: " + i.to_string())
}
2) While 循環(huán):
var i = 0
while (i < 10)
{
print("i: " + i.to_string())
++i
}
說明:跟C++一樣,在ChaiScript中也可以使用break關(guān)鍵字來中斷循環(huán)。
3、 容器(Vectors、Maps)
ChaiScript支持少數(shù)容器類型,也允許用簡捷的寫法創(chuàng)建Vectors和Maps。比如:
1) Vectors:
var x = [1, 2, 3]
print(x[1])
2) Maps:
var x = ["bob":1, "fred":2]
print(x["fred"])
同樣,我們也可以通過一個(gè)值的范圍來快速的創(chuàng)建Vector容器,例如:
var x = [1..10]
print(x)
4、 函數(shù)
在ChaiScript中定義函數(shù)有兩種書寫方法。
1) 使用“def”關(guān)鍵字定義靜態(tài)函數(shù),在全局范圍內(nèi)都可見。比如:
def add_elems(x, y)
{
x + y
}
print(add_elems(5, 10))
2) 創(chuàng)建一個(gè)匿名函數(shù),通常賦值給一個(gè)變量來使用。
var add_elems = fun(x, y) { x + y }
print(add_elems(5, 10))
5、 返回值
函數(shù)的返回值也可以有兩種寫法。
一種是跟C++一樣,明確的使用關(guān)鍵字”return”返回,用法跟C++一樣,例如:
def five()
{
return 5
}
print(five())
另一種是隱式的返回函數(shù)結(jié)果,就是當(dāng)一個(gè)函數(shù)沒有明確的使用return語句返回時(shí),最后的這個(gè)數(shù)值就作為函數(shù)的返回值,比如:
def five()
{
5
}
print(five())
這樣定義函數(shù)的返回值與上一種寫法是一樣的。
6、 方法(Methods)
其實(shí)ChaiScript并不支持什么方法,只是利用了函數(shù)的一個(gè)被叫作“syntactic sugar”的技術(shù)代替的方法。例如:
5.to_string()
to_string(5)
這就是:值在“.”的左邊作為第一個(gè)參數(shù)命名的函數(shù)而已,沒有別的區(qū)別。
7、 派遣函數(shù)(Dispatch Functions)
ChaiScript允許函數(shù)重載,使用派遣函數(shù)可以自動(dòng)的使用相應(yīng)的函數(shù)已達(dá)到相應(yīng)的功能。比如說,你想從一堆數(shù)中打印出正確的復(fù)數(shù),可以這樣寫代碼:
def print_num(x) : x == 0 || x > 1
{
print(x.to_string() + " units")
}
def print_num(x) : x == 1
{
print(x.to_string() + " unit")
}
print_num(2)
print_num(1)
print_num(0)
在VC工程中使用ChaiScript
1、一個(gè)chaiscript的測試程序
在我們下載回來的源代碼中有一個(gè)TestExample工程的例子,我不知道它是在VS的多少版本下創(chuàng)建的,改了一下它的工程文件,在2005下編譯過了,但是它目錄設(shè)置的很雜,所以,我們在這里自己新建一個(gè)控制臺(tái)工程,把它的源代碼copy過來,我們研究下,源代碼如下:
// TestExample.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#include "stdafx.h"
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
void log(const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] " << msg << std::endl;
}
void log(const std::string &module, const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] <" << module << "> " << msg << std::endl;
}
struct System
{
std::map<std::string, boost::function<std::string (const std::string &) > > m_callbacks;
void add_callback(const std::string &t_name,
const chaiscript::Proxy_Function &t_func)
{
m_callbacks[t_name] = chaiscript::functor<std::string (const std::string &)>(t_func);
}
void do_callbacks(const std::string &inp)
{
log("Running Callbacks: " + inp);
for (std::map<std::string, boost::function<std::string (const std::string &)> >::iterator itr = m_callbacks.begin();
itr != m_callbacks.end();
++itr)
{
log("Callback: " + itr->first, itr->second(inp));
}
}
};
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
//創(chuàng)建一個(gè)新的system 對(duì)象
System system;
//把它添加到chaiscript引擎中
chai.add(var(&system), "system");
//注冊System結(jié)構(gòu)體的兩個(gè)方法.
chai.add(fun(&System::add_callback), "add_callback");
chai.add(fun(&System::do_callbacks), "do_callbacks");
// 讓我們用chaiscript給我們的system結(jié)構(gòu)添加一個(gè)新的回調(diào)函數(shù)。
// 在Chaiscript中創(chuàng)建函數(shù)"{ 'Callback1' + x }" 并通過我們C++程序System結(jié)構(gòu)中的add_callback函數(shù)
// 將這個(gè)chaiscript腳本函數(shù)轉(zhuǎn)換到boost::function,
// so it can be handled and called easily and type-safely
chai.eval("system.add_callback(\"#1\", fun(x) { \"Callback1 \" + x });");
// 由于我們在chaiscript引擎中共享了“system”對(duì)象
// 所以我們可以同時(shí)在C++代碼中和Chaiscript腳本中使用這個(gè)對(duì)像
system.do_callbacks("TestString");
chai.eval("system.do_callbacks(\"TestString\");");
// 日志函數(shù)被(log)重載過了, 因此我們必須得同時(shí)C++編譯器,我們要注冊那個(gè)版本的日志函數(shù)。
// 這樣,唯一的一個(gè)方法就是用typedef創(chuàng)建一個(gè)函數(shù)指針,然后注冊這個(gè)函數(shù)指針就可以了
typedef void (*PlainLog)(const std::string &);
typedef void (*ModuleLog)(const std::string &, const std::string &);
chai.add(fun(PlainLog(&log)), "log");
chai.add(fun(ModuleLog(&log)), "log");
chai.eval("log(\"Test Message\")");
// 由于eval函數(shù)重載了操作符(),因此我們可以直接使用chai關(guān)鍵字而省去eval方法。
chai("log(\"Test Module\", \"Test Message\");");
//最后, 我們可以注冊任何一個(gè)boost::function 作為sys對(duì)象的函數(shù), 按照這樣來說,
//我們可以給system對(duì)象添加一個(gè)受限的成員函數(shù),例如:
chai.add(fun(boost::function<void ()>(boost::bind(&System::do_callbacks, boost::ref(system), "Bound Test"))), "do_callbacks");
//調(diào)用限制版本的do_callbacks函數(shù)
chai("do_callbacks()");
boost::function<void ()> caller = chai.functor<void ()>("fun() { system.do_callbacks(\"From Functor\"); }");
caller();
//如果我們想從所有的調(diào)用中得到一個(gè)類型安全的返回值,那我們可以使用eval的模板,如下:
int i = chai.eval<int>("5+5");
std::cout << "5+5: " << i << std::endl;
//添加一個(gè)新的變量
chai("var scripti = 15");
//我們甚至可以操作system的變量
int &scripti = chai.eval<int &>("scripti");
std::cout << "scripti: " << scripti << std::endl;
scripti *= 2;
std::cout << "scripti (updated): " << scripti << std::endl;
chai("print(\"Scripti from chai: \" + to_string(scripti))");
//要做到: 在需要時(shí)添加一個(gè)直接處理Boxed_Values 的實(shí)例。
//在堆棧上創(chuàng)建和使用函數(shù)
int x = chai.functor<int (int, int)>("fun (x, y) { return x + y; }")(5, 6);
log("Functor test output", boost::lexical_cast<std::string>(x));
//在需要的時(shí)候,可以創(chuàng)建自己的容器. 目前可以支持大多數(shù)的std::vector 和std::map
chai.add(bootstrap::vector_type<std::vector<int> >("IntVector"));
return 0;
}
我這破英文底子,哎~~我硬著頭皮大概翻譯了一下,方便比我還差的朋友閱讀,翻譯的肯定有不對(duì)的,大家可以參考源程序的英文注釋,這里還望大家多多指正。
根據(jù)這些注釋,我想看懂這個(gè)程序應(yīng)該不難的。這樣,我們也就知道ChaiScript的基礎(chǔ)用法了。
下面我將更詳細(xì)的講述著程序中的知識(shí)點(diǎn)。
2、將ChaiScript添加到現(xiàn)有的VC工程中。
在ChaiScript文件夾的Readme.txt文件告訴我們:
² 把ChaiScript的include目錄添加到工程的包含目錄中。
² 在我們的工程原文件中包含 "#include <chaiscript/chaiscript.hpp> 。
² 在我們的工程中創(chuàng)建ChaiScript引擎的實(shí)例.
例如, 創(chuàng)建一個(gè)新的名為“chai”的引擎可以這樣編寫代碼:
using namespace chaiscript;
ChaiScript chai;
或者寫成:
chaiscript::ChaiScript chai;
3、ChaiScript腳本與C++相互調(diào)用
1) 在C++中調(diào)用/執(zhí)行ChaiScript代碼
現(xiàn)在,我們有兩種方法來調(diào)用/執(zhí)行ChaiScript腳本代碼:
² 第一種方法:直接在C++代碼中編寫并執(zhí)行ChaiScript代碼,例如:
//添加一個(gè)新的變量
chai("var scripti = 15");
甚至我們都可以直接獲取腳本的表達(dá)式的值,例如:
int i = chai.eval<int>("5+5");
² 第二種方式是直接調(diào)用ChaiScript腳本文件來執(zhí)行。例如:
我們可以編寫腳本如下:
// HelloWorld.chai
print("hello chaiscript world...")
編寫控制臺(tái)程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
chai.eval_file(".\\HelloWorld.chai\0"); // 調(diào)用我們剛寫的腳本
return 0;
}
程序執(zhí)行結(jié)果如下:

2) 在C++中使用腳本的變量(對(duì)象)
//添加一個(gè)新的變量
chai("var scripti = 15");
//我們甚至可以操作system的變量
int &scripti = chai.eval<int &>("scripti");
std::cout << "scripti: " << scripti << std::endl;
scripti *= 2;
std::cout << "scripti (updated): " << scripti << std::endl;
chai("print(\"Scripti from chai: \" + to_string(scripti))");
上面這段代碼,相信大家應(yīng)該都有印象吧,這段代碼很簡明的描述了如何在C++中操作ChaiScript中的變量的方法:使用引用。
3) 在C++中調(diào)用ChaiScript中定義的函數(shù)。
總是感覺ChaiScript::functor這個(gè)模板的功能很神奇哈,在我們一開始的測試程序中,看到了關(guān)于它的很多神奇的用法,由于我的個(gè)人能力有限,值說明一下它的基本用法,至于其它的,大家可以自己去摸索。
先看下functor的聲明吧:
template<typename FunctionType>
boost::function<FunctionType> functor(const std::string &script)
{
return chaiscript::functor<FunctionType>(eval(script));
}
很明白了吧,我們這個(gè)字符串就相當(dāng)于直接在ChaiScript寫腳本了,沒有什么別的估計(jì)的,直接調(diào)用我們編寫的函數(shù)就好,OK,不多廢話,在腳本中創(chuàng)建函數(shù)如下:
// TestFor.chai
def TestFor()
{
for (var i = 0; i < 10; ++i)
{
print("i: " + i.to_string())
}
}
編寫我們的C程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
chai.eval_file(".\\TestFor.chai\0");
boost::function<void ()> TestFor = chai.functor<void ()>("TestFor"); // 關(guān)聯(lián)我們的腳本函數(shù)
TestFor(); // 調(diào)用腳本函數(shù)
return 0;
}
程序執(zhí)行結(jié)果如下:

4) 在腳本中調(diào)用C++函數(shù)/ 對(duì)象
到現(xiàn)在,我們應(yīng)該對(duì)ChaiScript有一定得了解了,應(yīng)該知道,如果要在腳本中使用C++對(duì)象或者使用C++的函數(shù)應(yīng)該先把函數(shù)注冊到ChaiScript引擎中。
在ChaiScript1.1中注冊函數(shù)或者對(duì)象的方法已經(jīng)與1.0版本發(fā)生的一點(diǎn)變化,1.1版本注冊函數(shù)/對(duì)象的語法格式如下:
注冊對(duì)象的語法格式:
/創(chuàng)建一個(gè)新的system 對(duì)象
System system;
//把它添加到chaiscript引擎中
chai.add(var(&system), "system");
注冊函數(shù)的語法格式:
chai.add(fun(&MyClass::function), "method");
上述格式注冊的是我們的類成員函數(shù),如果要注冊一個(gè)獨(dú)立的函數(shù),那可以先聲明一個(gè)這個(gè)函數(shù)的指針然后在注冊,如下例子:
我們可以編寫腳本如下:
// HelloWorld.chai
log("hello chaiscript world...")
編寫控制臺(tái)程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
void log(const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] " << msg << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
typedef void (*PlainLog)(const std::string &);
ChaiScript chai;
chai.add(fun(PlainLog(&log)), "log"); // 注冊C++函數(shù),以便在腳本中調(diào)用它
chai.eval_file(".\\HelloWorld.chai\0"); // 調(diào)用我們剛寫的腳本
return 0;
}
程序執(zhí)行結(jié)果如下:

好,到這里,在VC中使用ChaiScript腳本的內(nèi)容就講完了,其實(shí)重點(diǎn)就是幾個(gè)ChaiScript中的幾個(gè)API的使用,如:ChaiScript::add、ChaiScript::eval、ChaiScript::eval_file、 ChaiScript::functor等等。
這些ChaiScript API更多更詳細(xì)的介紹,請大家關(guān)注:http://www.chaiscript.com/node/28