1. 程式人生 > >lua模組註冊

lua模組註冊

Lua自帶的模組並不多,好處就是Lua足夠的小,畢竟它的設計目標是定位成一個嵌入式的輕量級語言的.

相關的函式index2adr

static TValue *index2adr (lua_State *L, int idx) {
  if (idx > 0) {
    TValue *o = L->base + (idx - 1);
    api_check(L, idx <= L->ci->top - L->base);
    if (o >= L->top) return cast(TValue *, luaO_nilobject);
    else return o;
  }
  else if (idx > LUA_REGISTRYINDEX) {
    api_check(L, idx != 0 && -idx <= L->top - L->base);
    return L->top + idx;
  }
  else switch (idx) {  /* pseudo-indices */
    case LUA_REGISTRYINDEX: return registry(L);
    case LUA_ENVIRONINDEX: {
      Closure *func = curr_func(L);
      sethvalue(L, &L->env, func->c.env);
      return &L->env;
    }
    case LUA_GLOBALSINDEX: return gt(L);
    default: {
      Closure *func = curr_func(L);
      idx = LUA_GLOBALSINDEX - idx;
      return (idx <= func->c.nupvalues)
                ? &func->c.upvalue[idx-1]
                : cast(TValue *, luaO_nilobject);
    }
  }
}

一個Lua函式棧由兩個指標base和top來指定,base指向函式棧底,top則指向棧頂.
回到index2addr函式中,幾種情況:

  1. 如果索引為正,則從函式棧底為起始位置向上查詢資料
  2. 如果索引為負,則從函式棧頂為起始位置向下查詢資料
  3. 緊跟著是幾種特殊的索引值,都定義了非常大的資料,由於Lua棧限定了函式的棧尺寸,所以不會有那麼大的索引,大可放心使用.

索引值為LUA_REGISTRYINDEX時,則返回的是全域性資料global_state的l_registry表;如果索引值為LUA_GLOBALSINDEX,則返回該Lua_State的l_gt表.

lua模組註冊

Lua內部所有模組的註冊都在linit.c的函式luaL_openlibs中提供.可以看到的是,它依次訪問一個數組,陣列中定義了每個模組的模組名及相應的模組註冊函式,依次呼叫函式就完成了模組的註冊.

static const luaL_Reg lualibs[] = {
  {"", luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},
  {LUA_OSLIBNAME, luaopen_os},
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_DBLIBNAME, luaopen_debug},
  {NULL, NULL}
};

LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib = lualibs;
  for (; lib->func; lib++) {
    lua_pushcfunction(L, lib->func);
    lua_pushstring(L, lib->name);
    lua_call(L, 1, 0);
  }
}

我沒有詳細的檢視每個模組的註冊函式,不過還是以最簡單的例子來講解,就是最常用的print函式.

由於這個函式沒有字首,因此的它所在的模組是”",也就是一個空字串,因此它是在base模組中註冊的,呼叫的註冊函式是luaopen_base.

緊跟著繼續看luaopen_base內部呼叫的第一個函式base_open:

static void base_open (lua_State *L) {
  /* set global _G */
  lua_pushvalue(L, LUA_GLOBALSINDEX);
  lua_setglobal(L, "_G");
  /* open lib into global table */
  luaL_register(L, "_G", base_funcs);

  // ....
}

首先來看最前面的兩句:

  /* set global _G */
  lua_pushvalue(L, LUA_GLOBALSINDEX);
  lua_setglobal(L, "_G");

這兩句首先將LUA_GLOBALSINDEX對應的值壓入棧中,其次呼叫”lua_setglobal(L, “_G”);”,這句程式碼的意思是在Lua_state的l_gt表中,當查詢”_G”時,查詢到的是索引值為LUA_GLOBALSINDEX的表.如果覺得有點繞,可以簡單這個理解,在Lua中的G表,也就是全域性表,滿足這個等式”_G = _G["_G"]“,也就是這個叫”_G”的表,內部有一個key為”_G”的表是指向自己的.懷疑這個結論的,可以在Lua命令列中執行print(_G)和print(_G["_G"])看看輸出結果是不是一致的.

Lua中要這麼處理的理由是:為了讓G表和處理其它表使用同樣的機制.查詢一個變數時,最終會一直查到G表中,這是很自然的事情;所以為了也能按照這個機制順利的查詢到自己,於是在G表中有一個同名成員指向自己.

好了,前面兩句的作用已經分析完畢.其結果有兩個:

  1. _G = _G["_G"]
  2. _G表的值壓入函式棧中方便了下面的呼叫.

繼續看下面的語句:
**luaL_register(L, “_G”, base_funcs);
它最終會將base_funcs中的函式註冊到G表中,但是裡面還有些細節需要看看的.**

LUALIB_API void luaI_openlib (lua_State *L, const char *libname,
                              const luaL_Reg *l, int nup) {
  if (libname) {
    int size = libsize(l);
    /* check whether lib already exists */
    luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
    lua_getfield(L, -1, libname);  /* get _LOADED[libname] */
    if (!lua_istable(L, -1)) {  /* not found? */
      lua_pop(L, 1);  /* remove previous result */
      /* try global variable (and create one if it does not exist) */
      if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
        luaL_error(L, "name conflict for module " LUA_QS, libname);
      lua_pushvalue(L, -1);
      lua_setfield(L, -3, libname);  /* _LOADED[libname] = new table */
    }
    lua_remove(L, -2);  /* remove _LOADED table */
    lua_insert(L, -(nup+1));  /* move library table to below upvalues */
  }

// ...
}

註冊這些函式之前,首先會到l_registry表的成員_LOADED表中查詢該庫,如果不存在則再在G表中查詢這個庫,不存在則建立一個表.因此,不管是lua中內部的庫或者是外部使用require引用的庫,都會走這個流程並最終在G表和l_registry["_LOADED"]中存放該庫的表.最後,再遍歷傳進來的函式指標陣列,完成庫函式的註冊.

比如,註冊os.print時,首先將print函式繫結在一個函式指標上,再去l_registry["_LOADED"]和G表中查詢該名為”os”的庫是否存在,不存在則建立一個表,即:
G["os"] = {}

緊跟著註冊print函式,即: G["os"]["print"] = 待註冊的函式指標.這樣,在呼叫lua程式碼os.print(1)時,首先根據”os”到G表中查詢對應的表,再在這個表中查詢”print”成員得到函式指標,最後完成函式的呼叫.

註冊外部模組

luaL_newlibtable 它僅僅是建立了一個table,然後把數組裡的函式放進去而已

luaL_setfuncs它把陣列l中的所有函式註冊入棧頂的table,並給所有函式綁上nupupvalue

define luaL_newlibtable(L, l)

lua_createtble(L, 0, sizeof(l)/sizeof((l)[0]) - 1)

define luaL_newlib(L, l)

(luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup){
    luaL_checkversion(L);
    luaL_checkstack(L, nup, "too_many_upvalue");
    for(; l->name != NULL; i++){/* fill the table with given functions*/
        int i;
        for(i = 0; i < nup; i++)/copy upvalues to the top/
            lua_pushvalue(L, -nup);
        lua_pushclosure(L, l->func, nup);/closure with those upvalues/
        lua_setfield(L, -(nup + 2), l->name);
    }
    lua_pop(L, nup);/remove upvalues/
}