【前言】

Lua语言本身是一个功能非常有限,而比较单调的语言,而且标准库也非常的平庸,它的NB之处就在于,它能和C、C++等高级语言完美“私通”。我们可以使用C、C++语言去给Lua写一个完美的库,让Lua调用。而这里,我就要好好的总结一下,如何让Lua来“控制”C。

【基本知识】

上面说了,使用C、C++写一个库,让Lua调用,这就是扩展Lua,应用程序将新的C函数注册到Lua中,这样Lua就可以“控制”C了,说起来貌似很简单,其实并不是那样子的。

Lua能调用 C函数,但并不意味着Lua可以调用任意C函数。在《C“控制”Lua》一文中,当C语言调用Lua函数时,它必须遵循一个简单的协议,以此来向Lua传递参数,并从Lua获取结果。同样,对于一个能被Lua调用的C函数,它也必须遵循一个获取参数和返回结果的协议。此外,还必须注册C函数,以便用某种适当的方式将函数地址告诉Lua。

当Lua调用C函数时,也使用了一个与C语言调用Lua时相同的栈。C语言从栈中获取函数参数,并将结果压入栈中。为了在栈中将函数结果与其它值区分开,C函数还应返回其压入栈中的结果数量。

栈不是一个全局性的结构,这是一个很重要的概念。每个函数都有自己的局部私有栈。当Lua调用一个C函数时,第一个参数总是这个局部栈的索引1。即使这个C函数调用了Lua代码,并且Lua代码中又调用了相同的C函数,这些C函数调用只看到自己的私有栈,它们的第一个参数都是索引1。

上面说到了需要注册C函数,这里就有特殊要求了,所有注册到Lua中的函数都具有相同的原型,该原型在lua.h中定义,原型如下:

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

可以看到,这个C函数仅有一个参数,即Lua的状态。它返回一个整数,表示其压入栈中的返回值数量。在它返回后,Lua会自动删除栈中结果之下的内容。因此,这个函数无需在压入结果前清空栈。

【不练是空把式】

之前我说过,Lua的底层是C;所以我们可以在Lua的解释器源码(lua.c)中加入我们的C函数,然后再重新编译这个Lua解释器,得到exe解释器以后,就可以调用我们的函数了。我相信我说了这么多,你肯定一头雾水,好吧。你应该这样做:

  1. 去这里下载Lua的源代码,是的,Lua是开源的;
  2. 按照源代码中的文件说明,编译源代码,不管你使用什么工具(我用的是Visual Studio 2013),编译它;如果你说,你对编译还是不清楚,请告诉我,我帮你;
  3. 对了,你只需要得到DLL和解释器就好了,不用得到编译器。

好了,加上我们的C函数代码吧,让Lua可以调用我们的C函数。

  1. 在lua.c文件中加入以下函数定义:

    // This is my function
    static int testFunc(lua_State *L)
    {
    // 向函数栈中压入一个值
    lua_pushnumber(L, );
    printf("Hello World");
    return ;
    }
  2. 在pmain函数中,luaL_openlibs函数后加入以下代码:
    lua_pushcfunction(L, testFunc);
    lua_setglobal(L, "Test");

这就相当于注册C函数到Lua中。接下来重新编译解释器,得到一个exe文件,运行这个exe文件吧。运行截图如下:

等什么,你也赶紧试一试。单击这里下载我编译好的文件。

【C模块】

上面我们都是在lua.c文件中添加代码,这就有人说了,修改人家的代码算怎么回事?如何进行模块开发,我自己定义一个模块,让Lua直接调用我的模块就好了,是的,没问题,现在就来说说如何开发一个C模块,让Lua调用我们开发的C模块。

《Lua中的模块与包》一文中也说到了,Lua模块是一个程序块,其中定义了一些Lua函数,这些函数通常存储为table的条目。一个为Lua编写的C模块可以模仿这种行为。除了C函数的定义外,C模块还必须定义一个特殊的函数,这个函数相当于一个Lua模块中的主程序块。它应该注册模块中所有的C函数,并将它们存储在一个适当的地方,并且这个函数还应初始化模块中所有需要初始化的东西。

Lua通过这个注册过程记录下C函数,然后使用这些函数地址直接调用它。也就是说,Lua调用C函数时,并不依赖于函数名、包的位置或可见性规则,而只依赖于注册时传入的函数地址。通常,C模块中只有一个公共(外部)函数,用于创建C模块,而其它所有函数都是私有的,在C语言中声明为static。

在将就OOP和模块开发的今天,最好将代码设计为一个C模块。有以下优点:

  1. 便于日后的扩展;
  2. 便于代码的集中管理;
  3. 便于代码分发。

接下来,我就编写一个C模块供Lua调用。定义这样的一个C模块主要要完成以下几个任务:

  1. 定义模块函数,就是这个模块可以提供的一些功能函数;
  2. 定义一个luaL_Reg类型的数组,数组的每个元素都是luaL_Reg结构,这个结构有两个字段,一个 字符串和一个函数指针;
  3. 最后声明一个主函数,注册所有的函数。

现在贴上代码:

#ifndef _TESTLIB_H
#define _TESTLIB_H #ifdef __cplusplus
extern "C" {
#endif #include "lua.h"
#include "lualib.h"
#include "lauxlib.h" #ifdef TESTLIB_EXPORTS
#define TESTLIB_EXPORTS __declspec(dllexport)
#else
#define TESTLIB_EXPORTS __declspec(dllimport)
#endif // 全局的函数,用于导出,内部完成注册和初始化功能
TESTLIB_EXPORTS int luaopen_testLib(lua_State *L); #ifdef __cplusplus
}
#endif
#endif

这个是头文件,如果对这个头文件不熟悉的同学,请转移到《在Visual Studio中使用C++创建和使用DLL》这篇文章。

#include <stdio.h>
#include "testLib.h" static int testFunc(lua_State *L)
{
printf("http://www.jellthink.com\n");
lua_pushnumber(L, );
return ;
} static const struct luaL_Reg myLib[] =
{
{"test", testFunc},
{NULL, NULL}
}; int luaopen_testLib(lua_State *L)
{
luaL_register(L, "testLib", myLib);
return ; // 把表压入了栈中,所以就需要返回1
}

这个就是主要的实现文件了,主要是使用luaL_register函数完成注册功能。以下是Lua测试代码:

require "testLib"
local a = testLib.test()
print(a)

require会去主动搜索testLib.dll文件,当然了,在Linux系统上,就应该是testLib.so文件了。点击这里下载工程文件。

Lua“控制”C的更多相关文章

  1. 用 Lua 控制 MIDI 合成器来播放自定义格式乐谱

    用 Lua 控制 MIDI 合成器来播放自定义格式乐谱 作者: FreeBlues 最新: https://www.cnblogs.com/freeblues/p/9936844.html 说明: 本 ...

  2. 用Lua控制Nginx静态文件的url访问权限

    需求背景:比如我们有一个存储文件的web服务器,一般通过url可直接访问到:http://127.0.0.1/uploads/test.rar,如果我们需要限制别人的访问,可以通过添加lua脚本来控制 ...

  3. Lua教程

    Lua中的类型与值 Lua中的表达式 Lua中的语句 Lua中的函数 Lua中的闭包 Lua 中 pairs 和 ipairs 的区别 Lua中的迭代器与泛型for Lua中的协同程序 Lua中__i ...

  4. 基于nginx + lua实现的反向代理动态更新

    大家都知道,nginx是当前应用非常广泛的web服务器,热度因为他的高并发高性能高可靠性,且轻量级!牛逼的不行,不多说这些. 今天要介绍的是,如何基于nginx和lua脚本,也就是在openresty ...

  5. 浅析C++绑定到Lua的方法

    注:原文也在公司内部论坛上发了  概述       尽管将C++对象绑定到Lua已经有tolua++(Cocos2d-x 3.0用的就是这个).LuaBridge(我们游戏client对这个库进行了改 ...

  6. Unity的学习笔记(XLua的初学用法并在lua中使用unity周期函数)

    自己最近也在研究怎么用lua控制UI,然后看着网上介绍,决定选用XLua,毕竟TX爸爸出的,有人维护,自己琢磨着怎么用,于是弄出来一个能用的作为记录. 当然,XLua主要是用于热更新,我自己是拿来尝试 ...

  7. 跨平台轻量级redis、ssdb代理服务器(C++ 11编写)

    dbproxy 是我业余采用C++11编写的跨平台代理服务器(并使用lua和自己的网络库),以扩展系统负载,同时使用多个后端数据库,后端数据库支持redis和ssdb. 需要由用户自己编写lua脚本控 ...

  8. 【转】频点CTO张成:基于Cocos2d的MMORPG开发经验

    http://www.9ria.com/plus/view.php?aid=27698 作者: zhiyuanzhe3 发表时间: 2013-06-29 17:46 6月29日,由9Tech社区.51 ...

  9. 再说C模块的编写(1)

    [前言] 在<Lua“控制”C>中对Lua调用C函数做了初步的学习,而这篇才是重中之重,这篇文章会重点的总结C模块编写过程中遇到的一些问题,比如数组操作.字符串操作和C函数的状态保存等问题 ...

随机推荐

  1. React-propsType和defaultProps

    TodoItem.propTypes={ content:PropTypes.string, text:PropTypes.string.isRequired, handleDeleteItem:Pr ...

  2. 【原创】分布式事务之TCC事务模型

    引言 在上篇文章<老生常谈--利用消息队列处理分布式事务>一文中留了一个坑,今天来填坑.如下图所示 如果服务A和服务B之间是同步调用,比如服务C需要按流程调服务A和服务B,服务A和服务B要 ...

  3. C#自定义应用程序上下文对象+IOC自己实现依赖注入

    以前的好多代码都丢失了,加上最近时间空一些,于是想起整理一下以前的个人半拉子项目,试试让它们重生.自从养成了架构师视觉 搭建框架之后,越来 越看不上以前搭的框架了.先撸个上下文对象加上实现依赖注入.由 ...

  4. codeforces#439 D. Devu and his Brother (二分)

    题意:给出a数组和b数组,他们的长度最大1e5,元素范围是1到1e9,问你让a数组最小的数比b数组最大的数要大需要的最少改变次数是多少.每次改变可以让一个数加一或减一 分析:枚举a数组和b数组的所有的 ...

  5. ABP中的拦截器之EntityHistoryInterceptor

    今天我们接着之前的系列接着来写另外一种拦截器EntityHistoryInterceptor,这个拦截器到底是做什么的呢?这个从字面上理解是实体历史?这个到底是什么意思?带着这个问题我们来一步步去分析 ...

  6. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  7. 五、Java多人博客系统-2.0版本-数据库设计

    数据库设计表如下:文章类别表.文章表.评论表.留言表. 文章列表表:存放文章类别,首页菜单生成也是从这个表取的. 文章表:存放文章标题.发表时间.内容等信息. 评论表:文章评论内容. 留言表:用户发表 ...

  8. 【深入Java虚拟机】之三:内存溢出

    为了更直接的了解各区域,下面我们来看几个示例. 1.Java 堆溢出 下面的程中我们限制Java 堆的大小为20MB,不可扩展(将堆的最小值-Xms 参 数与最大值-Xmx 参数设置为一样即可避免堆自 ...

  9. CF618G(利用浮点数精度+矩乘优化DP)

    这题真的太神辣,%了一发题解,原来还能这么搞QWQ 设\(A_{i,j}\)表示不加任何限制时,第\(i\)个格子会出现权值为\(j\)的史莱姆的概率,则有: \[A_{i,j}=A_{i,j-1}* ...

  10. yii2 redirect重定向

    redirect使用方法 $this->redirect(array('/site/contact','id'=>12)); //http://www.kuitao8.com/testwe ...