前言
最近在看到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函數。有種瞎貓撞上死耗子的感覺
這個問題搞了好幾天 ( ゜- ゜)つロ乾杯 []( ̄▽ ̄)*