lua 和 cpp 互调

作者: orientlu | 来源:发表于2018-12-23 14:05 被阅读2次

    本文编译 lua 版本为 5.3

    编译 lua5.3

    例子中涉及为 lua 编写 so,(lua require 加载)
    需要修改 lua/src 下的makefile
    cppflag 加 -FPIC, 这样后续链接so才不会报错

    $ curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
    $ tar vzxf lua-5.3.0.tar.gz
    $ cd ./lua-5.3.0/
    #sudo apt-get install libreadline-dev
    $ make linux test
    $ sudo make install
    

    cpp 调用lua

    cpp 调用lua的基本流程是:lua 函数名入栈,参数依次入栈,调用lua call 接口。
    如下例子,lua 脚本 test.lua 中定义一个函数

    function add(x, y)
        print("add");
        return x + y;
    end
    

    通过 cpp 代码加载脚本并调用 add 函数的实现如下:

    #include <iostream>
    #include <lua.hpp>  // lua 库所有头文件
    using namespace std;
    
    int main()
    {
        // 创建lua vm
        lua_State* L = luaL_newstate();
        // 加载 std lib 到 L
        luaL_openlibs(L);
        // 加载脚本 执行
        luaL_dofile(L, "./test.lua");
    
        int sum;
        // 1 调用函数名入栈
        lua_getglobal(L, "add");
        // 2 第一个参数入栈
        lua_pushnumber(L, x);
        // 3 第二个参数入栈
        lua_pushnumber(L, y);
        // 调用函数,2个入参,返回值一个
        lua_call(L, 2, 1);
        // 栈顶获取返回值
        sum = (int)lua_tonumber(L, -1);
        cout << "sum is" << sum << endl;
        // 释放 L
        lua_close(L);
        return 0;
    }
    

    完整例子

    lua 调用cpp

    为lua 编写库,通过lua调用的方式有两种:

    • lua require 库后调用,运行主体是 lua;
    • cpp 注册库函数,加载lua,lua脚本中调用, 运行主体是 cpp;

    第一种就是我们平时直接运行 lua 脚本,脚本中执行标准库函数一样,第二种结合上一节,指在 cpp 调用 lua 脚本,在被调用的 lua 中又需要调用到 cpp 中的函数。

    不管那一种,编写供lua调用的函数原型都是 :

    typedef int (*lua_CFunction) (lua_State *L); // 定义在"lua.h"中
    通过 lua_State 获取调用参数和返回结果,通过返回值表示返回结果个数。

    定义被 lua 加载的函数

    // mylualib.h
    #ifndef _MYLUALIB_H
    #define _MYLUALIB_H
    #pragma once
    #include <lua.hpp>
    /// call by lua when require
    extern "C" int luaopen_mylualib(lua_State *L);
    #endif
    
    // mylualib.cpp
    #include<iostream>
    using namespace std;
    #include "mylualib.h"
    
    static int add(lua_State *L) {
        // argc
        int n = lua_gettop(L);
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            // check,is number, return number, otherwise, error
            sum += luaL_checknumber(L, i+1);
        }
        // push return values
        lua_pushnumber(L, sum); // first value
        lua_pushnumber(L, n);
        // 返回lua时会自动清理栈上返回结果下的其他内容
        // 所以在push时不需要做清理
        
        // mean has return 2 value
        return 2;
    }
    
    static const luaL_Reg m_lib[] = {
        {"c_add", add},
        {NULL, NULL} // END
    };
    
    // lua 中 require "xxx"
    // 对应调用 luaopen_xxx()
    int luaopen_mylualib(lua_State* L) {
        luaL_newlib(L, m_lib);
        // 1 value return :  m_lib,
        // 返回函数调用的表
        return 1;
    }
    

    lua 主体,require cpp 库

    如上定义的 mylualib 例子,如果想通过 lua 直接 require 使用,需要先编译成 so

    g++ -O2 -Wall -fPIC --shared -o mylualib.so mylualib.cpp -llua
    然后就可以使用了

    -- excute open_mylualib(L)
    local mylualib = require "mylualib"
    
    print("lua call, 2 args")
    sum, count = mylualib.c_add(1, 2);
    print("sum is " .. sum)
    print("argc is " .. count)
    

    cpp 主体,加载 lua,在 lua 中调用 cpp 注册的函数

    有个等待被 cpp 加载的脚本中调用了cpp 中的函数

    print("lua call, 2 args")
    sum, count = mylualib.c_add(1, 1);
    print("sum is " .. sum)
    print("argc is " .. count)
    

    结合第一节,通过 cpp 注册函数给这个 lua 脚本调用

    include<iostream>
    using namespace std;
    #include <lua.hpp>
    #include "mylualib.h"
    
    int  main(int argc, char* argv[])
    {
        lua_State * L = luaL_newstate();
        if (!L) {
            cout << "luaL_newstate error" << endl;
            return -1;
        }
        // open L vm std lib
        luaL_openlibs(L);
    
        // load my lua lib
        // mylualib.xxx in lua
        luaL_requiref(L, "mylualib", luaopen_mylualib, 1);
    
        // load lua script
        luaL_dofile(L, "./lua_call_cpp_no_require.lua");
        cout << lua_tostring(L, -1);
        return 0;
    }
    

    编译后执行

    g++ ./lua_call_cpp.cpp ./mylualib.cpp -llua -ldl -o lua_call_cpp_1

    绑定cpp 类到lua 中

    在 lua 中通过表和元表实现对象,类似如下

    BaseClass = {name = "BaseClass_name"}
    function BaseClass:new(o)
        o = o or {} -- new table
        -- 这里,新表以 Baseclass 为元表(父类)
        setmetatable(o, self)
        self.__index = self -- 元表的__index
        return o
    end
    
    function BaseClass:getName()
        return self.name
    end
    
    obj = BaseClass:new(nil)
    print(obj:getName())
    

    工程中,cpp 绑定对象到 lua 有很多成熟的库可以直接使用,如tolua++,Lunar 等。
    但是本着了解下实现原理心态,以上面为基础,尝试绑定下面这个简单的类到 lua,提供 lua 面向对象访问的方式。

    #ifndef _STUDENT_H
    #define _STUDENT_H
    #pragma once
    #include <iostream>
    #include <string>
    class Student {
    public:
        Student() : m_name{"default name"} {}
        ~Student() {
            std::cout << "Student " << m_name << " gone .>>>" << std::endl;
        }
        Student& SetName(const std::string& name) {
            m_name = name;
            return *this;
        }
        const std::string& GetName(void) const {
            return m_name;
        }
    private:
        std::string m_name;
    };
    #endif
    

    so 实现,
    头文件 l_student.h

    #ifndef _L_STUDENT_H
    #define _L_STUDENT_H
    #pragma once
    #include "lua.hpp"
    #include "../student.h"
    extern "C" int luaopen_student(lua_State* L);
    #endif
    

    实现文件

    #include "l_student.h"
    #include <iostream>
    #include <string>
    
    #define STUDENT_METATABLE "mt.student"
    
    int l_student_create(lua_State* L) {
        Student **s = (Student**)lua_newuserdata(L, sizeof(Student*));
        *s = new Student;
        // s 的metatable 设置为 全局 mt.student
        luaL_getmetatable(L, STUDENT_METATABLE);
        lua_setmetatable(L, -2);
        return 1;
    }
    int l_student_getName(lua_State* L) {
        // obj 在 栈底
        // 判断 s是否包含 STUDENT_METATABLE
        Student **s = (Student**)luaL_checkudata(L, 1, STUDENT_METATABLE);
        luaL_argcheck(L, s != NULL, 1, "invalid user data");
    
        lua_pushstring(L, (*s)->GetName().c_str());
        return 1;
    }
    int l_student_setName(lua_State* L) {
        // obj 在 栈底
        Student **s = (Student**)luaL_checkudata(L, 1, STUDENT_METATABLE);
        luaL_argcheck(L, s != NULL, 1, "invalid user data");
    
        luaL_checkstring(L, -1);
    
        std::string name = lua_tostring(L, -1);
        (*s)->SetName(name);
    
        return 0;
    }
    int l_student_gc(lua_State* L) {
        // obj 在 栈底
        Student **s = (Student**)luaL_checkudata(L, 1, STUDENT_METATABLE);
        luaL_argcheck(L, s != NULL, 1, "invalid user data");
        if (*s) {
            delete *s;
        }
        return 0;
    }
    
    static const luaL_Reg m_lib[] = {
        {"create", l_student_create},
        {NULL, NULL} // END
    };
    // 创建一个元表
    static const luaL_Reg m_student_metatable[] = {
        {"set", l_student_setName},
        {"get", l_student_getName},
        {"__gc", l_student_gc},
        {NULL, NULL} // END
    };
    
    int luaopen_student(lua_State* L) {
    
        // 设置一个全局表作为 student 的元表
        luaL_newmetatable(L, STUDENT_METATABLE); // push metatable
        lua_pushvalue(L, -1); // push metatable, 下一个设置会pop
        /* now l stack
         *|mt.Student| <-func
         *|mt.Student| <-__index
         * */
        // mt.Student.__index = mt,Student
        lua_setfield(L, -2, "__index"); // set and pop metatable
        luaL_setfuncs(L, m_student_metatable, 0);
    
    
        luaL_newlib(L, m_lib); // push m_lib
        return 1;
    }
    

    编译;
    g++ -Wall -fPIC --shared -o student.so l_student.cpp -llua

    lua 测试代码

    local Student = require "student"
    
    s1 = Student.create()
    s1:set("orientlu_1");
    print(s1:get())
    
    s1 = Student.create()
    s1:set("orientlu_2");
    print(s1:get())
    

    参考

    相关文章

      网友评论

        本文标题:lua 和 cpp 互调

        本文链接:https://www.haomeiwen.com/subject/syhnkqtx.html