Lua基礎學習(二)

本節我將一步一步帶領大家完成c++調用lua函數並接受lua的返回值,通過分析調用的方式來封裝一個類,最終封裝完成的類並不是最優的,但應該能夠滿足一般的項目中對lua調用的功能。不足之處歡迎大家給予指正。

 

1    基本概念

1.1      棧

c++調用lua是通過一個抽象的棧來實現數據的交換的。C++調用lua時,首先需要把lua函數需要的參數壓入這個抽象的棧中,如果c++想要從lua中獲取數據,則lua需要先把數據壓入棧中,然後c++從棧中取得需要的數據。Lua是以嚴格的LIFO規則來操作棧的,即後進先出原則,而c++則可以操作棧上的任何一個元素。

2    常用函數

  • void lua_pushnil (lua_State *L);

往棧中壓入空值

  • void lua_pushboolean (lua_State *L, int bool);

往棧中壓入布爾型值

  • void lua_pushnumber (lua_State *L, double n);

往棧中壓入double型數值

  • void lua_pushlstring (lua_State *L, const char *s, size_t length);

往棧中壓入字符串,但是該字符串中可以包含'\0',字符串的長度爲length

  • void lua_pushstring (lua_State *L, const char *s);

往棧中壓入c風格的字符串,以'\0'結尾

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

用來檢查棧上的一個元素是否指定的類型

  • int lua_type (lua_State *L, int index);

返回棧中元素的類型

在lua.h中定義了各個元素對應的常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD

  • 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);

該函數返回一個指向lua棧內元素的指針,該指針是不允許修改的

切記這裏的指針是指向棧上元素的,如果lua清空了棧內元素,則該指針就無效了。

  • int  lua_gettop (lua_State *L);

返回堆棧中的元素個數,它也是棧頂元素的索引

  • void lua_settop (lua_State *L, int index);

設置棧頂爲一個指定的值

如果原棧頂大於新棧頂,則頂部的值被丟棄,如果原棧頂小於新棧頂,則將多出的元素賦nil。lua_settop(L,0)清空堆棧。

你也可以用負數索引作爲調用lua_settop的參數;那將會設置棧頂到指定的索引。利用這種技巧,API提供了下面這個宏,它從堆棧中彈出n個元素:

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

Ø  void lua_pushvalue (lua_State *L, int index);

將指定元素的一個備份拷貝到棧頂

Ø  void lua_replace (lua_State *L, int index);
移除指定元素,其上的其它元素依次下移
Ø  void lua_insert (lua_State *L, int index); 
將棧頂的元素移動到指定的索引處,其它元素依次上移
Ø  void lua_replace (lua_State *L, int index); 
將棧頂的值替換指定索引的元素,其它元素不變

3    一個簡單的例子

該例子最終是通過c++代碼調用lua腳本實現加法的操作並把返回值打印到控制檯上。以下是代碼:

test.cpp

複製代碼
#include <iostream>
#include "lua.hpp"
using namespace std;

int main()
{
    int iRet = 0;
    double iValue = 0;
    lua_State * L = luaL_newstate();// 創建lua狀態
    if (NULL == L)
    {
        cout << "luaL_newstate()沒有足夠的內存分配\n" << endl;
        return 0;
    }
    iRet = luaL_dofile(L, "test.lua");// 加載並運行lua腳本
    if (0 != iRet)
    {
        cout << "luaL_dofile() failed\n" << endl;
        return 0;
    }
    lua_getglobal(L, "add"); // 把函數名壓入堆棧
    
    lua_pushnumber(L, 2);
    lua_pushnumber(L, 3);
    iRet = lua_pcall(L, 2, 1, 0); // 調用函數
    if (0 != iRet)
    {
        printf("lua_pcall failed:%s\n",lua_tostring(L,-1));
        return 0;
    }
    
    if (lua_isnumber(L, -1) == 1)
    {
        iValue = lua_tonumber(L, -1);
    }
    cout << iValue << endl;
    
    lua_close(L);
    return 0;
}
複製代碼



test.lua

function add (x,y)
    return x+y
end



編譯:g++ -o test test.cpp -ltolua++

運行結果:5

以下是對該例子中用到的函數的解釋:

  • lua_State *luaL_newstate (void);

該函數用於創建一個新的lua狀態,當內存分配錯誤的時候返回NULL

  • int luaL_loadfile (lua_State *L, const char *filename);

該函數加載lua文件,但不運行。

  • int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

該函數用於調用lua的函數,nargs是入參個數,nresults是出參個數,如果errfunc爲0,表示把出錯的信息放入堆棧中,如果不爲0,則該值代表一個錯誤處理函數在lua堆棧中的索引。函數成功時返回0,錯誤時返回如下三個值:

LUA_ERRRUN:運行時錯誤;

LUA_ERRMEM:內存分配錯誤。對於這樣的錯誤,Lua不調用錯誤處理函數;

LUA_ERRERR:運行錯誤處理函數的錯誤。

  • int luaL_dofile (lua_State *L, const char *filename);

加載並運行指定的文件,正確的時候返回0,錯誤的時候返回1。該函數在lua中被定義爲一個宏:

(luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))

  • void lua_getglobal (lua_State *L, const char *name);

將函數名壓入到堆棧中,它被定義爲如下宏:

#define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, s)

  • void lua_close (lua_State *L);

該函數銷燬lua狀態的所有對象,如果是守護進程或web服務器,則需要在使用完後儘快釋放,以避免過大。

如果想直接驗證lua腳本可以按如下步驟操作:

/home/tolua/test#lua

Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio

> dofile("test.lua")

> print (add(1,4))

5

4    封裝

讓我們仔細研究下這個簡單的例子,是不是發現了很多共同點呢?我們完全可以利用 c++的特性對其進行封裝,這樣我們再調用lua的時候,就可以直接調用我們封裝好的類啦。

CToLua.h

複製代碼
#ifndef    CTOLUA_H_
#define  CTOLUA_H_

#include "lua.hpp"
#include <iostream>
using namespace std;

class CToLua
{
public:
    CToLua();
    ~CToLua();
    bool loadLuaFile(const char* pFileName); //加載指定的Lua文件
    double callFileFn(const char* pFunctionName, const char* format, ...); //執行指定Lua文件中的函數
    lua_State* getState();
private:
    int parseParameter(const char* format, va_list& arg_ptr);
    lua_State* m_pState;
};

#endif // CTOLUA_H_
複製代碼


複製代碼
#include "CToLua.h"

extern "C"
{
#include "tolua++.h"
}

CToLua::CToLua()
{
    m_pState = luaL_newstate();
    if (NULL == m_pState)
    {
        printf("luaL_newstate()沒有足夠的內存分配\n");
    }
    else
    {
        luaopen_base(m_pState);
    }
}

CToLua::~CToLua()
{
    if (NULL != m_pState)
    {
        lua_close(m_pState);
        m_pState = NULL;
    }
}

bool CToLua::loadLuaFile(const char* pFileName)
{
    int iRet = 0;
    if (NULL == m_pState)
    {
        printf("[CToLua::LoadLuaFile]m_pState is NULL.\n");
        return false;
    }

    iRet = luaL_dofile(m_pState, pFileName);
    if (iRet != 0)
    {
        printf("[CToLua::LoadLuaFile]luaL_dofile(%s) is error(%d)(%s).\n",
            pFileName, iRet, lua_tostring(m_pState, -1));
        lua_pop(m_pState, 1); // 及時清理堆棧
        return false;
    }

    return true;
}

double CToLua::callFileFn(const char* pFunctionName, const char* format, ...)
{
    int iRet = 0;
    double iValue = 0;

    int iTop = lua_gettop(m_pState);
    lua_pop(m_pState, iTop); // 清棧

    if(NULL == m_pState)
    {
        printf("[CLuaFn::CallFileFn]m_pState is NULL.\n");
        return 0;
    }

    lua_getglobal(m_pState, pFunctionName);

    va_list arg_ptr;
    va_start(arg_ptr, format);
    iRet = parseParameter(format, arg_ptr);
    va_end(arg_ptr);

    iRet = lua_pcall(m_pState, iRet, 1, 0);
    if (iRet != 0)
    {
        printf("[CLuaFn::CallFileFn]call function(%s) error(%d).\n", pFunctionName, iRet);
        return 0;
    }

    if (lua_isnumber(m_pState, -1) == 1)
    {
        iValue = lua_tonumber(m_pState, -1);
    }

    return iValue;
}

int CToLua::parseParameter(const char* format, va_list& arg_ptr)
{
    int iRet = 0;
    char* pFormat = (char*) format;
    while (*pFormat != '\0')
    {
        if ('%' == *pFormat)
        {
            ++pFormat;
            switch (*pFormat)
            {
            case 'f':
                lua_pushnumber(m_pState, va_arg( arg_ptr, double));
                break;
            case 'd':
            case 'i':
                lua_pushnumber(m_pState, va_arg( arg_ptr, int));
                break;
            case 's':
                lua_pushstring(m_pState, va_arg( arg_ptr, char*));
                break;
            case 'z':
                lua_pushlightuserdata(m_pState, va_arg( arg_ptr, void*));
                break;
            default:
                break;
            }
            ++iRet;
        }
        ++pFormat;
    }
    return iRet;
}

lua_State* CToLua::getState()
{
    return m_pState;
}
複製代碼

 

main.cpp

複製代碼
#include "CToLua.h"

int main()
{
    CToLua tolua;
    tolua.loadLuaFile("C:\\c++\\lua\\test.lua");
    double iValue = tolua.callFileFn("add", "%d%f", 20, 3.69); // 23.69
    cout << iValue << endl;
    iValue = tolua.callFileFn("sub", "%f%i", 23.69,20); // 3.69
    cout << iValue << endl;
    return 0;
}
複製代碼

 

test.lua

複製代碼
function add (x,y)
        return x+y
end

function sub (x,y)
        return x-y
end
複製代碼

 

callFileFn方法使用類似printf,%d或%i代表整數,%f代表浮點數,%s代表字符串,%z代表自定義類型。

由於lua可以支持多返回值,所以原先的設計思想是返回一個vector,裏邊包含一個結構體,有兩個成員,一個是string type,一個是void* pValue。但當我實現了後,發現雖然支持了多個返回值了,但是使用起來卻不是那麼方便。所以索性就不實現這種返回多個返回值的接口了。函數的返回值固定,只返回double類型的值,如果想要多個返回值,可以傳自定義參數來實現。

 

在這裏推薦一下linux下的eclipse c++開發環境,本次代碼編寫就是在該環境下進行的,使用起來還算比較舒服。搭建方式參考http://www.cnblogs.com/osyun/archive/2011/12/05/2276412.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章