C与Lua的互调

Lua源码下载
本篇主要介绍Lua与C的互调方式。
首先下载Lua源码,这里使用Lua5.15。
lua.c和luac.c都有main方法。lua.c的main是编译运行时用的,luac.c的main是编译一个将lua代码编译成二进制代码的软件用的,使用exe打包时需要移除其中一个,这里移除luac.c。
项目地址

Lua提供了下方三个伪索引,所谓伪索引就是数据不在栈上,却可以像操作栈内元素一样使用。这里我们主要查看LUA_REGISTRYINDEX为注册表索引,可通过该索引访问到注册表。该表内只有两个子表:_LOADED和_LOADLIB。_LOADED表用于存储模块,默认有string、table、coroutine、package、_G等Lua基础模块,_LOADLIB表内只有一个__gc函数。
LUA_GLOBALSINDEX为全局表索引,即可通过该索引访问到上面的_G模块。

#define LUA_REGISTRYINDEX   (-10000)
#define LUA_ENVIRONINDEX    (-10001)
#define LUA_GLOBALSINDEX    (-10002)
//iapi.c
static TValue *index2adr (lua_State *L, int idx) {
  if (idx > 0) {
    TValue *o = L->base + (idx - 1);
    api_check(L, idx <= L->ci->top - L->base);
    if (o >= L->top) return cast(TValue *, luaO_nilobject);
    else return o;
  }
  else if (idx > LUA_REGISTRYINDEX) {
    api_check(L, idx != 0 && -idx <= L->top - L->base);
    return L->top + idx;
  }
  else switch (idx) {  /* pseudo-indices */
    case LUA_REGISTRYINDEX: return registry(L);  //获取注册表
    case LUA_ENVIRONINDEX: {  //获取当前函数环境
      Closure *func = curr_func(L);
      sethvalue(L, &L->env, func->c.env);
      return &L->env;
    }
    case LUA_GLOBALSINDEX: return gt(L);  //获取全局表
    default: {
      Closure *func = curr_func(L);
      idx = LUA_GLOBALSINDEX - idx;
      return (idx <= func->c.nupvalues)
                ? &func->c.upvalue[idx-1]
                : cast(TValue *, luaO_nilobject);
    }
  }
}

Lua通过require加载模块,首先会在package.loaded表中查找(loaded表也就是使用LUA_REGISTRYINDEX访问到的表),不存在再加载。

当我们需要为Lua制作插件时,可以使用CPP项目生成dll文件供lua调用。
下方为加载模块模板,函数名格式为luaopen_DLL文件名_自定义模块名

__declspec(dllexport) int luaopen_LuaAPI_core(lua_State* L);

#include 
extern "C"
{
#include "lua.h"
#include "LuaAPI.h"
#include "lauxlib.h"
}

static int Hello(lua_State* L)
{
    std::cout << "hello" << std::endl;
    return 0;
}

static luaL_reg Functions[] = //注册函数表,函数名对应函数地址
{
    {"hello",Hello},
};

int luaopen_LuaAPI_core(lua_State * L) 
{
    luaL_openlib(L, "luo",Functions,0);  //将luo模块加入_LOADED表内,可以改为加入_G表内

    std::cout << "LuaAPI.core注册完成" << std::endl;  //require  "LuaAPI.core"
    return 1;  //表示返回一个参数
}

lua调用

local core=require "LuaAPI.core"
core.hello(); --或者luo.hello

上方Lua代码只是调用了一个C函数,并没有形参和返回值接口。Lua与宿主语言的交互通过一个虚拟栈调用,也就是说所有信息都包含在LuaState这个结构体内。
Lua可以将参数压入栈内传到C函数内,C函数从栈中获取参数并处理后可以将返回值压入栈内,传到Lua中。这里实现Lua调用一个C函数完成加法操作

static int add(lua_State* L)
{
    if (lua_type(L, -2) == LUA_TNUMBER && lua_type(L, -1) == LUA_TNUMBER)   //参数1和2都为number类型
    {
        //参数从左到右入栈,则从右到左出栈
        int a=lua_tointeger(L, -2);     //获取参数1
        int b = lua_tointeger(L, -1);   //获取参数2
        lua_pop(L,2);  //弹出两个参数

        int sum = a + b;
        lua_pushinteger(L, sum);  //将返回值压入栈
    }
    else
    {
        lua_error(L);
    }

    return 1; //表示返回一个参数
}

//见上,注册函数中加入add
static luaL_reg Functions[] = 
{
    {"hello",Hello},
    {"add",add},
};

lua调用

local core=require "LuaAPI.core"
core.hello(); --或者luo.hello

local result=core.add(1,2);
print(result)
Lua虚拟栈

附录

一个简单的打印栈函数,不打印嵌套表

static std::string typeNames[]=
{
"nil",
"boolean",
"light userdata" ,
"number",
"string",
"table",
"function",
"userdata",
"thread",
};
static void print_table(lua_State* L, int index, int nIndex)
{
    printf("%d\t table\t", index);
    lua_pushvalue(L, index); //将需打印的table复制至栈顶

    lua_pushnil(L);     //压入初始key
    while (lua_next(L, -2))  //遍历表
    {
        switch (lua_type(L,-2))//key值
        {
        case  LUA_TBOOLEAN:
            std::cout << "key:" << (lua_toboolean(L, -2) == 1) << "\t";
            break;
        case LUA_TNUMBER:
            std::cout << "key:" << lua_tonumber(L, -2) << "\t";
            break;
        case LUA_TSTRING:
            std::cout << "key:" << lua_tostring(L, -2) << "\t";
            break;
        default:  
            std::cout << "key type:" << typeNames[lua_type(L, -2)] << "\t";
            break;
        }
        std::cout << ",";

        switch (lua_type(L, -1))//value值
        {
        case  LUA_TBOOLEAN:
            std::cout << "value:" << (lua_toboolean(L, -1) == 1) << "\t";
            break;
        case LUA_TNUMBER:
            std::cout << "value:" << lua_tonumber(L, -1) << "\t";
            break;
        case LUA_TSTRING:
            std::cout << "value:" << lua_tostring(L, -1) << "\t";
            break;
        default:    //表嵌套未实现
            std::cout << "value type:" << typeNames[lua_type(L, -1)] << "\t";
            break;
        }

        lua_pop(L, 1);      //弹出一个value,才能获取下一个value
    }

    lua_pop(L, 1); //弹出之前复制的表
    printf("%10d\n", nIndex);
}

static int print_stack(lua_State* L)
{
    int stackCount = lua_gettop(L);
    if (stackCount==0)
    {
        return 0;
    }

    std::cout << "====================栈顶===================" << std::endl;
    for (int i = stackCount; i > 0; i--)    //存在table类型的话需要从右往左取参,所以一般从栈底开始
    {
        int nIndex = i - stackCount - 1;

        switch (lua_type(L, i))
        {
        case LUA_TNIL:
            printf("%d\t nil \t%d\n", i, nIndex);
            break;
        case LUA_TBOOLEAN:
            printf("%d\t boolean:%d \t%d\n", i, lua_toboolean(L, i) == 1, nIndex);
            break;
        case LUA_TLIGHTUSERDATA:
            printf("%d\t light userdata \t%d\n", i, nIndex);
            break;
        case  LUA_TNUMBER:
            printf("%d\t number:%lf \t%d\n", i, lua_tonumber(L, nIndex), nIndex);
            break;
        case LUA_TSTRING:
            printf("%d\t string:%s \t%d\n", i, lua_tostring(L, i), nIndex);
            break;
        case LUA_TTABLE:
            print_table(L, i, nIndex);
            break;
        case LUA_TFUNCTION:
            printf("%d\t function \t%d\n", i, nIndex);
            break;
        case LUA_TUSERDATA:
            printf("%d\t userdata \t%d\n", i, nIndex);
            break;
        case LUA_TTHREAD:
            printf("%d\t thread \t%d\n", i, nIndex);
            break;
        }
    }
    std::cout << "====================栈尾===================" << std::endl;
    return 0;
}

Lua文件的加载路径为package.path,可自行修改,默认使用exe文件路径为相对路径的基准。

Lua默认不支持中文变量,可以修改llex.c的llex方法使其支持,有三处需要修改,在下方标出。
llex.c的422行到431行

        else if (isalpha(ls->current) || ls->current == '_' || ls->current > 0X80) //第一处ls->current >0x80
        {
          /* identifier or reserved word */
          TString *ts;
          do {
              if (ls->current > 0X80) //第二处
              {
                  save_and_next(ls);
             }
            save_and_next(ls);
          } while (isalnum(ls->current) || ls->current == '_' || ls->current > 0X80); //第三处

tolua之C#与Lua的交互方式

你可能感兴趣的:(C与Lua的互调)