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)的差异
支持类成员函数调用
简单方便,一行声明语句即可

posted @ 2014-05-29 14:03  Kiveen  阅读(688)  评论(0编辑  收藏  举报