流逝的时光
总有一天我们都会离去 email: zzxhang@gmail.com
posts - 21,comments - 111,trackbacks - 0

  续上篇文章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 清風 阅读(1249) 评论(1)  编辑 收藏 引用 所属分类: LuckyScript

FeedBack:
# re: LuckyScript与主程序的交互
2009-04-18 10:30 | 陈梓瀚(vczh)
我现在调用dll的方法是吧整个脚本都弄成机器码,然后在里面调……  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理