22
\$\begingroup\$

Background

I'm using Lua with luaglut to do some OpenGL stuff. The luaglut API is almost identical to the gl/glut C APIs. Sometimes, gl functions want a pointer to some data, for example:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
    GL_UNSIGNED_BYTE, oh_crap_a_pointer)

In these cases, the luaglut bindings take a light userdata containing the pointer. In the luaglut demos, a small example library called memarray does the work of setting up a C array and returning a pointer to it as a light userdata.

You might use "memarray" something like this:

require 'memarray'
function loadTexture(filename)
  local file = assert(io.open(filename, 'rb'))
  local data = file:read('*a')
  file:close()
  local array = memarray('uchar', #data)
  array:from_str(data)

  return array
end

-- ... later

local texture = loadTexture('whatever.rgba')
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA,
    GL_UNSIGNED_BYTE, texture:ptr())

That got me thinking, why do we need memarray at all? Lua strings should be binary safe; all we really need is to get a pointer to the string returned from file:read('*a') as a light userdata. It would look something like this:

function loadTexture(filename)
  local file = assert(io.open(filename, 'rb'))
  local data = file:read('*a')
  file:close()

  return data
end

-- ... later

local stringpointer = require 'stringpointer'
local texture = loadTexture('whatever.rgba')
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA,
    GL_UNSIGNED_BYTE, stringpointer.get(texture))

Of course we pass the string around instead of the pointer, because the GC could free the memory the string was in if there are no more references to it, invalidating the pointer. Internally Lua strings are immutable and passed by reference anyway, so this is no big deal.

Code

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int get_pointer(lua_State *L)
{
    luaL_checktype(L, 1, LUA_TSTRING);
    lua_pushlightuserdata(L, (void *)lua_tostring(L, 1));
    return 1;
}

int luaopen_stringpointer(lua_State *L)
{
    const luaL_Reg api[] = {
        {"get", get_pointer},
        {NULL, NULL}
    };

#if LUA_VERSION_NUM == 501
    luaL_register(L, "stringpointer", api);
#else
    luaL_newlib(L, api);
#endif

    return 1;
}

It's really tiny, but my C is rusty so maybe I did something stupid. It seems to work fine so far. Any input is welcome.

\$\endgroup\$
  • \$\begingroup\$ Is there a reason why int get_pointer(lua_State *L) and int luaopen_stringpointer(lua_State *L) are not void functions? I cannot tell from the context if they have to return ints or not. \$\endgroup\$ – bazola Sep 5 '14 at 18:59
  • \$\begingroup\$ @bazola the int returned from get_pointer is the number of return values it gives when you call it from Lua. That means return the last thing on the stack, the lightuserdata pushed on the line before. \$\endgroup\$ – Dagg Sep 5 '14 at 19:28
  • \$\begingroup\$ luaopen_stringpointer is the function Lua looks for when you require 'stringpointer'. The return value means the same thing there, it returns one value, a table containing a "get" function. \$\endgroup\$ – Dagg Sep 5 '14 at 19:30
  • \$\begingroup\$ What does glTexImage2D do with the string data passed to it? Does it own it? Does it free it? Does it use it immediately? Does it use it at some, unspecified, later date? \$\endgroup\$ – Etan Reisner Sep 19 '14 at 22:26
  • \$\begingroup\$ @EtanReisner It copies the data and uses that copy later. It doesn't attempt to free the memory at the pointer you pass in. It's not really string data, it's in a somewhat arbitrary format described by the rest of the arguments. \$\endgroup\$ – Dagg Sep 20 '14 at 0:01
6
\$\begingroup\$

I'm no Lua expert, but I can at least give you a pointer (no pun intended) about your C code.

Your get_pointer function looks like it works fine, but it could be made even more concise! Lua provides a luaL_checkstring function that combines luaL_checktype and luaL_tostring. Therefore, you could just do this:

int get_pointer(lua_State *L)
{
    lua_pushlightuserdata(L, (void *) luaL_checkstring(L, 1));
    return 1;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.