lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Sunday 27, CrazyButcher wrote:
> I try to box the content of a cdata in my own userdata via a classic
> lua plugin function, to which I can hand the metatable for the
> userdata.
> 
> lua_topointer -> gives pointer to cdata obj, not the pointer it stores
> lua_touserdata -> fails on cdata
> 
> lua_tonumber -> works but it feels weird
> 
> I create the number in the lua code through
> tonumber(ffi.cast("uintptr_t",cdata))
> 
> and on C side
> const void orig = (const void*)(uintptr_t)lua_tonumber(...)

I do not think this would be 64bit safe.  It might work most of the time if 
all your 64bit pointer only use the lower 48bits.

For the FFI-based bindings generated by LuaNativeObjects [1], I exported 
(private to the FFI bindings code) a C function which takes two arguments size 
& a metatable and returns a new userdata object.  Then in the FFI bindings 
code the userdata object is cast to a structure pointer.

/* c code. */
static int nobj_udata_new_ffi(lua_State *L) {
	size_t size = luaL_checkinteger(L, 1);
	void *ud;
	luaL_checktype(L, 2, LUA_TTABLE);
	lua_settop(L, 2);
	/* create userdata. */
	ud = lua_newuserdata(L, size);
	lua_replace(L, 1);
	/* set userdata's metatable. */
	lua_setmetatable(L, 1);
	return 1;
}

-- lua side FFI code
ffi.cdef[[
typedef struct obj_udata {
  void *obj;
  int flags;
} obj_udata;
]]

-- create new userdata wrapped cdata object.
function new_cdata_obj(cdata, size)
  local meta = {} -- new metatable or use some existing metatable
  if not size then
    local udata = nobj_udata_new_ffi(ffi.sizeof('obj_udata'), meta)
    local obj_data = ffi.cast('obj_udata *')
    obj_data.obj = cdata -- cdata pointer.
    obj_data.flags = 0
  else
    local udata = nobj_udata_new_ffi(size, meta)
    local obj_data = ffi.cast('void *')
    ffi.copy(obj_data, cdata, size)
  end
  -- return userdata
  return udata, meta
end

Also note that the object's methods will receive the userdata object as the 
'self' argument and it will need to be cast back to a cdata value and you 
should check the metatable on the userdata to make sure it is the right type 
(someone could pass any other userdata object can cause bad things to happen).  
If you don't do type checking, then your bindings can't be used in a sandbox 
safely.

For performance of the metatable type checking, I use a weak table per object 
type that maps each userdata value to the wrapped cdata value.

local function obj_udata_luacheck_internal(obj, type_mt)
  -- need the debug version of getmetatable, since the metatable might be
  -- hidden (i.e. if '__metatable' is set)
  local obj_mt = debug.getmetatable(obj)
  if obj_mt == type_mt then
    -- convert userdata to cdata.
    local ud = ffi.cast('obj_data *',obj)
    return ud.obj
  end
  error("(expected `" .. type_mt['.name'] .. "`, got " .. type(obj) .. ")", 2)
end

-- '${object_name}' is replaced with the cdata C typename.
local ${object_name}_objects[udata] = setmetatable({}, { __mode = "k" })
local function obj_type_${object_name}_check(udata)
  local c_obj = ${object_name}_objects[udata]
  if c_obj == nil then
    -- cdata object not in cache
    c_obj = obj_udata_luacheck(udata, ${object_name}_mt)
    c_obj = ffi.cast("${object_name} *", c_obj) -- cast from 'void *'
    ${object_name}_objects[udata] = c_obj -- cache object
  end
  return c_obj
end

1. https://github.com/Neopallium/LuaNativeObjects

-- 
Robert G. Jakabosky