C语言API概要

什么是Lua

  1. Lua是一种嵌入式语言
    1. Lua并不是一个独立运行的应用,而是一个库
    2. 可以链接到其他应用程序
    3. C拥有控制权,而Lua语言被用作库
  2. Lua解释器
    1. 它与用Lua标准库实现的独立解释器
    2. 它负责与用户交互,将用户的文件和字符串传递给Lua标准库,标准库完成主要工作
  3. Lua是一种扩展语言
    1. 可以在Lua环境中注册新的函数(比如C语言),从而增加一些无法直接在Lua语言编写的功能
    2. Lua语言拥有控制权,而C语言作为库

通过CAPI 将Lua和C语言进行通信

什么是CAPI

  1. CAPI是一个函数、常量和类型组成的集合
  2. CAPI包括读写Lua全局变量的函数、调用Lua函数的函数、运行Lua代码段的函数,以及注册C函数(被Lua调用)函数等
  3. CAPI是C语言
  4. Lua独立解释器:lua.c
  5. Lua标准库:Lmathlib.c, Lstrlib.c 等

第一个示例

#include <stdio.h>

#include "lualib/lua.h"
#include "lualib/lauxlib.h"
#include "lualib/lualib.h"

int main(int argc, char *argv[]) {

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

/*
	//加载lua文件
	if (argc != 2) return -1;
	char* path = argv[1];
	int bRet = luaL_loadfile(L, path);
	lua_pcall(L, 0,0,0);
*/

    // 命令行输入
    int error;
   	char buff[1000];
	while (fgets(buff, sizeof(buff), stdin) != NULL) {
	    error = luaL_loadstring(L, buff) || lua_pcall(L, 0,0,0);
	    if (error) {
	        fprintf(stderr, "%s\n", lua_tostring(L, -1));
	        lua_pop(L, 1);
	    }
	}

    lua_close(L);

    return 0;
}
  1. lua.h
    1. 声明了Lua提供的基础函数(读写Lua全局变量的函数、调用Lua函数的函数、运行Lua代码段的函数,以及注册C函数函数)
    2. 所有内容都使用lua_前缀
    3. 可访问Lua内部元素
    4. 精简
  2. lauxlib.h
    1. 声明辅助库(auxiliary library, auxlib)
    2. 所有内容都使用luaL_前缀
    3. 不可访问Lua内部元素,只提供工具方法
    4. 实用
  3. lua_State
    1. Lua标准库没有定义任何C语言全局变量
    2. 所有状态都保存在动态的结构体lua_State中
    3. Lua中所有函数都接受一个指向该结构体的指针
  4. luaL_newstate
    1. 创建一个新的Lua状态(栈)
    2. 没有任何预定义的函数
  5. lualib.h
    1. lua标准库
    2. 使用LuaL_openlibs打开所有标准库
  6. luaL_loadstring
    1. 编译lua内容
    2. 正确则返回零,并向栈中压入编译后的函数
  7. luaL_pcall
    1. 从栈中弹出编译后的函数
    2. 以保护模式运行
    3. 正确则返回零
    4. 如果错误,将错误信息压入栈
    5. lua_pop 获取错误信息,并从栈中删除
  8. lua_close
    1. 关闭lua
  • lua即可以用为C代码编译,也可以作为C++代码编译,所以并不需要标注c或cpp
#ifdef __cplusplus
extern "C" {
#endif
	...
#ifdef __cplusplus
}
#endif
  • 如果Lua作为C代码编译后又需要在C++中使用,可以引入lua.hpp来替代lua.h,定义如下:
extern "C" {
#include "lua.h"
}

Lua和C之间通信主要使用虚拟栈(stack)

设计

问题

  1. 动态类型和静态类型体系之间不匹配
  2. 自动内存管理和手动内存管理之间不匹配

解决方案

Lua通信方式

  1. 栈中保存Lua中任意类型的值
  2. Lua取C值时,C将指定的值压入栈,然后调用Lua将其从栈中弹出即可
  3. C取Lua值时,调用lua,lua会将值压入栈,C从栈中取值
  4. Lua栈是lua状态的一部分,隐藏垃圾收集器知道C语言正在使用哪些值

压入元素

//常量nil使用lua_pushnil
void lua_pushnil(lua_State *L)
//布尔值(在C语言中是整型)
void lua_pushboolean(lua_State *L)
//双精度浮点数
void lua_pushnmber(lua_State *L, lua_Number n)
//整型
void lua_pushinteger(lua_State *L, lua_Integer n)
//任意字符串(一个指向char的指针,外加一个长度)
void lua_pushlstring(lua_State *L, const char *s, size_t len)
//以\0终止的字符串
void lua_pushstring(lua_State *L, const char *s)

检查栈中的空间

  • lua CAPI 方法
    /*
    	如果可能,则增加栈的大小,否则,返回零
    	@param sz 所需额外栈的数量
    */
    int lua_checkstack(lua_State *L, int sz)
  • lua 标准库方法
    /*
    	如果可能,则增加栈的大小,否则,返回错误信息
    	@param sz 所需额外栈的数量
    */
    void luaL_checkstack(lua_State *L, int sz, const char *msg)

    查询元素

  1. 使用索引来引用栈的元素
  2. 第一个压入栈的索引为1
  3. 栈顶(最后一个)一个索引为-1
  • lua_is*
  1. 检查栈中一个元素是否为特定的类型
  2. 并不是检查某个值是否为特定类型,而是检查该值是否能被转为特定类型
  • lua_type
  1. 返回栈中元素的类型
  2. 每种类型对应常量(LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER,LUA_TSTRING)
  • lua_to*
  1. 用于从栈中取值

    int lua_toboolean(lua_State *L, int index)
    const char *lua_tolstring(lua_State *L, int index, size_t *len)
    lua_State *lua_tothread(lua_State *L, int index)
    lua_Number lua_tonumber(lua_State *L, int index)
    lua_Integer lua_tointeger(lua_State *L, int index)
  2. 5.2引入的新函数

    lua_Number lua_tonumberx(lua_State *L, int index, int *isnum)
    lua_Integer lua_tointegerx(lua_State *L, int index, int *isnum)
    1. isnum 返回一个布尔值,表示Lua值是否被强制转换为期望的类型
  3. lua_tolstring

    1. 返回一个指向该字符串内部副本的指针,并将字符串的长度存入到参数len指定位置
    2. Lua语言保证,字符串还在栈中,则指针就有效
    3. 返回的字符串末尾会添加一个\0
    4. len为字符串真实长度
      size_t len;
      const char *s = lua_tolstring(L, -1, &len); //任意lua字符串
      assert(s[len] == '\0'); //永远成立
      assert(strlen(s) <= len); //永远成立
      static void stackDump(lua_State *L) {
          int i;
          int top = lua_gettop(L); //栈的深度
          for(i = 1; i <= top; i++) {
              int t = lua_type(L, i);
              switch (t) {
                  case LUA_TSTRING: {
                      printf("'%s'", lua_tostring(L, i));
                      break;
                  }
                  case LUA_TBOOLEAN:{
                      printf(lua_toboolean(L, i) ? "true" : "false");
                      break;
                  }
                  case LUA_TNUMBER: {
      
                      /*
                      //5.3
                      if (lua_isinteger(L, i)) printf("%lld", lua_tointeger(L, i)); //整型
                      else printf("%g", lua_tonumber(L, i)); // 浮点型
                       */
      
                      printf("%g", lua_tonumber(L, i));
                      break;
                  }
                  default: {
                      printf("%s", lua_typename(L, t));
                      break;
                  }
              }
              printf("    ");
          }
          printf("\n");
      }

      其他操作

//返回栈中元素的个数,也是栈顶元素的索引
int lua_gettop(lua_State *L);
/* 
* 将栈顶设置为一个指定的值,修改栈中元素树龄
* 大于原有栈数量使用nil填充,否则丢弃
* lua_settop(L, 0) 清空 */
int lua_settop(lua_State *L, int index);
//弹出n个元素
int lua_pop(lua_State *L, n)
//lua中的定义
#define lua_pop(L, n) lua_settop(L, -(n)-1)
//将指定索引上的元素的副本压入栈
int lua_pushvalue(lua_State *L, int index);
//Lua5.3新引入,如果n>0, 向栈顶移动n个位置;如果n<0,向栈底移动n个位置
int lua_rotate(lua_State *L, int index, int n);
//删除指定索引的元素,并移动栈填补空缺
int lua_remove(lua_State *L, int index);
//lua中定义
#define lua_remove(L, index) (lua_rotate(L, (index), -1), lua_pop(L, 1))
//将栈顶元素移动到指定位置,并开辟一个位置
int lua_insert(lua_State *L, int index);
//lua中定义
#define lua_insert(L, index) lua_rotate(L, (index), 1)
//弹出一个值,并将栈顶设置为指定索引上的值
int lua_replace(lua_State *L, int index);
//将一个索引上的值复制到另一个索引上,并且原值不受影响
int lua_copy(lua_State *L, int fromindex, int toindex);

以下操作不会对空栈产生影响

//以下操作不会对空栈产生影响
lua_settop(L, -1); //将栈顶设置为当前的值
lua_insert(L, -1); //将栈顶的元素移动到栈顶
lua_copy(L, x, x); // 把一个元素复制到它当前的位置
lua_rotate(L, x, 0); //旋转零个位置
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
static void stackDump(lua_State *L);

int main(void) {
	lua_State *L = luaL_newstate();

	lua_pushboolean(L, 1);
	lua_pushnumber(L, 10);
	lua_pushnil(L);
	lua_pushstring(L, "hello");

	stackDump(L); // output: true 10 nil 'hello'

	lua_pushvalue(L, -4); stackDump(L); //output: true 10 nil 'hello' true

	lua_replace(L, 3); stackDump(L); //output: true 10 true 'hello'

	lua_settop(L, 6); stackDump(L); //output: true 10 true 'hello' nil nil

	lua_rotate(L, 3, 1); stackDump(L); // output: true 10 nil true 'hello' nil

	lua_remove(L, -3); stackDump(L); //output: true 10 nil 'hello' nil

	lua_settop(L, -5); stackDump(L); //output: true

	lua_close(L);

	return 0;

}

使用CAPI进行错误处理

处理应用代码中的错误

  1. C语言执行时出错
  2. 把C代码封装到一个函数F中,然后使用lua_pcall调用这个函数F
  3. C代码在保护模式下运行,即使内存分配失败,lua_pcall返回一个错误码,不会影响程序本身
static int foo(lua_State *L){
	code to run in protected mode (要以保护模式运行的代码)
	return 0;
}

int secure_foo(lua_State *L){
	lua_pushcfunction(L, foo);
	return (lua_pcall(L, 0, 0, 0) == 0);
}
  • 无论foo函数运行是否成功,都不会造成secure_foo崩溃

处理库代码中的错误

  1. Lua调用C方法时出错
  2. 由于Lua代码在lua_pcall中运行,所以都不会导致调用lua_pcall 奔溃
  3. 如果出错,调用lua_error做错误处理(luaL_error更好,它会格式化错误信息,然后调用lua_error)
  4. 将错误信息压入栈传递错误信息

内存分配

  1. Lua核心对内存不做任何处理,调用提供的分配函数进行内存分配

  2. LuaL_newstate 默认分配函数来创建Lua状态(使用标准函数malloc-realloc-free)

  3. 自定义分配函数使用Lua原始方法lua_newstate
    a. lua_newstate

    /*
    *  @param f 分配函数
    *  @param ud 用户数据
    */
    lua_State *luaL_newstate(lua_Alloc f, void *ud);

    b. 分配函数lua_Alloc的类型声明

    /*
    *	@param ud 用户数据
    *	@param ptr 正要被(重)分配或释放的块的地址
    *	@param size_t 原始块的大小
    *	@param osize 如果ptr不是NULL,Lua会保证其之前被分配的大小就是osize, 否则为0
    *	@parma nsize 请求块的大小
    */
    typedef void * (*lua_Alloc)(void *ud, void *ptr, size_t osize, size_t nsize);
  4. luaL_newstate的实现(在文件lauxlib.c中)

    static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
      (void)ud;
      (void)osize;
      if (nsize == 0) {
        free(ptr);
        return NULL;
      }
      else
        return realloc(ptr, nsize);
    }
    
    LUALIB_API lua_State *luaL_newstate (void) {
      lua_State *L = lua_newstate(l_alloc, NULL);
      if (L) lua_atpanic(L, &panic);
      return L;
    }