lua与C++ / Lua 与C交互


以上时函数,但是全局C函数满天飞肯定是不行的 更好一点的情况是把C++类注册进Lua

Quick-Cocos2d-x 3.3绑定自定义C++类02:生成并使用tolua++工具



Quick-Cocos2d-x 创建自定义Lua绑定C++类




Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

----------------------------------------------------

   到公司已经两个周了,学习Lua已经开始在项目中使用,但是由于使用的lua函数基本上都是公司在上面进行了一次封装的,没有源代码对两种语言的交互详情还是不甚了解。如:如果向LUA注册一个对象给LUA使用,如何调用LUA中函数这些在公司的SDK看来就是一个简单的RegisterObject对象的几个属性进行填写就行了。

    今天主要是对在Lua中如何调用C++函数和在C++中如何调用Lua函数进行学习。

在LUA中要调用C++函数,那就要在C++中对Lua进行注册,如何注册呢?需要一个宏来完成功能,就是lua_register(L,"add",add),该宏对应的是两个函数,一个是lua_pushcfunction(L,f),lua_setglobal(L,n);可以看出是先对函数进行压栈、然后给函数设置一个在lua中的调用名称,注意此处的n不是表示一个整数什么的,是name的意思,表示函数的注册名。而此处lua_setglobal也是一个宏,

 #define lua_setglobal(L,s)   lua_setfield(L, LUA_GLOBALSINDEX, s)
可以看出其实lua_setglobal只是lua_setfield的一个特例,因为LUA_GLOBALSINDEX是一直存在的,所以使用干函数来简单操作,当然如果我们要注册的函数不注册到全局的,而是在其他某个我们定义的表内可访问,那就要调用lua_setfield函数了。说到这个话题,设计到的函数就会越来越多,比如lua_getglobal与lua_setglobal对于,lua_getfield与lua_getfield对应,我们用到的时候再解释,不用到时就不说了。
  上面提到了使用lua_register函数进行注册,这里要强调的是该函数对注册的函数原型是有要求的,不是任何函数都可以注册,函数原型如下:
int (func*)(lua_State* L);我们应该能够理解,LUA和C++的交互通过栈来进行传参,因此只需要给一个参数lua_State表当前操作栈就行了,至于该函数有几个参数,返回值这些在func函数中去处理就好了,该函数返回值表示C++函数会向栈中放多少个值,而至于需要几个参数可以通过lua_tostring、lua_tointeger等函数通过传入栈中的序号来获取。由于注册函数原型固定了,但我们在写C++函数的时候不可能代码长短什么的都往该函数中塞,在实际中我们常常是只使用该函数来获取从LUA读取参数和返回参数,所以该函数一般是这样的形式:
int funcname(lua_State* L)
{
    //此处可以做数据类型检查这些
    ..
    //取值
    arg1 = lua_tostring(L,-1);
    arg2 = lua_tostring(L,-2);
    //调用真正的函数
    dosomething_function
   //进行参数压栈等也可以再上面调用的函数中压栈
   return n;//此处的n是C++向栈中压入的参数个数,如果和压入栈个数不一致,可能导致栈失衡
}
注册函数来说就上面几个步骤而已,比起注册C++类来说简单得多,注册C++类现在我也还没有掌握,在此处就不说了,下面说的是我们注册了C++函数现在该到LUA中去调用了,现在在test.lua文件中添加这样一个函数
function lua_add(a,b)
   return add(a,b)
end
现在如果在命令行执行该文件应该会失败,因为我上面注册的C++函数不是注册成一个动态库,我是直接在控制台可执行文件中写的,因此我就需要在
C++中来调用该lua_add函数了。C++中需要先知道有lua_add这样一个函数,需要先使用luaL_dofile(test.lua)来加载该文件,加载进去后该函数就存在于全局表中了,于是使用lua_getglobal函数来从表中获取函数地址lua_getglobal(L,"lua_add"),该函数是在全局表中查找lua_add函数并把它压到栈顶,到这里刚开始学习的时候会很疑惑,当初我看lua中很多函数介绍的时候就觉得很不解,很多函数就是对栈操作,单独看一个函数实在看不出想达到什么目的,但和其他函数配合使用功能就强大了,所以需要对lua中这些对栈操作的函数都有一个认识,才能够组合出自己需要的功能来。前面说把函数找到压栈了,下面就是要调用函数,调用函数前需要传参数,如何传?还是把参数放到栈中、如果我们需要给函数传两个参数,把连个参数压栈,使用函数lua_pushinteger(L,4)表示向栈中压入一个整数4,其他类型如lua_pushstring,lua_pushlstring等很多。参数也进栈了,下面才是开始真正调用,如何调?lua提供的函数lua_pcall来调用,该函数第一个参数为lua_State表示当前的栈吧,第二个参数表示要调用函数的参数个数,第三个表示被调用函数的返回值个数,这个不要想C++中函数只有一个返回值,这是表示我们要向lua中返回多少个值;第四个参数表示错误处理函数在栈上的索引,为0表示没有错误处理函数,和lua_pcall一样用于调用栈中函数的函数有lua_call和lua_cpcall等,这里不仔细介绍。在上面的函数调用中,由于返回值存在与栈中,在取回返回值后,需要调用lua_pop(L)对栈进行清空。
下面是一个测试代码:
 
 
  1. #include <stdio.h>  
  2.   
  3. extern "C"  
  4. {  
  5.     #include <lua.h>  
  6.     #include <lualib.h>  
  7.     #include <lauxlib.h>  
  8. }  
  9.   
  10. #include <string>  
  11. using namespace std;  
  12.   
  13. int add(lua_State* L)  
  14. {  
  15.     int a = lua_tointeger(L,1);//取得函数参数  
  16.     int b = lua_tointeger(L,2);  
  17.   
  18.     lua_pushinteger(L,a+b);//入栈返回值  
  19.     return 1;//1表示压入栈数据个数  
  20. }  
  21. struct luaL_Reg luaCppReg[] =//可以使用该结构体一次注册多个函数,则需要调用luaL_register函数,此处没有使用  
  22. {  
  23.     {"add",add},  
  24.     {NULL,NULL}  
  25. };  
  26. int _tmain(int argc, _TCHAR* argv[])  
  27. {  
  28.     char buff[256];  
  29.     int error;'  
  30.     lua_State *L = luaL_newstate();  /* opens Lua,由于我使用的是lua5.2版本,lua_open函数不存在了 */  
  31.     luaopen_base(L);         /* opens the basic library 这些是在引入一些库,就如如果add函数在编译成dll后如果在lua中要使用需要require “动态库名"一样*/  
  32.     luaopen_table(L);        /* opens the table library这些库是加在这里只是测试 */  
  33.     luaopen_io(L);           /* opens the I/O library */  
  34.     luaopen_string(L);       /* opens the string lib. */  
  35.     luaopen_math(L);         /* opens the math lib. */  
  36.   
  37.       
  38.     lua_register(L,"add",add);//注册add函数,好像还可以使用luaL_register函数注册,该函数使用结构体的方式  
  39.   
  40.     luaL_dofile(L,"test.lua");//加载lua文件,回将里面的函数加载到全局表中  
  41.     lua_getglobal(L,"lua_add");//查找lua_add函数,并压入栈底  
  42.     lua_pushinteger(L,6);//函数参数1  
  43.     lua_pushinteger(L,5);//函数参数2  
  44.     lua_pcall(L,2,1,0);//调用lua_add函数,同时会对lua_add及两个参加进行出栈操作,并压入返回值  
  45.     int result = lua_tointeger(L,-1);//从栈中取回返回值  
  46.     lua_pop(L,1);//清栈,由于当前只有一个返回值  
  47.     printf("result = %d",result);  
  48.   
  49.     lua_close(L);//关闭lua环境  
  50.     return 0;  
  51. }  

--------------------------------------------

lua

  栈  API   

一.lua堆栈

要理解lua和c++交互,首先要理解lua堆栈。

 

简单来说,Lua和C/c++语言通信的主要方法是一个无处不在的虚拟栈。栈的特点是先进后出。

在lua中,lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。如图:


二.堆栈的操作

因为lua与c/c++是通过栈来通信,lua提供了C API对栈进行操作。

#include <iostream>
#include <string.h>
using namespace std;
 
extern C
{
     #include lua.h
     #include lauxlib.h
     #include lualib.h
}
void main()
{
     //1.创建一个state
     lua_State *L = luaL_newstate();
     
     //2.入栈操作
     lua_pushstring(L, I am so cool~);
     lua_pushnumber(L, 20 );
 
     //3.取值操作
     if ( lua_isstring(L, 1 )){             //判断是否可以转为string
         cout<<lua_tostring(l, 1 )<<endl; 


  1.  调用lua_pushnumber()将参数的平均值压栈。    
  2.  lua_register(L, "avg", average);  //第二参数为Lua中调用的函数名     
  3.  lua_gettop函数返回栈顶的索引值。因为在Lua中栈是从1开始编号的,因此该函数获得的值就是参数的个

 可以简单理解为luaL_newstate返回一个指向堆栈的指针,其它看注释应该能懂了吧。
 
  //2.加载lua文件
     int  bRet = luaL_loadfile(L,hello.lua);
     if (bRet)
<br>
需要注意的是:函数参数里的lua_State是私有的,每一个函数都有自己的栈。当一个c/c++函数把返回值压入Lua栈以后,该栈会自动被清空。

三 总结
lua和c++是通过一个虚拟栈来交互的。 

c++调用lua实际上是:由c++先把数据放入栈中,由lua去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回c++。</p>
lua调c++也一样:先编写自己的c模块,然后注册函数到lua解释器中,然后由lua去调用这个模块的函数。</p>
 
例子: C++端:

  1. #include "stdafx.h"  
  2. #include<stdio.h>  
  3.   
  4. extern "C" {  //如不用extern会出现连接错误,编译成了C++文件  
  5. #include <lua.h>  
  6. #include <lualib.h>  
  7. #include <lauxlib.h>  
  8. }  
  9.   
  10. int average(lua_State *L){  
  11.     int n= lua_gettop(L);  
  12.     int i;  
  13.     double sum=0;  
  14.     for(i=0;i<n;i++){  
  15.         sum+=lua_tonumber(L,-1);  
  16.         lua_pop(L,1);  
  17.     }  
  18.     lua_pushnumber(L,sum/n);  
  19.     if(!lua_isnumber(L,-1))  
  20.         luaL_error(L,"push error!\n");  
  21.     return 1;  
  22. int _tmain(int argc, _TCHAR* argv[])  
  23. {  
  24.     lua_State* L = lua_open();  
  25.     luaL_openlibs(L);  //新版本库添加的方法  
  26.   
  27.     lua_register(L,"avg",average);  
  28.     luaL_dofile(L,"cof.lua");  
  29.   
  30.     getchar();  
  31.     return 0;  
  32. }  
  33. ———————————————————Lua和C语言的交互详解——————————————————————
  34. 交互常用的函数:

    压入栈:

    对于每种可以呈现在Lua中的C类型,API都有一个对应的压入函数,我这里把它们都列出来:

    void lua_pushnil(lua_State *L);
    void lua_pushboolean(lua_State *L, int bool);
    void lua_pushnumber(lua_State *L, lua_Number n);
    void lua_pushinteger(lua_State *L, lua_Integer n);
    void lua_pushlstring(lua_State *L, const char *s, size_t len);
    void lua_pushstring(lua_State *L, const char *s);
    上面的函数非常简单,从命名就能知道它们的含义。这里不多说

    查询元素:
    为了检查一个元素是否为特定的类型,API提供了一系列的函数lua_is*,其中*可以是任意Lua类型。这些函数有lua_isnumber、lua_isstring和lua_istable等,所有这些函数都有同样的原型:
    实际上,lua_isnumber不会检查值是否为数字类型,而是检查值是否能转换为数字类型。lua_isstring也具有同样的行为,这样就出现一种状况,对于能转换成string的值

    就出现了一个lua_type函数,它会返回栈中元素的类型,每种类型都对应一个常亮,这些常亮定义在头文件lua.h中,它们是:
    #define LUA_TNONE        (-1)
    #define LUA_TNIL        0
    #define LUA_TBOOLEAN        1
    #define LUA_TLIGHTUSERDATA    2
    #define LUA_TNUMBER        3
    #define LUA_TSTRING        4
    #define LUA_TTABLE        5
    #define LUA_TFUNCTION        6
    #define LUA_TUSERDATA        7
    #define LUA_TTHREAD        8
    取值

    我们一般使用lua_to*函数用于从栈中获取一个值,有以下常用的取值函数:

    lua_Number      lua_tonumber (lua_State *L, int idx);
    lua_Integer     lua_tointeger (lua_State *L, int idx);
    int             lua_toboolean (lua_State *L, int idx);
    const char     *lua_tolstring (lua_State *L, int idx, size_t *len);
    size_t          lua_objlen (lua_State *L, int idx);
    lua_CFunction   lua_tocfunction (lua_State *L, int idx);
    void           *lua_touserdata (lua_State *L, int idx);
    lua_State      *lua_tothread (lua_State *L, int idx);
    const void     *lua_topointer (lua_State *L, int idx);

    如果指定的元素不具有正确的类型,调用这些函数也不会有问题。在这种情况下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen会返回0,而其它函数会返回NULL

    其它栈操作

    除了在C语言和栈之间交换数据的函数外,API还提供了以下这些用于普通栈操作的函数:

     
    int   lua_gettop (lua_State *L);
    void  lua_settop (lua_State *L, int idx);
    void  lua_pushvalue (lua_State *L, int idx);
    void  lua_remove (lua_State *L, int idx);
    void  lua_insert (lua_State *L, int idx);
    void  lua_replace (lua_State *L, int idx);

    lua_gettop函数返回栈中元素的个数,也可以说是栈顶元素的索引。lua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数 量,如果之前的栈顶比新设置的更高,那么高出来的这些元素会被丢弃;反之,会向栈中压入nil来补足大小;比如,调用以下语句就能清空栈:

    lua_settop(L, 0);

    ————————————————————

    Lua和C++语言的交互详解

    ——————————————————————

    table操作

    在Lua中,对于table这种bug一样存在的东西,如果C API无法操作table,那我们还能不能愉快的玩耍了。让我们来看看C API如何操作table。现在有如下Lua语句:

    复制代码 代码如下:

    background = {r = 0.3, g = 1, b = 0.5}

    那么,C API如何读取这段代码,将其中的每个字段都解析出来呢。我先把代码贴上来,然后一句一句的分析:

    复制代码 代码如下:

    // 读取全局的数据到栈中
    lua_getglobal(L, "background");
    if (!lua_istable(L, -1))
    {
        // 如果不是table,就显示错误信息
        cout << "It's not a table." << endl;
        return 0;
    }
     
    // 读取table中字段的值,将值压入栈中
    lua_getfield(L, -1, "r");
     
    // 读取栈中的值
    if (!lua_isnumber(L, -1))
    {
        // 如果不是实数,就显示错误信息
        cout << "It's not a number." << endl;
        return 0;
    }
     
    double fValue = lua_tonumber(L, -1);
    cout << "r => " << fValue << endl;

    原谅我省略了luaL_newstate这样的代码。好了,读取一个table,同读取一个全局的变量是一个道理的。分为以下几步:

    1.使用lua_getglobal读取这个变量,将table读取到栈中;
    2.使用lua_getfield读取table中字段的值,将字段的值读取到栈中;
    3.使用lua_to*系列函数,将字段的值从栈中读取出来。

    这是读取table的操作,那设置table的操作呢?我们可以将我们自己的值写入到栈中,这该怎么操作?

    所以,C++调用Lua中的函数,分为以下几步:

    使用lua_getglobal来获取函数,然后将其压入栈;

    如果这个函数有参数的话,就需要依次将函数的参数也压入栈;

    这些准备工作都准备就绪以后,就调用lua_pcall开始调用函数了,调用完成以后,会将返回值压入栈中;

    最后取返回值得过程不用多说了,调用完毕。

    ------------------------------------------------------------------------------------------------

                Lua和C之间的交互

    (一)            Lua 调C函数

     

    1.        什么样类型的函数可以被Lua调用

     
    typedef int (*lua_CFunction) (lua_State *L);

     

    2.        符合类型的函数怎样处理后才可以被Lua调用

    使用lua_register或者 lua_pushfunction和lua_setglobal()把要调用的函数加入到lua状态机中。

     

    #define lua_register(L,n,f) /
                (lua_pushcfunction(L, f), lua_setglobal(L, n))

     

    lua_register的第二个参数就是Lua脚本中对这个函数的调用名称。

    举例:

    如果C函数名称是foo,使用lua_registe注册(L,”acfoo”,foo),那么在Lua脚本中使用acfoo来表示使用foo函数.

     

    3.        Lua 如何调用c函数

    简单,使用注册的名称直接调用

     

    4.        如何传递参数和计算结果

    ① 使用堆栈交互

    引用使用手册上的一段话:

    Lua 使用一个虚拟栈来和 C 传递值。栈上的的每个元素都是一个 Lua 值(nil,数字,字符串,等等)。

     

    无论何时 Lua 调用 C,被调用的函数都得到一个新的栈,这个栈独立于 C 函数本身的堆栈,也独立于以前的栈。(在 C 函数里,用 Lua API 不能访问到 Lua 状态机中本次调用之外的堆栈中的数据),它里面包含了 Lua 传递给 C 函数的所有参数,而 C 函数则把要返回的结果也放入堆栈以返回给调用者。

     

    方便起见,所有针对栈的 API 查询操作都不严格遵循栈的操作规则。而是可以用一个索引来指向栈上的任何元素:正的索引指的是栈上的绝对位置(从一开始);负的索引则指从栈顶开始的偏移量。更详细的说明一下,如果堆栈有 n 个元素,那么索引 1 表示第一个元素(也就是最先被压入堆栈的元素)而索引 n 则指最后一个元素;索引 -1 也是指最后一个元素(即栈顶的元素),索引 -n 是指第一个元素。如果索引在 1 到栈顶之间(也就是,1 ≤ abs(index) ≤ top)我们就说这是个有效的索引

     

       ② 从Lua脚本中获取参数  

    int n = lua_gettop(L);
    /* get each argument */
    lua_tostring(lua_State *L, int index)

         …….

         index: 1—左边第一个参数,2—左边第二个参数,......

     

         ③ 返回返回值

         按顺序返回,Lua按照返回顺序接受

         Lua_pushXXX(L,第一个返回值)

         Lua_pushXXX(L,第二个返回值)

         ………

     

    Lua调用C函数例子:

    C程序:

    static int average(lua_State *L)

    {

        /* get number of arguments */

        int n = lua_gettop(L);

        double sum = 0;

        int i;

     

        /* loop through each argument */

        for (i = 1; i <= n; i++)

        {

            if (!lua_isnumber(L, i))

            {

                lua_pushstring(L, "Incorrect argument to 'average'");

                lua_error(L);

            }

     

            /* total the arguments */

            sum += lua_tonumber(L, i);

        }

     

        /* push the average */

        lua_pushnumber(L, sum / n); //第一个返回值

     

        /* push the sum */

        lua_pushnumber(L, sum); //第二个返回值

     

        /* return the number of results */

        return 2;

    }

     

    void LuaCallC()

    {

        /* initialize Lua */

        lua_State * L = lua_open();

     

        /* load Lua base libraries */

        luaL_openlibs(L);

     

        /* register our function */

        lua_register(L, "average", average);

     

        /* run the script */

        luaL_dofile(L, "average.lua");

     

        /* cleanup Lua */

        lua_close(L);

         

    }

     

    Lua脚本,average.lua:

    avg, sum = average(20,40,50,60,80) ----------------为啥如此多的参数????? yang

    print("The average is ", avg)

    print("The sum is ", sum)

     

    (二)            Lua 从C库中调用

    1.        生成C函数库

    ① 所有可以被Lua调用的函数必须是lua_CFunction类型

     

    ②  所有被调用的函数加入到一个luaL_reg数组中

     

    ③ 一个luaopen_*(*表示库的名称)供lu调用库时打开库

        使用luaL_register(lua_State *L,

                        const char *libname,

                        const luaL_Reg *l)

        libname,注册lua使用这个库时的使用名称

        luaL_Reg *l,把luaL_Reg数组里的函数注册到lua栈里,供lua调用

     

       注意:BCB默认导出的c函数前面加了下划线,因此在动态库工程中加入一个def文件,在生成时不用加下划线。内容是:

       Export

           FunName = _FunName (FunName表示要导出的函数名称,Lua使用的库中就是luaopen_*)

     

    2.        Lua使用c库

    require(libname) – 打开使用的库

    libname.FunName – 使用c库中提供的函数

     

    Lua调用C函数库例子:

    C库代码,C函数的名称”dllforlua.dll”

    static int lua_msgbox(lua_State* L)

    {

        const char* message = luaL_checkstring(L, 1);

        const char* caption = luaL_optstring(L, 2, "");

        int result = MessageBox(NULL, message, caption, MB_YESNO);

        lua_pushnumber(L, result);

        return 1;

    }

    static const  luaL_Reg mylib[] =

    {

        {"msgbox", lua_msgbox},

        {NULL, NULL}

    };

    int __declspec(dllexport)  luaopen_dllforlua(lua_State* L)

    {

        luaL_register(L, "dllforlua", mylib);

        return 1;

    }

     

    Lua脚本,Test.lua

    require(“dllforlu”)

    dllforlua.msgbox("Hey, it worked!", "Lua Message Box");

     

    (三)            C调Lua 函数

    1.        初始化Lua环境

    Lua_open或者:lua_newstate        

                 luaL_newstate(调用lua_newstate,并且设置了一个恐慌函数)

     

    2.        加载Lua标准库

    Lua_openlibs(打开所有标准库)

    不打开所有库,打开需要的库:

     

     

           Luaopen_base

           luaopen_package

           luaopen_string

           luaopen_table

           luaopen_math

           ……….

           

    3.        加载Lua和函数函数

    luaL_dofile()

    lua_getglobal()

    大小写敏感,名字于Lua脚本的函数名称大小写完全一致

     

    4.        压入参数

         不同类型采用不同的函数,按照从左往右的顺序依次压栈

         lua_pushnumber,lua_pushstring,…..

     

    5.        执行函数

    lua_call, lua_pcall

     

    6.        获取返回值

    不同类型使用不同的函数,注意索引,获取前要检查类型

     

    7.        从栈中弹出返回值

         lua_pop()

     

    8.        关闭Lua状态机

    lua_close()

     

     

    C程序掉用Lua函数例子:

    C函数:

    void CCallLua()

    {

        // Create a LUA VMachine

        lua_State *L;

        //L = luaL_newstate();

        L = lua_open();

     

        //Load Libraries

        luaL_openlibs(L);

     

         // 运行脚本 /

        luaL_dofile(L, "clua.lua");

        lua_getglobal(L,"Sum");

     

        lua_pushnumber(L,2);//第一个参数

        lua_pushnumber(L,3);//第二个参数

        lua_pushnumber(L,4);//第三个参数

     

        lua_pcall(L,3,2,0);

     

        double sum=0,ave=0;

        if(lua_isnumber(L,1))

        {

            sum=lua_tonumber(L,1);

        }

        if(lua_isnumber(L,2))

        {

            ave=lua_tonumber(L,2);

        }

     

        lua_pop(L,2);

     

        cout<<"Sum ="<<sum

            <<"/nAve ="<<ave<<endl;

        // 清除Lua

        lua_close(L);

     

        getchar();

    }

     

    Lua脚本Clua.lua

    function Sum(...)

      local s=0

      local num=0     

      for k,v in pairs{...} do

        s = s + v

           num = k

      end

      return s,s/num

    end



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值