C开发lua模块(三) --- 返回userdata和结构体指针

到目前为止,我们写的C函数都还是单个的函数,函数之间没有联系,也没有涉及返回C结构体以及指针,但实际的业务场景中肯定不会这么简单,比如我们现在要扩展lua使之能够操作redis,首先至少需要一个连接redis服务器的方法并返回一个连接句柄,然后用这个连接句柄来进行各种查询,最后使用结束还需要关闭这个连接句柄,通常来说,这个连接句柄会是一个结构体,那问题是该怎么返回这个结构体给lua呢。

用C操作Redis大概像这样,这里用redis官方提供的hiredis API进行演示:


#include <stdio.h>
#include <hiredis/hiredis.h>
#include <stdlib.h>

int main()
{
        redisContext *conn = redisConnect("127.0.0.1", 6379);

        if (conn == NULL)
        {
                printf("connect fail\n");
                exit(EXIT_FAILURE);
        }

        if (conn->err)
        {
                printf("connect fail: %s\n", conn->errstr);
                exit(EXIT_FAILURE);
        }

        redisReply *rep = redisCommand(conn, "SET foo bar");

        if (rep->type == REDIS_REPLY_ERROR)
        {
                fprintf(stderr, "redis set error: %s\n", rep->str);
        }

        freeReplyObject(rep);

        rep = redisCommand(conn, "GET foo");

        if(rep->type == REDIS_REPLY_STRING)
        {
                printf("redis get: %s\n", rep->str);
        }

        freeReplyObject(rep);

        redisFree(conn);

        return 0;
}


运行结果:

redis get: bar

现在的问题就是该如何返回这个连接句柄的结构体指针,这就要提到lua API 中的userdata数据结构了,userdata分配了一块原始的内存,可以用来存放任何东西,并且,在Lua中userdata 没有任何预定义的操作。

void  *lua_newuserdata(luaState *L, size_t size);

上述函数会根据制定的size大小分配一块内存,并将这个userdata压入栈中,然后返回userdata的地址,所以这个函数有点类似于C函数中的malloc,不过他还会将这个userdata压入栈中。

针对示例中的连接句柄,我们可以分配一块redisContext * 结构体指针大小的userdata,该userdata是一个二级指针,利用该二级指针存储连接返回的结构体指针,这个lua连接Redis的函数可以这样写:

#include <lauxlib.h>
#include <hiredis/hiredis.h>

static int connect(lua_State *L)
{
        const char *ip = lua_tostring(L, 1);
        int port = lua_tointeger(L, 2);
        redisContext *conn = redisConnect(ip, port);
        if (conn == NULL)
        {
                lua_pushnil(L);
                lua_pushstring(L, "connect server fail");
                return 2;
        }

        if (conn->err)
        {
                lua_pushnil(L);
                lua_pushstring(L, conn->errstr);
                return 2;
        }

        redisContext **rcp = (redisContext **)lua_newuserdata(L, sizeof(redisContext *));
        *rcp = conn; //存储连接句柄指针

        return 1; //返回这个userdata
}


static const struct luaL_Reg funcs[] = {
        {"connect", connect},
        {NULL, NULL},
};


int luaopen_redis(lua_State *L)
{
        luaL_register(L, "redis", funcs);
        return 1;
}


假设现在这个模块编译后的名字叫redis.so, 在Lua中我们就可以这样来调用这个方法

local redis = require "redis"

local conn = redis.connect("127.0.0.1", 6379)

print(type(conn)) --userdata

现在我们在模块中添加执行查询和关闭连接的方法,假设这两个方法分别叫query和close:

#include <lauxlib.h>
#include <hiredis/hiredis.h>

static int connect(lua_State *L)
{       
        const char *ip = lua_tostring(L, 1);
        int port = lua_tointeger(L, 2);
        redisContext *conn = redisConnect(ip, port);
        if (conn == NULL)
        {       
                lua_pushnil(L);
                lua_pushstring(L, "connect server fail");
                return 2;
        }
        
        if (conn->err)
        {       
                lua_pushnil(L);
                lua_pushstring(L, conn->errstr);
                return 2;
        }
        
        redisContext **rcp = (redisContext **)lua_newuserdata(L, sizeof(redisContext *));
        *rcp = conn; //存储连接句柄指针
        
        return 1; //返回这个userdata
}

static int query(lua_State *L)
{
        redisContext **conn = (redisContext **)lua_touserdata(L, 1); //lua调用传入的连接句柄
        const char *cmd = lua_tostring(L, 2);  //查询语句

        redisReply *rep = redisCommand(*conn, cmd);
        if (rep == NULL)
        {
                lua_pushnil(L);
                lua_pushstring(L, "query fail");
                return 2;
        }

        switch(rep->type)
        {
                case REDIS_REPLY_ERROR:
                        lua_pushnil(L);
                        lua_pushlstring(L, rep->str, rep->len);
                        break;
                case REDIS_REPLY_STATUS:
                case REDIS_REPLY_STRING:
                        lua_pushstring(L, "string");
                        lua_pushlstring(L, rep->str, rep->len);
                        break;
                case REDIS_REPLY_NIL:  //返回空结果,比如GET一个不存在的key
                        lua_pushstring(L, "nil");
                        lua_pushnil(L);
                        break;
                case REDIS_REPLY_INTEGER:  // 返回数字,比如HSET一个新的field
                        lua_pushstring(L, "integer");
                        lua_pushinteger(L, rep->integer);
                        break;
                case REDIS_REPLY_ARRAY:
                        lua_pushstring(L, "table");
                        lua_newtable(L);
                        //todo
                        break;
        }

        freeReplyObject(rep);

        return 2;
}

static int close(lua_State *L)
{
        redisContext **conn = (redisContext **)lua_touserdata(L, 1);
        redisFree(*conn);  //关闭连接, userdata内存lua会进行管理,不需要我们手动释放
        return 0;
}


static const struct luaL_Reg funcs[] = {
        {"connect", connect},
        {"query", query},
        {"close", close},
        {NULL, NULL},
};

int luaopen_redis(lua_State *L)
{
        luaL_register(L, "redis", funcs);
        return 1;
}

这个查询方法返回两个值,第一个返回值表明结果的类型,如果是nil表明查询失败,第二个返回值是结果,对于返回的array类型由于比较麻烦这里暂不做处理,编译成redis.so 编写lua代码再测试下

local redis = require "redis"

local conn = redis.connect("127.0.0.1", 6379)

print(type(conn))

local rtype, res = redis.query(conn, "set name tom")
print(rtype, res)

rtype, res = redis.query(conn, "GET name")
print(rtype, res)

rtype, res = redis.query(conn, "HSET hset1 user Jim")
print(rtype, res)

rtype, res = redis.query(conn, "HGET hset1 user")
print(rtype, res)

rtype, res = redis.query(conn, "HGET hset1 age")
print(rtype, res) -- rtype = "nil" res = nil

rtype, res = redis.query(conn, "HGET name gender")
print(rtype, res) -- rtype = nil 此时res为报错信息

redis.close(conn)

运行结果:

[root@localhost lua]# luajit red.lua 
userdata
string	OK
string	tom
integer	0
string	Jim
nil	nil
nil	ERR Operation against a key holding the wrong kind of value

上述的C模块没有实现对 REDIS_REPLY_ARRAY 类型的返回结果处理,如果有兴趣,大家可以自行实现,或者参考我的Github上对该模块更完整的实现代码, that‘s all ^^ !
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值