Lua嵌入c入門

Lua介紹
html

      Lua 是一個小巧的腳本語言。其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。
windows

      Lua由標準C編寫而成,幾乎在全部操做系統和平臺上均可以編譯,運行。Lua並無提供強大的庫,一個完整的Lua解釋器不過200k,因此Lua不適合做爲開發獨立應用程序的語言。
數組

      Lua腳本能夠很容易的被C/C++ 代碼調用,也能夠反過來調用C/C++的函數,這使得Lua在應用程序中能夠被普遍應用。不只僅做爲擴展腳本,也能夠做爲普通的配置文件,代替XML,ini等文件格式,而且更容易理解和維護。微信

下載Lua
      下載頁面:http://www.lua.org/download.html
       目前最新的lua腳本是lua5.3
       在下載頁面能夠下載lua源代碼,也能夠下載編譯好的二進制文件,看你本身的須要。
函數

環境設置
      在windows平臺下咱們必須配置Visual C++,以便讓編譯器和鏈接器能找到Lua文件,配置步驟以下:
      1. 打開Visual C++,選擇Tools菜單中的選項菜單
      2. 展開"項目",並選擇"VC++ 目錄"
      3. 選擇"包含文件",添加一個新路徑 "lua安裝路徑/include"
      4. 再選擇"庫文件",添加路徑" lua安裝路徑/lib/dll"(這裏假設你下載的庫爲dll,你也能夠下載靜態連接庫)
      5. 肯定
      如今你能夠開始編譯你的第一個Lua應用了。lua

c調用Lua腳本
      程序代碼以下:spa

#include <stdio.h>  
extern "C" {   
#include "lua.h"   
#include "lualib.h"   
#include "lauxlib.h"  
}   
int main ( int argc, char *argv[] )
{   
    lua_State *L = luaL_newstate()  // 返回一個指向Lua解釋器的指針
    luaL_openlibs(L);            // 加載lua庫
    luaL_dofile(L, "test.lua");      // 執行lua腳本
    lua_close(L);                // 關閉Lua
    printf( "Press enter to exit…" );  
    getchar(); 
    return 0;  
}

 c與Lua的數據交換—Lua的堆棧
       lua使用一個抽象的棧在lua與c之間進行數據交換。棧中的每一條記錄均可以保存任何lua值。不管什麼時候你想要從lua請求一個值(好比一個全局變量的值),調用lua,被請求的值會被壓入棧。不管什麼時候你想要傳遞一個值給lua,首先將這個值壓入棧,而後調用lua(這個值將被彈出)。另外棧是由lua來管理的,垃圾回收器知道哪一個值正在被C使用。幾乎全部的API函數都用到了棧。Lua以一個嚴格的LIFO規則(後進先出,也就是說始終存取棧頂)來操做棧,當你調用lua時,它只會改變棧頂部分。你的C代碼卻有更多的自由,你能夠查詢棧上的任何元素,甚至在任何一個位置插入和刪除元素。操作系統

一、 壓入元素
      API有一系列壓棧函數,它將每種能夠用C來描述的Lua類型壓棧。設計

void lua_pushnil(lua_State *L);
void lua_pushboolean(lua_State *L, int bool);
void lua_pushnumber(lua_State *L,double n);
void lua_pushlstring(lua_State *L, const char *s, size_t length);
void lua_pushstring(lua_State *L,const char *s);

       Lua中的字符串不是以0爲結束符的,他們依賴於一個明確的長度,所以能夠包含任意的二進制數據。將字符串壓入棧的函數是lua_pushlstring,它要求一個明確的長度做爲參數。對於以0結束的字符串,你能夠用lua_pushstring(它用strlen來計算字符串長度)。Lua歷來不保持一個指向外部字符串的指針,對於它保持的全部字符串,Lua要麼作一分內部的拷貝,要麼從新利用已經存在的字符串。所以,一旦這些函數返回以後,你能夠自由的修改或是釋放你的緩衝區。指針

二、 查詢元素
      API用索引來訪問棧中的元素。在棧中的第一個元素(也就是第一個被壓入棧的)的索引是1,下一個被壓入棧的元素索引是2,依次類推。咱們也能夠用棧頂做爲參照來存取元素,利用負索引。在這種狀況下,-1指棧頂元素(也就是最後被壓入棧的元素),-2指出棧頂元素的前一個元素,依次類推。
      API提供了一套lua_is*函數來檢查一個元素是不是一個指定的類型,*能夠是任何Lua類型。所以有lua_isnumber, lua_isstring, lua_istable以及相似的函數。全部這些函數都有一樣的原型:

int lua_is...(lua_State *L,int index);

      lua_isnumber和lua_isstring函數不檢查這個值是不是指定的類型,而是看它是否能被轉換成指定的那種類型。例如任何數字類型都知足lua_isstring。
      還有一個lua_type函數,它返回棧中元素的類型。在lua.h頭文件中,每種類型都被定義爲一個常量:LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA以及LUA_TTHREAD。當咱們須要真正檢查字符串和數字類型時,咱們能夠用這個函數。
      爲了從棧中得到值,可使用lua_to*函數:

int lua_toboolean(lua_State *L, int index);
double lua_tonumber(lua_State *L, int index);
const char* lua_tostring(lua_State *L, int index);
size_t lua_strlen(lua_State *L, int index);

       即便給定的元素類型不明確,調用上面這些函數也沒有什麼問題。在這種狀況下,lua_toboolean, lua_tonumber和lua_strlen返回0,其餘函數返回NULL。
       lua_tostring函數返回一個指向字符串內部拷貝的指針,你不能修改它。只要這個指針對應的值還在棧內,Lue會保證這個指針一直有效。
       當一個C函數返回後,Lua會清理它的棧,因此,有一個原則:永遠不要將指向Lua字符串的指針保存到訪問他們的外部函數中。
       Lua_string返回的字符串結尾總會有一個字符串結束標誌0,可是字符串中間也可能包含0,lua_strlen返回字符串的實際長度。特殊狀況下,假定棧頂的值是一個字符串,下面的斷言老是有效的:

/*any lua string*/
const char *s = lua_tostring(L,-1);
/*it's length*/
size_t l = lua_strlen(L,-1);
assert(s[1] == '\0');
assert(strlen(s) <= 1);

三、 其餘堆棧操做
     除了上面所提到的C與堆棧交換值的函數外,API也提供了下列函數來完成一般的堆棧維護工做:

int lua_gettop(lua_State *L);
int lua_settop(lua_State *L,int index);
int lua_pushvalue(lua_State *L,int index);
int lua_remove(lua_State *L,int index);
int lua_insert(lua_State *L,int index);
int lua_replace(lua_State *L,int index);

      函數lua_gettop返回堆棧中的元素個數,它也是棧頂元素的索引。lua_settop設置棧頂(也就是堆棧中的元素個數)爲一個指定的值。若是以前的棧頂高於新的棧頂,頂部的值被丟棄,不然,爲了獲得指定的大小,這個函數壓入相應個數的空值(nil)到棧上。特別的,lua_settop(L,0)清空堆棧。你也能夠用負數索引做爲調用lua_settop的參數,那將會設置棧頂到指定的索引,利用這種技巧,API提供了下面這個宏,它從堆棧中彈出n個元素:

#define lua_pop(L,n) lua_settop(L,-(n)-1)

       函數lua_pushvalue壓入堆棧上指定索引的一個拷貝到棧頂;lua_remove移除指定索引位置的元素,並將其上面全部的元素下移來填補這個位置的空白;lua_insert移動棧頂元素到指定索引的位置,並將這個索引位置上面的元素所有上移至棧頂被移動留下的空隔;最後,lua_replace從棧頂彈出元素值,並將其設置到指定索引位置,沒有任何移動操做。

例子代碼:

#include <stdio.h>  
extern "C" {   
#include "lua.h"   
#include "lualib.h"   
#include "lauxlib.h"  
}
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:
                printf("%g",lua_tonumber(L,i));
                break;
            default:
                printf("%s",lua_typename(L,t));
                break;
        }
        printf("   ");
    }
    printf("\n");
}

int main ( int argc, char *argv[] )
{
    lua_State *L = luaL_newstate(); 
    luaL_openlibs(L); 
 
    lua_pushboolean( L,1 );
    lua_pushnumber( L,10 );
    lua_pushnil( L );
    lua_pushstring( L, "hello" );
    stackDump(L);
 
    lua_pushvalue( L, 4 );
    stackDump(L);
 
    lua_replace( L, 3 );
    stackDump(L);
 
    lua_settop( L, 6 );
    stackDump(L);
 
    lua_remove( L, -3 );
    stackDump(L);
 
    lua_settop( L, -5 );
    stackDump(L);
 
    lua_close(L);
 
    getchar();
   return 0;
}

c調用Lua函數
       Lua做爲配置文件的一個最大的長處在於它能夠定義一個被應用調用的函數。好比,你能夠寫一個應用程序來繪製一個函數的圖像,使用Lua來定義這個函數。
      使用API調用函數的方法很簡單:首先,將被調用的函數入棧;第二:依次將全部參數入棧;第三:使用lua_pcall調用函數;最後:從棧中獲取函數執行返回的結果。

例子代碼:

#include "stdafx.h"
#include "string.h"
#include "lua.hpp"   
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:
                printf("%g",lua_tonumber(L,i));
                break;
            default:
                printf("%s",lua_typename(L,t));
                break;
        }
        printf("   ");
    }
    printf("\n");
}

int main ( int argc, char *argv[] )
{   
    //初始化Lua  
    lua_State *L = luaL_newstate(); 
 
    //載入Lua基本庫     
    luaL_openlibs(L); 
 
    //調用lua函數
    if( luaL_loadfile(L,"script/test.lua") || lua_pcall(L,0,0,0) )
       printf("can not running the script file : %s",lua_tostring(L,-1));
    stackDump(L);
 
    double z;
    lua_getglobal(L,"f");
    stackDump(L);
    lua_pushnumber(L,3);
    stackDump(L);
    lua_pushnumber(L,4);
    stackDump(L);
    if( lua_pcall(L,2,1,0) != 0 )
    {
        printf("error running function f : %s!\n",lua_tostring(L,-1));
    }
    stackDump(L);
 
    if( !lua_isnumber(L,-1) )
    {
        printf("function f must return a number!\n");
    }
    stackDump(L);
    z = lua_tonumber(L,-1);
    printf("the call result is : %f\n",z);
    lua_pop(L,1);
    getchar();
    // 清除Lua   
    lua_close(L);   
    return 0;  
}

       在調用lua_pcall的時候,能夠指定參數的個數和返回結果的個數,第四個參數能夠指定一個錯誤處理函數,這個例子裏面沒有使用。和Lua中賦值操做同樣,lua_pcall會根據你的要求調整返回結果的個數,多餘的丟棄,少的用nil補足。在將結果入棧以前,lua_pcall會將棧內的函數和參數移除。若是函數返回多個結果,第一個結果被第一個入棧,所以若是有n個結果,第一個返回結果在棧中的位置爲-n,最後一個返回結果在棧中的位置爲-1。
        若是lua_pcall運行時出現錯誤,lua_pcall會返回一個非0的結果。另外,它將錯誤信息入棧(仍然會先將函數和參數從棧中移除)。在將錯誤信息入棧以前,若是指定了錯誤處理函數,lua_pcall會先調用錯誤處理函數。使用lua_pcall的最後一個參數來指定錯誤處理函數,0表明沒有錯誤處理函數,也就是說最終的錯誤信息就是原始的錯誤信息。不然那個參數應該是一個錯誤函數被加載時,在棧中的索引。所以,在這種狀況下,錯誤處理函數必須在被調用的函數和其餘參數入棧以前入棧。
對於通常的錯誤,lua_pcall返回錯誤代碼LUA_ERRRUN。有兩種特殊狀況,會返回特殊錯誤代碼。第一種狀況是內存分配錯誤,對於這種錯誤,lua_pcall老是返回LUA_ERRMEM。第二種狀況是當Lua正在運行錯誤處理函數時發生錯誤,這種狀況下,再次調用錯誤處理函數沒有意義,因此lua_pcall當即返回錯誤代碼LUA_ERRERR。

c擴展Lua
      擴展Lua的基本方法之一就是爲應用程序註冊新的C函數到Lua中去。
      當咱們提到Lua能夠調用C函數,不是指Lua能夠調用任何類型的C函數。正如咱們前面所看到的,當C調用Lua函數的時候,必須遵循一些協議來傳遞參數和得到返回結果。一樣的,從Lua中調用C函數,也必須遵循一些協議來傳遞參數和得到返回結果。另外,從Lua中調用C函數,咱們必須先註冊函數,也就是說,咱們必須把C函數的地址以一個適當的方式傳遞給Lua解釋器。
       當Lua調用C函數的時候,使用和C調用Lua相同類型的棧來交互。C函數從棧中獲取他的參數,調用結束後將返回結果放到棧中。爲了區分返回結果和棧中其餘的值,每一個C函數還會返回結果的個數。這裏有一個重要的概念:用於交互的棧不是全局變量,每個函數都有他本身的私有棧。當Lua調用C函數的時候,第一個參數老是在這個私有棧的index=1的位置。甚至當一個C函數調用Lua代碼(Lua代碼調用同一個C函數或者其餘的C函數),每個C函數都有本身的獨立的私有棧,而且第一個參數在index=1的位置。

擴展Lua的簡單步驟
      1:實現一個lua_CFunction類型的函數。任何在Lua中註冊的函數必須有一樣的原型,這個原型就是定義在lua.h中的lua_CFunction:

typedef int (*lua_CFunction)(lua_State *L);

      2:實現這個函數。

static int l_sin(lua_State *L){
    double d = luaL_checknumber(L,1);
    lua_pushnumber(L,sin(d));
    return 1;
}

     3:註冊這個函數。咱們使用lua_pushfunction來完成這個任務:它獲取指向C函數的指針,並在Lua中建立一個function類型的值來表示這個函數。一個方便快捷可是不專業的解決方案是將上面的函數定義代碼直接放到lua,c文件中,而且在調用luaL_newstate()後面適當的位置加上下面兩行:

lua_pushcfunction(1,l_sin);
lua_setglobal(1,"mysin");

       第一行將類型爲function的值入棧,第二行將function賦值給全局變量mysin。這樣修改以後,從新編譯Lua,你就能夠在你的Lua程序中使用新的mysin函數了。

使用C函數庫擴展Lua
       一個Lua庫其實是一個定義了一系列Lua函數的chunk,並將這些函數保存在適當的地方。Lua的C庫就是這樣實現的。除了定義C函數以外,還必須定義一個特殊的用了和Lua庫的主chunk通訊的特殊函數。一旦調用,這個函數就會註冊庫中的全部C函數,並將它們保存到適當的位置。像一個Lua主chunk同樣,它也會初始化其餘一些在庫中須要初始化的東西。
       Lua經過這個註冊過程,就能夠看到庫中的C函數。一旦一個C函數被註冊以後並保存在Lua中,在Lua程序中就能夠直接引用它的地址(註冊這個函數的時候傳給Lua的地址)來訪問這個函數了。換句話說,一旦C函數被註冊以後,Lua調用這個函數並不依賴於函數名,包的位置,或者調用函數的可見規則。一般C庫都有一個外部的用來打開庫的函數。其餘的函數可能都是私有的,在C中被聲明爲static。
       當你打算使用C函數來擴展Lua的時候,即便你僅僅只想註冊一個C函數,將你的C代碼設計爲一個庫是個比較好的思想,不久的未來你就會發現你須要其餘的函數。通常狀況下,輔助庫對這種實現提供了幫助。LuaL_openLib函數接受一個C函數的列表和他們對應的函數名,而且做爲一個庫在一個table中註冊全部這些函數。

 使用庫擴展Lua的步驟
      1:新建一個mylib.c文件
      2:在mylib.c文件裏面定義庫函數。

static int l_sin(lua_State *L){
    double d = luaL_checknumber(L,1);
    lua_pushnumber(L, sin(d));
    return 1; /* number of results */
}

     3:在mylib.c文件裏面聲明一個數組,保存全部的函數和他們對應的名字。這個數組的元素類型爲luaL_reg:是一個帶有兩個域的結構體,一個字符串和一個函數指針。

static const struct luaL_reg mylib [] = {
   {「mysin」,l_sin},
   {NULL,NULL} /* sentinel */
};

    在咱們的例子中,只有一個函數l_sin須要聲明。注意數組中最後一對必須是{NULL,NULL},用來表示結束。
    4:定義庫名稱和打開庫的API接口。在lualib.h裏面添加下面兩行代碼

#define LUA_MYLIBNAME "my"  /* 庫的名稱 */
LUAMOD_API int (luaopen_mylib) (lua_State *L);

    5:在mylib.c文件裏面使用luaL_newlib實現庫打開函數。

LUAMOD_API int luaopen_mylib (lua_State *L) {
    luaL_newlib(L, mylib);
    return 1;
}

   6:在linit.c文件裏面的loadedlibs數組裏面添加一行,如圖所示:

  

   7:從新編譯lua。

-----------------------------------------------------------------------------------------------------------------

歡迎關注個人微信公衆號 ^_^

相關文章
相關標籤/搜索