UCScript——C++集成脚本
使用UCScript作脚本,主要是因为它几个特点很适合用于集成(脚本引擎体积小,占用资源少,运行快,跨平台跨语言,与宿主语言交互方便),而且它的语法类C语言,不用去多学一门语言。在C++中集成UCScript脚本是轻而易举的事。我们接下来用实例来说明,如何把UCScript脚本集成到你的程序中去。
1. 创建UCScript引擎
ucScript ucs; 或者 ucScript* ucs= new ucScript;
创建一个ucScript对象,就是创建一个UCScript脚本引擎。可以创建任意多个脚本引擎,甚至可以分布在不同的线程当中。
注意:指定合适的预定义,Windows用_UC_WIN,Linux用_UC_LINUX(即编译参数-D_UC_LINUX)。如果以动态库的形式使用脚本,还得指定_UC_IMPORT。
2. 执行脚本程序 你可以从缓冲区中装载脚本,也可以直接解析文件:
const char * szCode="ret 1+2*3;";
ucScript ucs;
ucCalcVar vRet= ucs.Parse(szCode);
assert(vRet.value.iVal == 7);
如果你想对你的源代码进行保护而不是赤裸裸的袒露在大家的眼前,或者提高脚本性能,你可以把脚本编译成二进制中间码。
3. 如何让脚本调用C++的函数。比如让脚本以string: get_peername(int: id)的形式调用我的C++代码,代码可以写成:
UCSFUNC3(get_peername,CVT_STRING, "get peer name by peer id",
CVT_INTEGER,"puid",
CVT_STRING,"def=/"unknown/"",
1)
{
int puid;
// get an integer value from the first parameter
ucAdaptCVT::cvt2out(params[0], puid);
CPeer * p=CPeerMgr::Instance().GetPeer(puid);
if(p) {
return p->m_PeerInfo.name; // return the string of name
}
const char * def="unknown";
if(count>1) {
ucAdaptCVT::cvt2out(params[1], def); // get default name specified by parameter
}
return def;
}
上面代码用了UCSFUNC(xxx) 辅助宏,定义在ucScriptFunc.h头文件。其中xxx表示脚本变量类型个数(即返回值和参数的个数)。上例的宏参数分别表示:
get_peername | 函数名字 |
CVT_STRING | 函数返回值的类型为string |
函数的提示信息 | 在脚本集成开发环境ucDev可以看到 |
CVT_INTEGER | 第一个参数,类型为integer |
参数的提示信息 | 用在ucDev |
CVT_STRING | 第二个参数,类型string |
参数提示信息 | 用在ucDev |
1 | 有 1 个参数有默认值,即最后一个参数 |
脚本调用如下:
string: strName;
strName = get_peername(12); // Kevin's ID is 12, so strName = "Kevin"
strName = get_peername(-1); // none, strName = "unknown"
strName = get_peername(-1, "no user"); // none, strName = "no user"
最让我激动的是,该脚本把非标准类型的变量全部当作object类型,也就是说任何C++类型都可以接受。下面例子演示如何传递和返回非基本类型的变量。
UCSFUNC2(get_parentwnd, CVT_OBJECT, "get a parent window",
CVT_OBJECT,"wnd",
0)
{
ucCalcVar vRet(CVT_OBJECT);
CWnd * pWnd=NULL;
// get CWnd from parameter
ucAdaptCVT::cvt2out(params[0], pWnd);
if(pWnd && ::IsWindow(pWnd->GetSafeHwnd()) )
{
ucAdaptCVT::out2cvt(pWnd->GetParent(), vRet);
}
return vRet;
}
4. 结束语
UCScript是一种面向集成的脚本语言,上面例子是针对C++语言,还支持的其他语言有C#(.Net),Java,Delphi,Visual Basic等等。脚本开发工具可以从http://www.ucscript.com/view_download.asp?id=3下载。初学者可以从http://www.ucscript.com/download.asp下载脚本用户手册。
(上文转自http://blog.csdn.net/amken/article/details/1491877)
不同语言之间相互调用函数,这是很普遍的现象,尤其是多种语言并存开发的大项目。一般解决方案是,其中一种通过动态链接库或者COM组件暴露接口,而另一种语言去调用。
脚本语言调用其它语言,则是通过支持库或者脚本自身支持。就拿VBScript来说,可以通过实现IDispatch接口,脚本创建自动化对象来调用。各种脚本语言都有各自的方式来实现。UCScript是面向集成的脚本语言,这方面跟其它语言来比,更显简练和高效的优势。下面分别从普通函数、类成员函数这两方面介绍UCScript如何调用C++语言的函数。
1. 普通函数
在C++一提到函数调用,不得不提到的是调用约定(calling convention)。最常用的有__stdcall,__cdecl,__fastcall等几种方式。
__stdcall是Windows API函数使用的规范,由被调用函数负责参数从堆栈移走,生成的代码比__cdecl小。this指针压入堆栈。以“?”标识函数名的开始,后跟函数名; 函数名后面以“@@YG”标识参数表的开始,后跟参数表; 参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前。
__cdecl调用约定是C/C++默认的调用规范,由调用函数负责参数从堆栈移走,有利于参数个数可变。this指针压入堆栈。对于传送参数的栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 __cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。
__fastcall规则同上面的__stdcall调用约定,它的主要特点就是快,因为它是通过寄存器来传送参数的,即用ECX和EDX传送前两个参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。UCScript不支持这种调用的函数。
__stdcall和__cdecl的最大区别就是用来传递参数的堆栈空间的清除。UCScript底层屏蔽这个差异,也就是说不管是哪一种方式,对UCScript都是一样的。用户不必关心这些细节,可以降低用户使用复杂度。
int __cdecl strtoint(std::string str) {
printf("strtoint: str=%s\n", str.c_str());
return atol(str.c_str());
}
要让脚本调用这个C语言的函数,只需如下的一行语句即可
BIND_UCSFUNC2(strtoint, strtoint, CVT_INTEGER, "convert string to int", (std::string*)CVT_OBJECT, "str");
下面分析上面各个参数的含义。
BIND_UCSFUNC2 该函数有2个脚本类别(Script Type),返回值和一个参数
strtoint 脚本可以调用到的函数名字,不必跟C++语言函数相同名字
strtoint C++语言的函数名字
CVT_INTEGER 函数返回值是Integer
"convert string to int" 函数说明,在ucDev编程可以看到该提示
(std::string*)CVT_OBJECT 参数是Object,并且转换为std::string类型
"str" 参数说明,在ucDev编程可以看到该提示
2. 类成员函数
类成员函数可以分成两类。一类是this指针通过ECX寄存器来传递;另一类是this指针通过参数传递。UCScript的辅助宏通过参数来区别这种区别。如下例:
class CMyClass {
public:
CMyClass() { m_id = 100; }
int GetId() {
printf("CMyClass::GetId, id=%d\n", m_id);
return m_id;
}
void SetId(int id) {
printf("CMyClass::SetId, id=%d\n", id); m_id = id;
}
int m_id;
};
要让脚本调用到上面的GetId函数,只需下面语句
BIND_UCSFUNC_CM1(get_id, CMyClass, CMyClass::GetId, 1, CVT_INTEGER,"get id from a object");
上面的第4个参数指定this指针的传递方式:1表示标准方式,也就是this通过ECX寄存器传递;0是非标准方式,this通过参数传递。
当然,要调用这个函数还得传入一个对象,这个对象可以是其它函数的返回值或者用BIND_UCS_GLOBALVAR绑定的全局对象。比如脚本的代码可能如下:
object: obj;
// get obj by any way
... ...
int: id=get_id(obj);
3. 结束语
UCScript作为一种面向集成的脚本语言,在调用C++语言有自己独特的方式。总结一下有如下特点:
可以传递任意C++类型,如上例的std::string类
封装调用约定(calling convention)的差异
支持类成员函数调用
简单方便,一行声明语句即可