rapidjson包装与lua包装以及两者的交互

rapidjson包装

rapidjson是有名的开源c++ json库,其类java的API使得其易于使用,然而对于rapidjson中的setInt,setString等等setXXX的函数,以及getInt, getString等等getXXX的函数,作者觉得太过于繁琐,想到如果能将这些set和get封装起来,仅仅使用一个函数接口来调用,那么代码将会显得很简洁,维护起来也容易多了。

下面的代码给出了一个简单的尝试。

// for int
template <typename T>
typename std::enable_if<std::is_same<T, int>::value, void>::type
_set_value(rapidjson::Value* node, T value, rapidjson::Document& d)
{
    node->SetInt(value);
}

template <typename T>
typename std::enable_if<std::is_same<T, int>::value, void>::type
_get_value(rapidjson::Value* node, T& value)
{
    value = node->GetInt();
}  

// for string
template <typename T>
typename std::enable_if<std::is_same<T, std::string>::value, void>::type
    _set_value(rapidjson::Value* node, T value, rapidjson::Document& d)
{
    node->SetString(value.c_str(), value.length(), d.GetAllocator());
}

template <typename T>
typename std::enable_if<std::is_same<T, std::string>::value, void>::type
    _get_value(rapidjson::Value* node, T& value)
{
    value = std::string(node->GetString());
}

// for char *
template <typename T>
typename std::enable_if<std::is_same<T, const char *>::value, void>::type
    _set_value(rapidjson::Value* node, T value, rapidjson::Document& d)
{
    node->SetString(value, d.GetAllocator());
}

template <typename T>
typename std::enable_if<std::is_same<T, char *>::value, void>::type
_get_value(rapidjson::Value* node, T& value)
{
    value = node->GetString();
}

上面的代码分别对数据的类型进行了模板的特例化用来与rapidjson中setXXX, getXXX的数值类型保持一致。这样在使用时,可以统一使用一个函数来替换setXXX, getXXX函数簇。比如:

rapidjson::Value* node;
int value = 10;
_set_value(node, 10, d);
_get_value(node, value);

根据上面模板函数,可以扩展跟多的函数,比如:
<pre>
template <typename T>
int get_value(rapidjson::Document& d, const std::string& key, T& value)
{
using namespace std;
using namespace rapidjson;

vector<string> node_vec;
Value \*node = static_cast<Value *>(&d);

node_vec = split(key, '.');
for (int i = 0; i < node_vec.size(); i++) {
    auto itr = node->FindMember(node_vec[i].c_str());
    if (i == node_vec.size() - 1) {
        if (itr != 0) {
            Value* temp = &(itr->value);
            if (temp->IsObject())
                return -1;
            _get_value<T>(temp, value);
            return 0;
        } else {
            return -1;
        }
    } else {
        if (itr != 0) {
            node = &(itr->value);
            if (!node->IsObject()) {
                return -1;
            }
        }
        else {
            return -1;
        }
    }
}
return -1;

}
</pre>

这个模板函数通过一个使用"."(不包括引号)作为分隔符的字符串往一个rapidjson结构中获取数据,如果这个字符串按"."分割并且顺序与原字符串相同,那么每个子字符串表示一个json树中的节点,最后一个子串为叶子节点,value为其值。

相应地下面是set_value模板函数

<pre>
template <typename T>
int set_value(rapidjson::Document& d, const std::string& key, T value)
{
using namespace rapidjson;
using namespace std;

Value\* node = &d;
vector<string> node_vec;

node_vec = split(key, '.');
for (int i = 0; i < node_vec.size(); i++) {
    auto itr = node->FindMember(node_vec[i].c_str());
    if (i == node_vec.size() - 1) {
        if (itr != 0) {
            _set_value<T>(&(itr->value), value, d);
        } else {
            if (!node->IsObject()) {
                node->SetObject();
            }
            Value a;
            node->AddMember(node_vec[i].c_str(), d.GetAllocator(), a, d.GetAllocator());
            _set_value<T>(&((*node)[node_vec[i].c_str()]), value, d);
        }

    } else {
        if (itr != 0) {
            if (itr->value.IsObject()) {
                node = &(itr->value);
            } else {
                return -1;
            }
        } else {
            Value key_wrapper(node_vec[i].c_str(), d.GetAllocator());
            Value null_value;
            node->AddMember(key_wrapper, null_value, d.GetAllocator());
            node = &((*node)[node_vec[i].c_str()]);
            node->SetObject();
        }
    }
}
return 0;

}
</pre>

该函数会往json树中根据指定的节点路径去插入叶子节点的元素。

lua包装

和rapidjson类似,在lua的c扩展中有lua_getXXX, lua_pushXXX的函数簇,使用类似的方法将这些函数封装起来:
<pre>
// for int
template <typename T>
typename std::enable_if<std::is_same<T, int>::value, void>::type
lua_push(lua_State *L, T value)
{
lua_pushinteger(L, value);
}
template <typename T>
typename std::enable_if<std::is_same<T, int>::value, int>::type
lua_get(lua_State *L, T& value)
{
int isnum = 0;
value = lua_tointegerx(L, -1, &isnum);
return isnum != 1 ? -1 : 0;
}

// for double
template <typename T>
    typename std::enable_if<std::is_same<T, double>::value, void>::type
    lua_push(lua_State \*L, T value)
{
    lua_pushnumber(L, value);
}
template <typename T>
    typename std::enable_if<std::is_same<T, double>::value, int>::type
    lua_get(lua_State \*L, T& value)
{
    int isnum = 0;
    value = lua_tonumberx(L, -1, &isnum);
    return isnum != 1 ? -1 : 0;
}

// for char \* and string
template <typename T>
    typename std::enable_if<std::is_same<T, const char*>::value, void>::type
    lua_push(lua_State \*L, T value)
{
    lua_pushstring(L, value);
}
template <typename T>
    typename std::enable_if<std::is_same<T, std::string>::value, void>::type
    lua_push(lua_State \*L, T value)
{
    lua_pushstring(L, value.c_str());
}
template <typename T>
    typename std::enable_if<std::is_same<T, std::string>::value, int>::type
    lua_get(lua_State \*L, T& value)
{
    size_t len = 0;
    const char \*res = lua_tolstring(L, -1, &len);
    if (res == NULL) {
        return -1;
    } else {
        if (len == 0)
            value = std::string();
        else
            value = std::string(res, len);
        return 0;
    }
}

</pre>

对于char*类型的lua_getXXX函数建议不要包装,这样做是为了避免这种方式的使用,为了确保取出的字符串是独立内存地址的,上面的实现实际上是建议使用string的接口来处理字符串。

使用上面的模板函数可以构建更加灵活的模板函数,比如递归地往lua的栈中插入数据:
<pre>
// recursively push value
// base function

void lua_push_recursive(lua_State \*L) {;}

template <typename T>
    void lua_push_recursive(lua_State \*L, T value)
{
    lua_push<T>(L, value);
}

template <typename T, typename... Args>
    void lua_push_recursive(lua_State *L, T value, Args... args)
{
    lua_push<T>(L, value);
    lua_push_recursive(L, args...);
}

</pre>

跟进一步,利用上面的递归函数,可以实现一个通用的调用lua函数的C的接口:
<pre>
template <typename T, typename... Args>
int CallLuaFunc(lua_State *L, T& res, const char* func, Args... args)
{
lua_getglobal(L, func); /* push function */
const int args_num = sizeof...(args);
if (args_num > 0) {
lua_push_recursive(L, std::forward<Args>(args)...);
}
if (lua_pcall(L, args_num, 1, 0) != 0) {
fprintf(stderr, "%s with args_num: %d\n", lua_tostring(L, -1), args_num);
lua_pop(L, 1); /* pop error message from the stack */
return -1;
}
if (lua_get<T>(L, res) != 0) {
lua_pop(L, 1);
return -1;
}
lua_pop(L, 1);
return 0;
}
</pre>

如果lua中的函数返回一个返回值,那么可以通过上面的函数来调用该lua函数,比如
<pre>
#test.lua
function add(x, y)
return x+y
end
#test.cpp
int value = 0;
lua_State *L;
CallLuaFunc(L, value, "add", 1, 2);
# value = 3
</pre>

使用lua操作rapidjson数据

一些业务需要灵活地操作json数据,为了满足这一要求,使用lua通过c的API来操作rapidjson数据,然后c++程序再通过调用lua函数获取结果可以大大增加已经部署的程序的灵活度。下面给出一个简单的封装的类的定义:
<pre>
class LuaWrapper;
typedef int (LuaWrapper::*mem_func)(lua_State * L);
template <mem_func func>
int adapter(lua_State * L) {
LuaWrapper * ptr = *static_cast<LuaWrapper**>(lua_getextraspace(L));
return ((*ptr).*func)(L);
}

class LuaWrapper {
public:
    LuaWrapper(): doc(nullptr) {
        L_ = luaL_newstate();
        luaL_openlibs(L_);
        \*static_cast<LuaWrapper**>(lua_getextraspace(L_)) = this;
    }

    ~LuaWrapper() {
        lua_close(L_);
    }

    // a rapidjson document point is passed in, and this class is not
    // responsible for deleting this point
    int init(const char\* lua_file, rapidjson::Document *d) {
        doc = d;
        int error = luaL_dofile(L_, lua_file);
        if (error) {
            fprintf(stderr, "%s\n", lua_tostring(L_, -1));
            lua_pop(L_, 1);  /* pop error message from the stack */
            exit(-1);
        }

        const luaL_Reg regs[] = {
            { "get_value_str", &adapter<&LuaWrapper::_lua_get_value_str> },
            { "get_value_int", &adapter<&LuaWrapper::_lua_get_value_int> },
            { NULL, NULL }
        };
        //lua_newtable(L_); since the lua is quite simple, so remove table definition
        lua_pushglobaltable(L_);
        luaL_setfuncs(L_, regs, 0);
        return 0;
    }
    template <typename T, typename... Args>
    int GetProvider(T& res, const char\* func, Args... args) {
        assert(doc != nullptr);
        // the lua file must have a dispatcher function
        return lua_util::CallLuaFunc(L_,
                                     res,
                                     func,
                                     std::forward<Args>(args)...);
    }
private:
    int \_lua_get_value_str(lua_State \* L) {
        const char \*key = luaL_checkstring(L, 1);
        std::string str;
        int err = get_value(\*doc, key, str);
        if (err != 0) {
            lua_pushnil(L);
        } else {
            lua_push(L, str.c_str());
        }
        return 1;
    }
    int \_lua_get_value_int(lua_State \* L) {
        const char \*key = luaL_checkstring(L, 1);
        int val;
        int err = get_value(\*doc, key, val);
        if (err != 0) {
            lua_pushnil(L);
        } else {
            lua_push(L, val);
        }
        return 1;
    }
    rapidjson::Document \*doc;
    lua_State \*L_;
};

</pre>

在lua中注册c的API需要满足function<int(lua_State*)>这样的函数标识,而类的成员函数无法满足这样的标识,为了兼容lua的API,这里使用了一个适配器int adapter(lua_State * L),适配器满足lua的函数标识的要求,因此可以用来注册成lua的c的API。

这样,与lua的交互完全可以快速封装在一个类里,从而为调用者提供简单易懂的接口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容