zoukankan      html  css  js  c++  java
  • 基于method实现lua访问C++对象成员

    一、问题

    对于一个在C++创建的类对象,lua中如何调用这个对象的C++接口?进一步,如果我们想在lua中实现对这个C++类的接口扩展,该如何实现?

    二、lua对于类似于C++中meta类型的支持

    在lua中,为了模拟对于C++中面向对象中一个类接口的支持,提供一个专门的"NameSpace:function"类型的语法结构。

    1、如何声明

    从解析的地方看,这里是自动在函数的参数列表的最开始位置添加了一个self变量。
    lua-5.3.4srclparser.c
    static void body (LexState *ls, expdesc *e, int ismethod, int line) {
    /* body -> '(' parlist ')' block END */
    FuncState new_fs;
    BlockCnt bl;
    new_fs.f = addprototype(ls);
    new_fs.f->linedefined = line;
    open_func(ls, &new_fs, &bl);
    checknext(ls, '(');
    if (ismethod) {
    new_localvarliteral(ls, "self"); /* create 'self' parameter */
    adjustlocalvars(ls, 1);
    }
    ……
    }
    需要注意的是,这个地方是将":"作为函数名的一部分处理的。从这个解析可以看到,函数名的命名规则决定了它是否是一个“method”,但同时这个变量名也会被拆分,同样作为一个"fieldsel"语法,这意味着和table的field一样,这个地方会在table中添加一个名字为":"后面字符串的field,并且它指向的是这个函数。
    static int funcname (LexState *ls, expdesc *v) {
    /* funcname -> NAME {fieldsel} [':' NAME] */
    int ismethod = 0;
    singlevar(ls, v);
    while (ls->t.token == '.')
    fieldsel(ls, v);
    if (ls->t.token == ':') {
    ismethod = 1;
    fieldsel(ls, v);
    }
    return ismethod;
    }

    2、如何调用

    这里可以看到,在suffixedexp函数中传入的变量v表示的是之前解析出来的数值(例如some:little这种语法中,这个v表示的就是some这个表达式)
    static void suffixedexp (LexState *ls, expdesc *v) {
    /* suffixedexp ->
    primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */
    FuncState *fs = ls->fs;
    int line = ls->linenumber;
    primaryexp(ls, v);
    for (;;) {
    ……
    case ':': { /* ':' NAME funcargs */
    expdesc key;
    luaX_next(ls);
    checkname(ls, &key);
    luaK_self(fs, v, &key);
    funcargs(ls, v, line);
    break;
    ……
    }
    在这个地方生成了一个OP_SELF指令,并且预分配了两个寄存器,分别用来存储self和查询获得的function地址。
    lua-5.3.4srclcode.c
    /*
    ** Emit SELF instruction (convert expression 'e' into 'e:key(e,').
    */
    void luaK_self (FuncState *fs, expdesc *e, expdesc *key) {
    int ereg;
    luaK_exp2anyreg(fs, e);
    ereg = e->u.info; /* register where 'e' was placed */
    freeexp(fs, e);
    e->u.info = fs->freereg; /* base register for op_self */
    e->k = VNONRELOC; /* self expression has a fixed register */
    luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */
    luaK_codeABC(fs, OP_SELF, e->u.info, ereg, luaK_exp2RK(fs, key));
    freeexp(fs, key);
    }

    3、如何执行

    这里看到一个特殊的处理:除了将self(也就是前面suffixedexp压入的v值)通过setobjs2s(L, ra + 1, rb);原封不动的压入堆栈之外,还进行了一个额外的查找动作,就是从v中查找函数的内容,这个地方是一个“普通”的table中查找指定成员的操作,所以这个查找也可以通过它的__index成员完成。
    void luaV_execute (lua_State *L) {
    ……
    vmcase(OP_SELF) {
    const TValue *aux;
    StkId rb = RB(i);
    TValue *rc = RKC(i);
    TString *key = tsvalue(rc); /* key must be a string */
    setobjs2s(L, ra + 1, rb);
    if (luaV_fastget(L, rb, key, aux, luaH_getstr)) {
    setobj2s(L, ra, aux);
    }
    else Protect(luaV_finishget(L, rb, rc, ra, aux));
    vmbreak;
    }
    ……
    }

    4、当直接使用类名调用函数时

    这种情况下,self指向的就是metatable本身,大致略等于C++中调用静态成员函数(self指向metatable本身)
    tsecer@harry: cat lua.static.call.method.lua
    meta = {
    x = 1111,
    }

    function meta:func1()
    print("xxxx")
    print(self)
    for k,v in pairs(self) do
    print(k, v)
    end
    end

    meta:func1()
    tsecer@harry: /home/tsecer/study/lua-5.3.4/src/lua lua.static.call.method.lua
    xxxx
    table: 0x64b4b0
    x 1111
    func1 function: 0x64b0e0
    tsecer@harry:

    5、总结

    总起来说,定义的时候是metatable:funcname,调用的时候是obj:funcname。这一点的确是和C++的语法是神似的。

    三、如何使用

    0、限制

    这里的限制依然在于lua中只有table和userdata支持(lightuserdata也不支持)metatable,所以想使用lua的metatable方法一定需要使用userdata结构。

    1、在lua中调用C++中对象方法

    tsecer@harry: cat lua.oo.cpp
    extern "C"
    {
    #include <lua.h>
    #include <lualib.h>
    #include <lauxlib.h>
    }

    struct AA
    {
    AA(int x):
    m_x(x)
    {}

    int func1(int x)
    {
    printf("in %s x %d m_x %d ", __func__, x, m_x);
    return 0;
    }
    int func2(int x, int y)
    {
    printf("in %s x %d y %d m_x %d ", __func__, x, m_x);
    return 0;
    }
    int m_x;
    };

    int CallAAFunc1(lua_State *L)
    {
    //第一个参数保存self指针
    AA *pobj = *(AA**)lua_touserdata(L, 1);
    //接下来保存参数列表
    int parm1 = lua_tonumber(L, 2);
    //调用函数
    pobj->func1(parm1);
    return 0;
    }

    int CallAAFunc2(lua_State *L)
    {
    //由于是通过 AAObj::Func2调用,所以状态机中会有三个参数
    //从1到3一次为:self也就是AAObj对象,调用时的两个参数
    AA *pobj = *(AA**)lua_touserdata(L, 1);
    int parm1 = lua_tonumber(L, 2), parm2 = lua_tonumber(L, 3);
    pobj->func2(parm1, parm2);
    return 0;
    }

    //注册个lua的__index方法,当lua需要查找一个结构当前不存在的filed时调用
    int Index(lua_State *L)
    {
    //第一个参数为变量本身,它的metatable中存储有函数到Cfunction的映射
    int imetatable = lua_getmetatable(L, 1);
    if (imetatable != 1)
    {
    printf("get metatable failed ");
    return -1;
    }

    //第二个参数为函数名
    const char *funcname = lua_tostring(L, 2);
    if (funcname == nullptr)
    {
    printf("get funcname failed ");
    return -1;
    }

    //从metatable中以函数名为键值查找cfunction
    lua_getfield(L, -1, funcname);

    return 1;
    }

    int CreateMetaTable(lua_State *L)
    {
    //由于lua中的metatable也是一个简单的table,所以这个地方首先直接创建一个普通的table
    lua_createtable(L, 0, 0);
    //创建metatable中的__index方法
    lua_pushstring(L, "__index");
    //创建该方法对应的C函数
    lua_pushcfunction(L, Index);
    //将新生成的metatable的__index设置为新创建的cfunction
    lua_rawset(L, -3);

    //在metatable中添加转发函数
    //函数名
    lua_pushstring(L, "Func1");
    //C函数地址
    lua_pushcfunction(L, CallAAFunc1);
    //设置hashtable
    lua_rawset(L, -3);
    //在metatable中设置Func2为自定义的转发函数
    lua_pushstring(L, "Func2");
    lua_pushcfunction(L, CallAAFunc2);
    lua_rawset(L, -3);

    //将AA作为全局变量添加到lua虚拟机中
    lua_setglobal(L, "AA");
    return 1;
    }

    int BindObj(lua_State *L, AA *Obj)
    {
    //创建一个只包含一个指针类型的userdata,其中保存C++中创建对象的地址
    *(AA**)lua_newuserdata(L, sizeof(Obj)) = Obj;
    //将新创建变量赋值给lua中全局变量AAObj
    lua_setglobal(L, "AAObj");
    //从lua中查找到AAObj变量(结果在栈顶)
    lua_getglobal(L, "AAObj");
    //查找到之前创建的metatable:AA变量
    lua_getglobal(L, "AA");
    //将堆栈中-2位置变量(AAObj)的metatable设置为栈顶变量(AA变量)
    lua_setmetatable(L, -2);
    //在lua虚拟机中执行AAObj(userdata存储了对象指针的lua变量)
    //由于AAObj是一个userdata,所以会调用它metatable中的__index方法,也就是前面设置的Index函数
    //传入的参数是AAObj和需要查找的成员Func1/Func2
    const char *luascript = "AAObj:Func1(1)"
    "AAObj:Func2(2, 3)";
    if (luaL_loadstring(L, luascript) == LUA_OK) {
    if (lua_pcall(L, 0, 0, 0) == LUA_OK) {
    lua_pop(L, lua_gettop(L));
    }
    }
    else
    {
    printf("load failed ");
    return -1;
    }

    }

    int main(int argc, char ** argv) {

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    //lua_register(L, "CreateObject", CreateObject);
    CreateMetaTable(L);
    AA a1(1111), a2(2222);

    BindObj(L, &a1);
    BindObj(L, &a2);

    lua_close(L);
    return 0;
    }

    tsecer@harry: ./a.out
    in func1 x 1 m_x 1111
    in func2 x 2 y 1111 m_x 1
    in func1 x 1 m_x 2222

    2、在C++中调用lua method

    下面的例子比较简单,但是里面展示通过userdata的自定义结构配合metatable完成对C++不同类型对象的访问
    tsecer@harry: cat lua.oo.cpp
    extern "C"
    {
    #include <lua.h>
    #include <lualib.h>
    #include <lauxlib.h>
    }

    #include "string.h" //strcmp

    struct AA
    {
    AA(int x):
    m_x(x)
    {}

    int func1(int x)
    {
    printf("in %s x %d m_x %d ", __func__, x, m_x);
    return 0;
    }
    int func2(int x, int y)
    {
    printf("in %s x %d y %d m_x %d ", __func__, x, m_x);
    return 0;
    }
    int m_x;
    };

    int CallAAFunc1(lua_State *L)
    {
    //第一个参数保存self指针
    AA *pobj = *(AA**)lua_touserdata(L, 1);
    //接下来保存参数列表
    int parm1 = lua_tonumber(L, 2);
    //调用函数
    pobj->func1(parm1);
    return 0;
    }

    int CallAAFunc2(lua_State *L)
    {
    //由于是通过 AAObj::Func2调用,所以状态机中会有三个参数
    //从1到3一次为:self也就是AAObj对象,调用时的两个参数
    AA *pobj = *(AA**)lua_touserdata(L, 1);
    int parm1 = lua_tonumber(L, 2), parm2 = lua_tonumber(L, 3);
    pobj->func2(parm1, parm2);
    return 0;
    }

    //注册个lua的__index方法,当lua需要查找一个结构当前不存在的filed时调用
    int Index(lua_State *L)
    {
    //第一个参数为变量本身,它的metatable中存储有函数到Cfunction的映射
    int imetatable = lua_getmetatable(L, 1);
    if (imetatable != 1)
    {
    printf("get metatable failed ");
    return -1;
    }

    //第二个参数为函数名
    const char *funcname = lua_tostring(L, 2);
    if (funcname == nullptr)
    {
    printf("get funcname failed ");
    return -1;
    }

    AA *pobj = *(AA**)lua_touserdata(L, 1);
    if (strcmp(funcname, "m_x") == 0)
    {//对于成员做一个特殊处理
    lua_pushinteger(L, pobj->m_x);
    }
    else
    {
    //从metatable中以函数名为键值查找cfunction
    lua_getfield(L, -1, funcname);
    }
    return 1;
    }

    int CreateMetaTable(lua_State *L)
    {
    //由于lua中的metatable也是一个简单的table,所以这个地方首先直接创建一个普通的table
    lua_createtable(L, 0, 0);
    //创建metatable中的__index方法
    lua_pushstring(L, "__index");
    //创建该方法对应的C函数
    lua_pushcfunction(L, Index);
    //将新生成的metatable的__index设置为新创建的cfunction
    lua_rawset(L, -3);

    //在metatable中添加转发函数
    //函数名
    lua_pushstring(L, "Func1");
    //C函数地址
    lua_pushcfunction(L, CallAAFunc1);
    //设置hashtable
    lua_rawset(L, -3);
    //在metatable中设置Func2为自定义的转发函数
    lua_pushstring(L, "Func2");
    lua_pushcfunction(L, CallAAFunc2);
    lua_rawset(L, -3);

    //将AA作为全局变量添加到lua虚拟机中
    lua_setglobal(L, "AA");
    return 1;
    }

    int BindObj(lua_State *L, AA *Obj)
    {
    //现在lua中定义一个方法(method),这样在后面才能通过函数名(Func3)调用
    const char *luascript = "function AA:Func3(x) print(self.m_x * 100000 + x); end";
    if (luaL_loadstring(L, luascript) == LUA_OK) {
    if (lua_pcall(L, 0, 0, 0) == LUA_OK) {
    lua_pop(L, lua_gettop(L));
    }
    }
    else
    {
    printf("load failed ");
    return -1;
    }

    //从AA表中找到Func3地址
    lua_getglobal(L, "AA");
    //lua_pushvalue(L, -1);
    //查找其中的Func3方法
    lua_getfield(L, -1, "Func3");
    //创建一个userdata并把它保留在栈中,作为method调用的self参数
    //创建一个只包含一个指针类型的userdata,其中保存C++中创建对象的地址
    *(AA**)lua_newuserdata(L, sizeof(Obj)) = Obj;
    //从lua中读取AA table,该值作为新创建对象的metatable
    lua_getglobal(L, "AA");
    //将新创建对象的metatable(-2中保存的下标)设置为AA(table,栈中下标为-1)
    //该操作执行之后堆栈中的AA table会被自动从堆栈中弹出
    lua_setmetatable(L, -2);
    //函数调用参数入栈
    lua_pushinteger(L, 1234);

    //执行函数调用
    lua_call(L, 2, 1);

    return 0;
    }

    int main(int argc, char ** argv) {

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    //lua_register(L, "CreateObject", CreateObject);
    CreateMetaTable(L);
    AA a1(1111);

    BindObj(L, &a1);

    lua_close(L);
    return 0;
    }

    tsecer@harry:

    四、unlua如何实现

    可以看到,UnLua也是使用userdata结构,并在其中存储C++对象地址,并为对象设置专有的metatable实现。
    UnLua-masterPluginsUnLuaSourceUnLuaPrivateLuaCore.cpp
    /**
    * Push a UObject to Lua stack
    */
    void PushObjectCore(lua_State *L, UObjectBaseUtility *Object)
    {
    if (!Object)
    {
    lua_pushnil(L);
    return;
    }

    void **Dest = (void**)lua_newuserdata(L, sizeof(void*)); // create a userdata
    *Dest = Object; // store the UObject address
    ……
    // the UObject is object instance
    TStringConversion<TStringConvert<TCHAR, ANSICHAR>> ClassName(*FString::Printf(TEXT("%s%s"), Class->GetPrefixCPP(), *Class->GetName()));
    bSuccess = TryToSetMetatable(L, ClassName.Get());
    ……
    }

  • 相关阅读:
    排列数组所有情况
    查到的结果的某个字段在一串字符串之中
    element组件化跳转和路由式跳转
    vue路由and组件操作
    事件 绑定,取消冒泡,拖拽 ,点击,事件委托习题
    窗口属性 和DOM 元素尺寸位置 及习题加强
    DOM树的增删改查 和 Date定时任务
    JS DOM 初做了解,习题笔记
    struts配置及检验
    第一个JSP登录跳转
  • 原文地址:https://www.cnblogs.com/tsecer/p/14691337.html
Copyright © 2011-2022 走看看