Lua_第25章 调用 C 函数

第25章 调用 C 函数 

扩展 Lua 的基本方法之一就是为应用程序注册新的 C 函数到 Lua中去

当我们提到 Lua 可以调用 C 函数,不是指 Lua 可以调用任何类型的 C 函数(有一些包可以让 Lua 调用任意的 C 函数,但缺乏便捷和健壮性)。正如我们前面所看到的,当C 调用 Lua函数的时候,必须遵循一些简单的协议来传递参数和获取返回结果。相似的, 从Lua 中调用 C 函数,也必须遵循一些协议来传递参数和获得返回结果。另外,从
Lua 调用 C 函数我们必须注册函数,也就是说,我们必须把C 函数的地址以一个适当的方式传递给 Lua解释器

当 Lua调用 C 函数的时候,使用和 C调用Lua 相同类型的栈来交互。C 函数从栈中获取她的参数,调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值, 每个 C 函数还会返回结果的个数(the function returns(in C) the number of results it is leaving on  the stack)。这儿有一个重要的概念:用来交互的栈不是全局变量,每一个函数都有他自己的私有栈。当
Lua调用 C 函数的时候,第一个参数总是在这个私有栈的index = 1 的位置。甚至当一个C函数调用 Lua 代码(Lua 代码调用同一个 C 函数或者其他的C函数),每一个 C 函数都有自己的独立的私有栈,并且第一个参数在 index =1 的位置。

25.1C 函数

先看一个简单的例子,如何实现一个简单的函数返回给定数值的 sin  值(更专业的 实现应该检查他的参数是否为一个数字):

static int l_sin (lua_State *L) {
    double d = lua_tonumber(L, 1); /* get argument */
    lua_pushnumber(L, sin(d));/* push result */
    return 1;   /* number ofresults */
}

任何在 Lua  中注册的函数必须有同样的原型,这个原型声明定义就是 lua.h  中的lua_CFunction:

typedef int (*lua_CFunction) (lua_State *L);

从 C 的角度来看,一个 C函数接受单一的参数 Lua state,返回一个表示返回值个数的数字。所以,函数在将返回值入栈之前不需要清理栈,函数返回之后,Lua 自动的清除栈中返回结果下面的所有内容。

我们要想在 Lua  使 用这个函数,还必须首先注册这个函数。我们使用lua_pushcfunction来完成这个任务:他获取指向 C函数的指针,并在 Lua 中创建一个 function类型的值来表示这个函数。一个 quick-and-dirty 的解决方案是将这段代码直接放 到 lua.c 文件中,并在调用lua_open
后面适当的位置加上下面两行:

lua_pushcfunction(l,l_sin);
lua_setglobal(l, "mysin");

第一行将类型为 function 的值入栈,第二行将 function赋值给全局变量 mysin。这样修改之后,重新编译 Lua,你就可以在你的 Lua 程序中使用新的mysin 函数了。在下面 一节,我们将讨论以比较好的方法将新的C 函数添加到 Lua中去。

对于稍微专业点的 sin 函数,我们必须检查 sin 的参数的类型。有一个辅助库中的 luaL_checknumber 函数可以检查给定的参数是否为数字:当有错误发生的时候,将抛出 一个错误信息;否则返回作为参数的那个数字。将上面我们的函数稍作修改:

static int l_sin (lua_State *L) {
   double d = luaL_checknumber(L, 1);
   lua_pushnumber(L, sin(d));
    return 1; /* number ofresults */
}

根据上面的定义,如果你调用 mysin(‘a‘),会得到如下信息:

bad argument#1 to 'mysin' (number expected, got string)

注意看看 luaL_checknumber是如何自动使用:参数 number(1),函数名("mysin"), 期望的参数类型("number"),实际的参数类型("string")来拼接最终的错误信息的。

下面看一个稍微复杂的例子:写一个返回给定目录内容的函数。Lua 的标准库并没有提供这个函数,因为 ANSIC 没有可以实现这个功能的函数。在这儿,我们假定我们 的系统符合 POSIX 标准。我们的 dir 函数接受一个代表目录路径的字符串作为参数,以数组的形式返回目录的内容。比如,调用 dir("/home/lua")可能返回{".", "..", "src", "bin","lib"}。当有错误发生的时候,函数返回
nil 加上一个描述错误信息的字符串。

#include <dirent.h>
#include <errno.h>

static int l_dir (lua_State *L) {
  DIR *dir;
  struct dirent *entry;
  int i;
  const char *path = luaL_checkstring(L, 1);

   /* open directory */
   dir =opendir(path);
   if (dir == NULL) {   /* error opening thedirectory? */
   lua_pushnil(L); /* return niland ... */lua_pushstring(L, strerror(errno)); /* errormessage */
     return 2; /* number of results*/
}

    /* create resulttable */
     lua_newtable(L);
      i = 1;
      while ((entry = readdir(dir)) !=NULL) {
      lua_pushnumber(L, i++); /* push key */
      lua_pushstring(L, entry->d_name);  /* push value */
      lua_settable(L, -3);
}

    closedir(dir);
     return 1;         /* table isalready on top*/
}

辅助库的 luaL_checkstring 函数用来检测参数是否为字符串,与 luaL_checknumber 类似。(在极端情况下,上面的l_dir 的实现可能会导致小的内存泄漏。调用的三个 Lua 函数 lua_newtable、lua_pushstring和 lua_settable可能由于没有足够的内存而失败。其中任何一个调用失败都会抛出错误并且终止
l_dir,这种情况下,不会调用 closedir。正如 前面我们所讨论过的,对于大多数程序来说这不算个问题:如果程序导致内存不足,最好的处理方式是立即终止程序。另外,在 28章我们将看到另外一种解决方案可以避免这 个问题的发生)

25.2 C 函数库

一个 Lua 库实际上是一个定义了一系列 Lua 函数的 chunk,并将这些函数保存在适当的地方,通常作为 table 的域来保存。Lua的 C 库就是这样实现的。除了定义 C 函数 之外,还必须定义一个特殊的用来和 Lua 库的主 chunk 通信的特殊函数。一旦调用,这个函数就会注册库中所有的 C 函数,并将他们保存到适当的位置。像一个 Lua 主 chunk一样,她也会初始化其他一些在库中需要初始化的东西。

Lua 通过这个注册过程,就可以看到库中的 C函数。一旦一个C 函数被注册之后并保存到Lua中,在 Lua程序中就可以直接引用他的地址(当我们注册这个函数的时候传递给 Lua 的地址)来访问这个函数了。换句话说,一旦C 函数被注册之后,Lua 调用这 个函数并不依赖于函数名,包的位置,或者调用函数的可见的规则。通常 C 库都有一个外部(public/extern)的用来打开库的函数。其他的函数可能都是私有的,在
C  中被声明为 static。

当你打算使用 C 函数来扩展 Lua 的时候,即使你仅仅只想注册一个 C函数,将你的 C 代码设计为一个库是个比较好的思想:不久的将来你就会发现你需要其他的函数。一般情况下,辅助库对这种实现提供了帮助。luaL_openlib 函数接受一个 C 函数的列表和 他们对应的函数名,并且作为一个库在一个 table 中注册所有这些函数。看一个例子,假 定我们想用一个我们前面提过的 l_dir 函数创建一个库。首先,我们必须定义库函数:

static int l_dir (lua_State *L) {
...    /*as before */
}

第二步,我们声明一个数组,保存所有的函数和他们对应的名字。这个数组的元素 类型为 luaL_reg:是一个带有两个域的结构体,一个字符串和一个函数指针。

static const structluaL_reg mylib [] = {
{"dir", l_dir},
{NULL, NULL}  /* sentinel*/
};
 

在我们的例子中,只有一个函数 l_dir 需要声明。注意数组中最后一对必须是{NULL, NULL},用来表示结束。第三步,我们使用 luaL_openlib  声明主函数:

int luaopen_mylib (lua_State *L) {
     luaL_openlib(L, "mylib", mylib, 0);
     return 1;
}

luaL_openlib 的第二个参数是库的名称。这个函数按照指定的名字创建(或者 reuse) 一个表,并使用数组 mylib 中的 name-function 对填充这个表。luaL_openlib 还允许我们为库中所有的函数注册公共的 upvalues。例子中不需要使用 upvalues,所以最后一个参 数为 0。luaL_openlib 返回的时候,将保存库的表放到栈内。luaL_openlib 函数返回 1, 返回这个值给 Lua。CThe
luaopen_mylib function returns1 to return this value to Lua)(和 Lua 库一样,这个返回值是可选的,因为库本身己经赋给了一个全局变量。另外,像在 Lua   标准库中的一样,这个返回不会有额外的花费,在有时候可能是有用的。)

完成库的代码编写之后,我们必须将它链接到 Lua解释器最常用的方式使用动态连接库,如果你的 Lua解释器支持这个特性的话(我们在8.2 节己经讨论过了动态连接 库)。在这种情况下,你必须用你的代码创建动态连接库Cwindows 下.dll 文件,linux 下.so 文件)。到这一步,你就可以在Lua中直接使用 loadlib 加载你刚才定义的函数库了,下 面这个调用:

mylib = loadlib("fullname-of-your-library", "luaopen_mylib")

将 luaopen_mylib 函数转换成 Lua 中的一个 C 函数,并将这个函数赋值给 mylib(那就是为什么 luaopen_mylib 必须和其他的 C 函数有相同的原型的原因所在)。然后,调用mylib(),将运行
luaopen_mylib打开你定义的函数库。

如果你的解释器不支持动态链接库,你必须将你的新的函数库重新编译到你的Lua 中去。除了这以外,还不要一些方式告诉独立运行的 Lua解释器,当他打开一个新的状态的时候必须打开这个新定义的函数库。宏定义可以很容易实现这个功能。第一,你必 须使用下面的内容创建一个头文件(我们可以称之为  mylib.h):

int luaopen_mylib (lua_State *L);

#define LUA_EXTRALIBS { "mylib", luaopen_mylib },

第一行声明了打开库的函数。第二行定义了一个宏 LUA_EXTRALIBS 作为函数数 组的新的入口,当解释器创建新的状态的时候会调用这个宏。(这个函数数组的类型为 struct  luaL_reg[],因此我们需要将名字也放进去)

为了在解释器中包含这个头文件, 你可以在你 的编译选项 中定义一个 宏LUA_USERCONFIG。对于命令行的编译器,你只需添加一个下面这样的选项即可:

-DLUA_USERCONFIG=\"mylib.h\"

(反斜线防止双引号被 shell 解释,当我们在 C 中指定一个头文件时,这些引号是 必需的。)在一个整合的开发环境中,你必须在工程设置中添加类似的东西。然后当你重 新编译 lua.c 的时候,它包含 mylib.h,并且因此在函数库的列表中可以用新定义的 LUA_EXTRALIBS 来打开函数库。

时间: 2024-10-12 08:11:52

Lua_第25章 调用 C 函数的相关文章

第19章 SVC中断方式调用用户函数

第19章 SVC中断方式调用用户函数 RTX基础教程目录

RTX——第19章 SVC 中断方式调用用户函数(后期补历程)

本章节为大家讲解如何采用 SVC 中断方式调用用户函数. 当用户将 RTX 任务设置为工作在非特权级模式时,任务中是不允许访问特权级寄存器的,这个时候使用 SVC 中断,此问题就迎刃而解了. SVC 功能介绍SVC 用于产生系统函数的调用请求.例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件.因此,当用户程序想要控制特定的硬件时,它就要产生一个 SVC 异常,然后操作系统提供的SVC

JavaScript高级程序设计(第三版)学习笔记22、24、25章

第22章,高级技巧 高级函数 安全的类型检测 typeof会出现无法预知的行为 instanceof在多个全局作用域中并不能正确工作 调用Object原生的toString方法,会返回[Object NativeConstructorName]格式字符串.每个类内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名. 原生数组的构造函数名与全局作用域无关,因此使用toString方法能保证返回一致的值,为此可以创建如下函数: function isArray(value)

Lua_第23章 C API 纵览

第23章 C  API 纵览 Lua是一个嵌入式的语言,意味着 Lua 不仅可以是一个独立运行的程序包也可以是一个用来嵌入其他应用的程序库.你可能觉得奇怪:如果 Lua 不只是独立的程序,为什么到目前为止贯穿整本书我们都是在使用 Lua 独立程序呢? 这个问题的答案在于 Lua 解释器(可执行的 lua).Lua解释器是一个使用 Lua 标准库实现的独立的解释器,它是一 个很小的应用(总共不超过500 行的代码).解释器负责程序和使用者的接口:从使用者那里获取文件或者字符串,并传给 Lua 标准

Lua_第19章 String 库(上)

Lua_第19章String 库 Lua解释器对字符串的支持很有限.一个程序可以创建字符串并连接字符串,但不能截取子串,检查字符串的大小,检测字符串的内容.在 Lua中操纵字符串的功能基本来自于 string 库. String 库中的一些函数是非常简单的:string.len(s)返回字符串 s 的长度;string.rep(s, n)返回重复 n 次字符串 s 的串;你使用 string.rep("a", 2^20)可以创建一个 1M bytes 的字符 串(比如,为了测试需要);

Lua_第19章 String 库(下)

Lua_第19章 String 库(下) 19.3捕获(Captures) Capture(下面译为捕获或者capture,模式中捕获的概念指,使用临时变量来保存匹配的子模式,常用于 向前引用.)是这样一种机制:可以使用模式串的一部分匹配目标串的一部分.将你想捕 获的模式用圆括号括起来,就指定了一个capture.在 string.find 使用captures 的时候,函数会返回捕获的值作为额外的结果.这常被用 来将一个目标串拆分成多个: pair = "name =Anna" _,

范磊 C++ 第3章 初步了解函数

1 //范磊C++ 第3章 2 3 #include "stdafx.h" 4 #include "iostream" 5 6 //3.1 一个简单的函数 7 void show() 8 { 9 std :: cout << "Hello word!\n" ; 10 } 11 void fun1()///3.1 一个简单的函数 12 { 13 std :: cout << "函数开始\n" ; 14 s

mySQL教程 第7章 存储过程和函数

第7章 存储过程和函数 存储过程和存储函数 MySQL的存储过程(stored procedure)和函数(stored function)统称为stored routines. 1. MySQL存储过程和函数的区别 函数只能通过return语句返回单个值或者表对象.而存储过程不允许执行return,但是通过out参数返回多个值. 函数是可以嵌入在sql中使用的,可以在select中调用,而存储过程不行. 函数限制比较多,比如不能用临时表,只能用表变量.还有一些函数都不可用等等.而存储过程的限制

Lua_第16 章 Weak 表

Lua_第16 章 Weak 表 Lua自动进行内存的管理.程序只能创建对象(表,函数等),而没有执行删除对象 的函数.通过使用垃圾收集技术,Lua 会自动删除那些失效的对象.这可以使你从内存 管理的负担中解脱出来.更重要的,可以让你从那些由此引发的大部分 BUG中解脱出 来,比如指针挂起(dangling   pointers)和内存溢出. 和其他的不同,Lua 的垃圾收集器不存在循环的问题.在使用循环性的数据结构的 时候,你无须加入特殊的操作;他们会像其他数据一样被收集.当然,有些时候即使更