lua与C/C++交互

时间:2024-01-16 18:42:20
Lua设计小巧很容易与C/C++进行交互,下面我们具体讲解C/C++中如何调用lua,而lua中又如何调用C代码.
首先lua和C交互的一个重要的数据结构lua_State,它是进行数据交换的堆栈,按照严格的LIFO规则(后进先出,始终存取栈顶),lua提供了一些函数来压栈例如:
void   (lua_pushnil) ( lua_State *L );
void   (lua_pushnumber) ( lua_State *L , lua_Number n);
void   (lua_pushinteger) ( lua_State *L , lua_Integer n);
void   (lua_pushlstring) ( lua_State *L , const char *s , size_t l);
void   (lua_pushstring) ( lua_State *L , const char *s );
从函数名称我们可以看出我们可以向栈中压入nil, number, integer, string等类型.
lua还提供了函数,对堆栈进行插入,删除等操作例如:
int    (lua_gettop) ( lua_State *L );
void   (lua_settop) ( lua_State *L , int idx);
void   (lua_pushvalue) ( lua_State *L , int idx);
void   (lua_remove) ( lua_State *L , int idx);
void   (lua_insert) ( lua_State *L , int idx);
void   (lua_replace) ( lua_State *L , int idx);
int    (lua_checkstack) ( lua_State *L , int sz);
函数中的idx参数是数据在栈中的索引,栈中的我们以第一个压入栈中的索引为1,第二个为2,也可以使用负数索引,-1始终表示栈顶的索引,-2表示栈顶下面的第二个元素索引.
lua_gettop函数获取堆栈中元素的个数,也是栈顶元素索引,一个负数索引-x对应于正数索引为gettop-x+1;
lua_settop设置栈顶到指定索引,例如:lua_settop(L, 0)也就是清空堆栈;
lua_remove和lua_insert表示先指定索引插入和删除元素,其他元素作相应移动;
lua_replace从栈顶弹出元素设置到指定的索引位置;
另外我们还可以通过lua_is*一系列函数来检测栈中元素类型,例如:
int  ( lua_isnumber) (lua_State *L, int idx );
int  ( lua_isstring) (lua_State *L, int idx );
int  ( lua_iscfunction) (lua_State *L, int idx );
int  ( lua_isuserdata) (lua_State *L, int idx );
我们可以通过这些函数判断指定索引位置的元素是否是我们期望的类型如:number,string,function等.
好了介绍了这么多函数,我们要实践下写个小程序,实验下各种函数:
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#pragma comment (lib, "lua5.1.lib")
#ifdef __cplusplus
}
#endif
static int cadd( lua_State *L )
{
    /*lua中调用此函数,参数从栈中弹出 */
    double a = lua_tonumber( L, -2);
    double b = lua_tonumber( L, -1);
    lua_pushnumber(L , a + b);
    /*返回返回值个数*/
    return 1;
}
void load_string (lua_State* L)
{
    /*获取全局变量str,并置于栈顶 */
    lua_getglobal(L , "str");
    if (!lua_isstring (L, -1))
    {
        printf("Get 'str' error, %s\n" , lua_tostring( L, -1));
    }
    const char * pstr = lua_tostring(L , -1);
    printf("str : %s\n" , pstr);
    /*弹出栈中压入的元素,使栈恢复原来状态 */
    lua_pop(L , 1);
}
void load_number (lua_State * L)
{
    /*获取全局变量num,并置于栈顶 */
    lua_getglobal(L , "num");
    if (!lua_isnumber (L, -1))
    {
        printf("Get 'num' error, %s\n" , lua_tostring( L, -1));
    }
    double num = lua_tonumber( L, -1);
    printf("num : %lf\n" , num);
    lua_pop(L , 1);
}
void load_table (lua_State * L)
{
    /*获取全局变量data,并置于栈顶 */
    lua_getglobal(L , "data");
    const char * szkey[4] = { "lua", "ruby" , "python", "perl"};
    printf("data = { " );
    for (int i = 0; i < 4; ++ i )
    {
        /*在table 在栈顶时,获取 table中key 所对应的value值 ,首先将key 入栈, 此时table位于栈中索引为 ,*/
        /*然后通过调用gettable将 key对应value 压入栈顶, 最后弹出刚才已经读取的元素 ,此时table 重新成为栈顶元素 */
        lua_pushstring(L , szkey[ i]);
        lua_gettable(L , -2);
        printf(" %s = '%s' " , szkey[ i], lua_tostring (L, -1));
        lua_pop(L , 1);
    }
    printf(" }\n" );
    lua_pop(L , 1);
}
void call_luafunc (lua_State * L)
{
    /*C中调用lua 函数,首先将 lua函数压入栈顶,然后依次压入函数参数 , 函数参数压入从左到右*/
    lua_getglobal(L , "luaadd");
    lua_pushnumber(L , 10);
    lua_pushnumber(L , 20);
    /*保护模式运行luaadd函数 */
    lua_pcall(L , 2, 1, 0);
    printf("call luaadd result sum : %lf\n" , lua_tonumber( L, -1));
    lua_pop(L , 1);
}
void callback_cfunc (lua_State * L)
{
    lua_getglobal(L , "cfunc");
    lua_pushnumber(L , 10);
    lua_pushnumber(L , 20);
    lua_pcall(L , 2, 1, 0);
    printf("call back cadd result sum : %lf\n" , lua_tonumber( L, -1));
    lua_pop(L , 1);
}
void printtable (lua_State * L)
{
    lua_getglobal(L , "printtable");
    /*这里我们new 了一个table然后设置值 ,在lua 中进行打印 */
    lua_newtable(L );
    lua_pushstring(L , "name");
    lua_pushstring(L , "Tom");
    lua_settable(L , -3);
    lua_pushstring(L , "age");
    lua_pushstring(L , "20");
    lua_settable(L , -3);
    lua_pcall(L , 1, 0, 0);
}
int main (void)
{
    lua_State *L = lua_open();
    luaL_openlibs(L );
    /*载入lua 文件*/
    luaL_loadfile(L , "test.lua");
    /*这步很重要(test.lua中的 chunk放入堆栈)*/
    lua_pcall(L , 0, 0, 0);
    /*从lua 中读取字符串 */
    load_string(L );
    /*从lua 中读取数字类型 */
    load_number(L );
    /*从lua 中读取表类型 */
    load_table(L );
    /*调用lua 中函数*/
    call_luafunc(L );
    /*从lua 调用C函数 */
    callback_cfunc(L );
    /*在C 中建立一个 lua表类型, 在lua中进行打印输出 */
    printtable(L );
    lua_close(L );
    return 0;
}
--test.lua
str = "This is a string."
num = 22
data = {lua="lua", ruby="ruby", python="python", perl="perl"}

function luaadd(a, b)
        return a + b
end

function cfunc(a, b)
        --回调C中cadd函数,传递a, b两个参数,从左向右压入
        return cadd(a, b)
end

function printtable(t)
        for key in pairs(t) do
                print(key .. ":" .. t[key])
        end
end

一.要在C/C++中调用lua我们首先在C/C++中包含下面头文件,如果是C++程序则需要一个宏来保证,编译器生成的函数名按照C语言规则.
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#pragma comment (lib, "lua5.1.lib")
#ifdef __cplusplus
}
#endif
二.我们在main函数中依次调用了一系列函数打开lua环境, 加载标准lua库,然后我们调用luaL_loadfile函数加载一个lua文件,这里只进行了编译lua文中chunk到一个函数中并未进行调用,下面lua_pcall进行调用执行,这步
非常重要,这步就是调用执行编译生成的函数,否则我们后面的调用均不会成功的.另外lua_pcall是从C/C++中调用lua函数,它运行在保护模式,函数调用出错并不会导致程序崩溃,但会返回错误,还有另外一个函数lua_call运行在
非保护模式下.
三.具体函数功能注释已经写的很清楚了,函数调用时参数入栈出栈操作其实和C函数调用时的概念是一致的,明白C语言函数调用的过程,其实很容易理解.
四.lua和C/C++还可以交互userdata类型数据,各种错误处理等这里不再讲述.