现在我们需要在Lua中调用c++的函数,完成Lua语言实现起来不太方便的功能,这种方法网上有很多说明,但是写得不够详细,初学者看了一头雾水,下面我写一下自己的方法,目的仅仅是让我们先把程序跑起来!

首先来看下代码,然后说配置(在Mac下演示),再解释代码。

#include <iostream>

#ifdef __cplusplus
extern "C"
{
#endif

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#ifdef __cplusplus
}
#endif

//要想注册进lua,函数的定义为 typedef int (*lua_CFunction)(lua_State* L)
int printHello(lua_State * l)
{
    lua_pushstring(l,"hello lua");

    //返回值代表向栈内压入的元素个数
    return 1;
}

int foo(lua_State * l)
{
    //获得Lua传递过来的参数个数
    int n = lua_gettop(l);
    if(n != 0)
    {
        //获得第一个参数
        int i = lua_tonumber(l,1);
        //将传递过来的参数加一以后最为返回值传递回去
        lua_pushnumber(l,i+1);
        return 1;
    }

    return 0;
}

//相加
int add(lua_State * l)
{
    int n = lua_gettop(l);
    int sum = 0;
    for (int i=0;i<n;i++)
    {
        sum += lua_tonumber(l,i+1);
    }
    if(n!=0)
    {
        lua_pushnumber(l,sum);
        return 1;
    }

    return 0;
}

//把需要用到的函数都放到注册表中,统一进行注册
const luaL_Reg lib[]=
{
    {"printHello",printHello},
    {"foo",foo},
    {"add",add},
    {nullptr,nullptr}
};

int main(int argc, const char * argv[])
{
    //创建一个新的Lua环境
    lua_State * l= lua_open();

    //打开需要的库
    luaL_openlibs(l);

    //把foo函数注册进lua,第二个参数代表Lua中要调用的函数名称,第三个参数就是c层的函数名称
    //单独注册一个函数
//    lua_register(l,"foo",foo);

    //统一注册lua中调用的函数
    const luaL_Reg* libf =lib;
    for (; libf->func; libf++)
    {
        //把foo函数注册进lua,第二个参数代表Lua中要调用的函数名称,第三个参数就是c层的函数名称
        lua_register(l,libf->name,libf->func);
        //将栈顶清空
        lua_settop(l,0);
    }

    //加载并且执行lua文件
    luaL_dofile(l,"test.lua");
    //使用dostring直接执行字符串代表的内容,是一段Lua代码
    //    luaL_dostring(l,"print(foo(100))");

    //关闭
    lua_close(l);

    return 0;
}

如果你运行这个程序,会发现编译错误,连头文件都找不到的,所以,我们就要配置一下环境了。首先我们需要下载lua源码,然后解压出来进入Lua的目录,打开控制台切换到Lua目录下,输入make macosx。学过Linux的人就知道这个命令是什么意思,他会编译源码,产生我们要得库,最后会在src目录下产生三个文件lua (the interpreter), luac (the compiler), and liblua.a (the library)。最后为了验证编译的正确性,可以运行make test,过程如图所示。

在Lua中调用c++函数 在Lua中调用c++函数 在Lua中调用c++函数

接下来打开Mac的工程,我们需要进行如图所示的配置。我们需要做的就是对工程配置一下头文件的路径和库的路径,大家根据自己刚才存放的源码目录进行配置,我把用到的库放到了工程下,所以库的路径就是工程的路径。

在Lua中调用c++函数

完成了以上的工作就需要解释一下代码的含义了。头文件的包含这个就不用说了,然后在main函数中,执行了打开lua环境,打开需要用到的库,将要被Lua调用的函数注册一下,然后调用Lua文件,最后关闭Lua环境。代码上都有注释,大家可以查看。而我们注册给Lua使用的函数,必须采用以下的定义方式,typedef int (*lua_CFunction)(lua_State* L),返回值代表的就是要返回给Lua层多少个返回值,这些返回值都使用push压入了栈中,而函数的参数代表的是Lua的全局状态。涉及到得具体函数作用,代码已经有说明,test.lua如下。

print(printHello())
print(foo(99))
print(add(1,5,3,6))

为了打印出结果,我们还需要做的一个步骤如图。

在Lua中调用c++函数

在Lua中调用c++函数

然而为了更好的理解编译链接库的原理,我们在控制台下对刚才的工程做一个编译,其实用到的仅仅就是工程中得一个main函数。我们用到的编译工具是gcc,而不是Xcode集成好的环境。直接使用g++ main.cpp -o main编译main.cpp,希望产生的可执行文件是main。然后接着就报错了,说没有找到lua.h的头文件,好吧,那我们就使用一个-I参数,后边跟上头文件所在的路径,就是你下载下来的源码src文件夹所在的路径:g++ main.cpp -I /Users/mac/Documents/lua-5.1/src -o main(-I参数的含义就是指定头文件的路径),想不到继续报错,说链接失败,原因当然是没有找到Lua库了,所以我们使用-l参数加上库的名称来指定一下库名,接着报错,说库名没有找到,好吧,继续加上-L指定你的库路径。最后的命令是g++ main.cpp -I /Users/mac/Documents/lua-5.1/src -o main -llua -L .这次真的是OK了,然后运行程序,出现了我们要得结果。

在Lua中调用c++函数 在Lua中调用c++函数

现在我们需要将我们用到的这些函数封装到一个模块中,就像cocos的做法一样,使用模块名来调用这些函数,这样组织代码看起来才会好一点。

//要想注册进lua,函数的定义为 typedef int (*lua_CFunction)(lua_State* L)
int printHello(lua_State * l)
{
    lua_pushstring(l,"hello lua");

    //返回值代表向栈内压入的元素个数
    return 1;
}

int foo(lua_State * l)
{
    //获得Lua传递过来的参数个数
    int n = lua_gettop(l);
    if(n != 0)
    {
        //获得第一个参数
        int i = lua_tonumber(l,1);
        //将传递过来的参数加一以后最为返回值传递回去
        lua_pushnumber(l,i+1);
        return 1;
    }

    return 0;
}

//相加
int add(lua_State * l)
{
    int n = lua_gettop(l);
    int sum = 0;
    for (int i=0;i<n;i++)
    {
        sum += lua_tonumber(l,i+1);
    }
    if(n!=0)
    {
        lua_pushnumber(l,sum);
        return 1;
    }

    return 0;
}

//把需要用到的函数都放到注册表中,统一进行注册
const luaL_Reg lib[]=
{
    {"printHello",printHello},
    {"foo",foo},
    {"add",add},
    {nullptr,nullptr}
};

//把上边的函数封装到一个模块里边
int luaopen_tt(lua_State * l)
{
    //首先创建一个table,然后把成员函数名做key,成员函数作为value放入该table中
    luaL_newlib(l,lib);

    return 1;
}

int main(int argc, const char * argv[])
{
    //创建一个新的Lua环境
    lua_State * l= luaL_newstate();

    //打开需要的库
    luaL_openlibs(l);

    //注册模块
    luaL_requiref(l,"tt",luaopen_tt,1);

    //执行test2.lua文件
    luaL_dofile(l,"test2.lua");

    //关闭
    lua_close(l);

    return 0;
}

上边的代码有俩处比较重要,一个是模块注册函数luaopen_tt,我想注册一个tt模块,所以这个模块的名字就叫做luaopen_tt,在Lua层require一个模块名的时候其实是去找一个函数名叫luaopen_模块名的函数,所以你的模块名的函数也需要按照这种格式来写,包括参数和返回值。在这个函数中,使用luaL_newlib函数,把用到的c++函数都放到了全局的表里边,这样就可以使用模块名调用了。另一个重要的地方是函数luaL_requiref(l,"tt",luaopen_tt,1),第二个参数是模块的名字,要和你上边的luaopen后边的名字相同。这样我们在test2.lua文件中就可以这么使用了。通过这种方法,我们就将c++要导出来的函数封装到一个模块中了。

require "tt"
print(tt.printHello())

现在需要做的一件事是,在Cocos2d-x的函数代码中导出函数给Lua用,这个过程是相同的,只不过要看看Cocos给我们提供了什么。使用Cocos Code IDE创建一个Cocos Lua工程,默认情况下工程下是没有c++层的源代码的,所以我们要让这些c++的源代码出来。点击工程选择Cocos Tools,然后选择Add Native Codes Support。这样,在你工程的目录下就会出现一个frameworks的目录,里边放的就是c++的源码。

在Lua中调用c++函数

有必要了解一下Cocos Code IDE生成的工程的目录都是神马意思,如下图所示。res和src不用说了,config.json文件是一个配置文件,配置窗口的大小和窗口标题以及调试的端口号等等这些东西,还有很多内容,需要我们配置的时候就去这里找好了。framework就是源码了,里边的cocos2d-x就是cocos的源码了,runtime-src下得classes下就是c++层用到的源码。其实我们使用Cocos Code IDE来做游戏开发,程序的启动是从c++层启动的,有时候虽然我们整个游戏项目写的都是Lua脚本,没写过一行c++代码,但是,程序启动是从c++代码启动的,是从c++层调用的Lua脚本,然后Lua脚本调用cocos绑定的函数。所以最后的那个runtime文件夹就是编译出来得运行环境,每当我们c++层改变代码的时候都需要借助Cocos Code IDE这个工具来重新生成一个runtime。

在Lua中调用c++函数

现在言归正传,打开AppDelegate.cpp文件,在applicationDidFinishLaunching函数的如下地方注册我们得导出函数。

在Lua中调用c++函数

我将要导出的函数写到了一个新的.cpp文件中了,单击工程创建一个.cpp文件和一个.h文件,我的文件内容如下。

#ifndef __Test10__TestLua__
#define __Test10__TestLua__

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

int test(lua_State * l);
#include "TestLua.h"

int test(lua_State * l)
{
    lua_pushstring(l,"单独的c文件");
    return 1;
}

AppDelegate.cpp文件一定要包含TestLua.h文件哈,要不注册的时候怎么知道有test这个函数。关于导出函数的写法和在AppDelegate中怎么注册我就不用说了吧,和前边的一样,使用stack->getLuaState()就是获得运行时的全局环境,有了这个全局的环境就可以注册了。这里需要注意的是,新建的TestLua.h和TestLua.cpp文件一定是加入到这个工程中得,否则编译的时候不会编译这俩个文件,没有加入进去相信AppDelegate中就会报错了,这里个文件的创建就和我们平时写c++代码一样使用同样的方法做就OK了。接下来比较重要的是,在Lua工程中,我们需要重新编译一下runtime(在Cocos Tools菜单中找),原因我想不用说了吧,最后在工程的debug配置中看看是不是用的是新编译出来的runtime,不要用错了。

在Lua中调用c++函数

最后我们只需要在工程中简单得测试一下就OK了。

在Lua中调用c++函数在Lua中调用c++函数