zoukankan      html  css  js  c++  java
  • 《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的标准库,这些库实际上执行了多数的工作。
         这个使用库来扩展一个程序的特性使lua成为了一门扩展语言。与此同时,使用lua的程序可以在lua的环境中注册新的函数,而这些函数又是以C或者其他语言来实现的,因此他们可以不直接使用lua代码而添加某些特性,这又使得lua成为了一门可扩展的语言。
         Lua的两个语言特性(扩展语言和可扩展语言)组成了C和Lua的相互作用性。第一点,针对lua是一门扩展语言,C是主控而lua是扩展库,此时这种情况下两种语言的互相作用中C代码表现的是application code;第二点,针对lua是一门可扩展的语言,此时Lua是主控而C是扩展库,此时C代码被称之为library code。无论是appliction code还是library code 都是使用同样的API与Lua进行交互,这套API也被称之为C API。
         C API是一个程序集,使得C代码能与Lua产生交互作用。实现了诸如读取lua的全局变量,调用Lua函数,运行一段Lua的代码等。通常来说,Lua代码能做的,C代码通过C API同样能做。
         C API遵循的是C的运作方式,与Lua的运作方式大不一样。当我们用C编程的时候,我们一定要注意类型检测,错误处理,内存控制等。大多数API里面的函数并没有对其参数做正确性检测,这里将类型检测的任务交给了调用这些API的使用者。如果此时出现了错误,那么将会得到类似于"setmentation fault"(分割错误?)的信息,而不是一个完整的错误信息。另外,C API强调简便,有的时候会以减少使用为代价? 有的时候一些简单的任务都会引发多个API的调用。这可能会有点无聊枯燥,但是这给予了你充分的可控性。
         Lua和C之间进行交互最重要的组成部分是一个无所不在的virtual stack(虚拟栈?),几乎所有的API调用都是运行在这个栈上,所有的Lua至C和C至Lua的数据交互也是通过这个栈。不仅如此,你也可以使用这个栈来保存一些中介数据。这个栈可以用来解决Lua和C之间两个相阻抗的问题:第一个是,当lua开始进行垃圾回收的时候,而此时C请求了一个显示的释放操作;第二个是激烈的Lua动态类型和C静态类型的对抗。



    25.1 Example
         头文件lauxlib.h 定义了由辅助函数库提供了各种函数。所有的函数命名以luaL_ 为开头(如: luaL_loadstring).辅助库使用lua.h提供的基础API来提供高一级的抽象概念,特别是那些被标准库使用的。要记住的是辅助库没有权限来访问lua的内部文件。该库通过官方提供的基础API来实现它所要做的全部工作。
         Lua的库没有定义一个全局变量,而是将所有的状态存储至一个动态结构:lua_State中,Lua中的所有函数都接收一个指向该结构的指针作为参数,这种实现方式使得lua能够被多线程程序所使用。
         luaL_newstate 创建一个新的lua state,新state里面不包含任何预定义的函数,需要通过包含各种库来实现基础的功能。lualib.h 包含了打开库的函数,函数luaL_openlibs 将会将所有标准库都打开。



    25.2 Stack
         当Lua和C之间进行数据交互的时候将会面临两个问题:动态类型和静态类型转换导致的数据丢失;自动和手动内存管理的不协调。
         Lua提供的解决方案是使用一个抽象的栈来做Lua和C之间的数据交换。这个栈能存储任何类型的Lua变量。无论何时想从Lua中得到一个变量值,调用lua函数的时候会push这个值到栈中;无论何时想传递一个值到Lua,首先会将这个值push至stack中,然后调用lua函数的时候会pop掉这个值。而且因为这个栈是在lua内部的,所以垃圾回收机制是知道哪个值被C使用了,不影响垃圾回收机制的正常运作。
         几乎所有的API内部函数都使用了栈来做数据交互。Lua管理这个栈使用的是LIFO(后进先出)方式,当你调用Lua的时候,只会改变stack顶部的值。而C代码则拥有更大的自由,可以影响stack内部任何位置的值,甚至可以在任何位置做删除或者插入操作。

    Pushing elements
         API对应每个C类型的值都有一个Lua表示的函数:
    lua_pushnil                    常量值nil
    lua_pushboolean            布尔值
    lua_pushnumber             doubles
    lua_pushinteger              integers
    lua_pushunsigned           unsigned integers
    lua_pushlstring               任意的字符串(指向char型变量的指针加上一个长度)
    lua_pushstring                以0终止符结尾的字符串
    对应的函数:
    void lua_pushnil(lua_State *L);
    void lua_pushboolean(lua_State *L,int bool);
    void lua_pushnumber(lua_State *L,lua_Number n);
    void lua_pushinteger(lua_State *L,lua_Integer n);
    void lua_pushunsigned(lua_State *L,lua_Unsigned n);
    void lua_pushlstring(lua_State *L,const char *s,size_t len);
    void lua_pushstring(lua_State *L,const char *s);
         当push元素至stack的时候,必须确保stack有足够的空间来存储这些。一个安全的方法是使用之前先确认空间:
         int lua_checkstack(lua_State *L,int sz);
         参数是要push的元素的数量。

    Querying elements
    访问元素
         API使用indices,首先push进去的元素index为1,之后为2,如果使用的是负数,则-1代表最后push的元素,-2为倒数第二个。
         使用函数lua_isstring,lua_istable,lua_isnumber等来检测元素的类型。各个函数的形式都类似于:
         int lua_is*(lua_State *L,int index)
         并且事实上lua_isnumber并不是检测元素是否是number类型,而是检测能否转换为这个类型;lua_isstring也是类似的检测:所有的number都可以转换为string
         另外函数lua_type,返回元素的类型。这些类型定义在lua.h头文件中:LUA_TNIL,LUA_TBOOLEAN,LUA_TNUMBER,LUA_TSTRING,LUA_TTABLE,LUA_TTHERAD,LUA_TUSERDATA,LUA_TFUNCTION.
         从stack中得到元素值,有以下的函数:
    int lua_toboolean(lua_State *L,int index);
    const char* lua_tostring(lua_State *L,int index,size_t* len);
    lua_Number lua_tonumber(lua_State *L,int index);
    lua_Integer lua_tointeger(lua_State *L,int index);
    lua_Unsigned lua_tounsigned(lua_State *L,int index);
         函数lua_tostring 返回的字符串总是会默认在尾部带一个0,而实际上该字符串的长度是函数第三个参数所代表的值。

    Other stack operation
    int lua_gettop (lua_State *L)
    void lua_settop(lua_State *L,int index);
    void lua_pushvalue(lua_State *L,int index);
    void lua_remove(lua_State *L,int index);
    void lua_insert(lua_State *L,int index);
    void lua_replace(lua_State *L,int index);
    void lua_copy(lua_State *L,int fromidx,int toidx);
         
         lua_settop 函数的作用是将给定的一个元素设置为top元素,如果之前top元素的index值大于将要设定的,那么函数将会抛弃大于要设定index的值;lua_settop(L,0) 将会将栈置空;lua_settop(L,-(n)-1) 将会置空pop栈中的元素;
         lua_pushvalue 将给定位置的元素值复制一份在push进stack中;lua_remove 移除掉给定位置的值,并且将该位置之上的元素位置下移一位;lua_insert 将顶部元素移至给定的位置,并将所有在该位置之上的元素上移一位;lua_replace 从顶部pop一个元素,并且将该值设定为给定位置的值,不做任何位置移动;lua_copy 将一个位置元素的值复制给另一个位置的元素。



    25.3 Error Handling with the C API
         Lua中的数据结构都是动态类型的:按需要增加,自动收缩。这就意味着在内存回收时发生错误是不可避免的。几乎所有的操作都会遇到这个问题。不仅如此,很多操作可能会引发别的错误:例如访问全局变量可能会在元方法__index 中遇到错误。
         Lua使用异常机制来抛出错误,而不是在各个API操作中使用具体的错误操作代码。但是,C与C++和Java相比,并没有提供异常处理机制。为了处理这个不同,Lua在应对C的时候使用了setjmp特性,这是一个类似于异常处理机制的特性。因此大多数API函数都能抛出错误(调用 longjmp)而不是直接返回。
         当我们使用库代码(此时C函数可以被lua调用),使用long jumps就如同使用异常处理机制般方便,因为Lua可以捕获任何的偶然发生的错误。当我们使用应用代码(application code 此时是C代码调用Lua),我们需要提供捕获这些错误的方法。

    Error handing in application code(C调用Lua)
         此时为了能合理的处理错误,需要使用Lua来调用代码,也就是说设置一个合理的环境来捕获错误。此时我们也可以使用pcall来安全运行Lua代码,也可以使用lua_pcall来运行C代码。不仅如此,我们也可以用一个函数封装C代码然后用Lua来调用,使用lua_pcall,这样就可以在安全模式下运行C代码了。即便是在内存配置失败的情况下,lua_pcall也能返回一个正确的错误代码,保持运行正常。

    Error handing in library code(Lua调用C)
         Lua是一门安全语言(safe language?).这就意味着无论你写的是什么样的Lua代码,也无论里头有多大的错误,你都能从Lua角度理解到程序做出的反应??不仅如此,错误是从Lua角度来检测和解释的。这个可以和C做对比,看C的错误处理机制是如何的。
         无论何时C库中的函数检测到一个错误,都会调用lua_error函数来处理错误,函数会清理lua所有能清理的东西并且会回跳至引发错误的位置并安全执行(jumps back to the protected call that originated that execution),传递出错误信息。
  • 相关阅读:
    每周分享五个 PyCharm 使用技巧(一)
    深入理解 Python 中的上下文管理器
    Delphi中Chrome Chromium、Cef3学习笔记(四)
    DELPHI中自定义消息的发送和接收
    Delphi2010/XE2下隐藏程序系统任务栏的图标
    批处理经典入门教程!(从不懂到高手)第2/5页
    批处理经典入门教程!(从不懂到高手)1/5
    批处理taskkill运行结束不掉程序以及停留问题
    delphi EncdDecd.pas单元中Encoding方法出现#$D#$A的解决方法
    Delphi中Chrome Chromium、Cef3学习笔记(三)
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/4044563.html
Copyright © 2011-2022 走看看