C/C++設置LUA全局變量

前言

最近在看到freeswitch的LUA腳本有一個全局變量session。這個變量肯定是在freeswitch內部申請並且進行初始化,然後經過一系列設置後LUA可以直接使用。根據配置freeswitch每一通電話都會執行同一個LUA腳本,每通電話LUA腳本得到的session又不一樣,很好奇這是如何實現的。經過大量資料查閱和代碼測試,找到一種可靠的辦法創建/銷燬LUA全局變量,防止內存泄漏。
本文用一個示例模擬這個實現

代碼實現

C/C++代碼

#include <iostream>
#include <cstring>
#include <stdlib.h>
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

using namespace std;

/*
 * #define LUA_TNIL     0
 * #define LUA_TBOOLEAN     1
 * #define LUA_TLIGHTUSERDATA   2
 * #define LUA_TNUMBER      3
 * #define LUA_TSTRING      4
 * #define LUA_TTABLE       5
 * #define LUA_TFUNCTION        6
 * #define LUA_TUSERDATA        7
 * #define LUA_TTHREAD      8
 * */

char* get_val(lua_State *L, int idx)
{
    static char sData[32];
    sData[0] = '\0';

    int type = lua_type(L, idx);
    switch (type)
    {
        case 0: //nil
            {
            snprintf(sData, sizeof(sData), "%s", "nil");
            break;
            }
        case 1://bool
            {
            int val = lua_toboolean(L, idx);
            snprintf(sData, sizeof(sData), "%s", val == 1 ? "true" : "false");
            break;
            }
        case 3://number
            {
            double val = lua_tonumber(L, idx);
            snprintf(sData, sizeof(sData), "%f", val);
            break;
            }
        case 4://string
            {
            const char* val = lua_tostring(L, idx);
            snprintf(sData, sizeof(sData), "%s", val);
            break;
            }
        case 2:
        case 5:
        case 6:
        case 7:
        case 8:
        default:
            {
            const void* val = lua_topointer(L, idx);
            snprintf(sData, sizeof(sData), "%p", val);
            break;
            }

    }

    return sData;
}

int print_stack(lua_State *L)
{
    int iNum = lua_gettop(L);
    cout<<"==========Total:"<<iNum<<"=========="<<endl;
    for (int i = iNum; i >= 1; i--)
    {
        int idx = i - iNum - 1;
        int type = lua_type(L, i);
        const char* type_name = lua_typename(L, type);
        cout<<"idx:"<<idx<<" type:"<<type<<"("<< type_name<<") "<<get_val(L, i)<<endl;
    }
    cout<<"==========================="<<endl;
    return 0;
}

class Person
{
    public:
        Person(int iAge, int iScore):m_age(iAge), m_score(iScore){};
        ~Person(){cout<<"delete Person"<<endl;}
        int  getAge(){return m_age;}
        void setAge(int iAge){m_age = iAge;}
        static int autoGc(lua_State *L){
            Person** p = (Person**)lua_touserdata(L, 1); 
            cout << "auto gc. age: " << (*p)->m_age << " score: " << (*p)->m_score <<endl;
        }   
    public:
        int m_age;
        int m_score;
};

int create_person(lua_State *L)
{
    /* 創建userdata */
    Person** p = (Person**)lua_newuserdata(L, sizeof(Person*));
    /* 創建自定義類型 */
    *p = new Person(15, 100);

    /* 設置p的元表(LUA調用setAge/getAget就靠元表了) */
    luaL_getmetatable(L, "MetaPerson"); 
    lua_setmetatable(L, -2);

    /* 將p設置爲全局變量 */
    lua_setglobal(L, "sess");
    
    return 1;
}

int get_age(lua_State *L)
{
    //print_stack(L);//觀察進入該函數後LUA堆棧
    /* 獲取自定義類型並進行函數調用 */
    Person** p = (Person**)lua_touserdata(L, 1);
    int iAge = (*p)->getAge();
    lua_pushinteger(L, iAge);
    return 1;
}

int set_age(lua_State *L)
{
    //print_stack(L);//觀察進入該函數後LUA堆棧
    /* 獲取自定義類型並進行函數調用 */
    Person** p = (Person**)lua_touserdata(L, 1);
    int iAge = lua_tointeger(L, 2);
    cout << "set_age " << *p << " " << iAge << endl;
    (*p)->setAge(iAge);
    return 0;
}

int auto_gc (lua_State *L)
{
    /* 釋放內存 */
    Person** p = (Person**)lua_touserdata(L, 1);
    (*p)-> autoGc(L);
    delete *p;
    return 0;
}

int lua_openPerson(lua_State *L)
{
    /* 初始化元表,設置函數對應關係 */
    if (luaL_newmetatable(L, "MetaPerson"))
    {
        lua_pushcfunction(L, &get_age);
        lua_setfield(L, -2, "getAge");
        lua_pushcfunction(L, &set_age);
        lua_setfield(L, -2, "setAge");
        lua_pushcfunction(L, &auto_gc);
        lua_setfield(L, -2, "__gc");
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }

    return 1;
}

int main(int argc, char* argv[])
{
    /* LUA環境初始化 */
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    lua_openPerson(L);

    /* 創建自定義變量後,LUA可以直接使用設置好的全局變量了 */
    create_person(L);
    luaL_dofile(L, "test.lua");

    /* 釋放全局變量內存 */
    printf("delete sess global var memory\r\n");
    lua_getglobal(L,"sess");
    lua_pushnil(L);
    lua_setglobal(L, "sess");   /* 把全局變量設置爲nil,LUA會自動調用元表的__gc函數(也就是auto_gc) */

    /* 關閉LUA環境 */
    lua_settop(L, 0);
    lua_close(L);
    return 0;
}

LUA腳本

function main()
    local age = sess:getAge();
    sess:setAge(18);
    local age2 = sess:getAge();
    print("old: " .. age .. " new: " .. age2);
    print ""
    sess:setAge(19);
    print("old: " .. age .. " new: " .. sess:getAge());
end

main();
collectgarbage("collect");

輸出

[root@centos7 home]# g++ -g -llua ./test.cpp 
[root@centos7 home]# ./a.out 
set_age 0x12e9430 18
old: 15 new: 18

set_age 0x12e9430 19
old: 15 new: 19
delete sess global var memory
auto gc. age: 19 score: 100
delete Person
[root@centos7 home]# 

後記

由於lua_newuserdata創建的數據是在LUA堆棧上,一開始不知道如何釋放該內存,在網上也找了很久都沒找到如何正確的銷燬LUA全局變量,後來想到直接在腳本結束回到C/C++後將全局變量賦值爲nil試試,這招果然奏效,觸發了__gc函數。有種瞎貓撞上死耗子的感覺
這個問題搞了好幾天 ( ゜- ゜)つロ乾杯 []( ̄▽ ̄)*

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