zoukankan      html  css  js  c++  java
  • Lua与C/C++的交互

    Lua 作为一个底层通过 C 语言编写的脚本语言,以其 高效 短小精悍 语法简单 而被广泛应用于游戏配置脚本, 逻辑等方面.  用到通过C语言对其进行扩展。

    首先, C 调用 Lua 函数, 在 Lua 中 test.lua 中编写 Lua 函数,而后通过 C 调用。

    // test.lua
    local test = {}
    local function testLocalFunc(str_)
      print("testLocalFunct"..tostring(str_))
    end
    test.localFunc = testLocalFunc
    
    function testGlobalFuncWithParam(str_)
      print("testGlobalFuncWithParam param =" .. tostring(str_))
    end
    return test
    // End of test.lua
    
    // 在 C 中调用
    int testLocalFunc () {
        lua_State *L = luaL_newstate();  
    
        // test call testGlobalFuncWithParam
        const char* str = "Hello world!";
    
        /* push functions and arguments */
        int ret = luaL_dofile(L, "test.lua");
        
        lua_getglobal(L, "test");   /* function to be called */
        lua_getfield(L, "localFunc"); //<==> lua_pushlstring(L, "localFunc"); lua_gettable(L, -2);
    
        //lua_getglobal(L, "testGlobalFuncWithParam");
    
        //  lua_pushstring cannot handle a string contains ''.
        //  a string contain '' not end of it in C is Invalid, but valid in Lua!
        lua_pushlstring(L, str);    /* push 1st argument */
    
        /* do the call (1 arguments, 1 result) */
        if (lua_pcall(L, 1, 1, 0) != 0)
           error(L, "error running function `testGlobalFuncWithParam': %s",
               lua_tostring(L, -1));
    
        /* retrieve result */
        if (!lua_isnumber(L, -1))
           error(L, "function `testGlobalFuncWithParam' must return a number");
        int ret = lua_tonumber(L, -1);
        lua_pop(L, 1);  /* pop returned value */
        return ret;
    }

    lua 调用单个 C 函数, 首先编写 C 函数, 而后导出接口给 Lua 调用。(常见做法)

    //test.lua
    print("testCFunction = ".. tostring(testCFunction(99, 1)))
    // End of test.lua
    
    // extern "C" {  // in Cpp declarm C Func is Needed.
        #include <lua.h>
        #include <lualib.h>
        #include <lauxlib.h>
    // }   // in Cpp declarm C Func is Needed.
    
    int testCFunction(lua_State *L){
        int m = lua_tonumber(L, 2)
        int n = lua_tonumber(L, 1); 
    
        int ret = m + n;
    lua_pushnumber(L, ret);
    //将结果入栈 return 1; //返回一个结果 } int main(){ lua_State *L = lua_open(); //初始化 luaL_openlibs(L); //打开lua标准库 //#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) lua_register(L, "testCFunction", testCFunction); //注册c函数到lua环境 luaL_dofile(L, "test.lua"); //执行lua脚本 lua_close(L); //关闭环境 return0; }

    但是要注意, 注册到 Lua 到 函数在 lua.h 文件中必须有同样的原型,这个原型声明定义就是lua.h中的lua_CFunction:

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

    否则会导致注册失败。

    你也可以重新编译 Lua 库  将注册代码 直接添加到 lua_open() 后面的合适位置。

    二、以上是注册单个 全局函数 到 Lua 环境, 如果是一系列的函数,组成一个库,一个个注册会很麻烦, 就可以写成这样:

    #include <stdio.h>
    #include <string.h>
    #include <lua.hpp>
    #include <lauxlib.h>
    #include <lualib.h>
    
    extern "C" 
    static int testCFunctionAdd(lua_State *L){
        int m = lua_tonumber(L, 2)
        int n = lua_tonumber(L, 1); //取栈底值(传入的第一个参数)
    
        int ret = m + n;
        lua_pushnumber(L, ret);   //将结果入栈
        return 1;                   //返回一个结果
    }
    
    extern "C" 
    static int testCFunctionSub(lua_State *L){
        int m = lua_tonumber(L, 2)
        int n = lua_tonumber(L, 1); //取栈底值(传入的第一个参数)
    
        int ret = m - n;
        lua_pushnumber(L, ret);   //将结果入栈
        return 1;                 //返回一个结果
    }
    
    static luaL_Reg mylibs[] = { 
        {"testCFunctionAdd", testCFunctionAdd},
        {"testCFunctionSub", testCFunctionSub},
        {NULL, NULL} 
    }; 
    
    int luaopen_MyTestlib(lua_State* L) {
        const char* libName = "MyTestlib";
        luaL_register(L,libName,mylibs);
        return 1;
    }

    注意:
    函数名必须为luaopen_xxx,其中xxx表示库名称。Lua代码require "xxx" 必须与之对应。
    在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。
    需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定,否则将无法调用。

    在动态链接库中你可以这样用 loadlib 加载你的函数库 mylib = loadlib("fullname-of-your-library", "luaopen_MyTestlib")

    然后就可以调用 mylib(),运行 luaopen_MyTestlib 打开你定义的函数库。

    还有小知识点:通过 C++ 操作 lua table。

    void printLuaTable(lua_State* tolua_S){
        
        int argc = lua_gettop(tolua_S);
        if (argc < 2) {
            CCLOG("argc = %d", argc);
        }
        
        std::map<std::string, std::string> dict;
    
        lua_pushnil(tolua_S);
        while (lua_next(tolua_S, -2) != 0) {
            if (lua_isstring(tolua_S,-2) && lua_isstring(tolua_S, -1)){
                dict.insert(std::make_pair(lua_tostring(tolua_S, -2), lua_tostring(tolua_S, -1)));
            }
            lua_pop(tolua_S, 1);
            CCLOG("top = %d", lua_gettop(tolua_S));
        }
    
        for(std::map<std::string, std::string>::const_iterator iter = dict.begin(); iter != dict.end(); iter++){
            CCLOG("key:%s", iter->first.c_str() );
            CCLOG("value:%s", iter->second.c_str());
        }
    }

    三、可以使用 tolua++ 工具导出 C/C++ 接口给 Lua 调用。 

    以下摘自:  https://note.youdao.com/share/?id=0f4132271151c4b62f9afb712e8304d9&type=note#/

    第三层:了解为什么要使用toLua++来注册C++类

    因为Lua的本质是C,不是C++,Lua提供给C用的API也都是基于面向过程的C函数来用的,要把C++类注册进Lua形成一个一个的table环境是不太容易办到的事,因为这需要把C++类变成各种其他类型注册进Lua。单纯地手写lua_register()等代码来注册C++类是行不通的、代价高昂的,所以需要借助toLua++这个工具。

    只有理解了手工用lua_register()去注册C++类的难度,才能理解使用toLua++这类工具的必要性。只有理解了使用toLua++工具的必要性,才会潜下心来冷静地接受toLua++本身的优点和缺点。只有看到了toLua++本身的缺点和使用上的麻烦,才会真心理解cocos2d-x使用bindings-generator脚本带来的好处。只有理解了bindings-generator脚本带来的好处,才能谅解这个脚本本身在使用上的一些不便之处。

    第四层:在纯C++环境下,使用toLua++来把一个C++类注册进Lua环境

    理解toLua++本身的用法还是必要的,知道了toLua++原本的用法,才能更好地理解cocos2d-x是怎么把自己的C++类都注册进Lua环境的。

    使用toLua++的标准做法是:

    1、定义实现c++类
    2、仿造这个类的.h文件,改一个.pkg文件出来,具体格式要按照toLua++的规定,比如移除所有的private成员等
    3、建一个专门用来桥接C++和Lua之间的C++类,使用特殊的函数签名来写它的.h文件,.cpp文件不写,等着toLua++来生成
    4、给这个桥接的C++类写一个.pkg文件,按照toLua++的特殊格式来写,目的是把真正做事的C++类给定义进去
    5、在命令行下用toLua++生成桥接类的.cpp文件
    6、程序入口引用这个桥接类,执行生成的桥接函数,Lua环境中就可以使用真正做事的C++类了

    下面我以尽量最少的代码来走一遍toLua++的流程,注意这是在纯C++环境下,跟任何框架都没关系,也不考虑内存释放等细节:

    MyClass.h

    class MyClass {
    public:
      MyClass() {};
      int foo(int i);
    };

    MyClass.cpp

    #include "MyClass.h"
    int MyClass::foo(int i)
    {
      return i + 100;
    }
    

    MyClass.pkg c++类的pkg文件

    class MyClass {
      MyClass();
      int foo(int i);
    };
    

    MyLuaModule.h 桥接类

    extern "C" {
        #include "tolua++.h"
    }
    
    #include "MyClass.h"
    TOLUA_API int tolua_MyLuaModule_open(lua_State* tolua_S);
    

    MyLuaModule.pkg 桥接类的pkg文件

    $#include "MyLuaModule.h"
    $pfile "MyClass.pkg"

    main.cpp

    extern "C" { 
      #include <lua.h>
      #include <lualib.h>
      #include <lauxlib.h>
    }
    
    #include "MyLuaModule.h"
    int main() {
      lua_State *L = lua_open();
      luaL_openlibs(L);
      tolua_MyLuaModule_open(L);
      luaL_dofile(L, "main.lua");
      lua_close(L);
      return 0;
    }
    

    main.lua

    local test = MyClass:new()
    print(test:foo(99))
    

    先在命令行下执行:(生成桥接文件的实现体MyLuaModule.cpp)

    tolua++ -o MyLuaModule.cpp MyLuaModule.pkg

    意命令行中-o参数的顺序不能随意摆放,

    生成好MyLuaModule.cpp文件后,就能看到它里面的那一大堆桥接代码了,比如tolua_beginmoduletolua_function等。

    以后看到这些东西就不陌生了,就明白这些函数只是toLua++用来做桥接的必备代码了,

    简单看一下代码,就理解toLua++是怎样把MyClass这个C++类注册进Lua中的了:

    toLua++生成的桥接代码

    接下来,用g++来编译:

    g++ MyClass.cpp MyLuaModule.cpp main.cpp -llua -ltolua++
    

    默认就生成了a.out文件,执行,就能看到main.lua的执行结果了:

    toLua++的执行结果

    至此,对toLua++的运作原理心里就透亮了,无非就是:

    1、把自己该写的类写好
    2、写个.pkg文件,告诉toLua++这个类暴露出哪些接口给Lua环境
    3、再写个桥接的.h和.pkg文件,让toLua++去生成桥接代码
    4、在程序里使用这个桥接代码,类就注册进Lua环境里 

    第五层:使用cocos2d-x的方式来将C++类注册进Lua环境

    所以从cocos2d-x 3.x开始,用bindings-generator脚本代替了toLua++。

    bindings-generator脚本的工作机制是:

    1、不用挨个类地写桥接.pkg和.h文件了,直接定义一个ini文件,告诉脚本哪些类的哪些方法要暴露出来,注册到Lua环境里的模块名是什么
    2、摸清了toLua++工具的生成方法,改由Python脚本动态分析C++类,自动生成桥接的.h和.cpp代码,不调用tolua++命令了
    3、虽然不再调用tolua++命令了,但是底层仍然使用toLua++的库函数,比如tolua_function,bindings-generator脚本生成的代码就跟使用toLua++工具生成的几乎一样

    bindings-generator脚本掌握了生成toLua++桥接代码的主动权,不仅可以省下大量的.pkg和.h文件,而且可以更好地插入自定义代码,达到cocos2d-x环境下的一些特殊目的,比如内存回收之类的。

    接下来说怎么用bindings-generator脚本:

    1、写自己的C++类,按照cocos2d-x的规矩,继承cocos2d::Ref类,以便使用cocos2d-x的内存回收机制。
    2、编写一个.ini文件,让bindings-generator可以根据这个配置文件知道C++类该怎么暴露出来
    3、修改bindings-generator脚本,让它去读取这个.ini文件
    4执行bindings-generator脚本,生成桥接C++类方法
    5、将自定义的C++类和生成的桥接文件加入工程,不然编译不到
    6、修改AppDelegate.cpp,执行桥接方法,自定义的C++类就注册进Lua环境里了

    看着步骤挺多,其实都狠简单。下面一步一步来。

    1.首先是自定义的C++类。我习惯将文件保存在frameworks/runtime-src/Classes/目录下:

    frameworks/runtime-src/Classes/MyClass.h

    #include "cocos2d.h"
    using namespace cocos2d;
    class MyClass : public Ref {
    public:
      MyClass()   {};
      ~MyClass()  {};
      bool init() { return true; };
      CREATE_FUNC(MyClass);
      int foo(int i);
    };
    

    frameworks/runtime-src/Classes/MyClass.cpp

    #include "MyClass.h"
    int MyClass::foo(int i) {
      return i + 100;
    }
    

    2.然后编写.ini文件。在frameworks/cocos2d-x/tools/tolua/目录下能看到genbindings.py脚本和一大堆.ini文件,这些就是bindings-generator的实际执行环境了。随便找一个内容比较少的.ini文件,复制一份,重新命名为MyClass.ini。大部分内容都可以凑合不需要改,这里仅列出必须要改的重要部分:

    frameworks/cocos2d-x/tools/tolua/MyClass.ini

    [MyClass]
    prefix           = MyClass
    target_namespace = my
    headers          = %(cocosdir)s/../runtime-src/Classes/MyClass.h
    classes          = MyClass
    

    也即在MyClass.ini中指定MyClass.h文件的位置,指定要暴露出来的类,指定注册进Lua环境的模块名。

    然后修改genbindings.pyMyClass.ini文件加进去:

    3.frameworks/cocos2d-x/tools/tolua/genbindings.py

    cmd_args = {'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), 
                'MyClass.ini' : ('MyClass', 'lua_MyClass_auto'), 
                ...

    4.至此,生成桥接文件的准备工作就做好了,执行genbindings.py脚本:

    python ./genbindings.py

    成功执行genbindings.py脚本后,会在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下看到新生成的文件:

    成功执行genbindings.py后生成的桥接C++文件

    每次执行genbindings.py脚本时间都挺长的,因为它要重新处理一遍所有的.ini文件,建议大胆修改脚本文件,灵活处理,让它每次只处理需要的.ini文件就可以了,比如像这个样子:

    修改genbindings.py使其只生成自定义的桥接类

    frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目录下观察一下生成的C++桥接文件lua_MyClass_auto.cpp,里面的注册函数名字为register_all_MyClass(),这就是将MyClass类注册进Lua环境的关键函数:

    生成的桥接文件内容

    5.编辑frameworks/runtime-src/Classes/AppDelegate.cpp文件,首先在文件头加入对lua_MyClass_auto.hpp文件的引用:

    AppDelegate.cpp文件的头加入对lua_MyClass_auto.hpp文件的引用

    然后在正确的代码位置加入对register_all_MyClass函数的调用:

    修改AppDelegate.cpp文件,将MyClass类注册进Lua环境

    如何是lua工程则在:lua_module_register.h 中添加上述调用。

    最后在执行编译前,将新加入的这几个C++文件都加入到Xcode工程中,使得编译环境知道它们的存在:

    在Xcode中加入新冒出来的C++文件

    这其中还有一个小坑,由于lua_MyClass_auto.cpp文件要引用MyClass.h文件,而这俩文件分属于不同的子项目,互相不认识头文件的搜寻路径,因此需要手工修改一下cocos2d_lua_bindings.xcodeproj子项目的User Header Search Paths配置。特别注意一共有几个../

    需要修改cocos2d_lua_bindings子项目的User Header Search Paths配置

    最后,就可以用cocos compile -p mac命令重新编译整个项目了,不出意外的话编译一定是成功的。

    修改main.lua文件中,尝试调用一下MyClass类:

    local test = my.MyClass:create()
    print("lua bind: " .. test:foo(99))
    6.android上运行的话需要做的事情是要将生成的桥接文件lua_MyClass_auto.cpp放到android.mk中。

    配置ini时需要注意的选项:

    • [title]:要配置将被使用的工具/ tolua的/ gengindings.py脚本的称号。一般来说,标题可以是文件名。

    • prefix:要配置一个函数名的前缀,通常,我们还可以使用文件名作为前缀 生成函数一次为前缀。

    • target_namespace:要配置在脚本层模块的名字。在这里,我们使用cc作为模块名,当你想在脚本层REF的名称,您必须将一个名为前缀,CC在名称的前面。例如,CustomClass可以参考作为cc.CustomClass

    • headers:要配置所有需要解析的头文件和%(cocosdir)s是的Cocos2d-x的引擎的根路径。

    • classes:要配置所有绑定所需的类。在这里,它支持正则表达式。因此,我们可以设置MyCustomClass。*在这里,用于查找多个特定的用法,你可以对照到tools/tolua/cocos2dx.ini

    • skip:要配置需要被忽略的功能。现在绑定发电机无法解析的void *类型,并委托类型,所以这些类型的需要进行手动绑定。而在这种情况下,你应该忽略所有这些类型,然后再手动将它们绑定。你可以对照到配置文件路径下的cocos/scripting/lua-bindings/auto 。

    • rename_functions:要配置的功能需要在脚本层进行重命名。由于某些原因,开发者希望更多的脚本友好的API,所以配置选项就是为了这个目的。

    • rename_classes:不在使用。

    • remove_prefix:不在使用。

    • base_classes_to_skip = #当被它们的子类发现的时候会跳过的基类
    • classes_have_no_parents:要配置是过滤器所需要的父类。这个选项是很少修改。

    • abstract_classes:要配置的公共构造并不需要导出的类。

    • script_control_cpp:是的。要配置脚本层是否管理对象的生命周期。如果没有,那么C++层关心他们的生命周期。 
      现在,它是不完善的,以控制原生对象的续航时间在脚本层。所以,你可以简单地把它设置为no。

    对于 Lua 与 C++ 的交互,还可以 用 C++ 的模板来进行, 例如 luna模板

    #include <iostream>
    #include <cstring>
    extern "C" {
    #include <lua.h>
    #include <lualib.h>
    #include <lauxlib.h>
    }
    
    using namespace std;
    
    #define DECLARE_LUNA_CLASS(obj) 
        static const char *name;
        static luna<obj>::TMethod methods[];
    
    #define EXPORT_LUNA_FUNCTION_BEGIN(obj) 
        const char* obj::name = #obj;
        luna<obj>::TMethod obj::methods[] = {
    
    #define EXPORT_LUNA_MEMBER_INT(obj, member) 
        {#member, nullptr},
    
    #define EXPORT_LUNA_FUNCTION(obj, func) 
        {#func, &obj::func},
    
    #define EXPORT_LUNA_FUNCTION_END(obj) 
        {nullptr, nullptr}
        };
    
    template<typename T>
    class luna
    {
        public:
            typedef struct {T* _u;} TObject;
            typedef int (T::*TPfn)(lua_State* L); 
            typedef struct {const char* name; TPfn pf;} TMethod;
        public:
            static int regist(lua_State* L); 
            static int create(lua_State* L); 
            static int call(lua_State* L); 
            static int gc(lua_State* L); 
    };
    
    template<typename T>
    int luna<T>::regist(lua_State* L)
    {
        //原表Shape
        if (luaL_newmetatable(L, T::name))
        {   
            //注册Shape到全局
            lua_newtable(L);
            lua_pushvalue(L, -1);
            lua_setglobal(L, T::name);
    
            //设置Shape的原表,主要是__call,使其看起来更像C++初始化
            lua_newtable(L);
            lua_pushcfunction(L, luna<T>::create);
            lua_setfield(L, -2, "__call");
            lua_setmetatable(L, -2);
            lua_pop(L, 1); //这时候栈只剩下元表
    
            //设置元表Shape index指向自己
            lua_pushvalue(L, -1);
            lua_setfield(L, -2, "__index");
            lua_pushcfunction(L, luna<T>::gc);
            lua_setfield(L, -2, "__gc");
        }
        return 0;
    }
    
    template<typename T>
    int luna<T>::create(lua_State* L)
    {
        lua_remove(L, 1);
        TObject* p = (TObject*)lua_newuserdata(L, sizeof(TObject));
        p->_u = new T();
    
        luaL_getmetatable(L, T::name);
        lua_setmetatable(L, -2);
    
        luaL_getmetatable(L, T::name);
        for (auto* l = T::methods; l->name; l++)
        {
            lua_pushlightuserdata(L,(void*)l);
            lua_pushlightuserdata(L,(void*)p);
            lua_pushcclosure(L, luna<T>::call, 2);
            lua_setfield(L, -2, l->name);
        }
    
        lua_pop(L, 1);
    
        return 1;
    }
    
    template<typename T>
    int luna<T>::call(lua_State* L)
    {
        TMethod* v = (TMethod*)lua_topointer(L, lua_upvalueindex(1));
        cout<<"luna<T>::call:"<<v->name<<endl;
    
        TObject* p = (TObject*)lua_topointer(L, lua_upvalueindex(2));
    
    
        return ((p->_u)->*(v->pf))(L);
    }
    
    template<typename T>
    int luna<T>::gc(lua_State* L)
    {
        TObject* p = (TObject*)lua_touserdata(L, 1);
        (p->_u)->~T();
        return 0;
    }

    参考: https://www.cnblogs.com/liao0001/p/9791495.html

    参考: https://www.cnblogs.com/sevenyuan/p/4511808.html

    参考:   http://book.luaer.cn/

    参考:   https://note.youdao.com/share/?id=0f4132271151c4b62f9afb712e8304d9&type=note#/

    参考:   http://webserver2.tecgraf.puc-rio.br/~celes/tolua/tolua-3.2.html#introduction

  • 相关阅读:
    Android 仿淘宝头条竖直跑马灯式新闻标题及“分页思想
    Android setOnPageChangeListener 过时了怎么办?
    Android requestWindowFeature(Window.FEATURE_NO_TITLE)无效解决方法
    Android ViewPager+HorizontalScrollView实现标题栏滑动(腾讯新闻)
    Android OKHttp网络框架
    安卓开发常用网络请求框架OkHttp、Volley、XUtils、Retrofit对比
    Android 修改源码自定义SwipeRefreshLayout样式——高仿微信朋友圈下拉刷新
    Android 比SwipeRefreshLayout更漂亮和强大的下拉刷新控件:Android-MaterialRefreshLayout
    Android Error:Unable to find method 'com.android.build.gradle.api.BaseVariant.getOutputs()Ljava/util/List;'.
    Android 浅谈 RxAndroid + Retrofit + Databinding
  • 原文地址:https://www.cnblogs.com/lesten/p/8969930.html
Copyright © 2011-2022 走看看