续上篇文章http://www.cppblog.com/zzxhang/archive/2009/03/13/76490.html,继续说明LuckyScript作为一门脚本是如何与主程序交互的,到目前为止,我已基本实现了大部分我最初对这门脚本的设想,我想,很快我就可以将它发布出去了,也许本来是可以更快一点的,这段时间烦人的事太多,而且,工作也开始忙起来了,我所谓的业余时间已经越来越少,我想,是时候结束这个吉祥物的开发了。
1、调用主程序函数
所有提供给脚本调用的函数都必须满足luckyScript主程序函数的原型定义,这个原型是typedef void (*Lucky_Host_Func)(RuntimeState*),比如如果我们想提供一个求和的函数给脚本定义,那么首先必须在主程序中这样定义这个求和函数:
void add(RuntimeState* state)
RuntimeState保存了脚本运行时的所有状态信息,脚本在调用这个主函数时会把所有参数值推栈保存,需要注意的是参数是从左到右先后入栈的,所以取出参数的顺序是从右到左
1void add(RuntimeState* state)
2{
3 //取出参数,右边的参数先出栈
4 int val2 = lucky_popValueAsInt(state);
5 int val1 = lucky_popValueAsInt(state);
6//取出参数,右边的参数先出栈
7
8 int sum = val1 + val2;
9
10 //把结果传进脚本
11 lucky_setReturnValue(state,sum);
12}
最后调用lucky_setReturnValue把结果传进脚本,在完成这么个函数的定义后,我们必须把它注册给脚本
lucky_registerHostFunc(state,add,"add")
这样在脚本中就可以使用这个函数了,另外如果这个主程序函数返回的是脚本中所没有的类型(比如对象,当然必须先注册给脚本),那么还必须指定第四个参数returnType说明返回的类型。
2、调用DLL函数
在脚本中,我们可以导入DLL,并使用其中函数,luckyScript提供了两个命令__importdll,__importdllfunction用于在脚本中导入DLL函数,例如,我们可以建一个DLL工程,在里面添加下面代码:
extern "C" __declspec(dllexport)
int add(int a,int b)
{
return a + b;
}
假设导出的DLL名为test.dl,那么在脚本中,我们可以这样导入这个函数
1__importdll "test.dll"
2//给出函数的原型定义
3__importdllfunction int __cdecl add(int a,int b)
4
5func Main()
6{
7 var sum = add(2,4);
8}
函数的调用约定可以指定为__cdecl或者__stdcall,至于值的类型则包含这些:int ,float,double,int64, char,wchar,ptr(指针), str(字符串) ,void,如果是已经在脚本注册过的对象类型,那么指定为ptr.
3.用户数据
类似lua,luckyScript允许用户往脚本添加自己的数据,在适当的时候,我们可以再取出这些数据,这个所谓"适当的时候"通常也就是脚本调用主程序函数的时候,我们得到事先添加进脚本的数据,然后再用它做一些我们自己的事情,对于用户数据的操作,luckyScript提供了以下几个API:
1LUCKY_API void* lucky_addUserData(size_t size);
2
3 LUCKY_API int lucky_getLastUserDataIndex();
4
5 LUCKY_API void* lucky_getUserData(int index);
6
7 LUCKY_API void lucky_clearAddedUserData();
这几个函数,恩,老实说,用法有点别扭,需要具体点说明,先看下面的代码:
1struct TestData
2{
3 int val;
4};
5
6void doSomething(RuntimeState* state)
7{
8 TestData* d = (TestData*)lucky_popValueAsUserData(state);
9 const char* str = lucky_popValueAsString(state);
10 print("%s: %d",str,d->val)
11}
12
13void TestFunc()
14{
15 TestData t;
16 t.val = 4;
17
18 lucky_initScript();
19
20 void* data = lucky_addUserData(sizeof(TestData));
21 memcpy(data,&t,sizeof(TestData));
22
23 //得到索引
24 int index = lucky_getLastUserDataIndex();
25
26 lucky_registerGlobalHostFunc(state,doSomething,"doSomething");
27
28 //清空
29 lucky_clearAddedUserData();
30
31 //取出userData
32 TestData* t2 = (TestData*)lucky_getUserData(index);
33
34 print("value(in TestFunc): %d",t2->val);
35
36 lucky_doString("doSomething(\"value(in doSomething): \")");
37
38 lucky_exitScript();
39}
40
41
在某个地方调用这个TestFunc,一切顺利的话,应该会输出"value(in TestFunc): 4 value(in doSomething): 4",但我并不确定,以上及以下的代码都是我随手打的,只用我的眼睛编译过....如果你认真看完了上面的代码,那么我想对这几个函数的用法你应该都已经了解了,唯一需要解释的是23-30行之间的代码:为了更紧密地与主程序结合,当lucky_registerGlobalHostFunc或lucky_registerHostFunc被调用的时候,在内部,脚本引擎会把从上一次调用或还没调用lucky_clearAddedUserData到现在为止所添加进去的用户数据跟lucky_registerGlobalFunc所注册的主程序函数绑定,当我们在脚本中调用这个主程序函数时,这些用户数据就会被当作参数一样压栈,这样,在主程序函数中,我们就可以调用lucky_popValueAsUserData取出这些数据,取出数据的顺序跟添加的顺序相反,也就是说,最后添加的用户数据会被放在栈顶。利用这个特性,我们可以对luckyScript进行高层的封装,使之可以更方便地注册C++的类跟函数,在下一篇文章中,我会向你展示这个特性是如何被利用的。
4.主程序对象
前面已经多次提到关于主程序对象的注册,luckyScript允许用户往脚本添加自己的对象类型,但不得不说,这个过程是有点小麻烦的,luckyScrip采用一套预定义的规定来进行主程序对象数据与脚本间的通信,在脚本中,所有主程序对象的操作,包括构造,析构,成员调用等都是由一些预定义命名规范的主程序函数来完成的,当一个主程序对象在脚本中构造时,与此对象类型同名的主程序函数将会被调用,当调用主程序对象的方法时,脚本将会采用className + memberFuncName的命名方式来call主程序函数,具体的命名规范如下所示:
构造函数:与类型名同名
析构函数:下划线 + 类型名
调用成员函数:类型名 + 下划线 + 成员函数名
操作符重载:类型名 + 下划线 + Overide + 下划线 + 操作符英文符号(如 '+' 为 Add)
成员变量存:类型名 + 下划线 + set + 下划线 + 成员变量名
成员变量取:类型名 + 下划线 + get + 下划线 + 成员变量名
接下来用一个完整的例子代码进一步说明主程序对象的注册方法
1class TestObj
2{
3public:
4 TestObj()
5 {
6
7 }
8
9 ~TestObj()
10 {
11
12 }
13
14 void operator = (const TestObj& otherObj)
15 {
16 val = otherObj.val;
17 }
18
19 void doSomething()
20 {
21
22 }
23
24 int val;
25};
26
27void TestObjConstructor(RuntimeState* state)
28{
29 void* data = lucky_popValueAsUserData(state);
30
31 new(data) TestObj();
32}
33
34void TestObjDesConstructor(RuntimeState* state)
35{
36 TestObj* obj = (TestObj*)lucky_popValueAsUserData(state);
37
38 obj->~TestObj();
39}
40
41void TestObjSetVal(RuntimeState* state)
42{
43 TestObj* obj = (TestObj*)lucky_popValueAsUserData(state);
44
45 int setVal = lucky_popValueAsInt(state);
46
47 obj->val = setVal;
48}
49
50void TestObjGetVal(RuntimeState* state)
51{
52 TestObj* obj = (TestObj*)lucky_popValueAsUserData(state);
53
54 lucky_setReturnValue(state,obj->val);
55}
56
57void TestObjDoSomethingFunc(RuntimeState* state)
58{
59 TestObj* obj = (TestObj*)lucky_popValueAsUserData(state);
60
61 obj->doSomething();
62}
63
64void TestObjOverideAssign(RuntimeState* state)
65{
66 TestObj* otherObj = (TestObj*)lucky_popValueAsUserData(state);
67 TestObj* obj = (TestObj*)lucky_popValueAsUserData(state);
68
69 (*obj) = (*otherObj);
70}
71
72int main()
73{
74 lucky_initScript();
75
76 lucky_registerHostClass("TestObj",sizeof(TestObj));
//构造函数处理回调函数命名规范:类型名
lucky_registerGlobalHostFunc(TestObjConstructor,"Test");
//析构函数处理回调函数命名规范:下划线 + 类型名
lucky_registerGlobalHostFunc(TestObjDesConstructor,"_Test");
77 lucky_addHostMemberFunc("TestObj","doSomething");
78 //成员函数处理回调函数命名规范:类型名 + 下划线 + 成员函数名
79 lucky_registerGlobalHostFunc(TestObjDoSomethingFunc,"TestObj_doSomething");
80
81 lucky_addHostMemberVal("TestObj","val");
82 //成员变量处理回调函数命名规范:类型名 + 下划线 + set/get + 下划线 + 成员函数名
83 lucky_registerGlobalHostFunc(TestObjSetVal,"TestObj_set_val");
84 lucky_registerGlobalHostFunc(TestObjGetVal,"TestObj_get_val");
85
86 //操作符重载处理回调函数命名规范:类型名 + 下划线 + Overide + 下划线 + 操作符英文符号
87 lucky_registerGlobalHostFunc(TestObjOverideAssign,"TestObj_Overide_Assign");
88
89 lucky_doString("var t = new TestObj();\
90 var t2 = new TestObj();\
91 t.doSomething();\
92 t.val = 3;\
93 t2.val = t.val + 1;\
94 t = t2;");
95
96 lucky_exitScript();
97}
同样,我不能保证上面代码的正确性,我甚至没有检查过,但用它来说明问题已经足够
5.调用脚本函数
脚本中能引用主程序的方法对象,主程序当然也可以用脚本的一些东西,luckyScript直接提供了API用于调用脚本函数:
LUCKY_API void lucky_callFunc(RuntimeState* state,const char* funcName,int paramNum);
LUCKY_API void lucky_callFunc(RuntimeState* state,const char* funcName,char** paramsTypeName,int paramNum);
需要说明下的是第二个API,假如你想调用的API包含主程序对象类型的话,那么还必须把所有参数的类型名传进来,顺序是从左到右,还有,两个API都必须提供参数个数.....你要问为什么会这么麻烦,我会告诉你,一切都源于那个该死的所谓泛化特性,调用同一函数,提供不同的参数列表会编译为不同的函数,当然函数名也会是不一样的,so,我得根据参数类型的情况具体处理。
6.脚本对象
在这一块我只提供了主程序对脚本全局变量的访问
1LUCKY_API int lucky_getGlobalIdentValAsInt(RuntimeState* state,const char* identName);
2
3LUCKY_API float lucky_getGlobalIdentValAsFloat(RuntimeState* state,const char* identName);
4
5LUCKY_API const char* lucky_getGlobalIdentValAsString(RuntimeState* state,const char* identName);
6
7LUCKY_API void* lucky_getGlobalIdentValAsUserData(RuntimeState* state,const char* identName);
8
9LUCKY_API void lucky_setGlobalIdentVal(RuntimeState* state,const char* identName,int val);
10
11LUCKY_API void lucky_setGlobalIdentVal(RuntimeState* state,const char* identName,float val);
12
13LUCKY_API void lucky_setGlobalIdentVal(RuntimeState* state,const char* identName,const char* val);
14
15LUCKY_API void lucky_setGlobalIdentVal(RuntimeState* state,const char* identName,void* val,const char* freeFuncName = "Null",size_t size = 0);
在最后一个API中,假设你提供的是主程序对象而又不打算在主程序中手动释放它的话,那么还必须提供此对象的释放主函数
可以看到,使用上面的介绍的方法来进行主程序跟luckyScript脚本的交互的话还是有诸多不方便的,因为这个原因,我已经为luckyScript实现了一个c++封装库,使用这个封装库可以方便的实现C++跟脚本间数据的通信,隐去一切琐碎的细节,在下篇文章中,我会详细介绍这个封装库。
posted on 2009-04-16 15:57
清風 阅读(1304)
评论(1) 编辑 收藏 引用 所属分类:
LuckyScript