《Programming in Lua 3》读书笔记(二十三)

日期:2014.8.7

PartⅣ The C API

27 Calling C from Lua

在这里说Lua调用C函数,并不意味着Lua可以调用任意的C函数。与之前C调用Lua函数一样,在这里同样需要遵循一些规则:传递参数,得到结果。不仅如此,Lua要调用C函数,我们首先需要注册这个函数,即需要将该函数的地址传递给Lua。

当Lua调用C函数的时候,也是使用栈来做参数和返回结果的传递。C函数从栈中得到参数,然后将结果push至栈中。

在这里一个重要的概念是:栈不是一个全局结构;每个函数都有它自己的局部栈。当Lua调用C函数的时候,第一个参数总是栈中index为1的那个值。甚至当一个C函数调用了Lua代码,然后Lua再调用一个C函数,每次调用都只可以访问到各自私有的栈,第一个参数都是栈中index为1的那个。

27.1 C Functions

以书上的例子,如何实现一个简单的返回一个给定数的sin值的函数:

e.g.
static int l_sin (lua_State *L)
{
     double d = lua_tonumber(L,1);               /* 得到一个参数 */
     lua_pushnumber(L,sin(d));                   /* 将结果push 进去 */
     return 1;                                   /* 返回结果的数量*/
}

任何注册到Lua的函数都有一个同样的原型,在头文件lua.h 中定义为lua_CFuntion :

typedef int (*lua_CFuntion) (lua_State *L)

从C的角度来看,一个C函数以一个Lua state作为其唯一参数,然后返回一个代表函数返回值数量的整型数。从这点来看,函数不需要在push结果之前将栈清空,在函数返回之后,Lua会自动存储其结果然后将整个栈清空。

在Lua使用这个函数之前,我们需要先注册这个函数。我们使用lua_pushcfunction 来实现这个功能:从C函数中得到一个指针,然后创建一个function类型的变量来在Lua中代表这个函数。一旦注册了这个函数,那么C函数就跟Lua中的函数表现一致了:

e.g.
lua_pushcfunction(L,l_sin);
lua_setglobal(L,"mysin");     /* 在lua中创建一个全局变量mysin ,然后将c函数赋值给mysin*/

在这之后,就可以在Lua中使用mysin这个函数了。

将函数处理得更为标准点,我们需要检查参数类型。此时辅助函数luaL_checknumber 将会做到这点,该函数检查给定的参数是否是一个number,假如遇到错误,那么该函数将会抛出错误信息,否则将会返回检查的这个数。修改之后的例子:

e.g.
static int l_sin(lua_State *L)
{
     double d = luaL_checknumber(L,1)     /* 检查参数 */
     lua_pushnumber(L,sin(d));
     return 1;
}

如果传递的参数为非数值类型,此时将会得到错误信息:

bad argument #1 to ‘myosin‘ (number expected,got string)

此时这里错误信息是怎么得到传递的参数,函数名字,函数需要的参数类型和实际的参数类型的,这个需要注意。

27.2 Continuations

通过lua_pcall 和 lua_call,一个被Lua调用的C函数可以再回调一个Lua函数。标准库中的几个函数支持这个操作:table.sort 可以调用一个排序函数;string.gusb 可以调用一个替代函数;pcall 和xpcall 可以在安全模式下调用一个函数。在这里提一下Lua的主程序是从C中(宿主程序)调用的,这里就有一个队列了:C(宿主)调用Lua(脚本),Lua脚本调用C(标准库),C(标准库)再调用Lua(回调)。

通常Lua处理这个队列是没有问题的;但是在协同程序中这个就有点困难了。

Lua中的每个协同程序都有自己的栈,用来保存那些未执行调用协同程序的信息。特别的是,这个栈将会存储每次访问的返回地址,参数,和一些局部变量。在调用Lua函数的时候,解释器使用合适的数据结构来实现这个栈(soft stack);而在调用C函数的时候,解释器就必须使用C的栈了。毕竟C函数的返回地址和局部变量是存储在C栈中的。

解释器是很容易拥有多个sofe stack的,但是ANSI C的runtime却只能有一个内部栈;这就意味着Lua不能延迟C函数的执行:假如有一个在从resume状态到其他状态的过程中访问的C函数,Lua是不能为下一次resume恢复而存储C函数的状态的。这里的一个例子:

e.g.
co = coroutine.wrap(function ( a )
                              return pcall(function ( x )
                                   coroutine.yield(x[1])
                                   return x[3]
                              end,a)
end)
print(co({10,3,-8,15}))

这个例子我在sublime中执行没有问题,书本上是说这个有问题的,需要再注意。书上对这个例子的解释是:pcall是一个c函数,因此Lua不能延迟这个函数的执行,因为在ANSI C中没有方法延迟一个C函数的执行然后在某个时间点再恢复这个函数。

在Lua5.2中使用continuations 对这个问题做了修正(现在我用的就是5.2版,可能此时已经有修正处理了),lua5.2使用long jumps来实现yields,与处理错误使用的是同一个方法。一个long jump仅仅是将C栈中的C函数的信息抛出,因此是可以再恢复这个函数的。

27.3 C Modules

Lua中的模块就是定义多个函数然后存储在一个合适的地方,一般是存储在table中。一个C的模块也差不多是这个意思。当然除了要定义C函数,还一定需要定义一个特殊的函数,其功能类似于Lua中的main chunk。这个函数用来注册模块中的所有C函数,然后将这些函数存储到一个合适的地方,这里也一般是table。和Lua的main chunk一样,该函数也应该初始化模块需要的任何东西。

Lua通过C函数的注册过程监听C函数。一旦一个C函数存储至了Lua中,那么Lua便可以立即通过其地址(注册函数时得到)调用该函数。这也表明,Lua并不是依靠函数名字,包的位置,等来调用C函数的。特别的,一个C模块有一个公有函数,该函数用来打开这个模块的,而模块中其余的函数可以是私有的,以static 来声明。

当你想用C函数来扩展Lua的时候,将扩展代码封装为C模块是一个不错的选择,即便仅有一个函数:因为最终你都会需要其他的函数的。和往常一样,辅助函数库提供了一个辅助函数:luaL_newlib 以各自函数名得到一个C函数列表然后将所有函数注册并存储至一个新的table中。以一个例子讲解:假如此时我们想创建一个库,有一个函数l_dir,首先我们需要定义这个库函数:

e.g.
static int l_dir(lua_State *L)
{
     /*code*/
}

下一步是需要声明一个数据结构以各自函数的名字存储需要加入到这个库中的所有函数,该数据结构的类型为:luaL_Reg,数组的数据结构有两个字段:一个字符串代表函数的名字,一个函数指针

e.g.
static const struct luaL_Reg mylib[] =
{
     {"dir",l_dir),
     {NULL,NULL}
};

以{NULL,NULL}结尾是必须的,用来作为结尾符号。最后声明一个主函数,使用luaL_newlib:

e.g.
int luaopen_mylib(lua_State *L)
{
     luaL_newlib(L,mylib);
     return 1;
}

创建完之后,便是将库链接到编译器了。最方便的方式便是采用动态链接的特性(windows中创建mylib.dll,linux中创建mylib.so )然后加入到C路径中去。经过这些步骤,便可以从Lua中加载这个库了,使用require实现:

e.g.
local mylib = require "mylib"

以上这步将动态链接库mylib链接到了lua中,找到luaopen_mylib函数,以一个C函数注册该函数然后调用该函数以打开模块。

动态链接器要求需要知道luaopen_mylib函数的名字以便能找到该函数。通常,以luaopen_+模块的名字来做寻找,因此假如模块的名字叫做mylib,那么函数的名字就必须为luaopen_mylib。

假如编译器不支持动态链接,那么就需要重新编译Lua以加入你新的库了。而且也需要告诉编译器需要打开你新加入的库,通常这里是将新的库加入到标准库的打开列表中去,在linit.c中,以便让luaL_openlibs打开这些库。

时间: 2024-08-24 08:00:23

《Programming in Lua 3》读书笔记(二十三)的相关文章

《Distributed Programming With Ruby》读书笔记二 Security and ID Conversion (Part1.1-2)

Security Although DRb provide some security, they fall short of a full, comprehensive solution. This can make DRb less than desirable in a lot of real world situations. However, in situations where security is a lesser concern,such as prototyping and

《Programming in Lua 3》读书笔记(二十二)

日期:2014.8.6 PartⅣ The C API 26 Extending Your Application 使用Lua很重要的一点是用来做配置语言.配合主语言做一些功能的配置. 26.1 The Basics 有的时候程序需要配置一些功能信息,很多时候可能有许多别的方法比用lua做配置要更简单:如使用环境变量或者读取文件,读取文件涉及到文件的解析.如果使用Lua进行配置的话,相当于用lua文件替代了要读取的如csv.txt文件等. 使用Lua进行配置的时候,就需要使用Lua API去控制

《Programming in Lua 3》读书笔记(二十一)

日期:2014.8.1 PartⅣ The C API 25 An Overview of the C API Lua是一种嵌入式语言.这就意味着Lua不是单独存在的,而是可以通过一系列的标准库将lua的特性嵌入至其他应用模块中. Lua以Lua interpreter(lua的解释器?)来解决了其不是独立程序,我们直到现在却又能独立使用Lua的问题.这个解释器是一个小型的程序(不超过500行代码),使用lua的标准库来实现独立解释程序,这个程序将处理与用户的交互等操作交给lua的标准库,这些库

Java编程思想第四版读书笔记——第十三章 字符串

Java编程思想第四版读书笔记--第十三章 字符串 字符串的操作是计算机程序设计中最常见的行为. 关键词: StringBuilder ,StringBuffer,toString(),format转换,正则表达式, 1.不可变String String对象时不可变的.每当把String对象作为方法的参数时,都会复制一份引用.(其实就是对函数中参数列表中参数的操作不会影响外面的原参数) 如下: import static net.mindview.util.Print.*; public cla

代码的未来读书笔记<二>

代码的未来读书笔记<二> 3.1语言的设计 对Ruby JavaScript Java Go 从服务端客户端以及静态动态这2个角度进行了对比. 这四种语言由于不同的设计方针,产生了不同的设计风格. Header 客户端 服务端 动态 Html5 Ruby 静态 Java Go 静态动态 静态:无需实际运行,仅根据程序代码就能确定结果. 动态:只有到了运行时才能确定结果.不过无论任何程序,或多或少都包含的动态的特性. 动态运行模式 运行中的程序能识别自身,并对自身进行操作.对程序自身进行操作的编

《卓有成效的程序员》----读书笔记二

六大方面对比Launchy和TypeAndRun(TAR) 对于快速启动工具,很多人都有自己的偏好,多次听到朋友介绍Launchy的好,虽然自己一直在使用着TAR,还是克制不住对于好软件的渴求,下载Launchy进行试用.很多软件都是有一个试用期的,也许新的软件确实不错,但是你习惯了以前使用的那个软件.今天就比较客观的将Launchy和TAR进行一下对比,从界面.上手速度到功能.自定义,以及软件的稳定性.占用资源进行详细的比较. [界面美观]Launchy:毫无疑问这是它的强项.1.0正式版自带

《R实战》读书笔记二

第一章 R简介 本章概要 1安装R 2理解R语言 3运行R程序 本章所介绍的内容概括如下. 一个典型的数据分析步骤如图1所示. 图1:典型数据分析步骤 简而言之,现今的数据分析要求我们从多种数据源中获取数据.数据合并.标注.清洗和分析,并且把分析的结果进行展示,形成报告或者系统,辅助决策.R能够满足现今数据分析的要求. 为什么用R? R是一个适合统计分析和绘图的环境与语言.它是开源.免费的,获得世界范围社区支持.统计分析和绘图工具已经很多了,例如:SPSS,SAS,Excel,Stata和Min

《学会提问》读书笔记二

<学会提问>读书笔记二 因为书中的小点知识和思考太多,我从这篇笔记开始就只记我害怕会遗忘的知识,思考过程就不提及了. 弱势批判性思维和强势批判性思维 弱势批判性思维的目的是用批判性思维来反驳.抵制那些和你意见不同的论述最终就是为了看到那些与你主张不一致的人服服帖帖的甘心认输,但是这样就意味着,你对于是否接近真理和发扬美德漠不关心,实际上也摧毁了批判性思维潜在的人性的一面和不断发展进步的特征.而且我认为,弱势批判性思维的出发点是自私的,他们盲目地认为自己的观点就是正确的,用批判性思维来批判其他人

angular学习笔记(二十三)-$http(1)-api

之前说到的$http.get和$http.post,都是基于$http的快捷方式.下面来说说完整的$http: $http(config) $http接受一个json格式的参数config: config的格式如下: { method:字符串 , url:字符串, params:json对象, data:请求数据, headers:请求头, transformRequest:函数,转换post请求的数据的格式, transformResponse:函数,转换响应到的数据的格式, cache:布尔