Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

Table of Contents generated with DocToc

4. C API

  • 这一节介绍Lua的C语言API,用于宿主程序中和Lua程序进行交流。
  • 所有的API和相关类型都定义在lua.h中。
  • 集成Lua源码的选择:
    • 可以选择将Lua(C源码)作为C++编译使用C++ABI,那么直接使用C++编译器编译即可。
    • 如果需要将Lua作为C编译使用C的ABI,那么在C++中调用时CABI的函数需要对所有声明加上extern "C",这一步需要自己来做。加入头文件lua.hpp,和其他Lua头文件放到一个目录即可。这个文件在官网下载Lua时是自带的(但源码中没有)。没有直接在Lua头文件中添加extern "C"是因为Lua的源码同样可以作为C++编译,留给了用户自己选择。
    // lua.hpp
    extern "C" {
    #include "lua.h"
    #include "lualib.h"
    #include "lauxlib.h"
    }
    • 一般纯C语言写的库的惯用做法都是用C编译器编译,通过C++调用时声明为extern "C",也就是说无论什么语言调用都使用C ABI。通过检测__cplusplus宏来做:
    // begining of file
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    // end of file
    #ifdef __cplusplus
    }
    #endif
  • 下面所说的函数,可能并不是一个真正的函数,比如API中可能通过宏来提供。除非显式说明,每一个这种宏都只使用每个参数一次(除非是第一个Lua State参数),所以调用的参数不要用任何副作用。
  • 大多数Lua的C API都不检查参数有效性和一致性,可以通过编译Lua时定义宏LUA_USE_APICHECK来做检查。
  • Lua库是完全可重入的,没有任何全局变量。将所有需要的信息放在一个动态的数据结构中,成为Lua State。
  • 每个Lua State有一个或者多个thread(线程?),对应于独立的或者合作的Lua执行代码。类型lua_State保存了这个线程,同样在线程中也可以访问到这个lua_State
  • 库中每个函数的第一个参数都是lua_State*,除了lua_newstate(创建指向主线程的新状态)。

4.1 栈

  • Lua使用一个虚拟栈来传递值到C以及从C中接收值。这个栈每个元素都是一个Lua value(nil, number, string, etc)。
  • 可以通过传入API中的lua状态参数获取到这个栈。
  • Lua中调用C函数时,调用的函数被分配一个新栈,独立于其他所有活跃的正在运行的栈。这个栈初始时包含了所有传给C函数的参数,这也是C函数中用来存放Lua的临时值以及保存返回给主调函数的返回值的地方(见lua_CFunction)。
  • API中访问栈的操作并不需要严格按照栈的要求,先出栈再访问,而是可以按照索引访问任意元素。1表示栈底,负索引则表示相对栈顶的相对位置。
    • 更具体来说,如果栈有n个元素,索引1表示第一个(推入栈中的)元素(栈底),索引n表示栈顶也就是最后一个元素。同样-1指向栈顶,-n指向栈底。

栈的大小:

  • 如果你和Lua API交互,那么你需要对栈溢出负责。当调用任何API时,需要保证栈中有足够空间保存所有结果。
  • 其中有一个例外:C中调用没有固定数量参数的Lua函数时(lua_call),Lua会保证栈有足够空间保存所有结果,但不保证有额外空间。在这种调用中压栈之前需要使用lua_checkstack
  • Lua中调用C函数时,保证栈中至少有LUA_MINSTACK个元素空间,这是可以安全压入的空间,默认值是20。所以通常不需要担心栈不够用。如果担心不够,压入新元素前使用lua_checkstack进行检查。

合法索引:

  • API中接受栈索引的函数只能在合法索引下工作。
  • 合法索引是指该位置保存了可修改的Lua值的索引。包含1~n -1~-n以及一些伪索引,伪索引(pesudo-indeices)表示一些能够在C代码中访问但是不在栈中的位置。通常用来访问注册表或者C函数中的upvalue。
  • 对这些函数来说不合法的索引会被视作其中包含一个虚拟的类型LUA_TNONE,表现得像一个nil

字符串指针:

  • 一些API返回一个指向栈中Lua字符串的指针(const char*)。lua_pushfstring lua_pushlstring lua_pushstring lua_tolstring以及辅助库中的luaL_checklstring luaL_checkstring luaL_tolstring
  • 同行来说,Lua的垃圾收集会回收内存并使指针失效。为了安全访问这些指针,这些API保证任何指向栈索引中的字符串的指针不会失效,直到对应字符串被从栈中移除(可以被移动到其他索引)。当索引是伪索引时,保证在函数调用期间都存在。
  • 某些调试接口也返回字符串指针,也保证在主调函数活跃期间字符串指针不会失效。
  • 除了这些保证之外,没有额外保证,垃圾收集可能在回收内部字符串的内存使指针失效。

4.2 C Closures

  • 当一个C函数创建的时候,可以将一些值关联在其之上,创建出一个C闭包(C closure,见lua_pushcclosure)。这些值称为upvalues(Lua函数中能访问到的外部局部变量也叫upvalue)。这些值可以在函数调用的任何时候被访问到。
  • C函数调用时,这些upvalues放在了特定的伪索引下,这些伪索引通过lua_upvalueindex创建。第一个是lua_upvalueindex(1)以此类推。超过当前upovalue数目的伪索引都是可接受但是不合法的。
  • C闭包中可以更改对应upvalue的值。

4.3 Registry注册表

  • Lua提供了一个注册表(Registry),一个预定义的表,可以被C代码使用以存储任何他需要的Lua值。
  • 这个表总是可以通过伪索引LUA_REGISTRYINDEX访问。
  • 任何C库都可以在这个表中存储数据,但是必须要选择和其他库不同的键名。通常来说,应该讲库名称包含在键里面(或者使用一个轻量userdata的C对象地址或者任何代码中创建的Lua对象作为键),以避免冲突。
  • 并且以下划线以及大写字母开头的字符串键由Lua保留,用户不应该使用。
  • 整数键被引用机制(reference machanism,luaL_ref)保留,不应该用于其他用途。
  • 当创建Lua State时,注册表中就保存了一些预定义的值。这些预定义的值是整数索引,作为常数定义在lua.h中。
    • LUA_RIDX_MAINTHREAD:这个索引中保存这个状态的main thread(即和state一起创建的那个)。
    • LUA_RIDX_GLOBALS:这个索引中保存全局环境。

4.4 C中的错误处理

  • 在C中,Lua使用C语言的longjmp机制处理错误(如果用C++编译则使用异常机制)。搜索源码中的LUAI_THROW查看细节。
  • 当Lua中发生一个错误时,这个错误会抛出,在C中他会做一个long jump,在保护环境中使用setjmp设置恢复点,任何错误的longjmp都会跳转到最近活跃的恢复点。
  • 在C中可以使用lua_error显式抛出一个错误。
  • API中的大多函数都可能抛出错误,比如内存错误。
  • 在保护环境外抛出错误,将会调用一个panic函数(通过lua_atpanic设置),其中会调用abort终止宿主程序。可以自己设置panic函数,在其中不返回而是long jump到Lua外的恢复点可以避免终止宿主程序。
  • lua.h中定义了一些状态码,API使用这些状态码来表示不同的错误:
LUA_OK (0): no errors.
LUA_ERRRUN: a runtime error.
LUA_ERRMEM: memory allocation error. For such errors, Lua does not call the message handler.
LUA_ERRERR: error while running the message handler.
LUA_ERRSYNTAX: syntax error during precompilation.
LUA_YIELD: the thread (coroutine) yields.
LUA_ERRFILE: a file-related error; e.g., it cannot open or read the file.

4.5 在C中处理yield

  • Lua内部使用longjmp处理协程的yield。因此如果一个C函数foo调用了一个API,并且这个API中yield了(直接或者间接),那么Lua再也不能回到foo了,因为longjmp会移除栈帧。
  • 为了避免这种情况,当yield跨越API边界时,Lua会抛出一个错误,除了函数lua_yieldk lua_callk lua_pcallk。这些函数会接收一个参数k作为continuation function,用来恢复yield后的协程执行。
  • 关于continuation function的更多细节见文档。

4.6 类型与函数

int lua_absindex (lua_State *L, int idx);                       转换索引为等价的绝对索引
typedef void * (*lua_Alloc) (void *ud,                          Lua使用的内存分配函数类型
                             void *ptr,
                             size_t osize,
                             size_t nsize);
void lua_arith (lua_State *L, int op);                          执行栈顶两个(或者一个)操作数的算术或者按位运算,弹出操作数,将结果压入栈顶
lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf); 设置panic函数并返回旧的
void lua_call (lua_State *L, int nargs, int nresults);          调用Lua函数
void lua_callk (lua_State *L,                                   就像lua_call,但允许调用的函数yield
                int nargs,
                int nresults,
                lua_KContext ctx,
                lua_KFunction k);
typedef int (*lua_CFunction) (lua_State *L);                    用于Lua中调用的C函数类型
int lua_checkstack (lua_State *L, int n);                       检查栈中的额外空间
void lua_close (lua_State *L);                                  关闭所有to-be-closed变量,回收内存
void lua_closeslot (lua_State *L, int index);                   关闭给定索引的to-be-closed变量
int lua_closethread (lua_State *L, lua_State *from);            关闭一个线程
int lua_compare (lua_State *L, int index1, int index2, int op); 执行两个Lua值的比较运算
void lua_concat (lua_State *L, int n);                          连接运算,连接栈顶n个值
void lua_copy (lua_State *L, int fromidx, int toidx);           拷贝一个元素,从一个索引到另一个
void lua_createtable (lua_State *L, int narr, int nrec);        创建新表并压入栈中
int lua_dump (lua_State *L,                                     将以Lua函数dump成一个二进制chunk
                        lua_Writer writer,
                        void *data,
                        int strip);
int lua_error (lua_State *L);                                   抛出lua错误
int lua_gc (lua_State *L, int what, ...);                       控制垃圾收集行为,执行垃圾收集、停止、统计等
lua_Alloc lua_getallocf (lua_State *L, void **ud);              得到给定状态的垃圾收集函数
int lua_getfield (lua_State *L, int index, const char *k);      获取栈中的表的给定域
void *lua_getextraspace (lua_State *L);                         获取Lua状态的底层内存指针
int lua_getglobal (lua_State *L, const char *name);             获取全局变量的值,压入栈顶
int lua_geti (lua_State *L, int index, lua_Integer i);          获取栈中的表的给定整数索引处的值
int lua_getmetatable (lua_State *L, int index);                 获取特定值的元表
int lua_gettable (lua_State *L, int index);                     获取栈中的表中的值
int lua_gettop (lua_State *L);                                  获取栈顶元素索引
int lua_getiuservalue (lua_State *L, int index, int n);         获取full userdata关联第n个user value
void lua_insert (lua_State *L, int index);                      移动栈顶元素到给定索引
typedef ... lua_Integer;                                        Lua整数类型,默认是long long
int lua_isboolean (lua_State *L, int index);                    判断栈的特定位置是否是一个布尔
int lua_iscfunction (lua_State *L, int index);                  判断元素是否是c函数
int lua_isfunction (lua_State *L, int index);                   判断是否是Lua函数,下面都是类型判断
int lua_isinteger (lua_State *L, int index);
int lua_islightuserdata (lua_State *L, int index);
int lua_isnil (lua_State *L, int index);
int lua_isnone (lua_State *L, int index);                       判断索引是否合法
int lua_isnoneornil (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isthread (lua_State *L, int index);
int lua_isyieldable (lua_State *L);
typedef ... lua_KContext;                                       continuation-function contexts的类型,数值类型,能够存储指针的类型
typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);  continuation-function的类型
void lua_len (lua_State *L, int index);                         长度,等价于Lua中的#运算符
int lua_load (lua_State *L,                                     加载一个Lua Chunk
              lua_Reader reader,
              void *data,
              const char *chunkname,
              const char *mode);
lua_State *lua_newstate (lua_Alloc f, void *ud);                创建新的state,并返回它的主线程
void lua_newtable (lua_State *L);                               创建一个新的空表,压入栈中
lua_State *lua_newthread (lua_State *L);                        创建一个新线程,压入栈中
void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue);   创建新的full user data
int lua_next (lua_State *L, int index);                         栈中弹出索引,并压入对应键值
typedef ... lua_Number;                                         Lua中的float类型
int lua_numbertointeger (lua_Number n, lua_Integer *p);         转换float为整数
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);    保护模式调用一个函数
int lua_pcallk (lua_State *L,                                   就像lua_pcall,允许调用函数yield
                int nargs,
                int nresults,
                int msgh,
                lua_KContext ctx,
                lua_KFunction k);
void lua_pop (lua_State *L, int n);                             出栈n个元素,实现为宏
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);  压入一个C闭包到栈中
void lua_pushcfunction (lua_State *L, lua_CFunction f);         压入一个C函数到栈中
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);   压入格式化字符串
void lua_pushglobaltable (lua_State *L);                        压入全局环境
void lua_pushinteger (lua_State *L, lua_Integer n);             压入整数,下面都是压入各种值
void lua_pushlightuserdata (lua_State *L, void *p);
const char *lua_pushliteral (lua_State *L, const char *s);
const char *lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushnil (lua_State *L);
void lua_pushnumber (lua_State *L, lua_Number n);
const char *lua_pushstring (lua_State *L, const char *s);
int lua_pushthread (lua_State *L);
void lua_pushvalue (lua_State *L, int index);
const char *lua_pushvfstring (lua_State *L,
                              const char *fmt,
                              va_list argp);
int lua_rawequal (lua_State *L, int index1, int index2);        判断两个值是否primitively equal(不会调用__eq元方法)
int lua_rawget (lua_State *L, int index);                       类似于 lua_gettable,但不会调用元方法
int lua_rawgeti (lua_State *L, int index, lua_Integer n);
int lua_rawgetp (lua_State *L, int index, const void *p);
lua_Unsigned lua_rawlen (lua_State *L, int index);
void lua_rawset (lua_State *L, int index);
void lua_rawseti (lua_State *L, int index, lua_Integer i);
void lua_rawsetp (lua_State *L, int index, const void *p);
typedef const char * (*lua_Reader) (lua_State *L,               lua_load使用的reader函数
                                    void *data,
                                    size_t *size);
void lua_register (lua_State *L, const char *name, lua_CFunction f);    设置C函数为全局变量name的新值
void lua_remove (lua_State *L, int index);                      移除给定元素,移动后面的元素
void lua_replace (lua_State *L, int index);                     用栈顶元素替换给定元素,出栈
int lua_resetthread (lua_State *L);                             已废弃
int lua_resume (lua_State *L, lua_State *from, int nargs,       恢复协程
                          int *nresults);
void lua_rotate (lua_State *L, int idx, int n);                 旋转栈中元素
void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);       设置内存分配函数
void lua_setfield (lua_State *L, int index, const char *k);     t[k] = v,设置域
void lua_setglobal (lua_State *L, const char *name);            设置全局变量
void lua_seti (lua_State *L, int index, lua_Integer n);
int lua_setiuservalue (lua_State *L, int index, int n);
int lua_setmetatable (lua_State *L, int index);
void lua_settable (lua_State *L, int index);
void lua_settop (lua_State *L, int index);
void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud);
typedef struct lua_State lua_State;                             Lua状态类型
int lua_status (lua_State *L);                                  返回线程L状态
size_t lua_stringtonumber (lua_State *L, const char *s);        字符串到整数
int lua_toboolean (lua_State *L, int index);                    转到布尔
lua_CFunction lua_tocfunction (lua_State *L, int index);        转换一个值为C函数
void lua_toclose (lua_State *L, int index);                     标记一个索引为to-be-closed slot
lua_Integer lua_tointeger (lua_State *L, int index);
lua_Integer lua_tointegerx (lua_State *L, int index, int *isnum);
const char *lua_tolstring (lua_State *L, int index, size_t *len);
lua_Number lua_tonumber (lua_State *L, int index);
lua_Number lua_tonumberx (lua_State *L, int index, int *isnum);
const void *lua_topointer (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
lua_State *lua_tothread (lua_State *L, int index);
void *lua_touserdata (lua_State *L, int index);
int lua_type (lua_State *L, int index);                         获取类型
const char *lua_typename (lua_State *L, int tp);                将lua_type返回值转换成字符串
typedef ... lua_Unsigned;                                       整数类型
int lua_upvalueindex (int i);                                   返回第i个upvalue的索引
lua_Number lua_version (lua_State *L);                          lua版本
typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont);   lua警告函数,lua中的warn会调用
void lua_warning (lua_State *L, const char *msg, int tocont);   发起一个lua警告
typedef int (*lua_Writer) (lua_State *L,                        lua_dump的writer函数类型
                           const void* p,
                           size_t sz,
                           void* ud);
void lua_xmove (lua_State *from, lua_State *to, int n);         交换两个线程中的值
int lua_yield (lua_State *L, int nresults);                     yield
int lua_yieldk (lua_State *L,                                   挂起一个协程
                int nresults,
                lua_KContext ctx,
                lua_KFunction k);

4.7 调试接口

typedef struct lua_Debug {
  int event;
  const char *name;           /* (n) */
  const char *namewhat;       /* (n) */
  const char *what;           /* (S) */
  const char *source;         /* (S) */
  size_t srclen;              /* (S) */
  int currentline;            /* (l) */
  int linedefined;            /* (S) */
  int lastlinedefined;        /* (S) */
  unsigned char nups;         /* (u) number of upvalues */
  unsigned char nparams;      /* (u) number of parameters */
  char isvararg;              /* (u) */
  char istailcall;            /* (t) */
  unsigned short ftransfer;   /* (r) index of first value transferred */
  unsigned short ntransfer;   /* (r) number of transferred values */
  char short_src[LUA_IDSIZE]; /* (S) */
  /* private part */
  other fields
} lua_Debug;                                保存函数或者活跃记录的各种信息的结构
lua_Hook lua_gethook (lua_State *L);        返回特定钩子函数
int lua_gethookcount (lua_State *L);        返回钩子函数数量
int lua_gethookmask (lua_State *L);
int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);        获取一个特定函数或者函数调用的信息
const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);
int lua_getstack (lua_State *L, int level, lua_Debug *ar);
const char *lua_getupvalue (lua_State *L, int funcindex, int n);
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);                 调试钩子函数类型
void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);       设置钩子函数
const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);    设置局部变量
const char *lua_setupvalue (lua_State *L, int funcindex, int n);
void *lua_upvalueid (lua_State *L, int funcindex, int n);               返回一个upvalue的唯一id
void lua_upvaluejoin (lua_State *L, int funcindex1, int n1,
                                    int funcindex2, int n2);