zoukankan      html  css  js  c++  java
  • Lua“控制”C

    【前言】

    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, 10);
          printf("Hello World");
          return 1;
      }
    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
    ");
        lua_pushnumber(L, 10);
        return 1;
    }
    
    static const struct luaL_Reg myLib[] = 
    {
        {"test", testFunc},
        {NULL, NULL}
    };
    
    int luaopen_testLib(lua_State *L)
    {
        luaL_register(L, "testLib", myLib);
        return 1; // 把表压入了栈中,所以就需要返回1
    }

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

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

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

  • 相关阅读:
    Sharding-JDBC多数据源动态切换
    U 盘安装 CentOS 7 时出现 No Caching mode page found 问题的解决
    sudo 密码直接添加到命令行以方便实现脚本自动化
    Python3 Windows 虚拟环境的若干问题
    20 张图让你彻底弄懂 HTTPS 原理!
    全网写得最好的分库分表之 Sharding-JDBC 中间件介绍
    以为线程池很简单,结果第一道题就被干趴下了!
    以为线程池很简单,没想到第一问就被干趴下了
    分布式事务,看这篇就够了!
    我是一个线程池
  • 原文地址:https://www.cnblogs.com/ring1992/p/6002890.html
Copyright © 2011-2022 走看看