前言
最近在看到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函数。有种瞎猫撞上死耗子的感觉
这个问题搞了好几天 ( ゜- ゜)つロ干杯 []( ̄▽ ̄)*