• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    把Lua函数传递到C/C++中实例

    问题

    在Lua中,因为函数也是第一类值,所以会出现将函数作为另一个函数的参数,或者函数作 为函数的返回值。这种机制在很多地方都能代码更灵活更简洁,例如:

    复制代码 代码如下:

    table.sort(table [,comp])

    这里的comp就要求传入一个函数,我们在调用时,大概会有如下形式:

    复制代码 代码如下:

    table.sort(t, comp) -- 直接写函数名
        table.sort(t, local_comp) -- 某个局部函数
        table.sort(t, function (a, b) xxx end ) -- 临时构造一个匿名函数

    其中最后一种方式最为灵活,任意时候在需要的时候构造一个匿名函数。这种在Lua自身的 环境中使用,自然没有问题。但是,当我们在C/C++中注册一些函数到Lua环境中,而这些 函数也需要使用函数参数的时候,问题就出来了。

    Lua本身是不支持将Lua函数作为函数参数传入C/C++的,不管这个想要传入的函数是全局的 、局部的、或者匿名的(匿名的本质上也算局部的)。一般情况下,我们唯一的交互方式, 不是传入一个函数,而是一个全局函数名。C/C++保存这个函数名,在需要回调Lua的时候, 就在Lua全局表中找到这个函数(根据函数名),然后再调用之。情况大致如下:

    复制代码 代码如下:

    function lua_func () xxx end
        cfunc(lua_func) -- wrong
        cfunc("lua_func") -- right

    我们这回的脚本模块,策划会大量使用需要回调函数的C/C++函数。显然,创建大量的全局 函数,先是从写代码的角度看,就是很伤神的。

    解决

    我们最终需要的方式,大概如下:

    复制代码 代码如下:

    cfunc(lua_func) -- ok
        cfunc(function () xxx end) -- ok
        local xxx = function () xxx end
        cfunc(xxx) -- ok

    要解决这个问题,我的思路是直接在Lua层做一些包装。因为C/C++那边仅支持传入一个全局 函数名(当然不一定得全局的,根据实际情况,可能在其他自己构造的表里也行),也就是 一个字符串,所以我的思路就是将Lua函数和一个唯一的字符串做映射。

    复制代码 代码如下:

    function wrap (fn)
            local id = generate_id()
            local fn_s = "__callback_fn"..id
            _G[fn_s] = fn
            return fn_s
        end

    这个wrap函数,就是将一个函数在全局表里映射到一个字符串上,那么在使用时:

    复制代码 代码如下:

    cfunc(wrap(function () xxx end))
        cfunc(const char *fn_name, xxx); -- cfunc的原型

    cfunc是C/C++方注册进Lua的函数,它的原型很中规中矩,即:只接收一个函数名,一个字 符串,如之前所说,C/C++要调用这个回调函数时,就根据这个字符串去查找对应的函数。 脚本方在调用时,如果想传入一个匿名函数了,就调用wrap函数包装一下即可。

    一个改进

    上面的方法有个很严重的问题,在多次调用wrap函数后,将导致全局表也随之膨胀。我们需 要想办法在C/C++完成回调后,来清除wrap建立的数据。这个工作当然可以放到C/C++来进行 ,例如每次发生回调后,就设置下全局表。但这明显是不对的,因为违背了接口的设计原则 ,这个额外的机制是在Lua里添加的,那么责任也最好由Lua来负。要解决这个问题,就可以 使用Lua的metamethods机制。这个机制可以在Lua内部发生特定事件时,让应用层得到通知。 这里,我们需要关注__call事件。

    Lua中只要有__call metamethod的值,均可被当作函数调用。例如:

    复制代码 代码如下:

    ab(1, 2)

    这里这个函数调用形式,Lua就会去找ab是否有__call metamethod,如果有,则调用它。这 个事实暗示我们,一个table也可以被调用。一个改进的wrap函数如下:

    复制代码 代码如下:

    local function create_callback_table (fn, name)
            local t = {}
            t.callback = fn
            setmetatable (t, {__call =  -- 关注__call
                function (func, ...) -- 在t(xx)时,将调用到这个函数
                    func.callback (...) -- 真正的回调
                    del_callback (name) -- 回调完毕,清除wrap建立的数据
                end })
            return t
        end
       
        function wrap (fn)
            local id = generate_func_id() -- 产生唯一的id
            local fn_s = "_callback_fn"..id
            _G[fn_s] = create_callback_table(fn, fn_s) -- _G[fn_s]对应的是一个表
            return fn_s
        end

    在我们的C/C++程序中,依然如往常一样,先是从_G里取出函数名对应的对象。虽然这个对 象现在已经是一个table。然后lua_call。

    上面的代码是否会在原有基础上增加不可接受的性能代价?虽然我没有做过实际测试,但是 从表明看来,排除meta table在Lua里的代价,也就多了几次Lua函数调用。

    最后,感叹一下,Lua里的table及metatable机制,实在非常强大。这种强大不是功能堆砌 出来的强大,而是简单东西组合出来的强大。其背后的设计思想,着实让人佩服。

    4.26.2011 Update

    之前的文中说“Lua本身是不支持将Lua函数作为函数参数传入C/C++的“,这句话严格来说不 正确(由某网友评论)。假设函数cfun由c/c++注册,我们是可以编写如下代码的:

    复制代码 代码如下:

    cfunc(print) -- 传入Lua函数

    但是问题在于,我们无法取出这个函数并保存在c/c++方。Lua提供了一些接口用于取cfunc 的参数,例如luaL_checknumber(封装lua_tonumber)。但没有类似luaL_checkfunction的 接口。Lua中的table有同样的问题。究其原因,主要是Lua中的函数没有直接的c/c++数据结 构对应。

    您可能感兴趣的文章:
    • Lua和C/C++互相调用实例分析
    • C++利用LuaIntf调用Lua的方法示例
    • Lua中调用C++函数示例
    • 使用Lua来扩展C++程序的方法
    • Lua和C++语言的交互详解
    • C++与Lua交互原理实例详解
    上一篇:使用lua实现php的var_dump()函数功能
    下一篇:Lua中实现StringBuffer功能
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    把Lua函数传递到C/C++中实例 把,Lua,函数,传递,到,C++,