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 <iostream>
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的交互方式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容