Lua编程之Lua和C之间互相调用方式

Lua和C调用主要有两种方式:

1.   在C中运行,C函数调用Lua,Lua要注册C函数库,意思就是c程序调用注册到lua中的c函数。

2.   程序在lua中运行,C注册到lua中。

 

第一种方式看起来很奇怪。既然程序主体运行在C中,而且最终使用的也是C中定义的函数,那么为何要将Lua函数注册给Lua,然后再通过Lua调用函数呢?其实lua库在此调用中只是一个中间库,他只是一个table保存C库函数的指针,一旦C函数注册到Lua中,Lua就可以直接通过C函数的引用获取到C函数的地址,这也是我们注册的意义,将C函数的地址提供给Lua。也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。而这个指针只是保存在了Lua虚拟栈中。 

首先看第二种Lua如何调用C函数。

lua可以将C函数注册到lua中,C函数必须遵循统一的原型格式,这个原型定义在lua.h中.

typedef int (*)(lua_State *)

被注册的C函数接收一个单一的lua_State类型的参数,同时返回一个表示返回值个数的数字。函数在将返回值入栈之前无需清理栈,在函数返回之后,Lua会自动清除栈中返回结果下面的所有内容。

用C函数扩展lua时,一般将所有的C函数编译成一个独立的模块,方便增加新的函数。

 

//mylib.c
#include<stdio.h>
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
#include<math.h>
 
/* 所有注册给Lua的C函数具有 
* "typedef int (*lua_CFunction) (lua_State *L);"的原型。  */
static int myadd(lua_State *L){
//如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。
                int a = luaL_checknumber(L, 1);
                int b = luaL_checknumber(L, 2);
/* 这里可以看出,C可以返回给Lua多个结果,      *通过多次调用lua_push*(),之后return返回结果的数量。     */
                lua_pushnumber(L, a+b);
                return 1;
}
//luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。
//每一个元素中包含此函数在Lua中的名字,以及该函数在C库中的函数指针。
//结构体数组中的最后一个元素的两个字段均为NULL(必须),用于提示Lua注册函数已经到达数组的末尾。
static conststruct luaL_Reg mylib [] = {
//mylib[]声明了模块中所有C函数列表,每一项映射了C函数在lua中的命名,比如下面代码中myadd函数在lua中用add表示,列表必须用{NULL,NULL}结束。
        {"add", myadd}, 
        {NULL, NULL}
};

//该C库的唯一入口函数。其函数名有如下几点说明://1. 我们可以将该函数简单的理解为模块的工厂函数。//2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。//3. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定, //   否则将无法调用。extern
  luaL_newlib(L, mylib);
  return 1;
}
将”mylib.c”编译为动态连接库,
 
prompt> gcc mylib
 -fPIC -shared -o mylib
 -Wall 
//call.lua
#!/usr/local/bin/lua
--[[ 这里"require"的参数对应C库中"luaopen_mylib()"中的"mylib"。      C库就放在"a.lua"的同级目录,"require"可以找到。]]
lib=require"mylib"
print(lib.add(1,2))

每个被lua调用的C函数都有自己的私有栈,压入参数的索引从1开始递增,结果值也是直接压入栈中,函数返回时会将压入的参数全部删除,只留下结果值。 luaL_newlib在栈中创建一个table,将mylib数组中的C函数注册进这个table中。luaopen_mylib将这个table中的函数加载进lua环境中。

附加:

1、每一个与Lua通信的C函数都有其独有的虚拟栈。 
2、在极端情况下,打印指定目录中文件的例子可能会造成小小的内存泄漏。在内存空间不足的情况下,l_dir()中的lua_newtable()、lua_pushstring()和lua_settable()都会立即抛出错误并终止程序的运行,这将导致closdir()无法被调用。 
3、通常C库中“特殊的函数”都被定义为公有的(使用extern修饰),而其他函数均被定义为私有的(使用static修饰)。 
4、当你想要使用C函数扩展你的Lua程序时,即使只有一个C函数,也最好使用C库的方式。因为在不久的将来(通常来说会很快),你将需要其他的C函数。

 

C函数调用lua

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h> 
static int l_sin(lua_State *L) {    
//如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。    
double d = luaL_checknumber(L,1);     
lua_pushnumber(L, sin(d));
 /* pushresult */     
/* 这里可以看出,C可以返回给Lua多个结果,    
* 通过多次调用lua_push*(),之后return返回结果的数量。     
*/   
 return1; 
 /* number of results */
}  
int main(void) {    
lua_State *L = luaL_newstate();    // 创建Lua状态机。    
  // 打开Lua状态机"L"中的所有Lua标准库。 
    
lua_pushcfunction(L, l_sin);    //将C函数转换为Lua的"function"并压入虚拟栈。     
lua_setglobal(L, "mysin");
/* 这两句话还有更简单的方法:
* lua_register(L, "mysin", l_sin)     
* 将C函数"l_sin"定义为Lua的全局变量"mysin"。     
* 其实现是如下宏:     
* #define lua_register(L,n,f)  (lua_pushcfunction(L, f), lua_setglobal(L,n))      */    
// 弹出栈顶元素,并在Lua中用名为"mysin"的全局变量存储。      
const char * testfunc ="print(mysin(3.14/ 2))";     
if(luaL_dostring(L,testfunc))   // 执行Lua命令。        
   printf("Failed toinvoke.\n");    
lua_close(L);    // 关闭Lua状态机。     
return0;
}
prompt> gcc main.c-llua-ldl-lm-Wall
prompt>./a.out


总结

Lua在调用 C时候,主体函数实在lua程序中得到运行,此时c程序作为lua的库函数,可在c程序中完成lua程序的扩展。由于Lua是动态类型语言,在Lua语言中没有类型定义的语法,每个值都携带了它自身的类型信息,而C语言是静态类型语言;另一个是Lua使用垃圾收集,可以自动管理内存,而C语言要求程序自己释放分配的内存,需应用程序自身管理内存。为了解决这个两个问题,Lua引入了一个虚拟栈。

所以如果要完成互相调用,要在C程序中提供可供lua调用的函数原型就是 typedef int (*) (lua_State *),其中的lua_State 就是Lua和C相互传递数据时用到的虚拟栈,在C程序中要完成以下几个步骤.

1.定义被调用的函数原型(typedef int (*) (lua_State *))。

2.注册函数名到luaL_Reg 类型的表中tt[],该表是一个{“key”,value}值类型,key表示在lua程序中要访问的函数名字,value表示C程序中函数原型的函数名。列表要以{null,null}结束。

3.要提供一个lua解释器可以调用的函数。该函数是一个公用函数要以extern 申明,意为在lua中直接查找调用。要以luaopen_xxx命名,此函数的功能是把上述luaL_Reg类型的列表tt注册到虚拟栈中。

4.把c程序编译为动态链接库。具体步骤省略。

5.在lua程序中require“xxx”,xxx与luaopen_xxx中的后缀xxx必须相同,这是应为lua解释器查找的原因。

C调用lua程序时,主体函数在C程序中运行,lua提供了很多 C API,也就是提供了一个状态机实现c程序对lua程序的加载和执行。步骤有以下几步:

1.   调用luaL_newstate()函数创建lua状态机 ,返回一个lua_State类型的虚拟栈。

2.   打开状态机。

3.   往虚拟栈中压人数据,

lua_pushcclosure(L,func, 0) //  创建并压入一个闭包
    lua_createtable(L,0, 0)        // 新建并压入一个表 
    lua_pushnumber(L,343)      //  压入一个数字
    lua_pushstring(L, “mystr”)   //  压入一个字符串

压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的。

4. 关闭状态机