内存管理
Squirrel使用引用计数作为内存管理中主要系统。然而Squirrel虚拟机使用一个辅助标识和垃圾收集器,该收集器可以被想强行调用。
两种可能的编译期操作:
第二种情况唯一的优点就是可以节省两个额外的指针,为每一个对象在默认的配置表中存放该指针以供垃圾收集器使用(32位系统存放8位的指针)。相关的类型有表,数组,函数,线程,userdata以及生成器。其他类型则被忽略。这些选项不会影响执行速度。
Unicode
默认情况下,Squirrel字符串这是纯粹的8为ASCII字符。但是如果在虚拟机中定义了宏“SQUNICODE”,编译器和API将使用16位字符。
64位机上使用Squirrel脚本
在C++预编译器上通过定义'_SQ64'宏,可以在64位机上编译Squirrel脚本。这个宏可以在包含了“squirrel.h”文件的任何工程里定义。
Squirrel中使用双精度浮点型
在C++预编译器上通过定义“SQUSEDOUBLE”使得Squirrel支持双精度。这个宏可以在任何一个包含了“squirrel.h”的工程中定义。如果在32位机上定义该宏,虚拟机会增加大量的内存。
错误约定
大多Squirrel函数返回SQRESULT,SQRESULT表示该函数是否成功执行。宏 SQ_SUCCEEDED() 和SQ_FAILED()负责测试函数的返回值。
if(SQ_FAILED(sq_getstring(v,-1,&s)))
printf("getstring failed");
初始化Squirrel
宿主程序首先必须做的事是创建一个虚拟机。宿主程序可以通过函数sq_open()创建任意多个虚拟机。
如果虚拟机不在需要,每一个单独的虚拟机都需要调用sq_close()释放。
int main(int argc, char* argv[])
{
HSQUIRRELVM v;
v = sq_open(1024); //创建虚拟机,其栈的初始大小为1024
//do some stuff with squirrel here
sq_close(v);
}
栈
Squirrel通过栈与虚拟机交换数据。这种机制从Lua语言继承而来。比如想从C中调用一个Squirrel需要函数,需要把该函数和参数压到栈上然后调用。当然当Squirrel调用C函数也必须在栈上进行。
栈索引
很多API函数都可以通过栈索引直接饮用栈上的任意元素。栈索引必须服从一下约定:
下面是个例子(我们假设下面的表就是虚拟机)
STACK | positive index | negative index | |
"test" | 4 | -1(top) | |
1 | 3 | -2 | |
0.5 | 2 | -3 | |
"foo" | 1(base) | -4 |
这种情况下,函数sq_gettop返回4
栈操作
API提供了很多函数去压栈和从栈上获取数据。
1 void sq_push(HSQUIRRELVM v,SQInteger idx)
压入一个值,该值已经出现在栈顶的位置。
2 void sq_pop(HSQUIRRELVM v,SQInteger nelemstopop);
弹出栈顶一定数量的元素。
3 void sq_remove(HSQUIRRELVM v,SQInteger idx);
从栈上删除一个元素
4 SQInteger sq_gettop(HSQUIRRELVM v);
如果你想获取当前栈上最顶索引(或者栈大小),你必须调用sq_gettop函数
5 void sq_settop(HSQUIRRELVM v,SQInteger newtop);
强行使得栈大小为制定大小,需要调用sq_settop设置,如果newtop比先前栈的大小大,就用null填充新多出的元素。
6 下面函数是向栈上压入一个C类型值
void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len);
void sq_pushfloat(HSQUIRRELVM v,SQFloat f);
void sq_pushinteger(HSQUIRRELVM v,SQInteger n);
void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p);
void sq_pushbool(HSQUIRRELVM v,SQBool b);
7 void sq_pushnull(HSQUIRRELVM v);
这个函数是向栈上压入一个null
8 SQObjectType sq_gettype(HSQUIRRELVM v,SQInteger idx);
返回栈上指定位置的值的类型,其类型可能下列中的一种:
OT_NULL,OT_INTEGER,OT_FLOAT,OT_STRING,OT_TABLE,OT_ARRAY,OT_USERDATA,OT_CLOSURE,OT_NATIVECLOSURE,OT_GENERATOR,OT_USERPOINTER,OT_BOOL,OT_INSTANCE,OT_CLASS,OT_WEAKREF
下面函数将一个Squirrel值转换为成C类型的值
SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c);
SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i);
SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f);
SQRESULT sq_getuserpointer(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p);
SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPointer *typetag);
SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *p);
9 SQInteger sq_cmp(HSQUIRRELVM v);
这个函数比较栈上两个值并返回其关系(像ANSI C中的strcmp函数)
运行期错误处理
当一个异常没能被Squirrel语句块try/catch处理,将会生成一个错误,当前程序执行中止。可以在宿主城中设置一个错误处理回调函数来捕获此类运行时错误。这样有利于向脚本编写者展示有意义的错误提示或实现一个可视化的调试器。下列函数的调用将会从栈上弹出一个Squirrel函数,然后设置自己的错误处理借口。
SQUIRREL_API void sq_seterrorhandler(HSQUIRRELVM v);
这个错误处理函数调用时会被传递两个参数,一个环境对象(this)和另外一个对象,这个对象可以是任意Squirrel类型。
编译脚本
你可以调用函数sq_compile编译一个脚本。
typedef SQInteger (*SQLEXREADFUNC)(SQUserPointer userdata);
SQRESULT sq_compile(HSQUIRRELVM v,SQREADFUNC read,SQUserPointer p,
const SQChar *sourcename,SQBool raiseerror);
为了编译脚本,宿主程序可能需要实现一个读函数(SQLEXREADFUNC);这个函数编译器提供脚本数据,每次编译器需要一个字符时该函数就会被调用。在成功的情况下,该函数每次必须返回一个字符代码,如果源脚本结束则返回0。
如果编译成功,该编译的脚本将以函数的像是被压在栈上。
注意:为了执行脚本,由sq_compile生成的函数必须通过sq_call()被调用执行。
SQInteger file_lexfeedASCII(SQUserPointer file)
{
int ret;
char c;
if( ( ret=fread(&c,sizeof(c),1,(FILE *)file )>0) )
return c;
return 0;
}
int compile_file(HSQUIRRELVM v,const char *filename)
{
FILE *f=fopen(filename,"rb");
if(f)
{
sq_compile(v,file_lexfeedASCII,file,filename,1);
fclose(f);
return 1;
}
return 0;
}
当编译失败或者有语法错误,虚拟机会尝试调用编译错误处理函数,该函数必须按照以下形式声明:
typedef void (*SQCOMPILERERROR)(HSQUIRRELVM /*v*/,const SQChar * /*desc*/,const SQChar *
/*source*/,SQInteger /*line*/,SQInteger /*column*/);
可以通过以下API设置该函数:
void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f);
调用函数
调用一个Squirrel函数可能需要向栈中压入被调用函数,然后压入参数。在调用函数sq_call。如果sq_call的最后一个参数大于0,sq_call调用结束后会弹出参数并且将返回值压如栈顶。
sq_pushroottable(v);
sq_pushstring(v,"foo",-1);
sq_get(v,-2); //从跟表中获取函数foo
sq_pushroottable(v); //'this' (函数环境对象)
sq_pushinteger(v,1);
sq_pushfloat(v,2.0);
sq_pushstring(v,"three",-1);
sq_call(v,4,SQFalse);
sq_pop(v,2); //弹出root表和函数。
这等价于下面的Squirrel脚本:
foo(1,2.0,"three");
如果在脚本执行期间出现错误或有异常抛出,sq_call函数调用失败。