Contact Us

Lua FFI

Below is a description of a rather primitive FFI for Lua on x64 Windows, and now also Linux (and beyond — the SysV ABI).

Even so, it lets you do things like this:

local ffi = require ("ffi")
local msg = ffi("user32.dll", "MessageBoxA")
msg(0, "Message", "Title", 0)

or take glfw3.dll, for example, and via

local glfw = ffi("glfw3")

make every function exported by the glfw3.dll library available for calling from Lua.

!The ffi.dll itself came out at just 9 KB; here it is in full in an image 32x96 pixels in size. You can save this image, convert it to bmp (bmp isn't supported here, so it had to be packed into png as well), then manually delete the first 54 bytes of the header (up to 'MZ') and use it.

But be very careful, because in the end it still turned out that dirty boots stomped into Lua's tidy little children's sandbox, dragged in all sorts of unsafe things from C — manual memory management and pointer handling like *(double*) (void * ptr) — and in general, using such things teaches all kinds of bad habits.

A series of articles on 'harmful advice' in programming recently came out — '60 antipatterns for C++ programmers'. Below you can find a practical implementation of most of them. Not all of them, of course, but that's only because not all of them apply to C, without the ++ :)

First, usage examples; the second part describes how the library is put together and its rich inner world.

To avoid loading every function in sequence, you can name the specific functions you need:

local gl = ffi("opengl32", {"glClearColor", "glClear", "glViewport"})

Functions loaded from a dynamic library, when there are several of them, already sit in their own separate table (think namespace), so you can strip the prefix off the function names and use them as glfw.Init()

local glfw = {}
for k,v in pairs(ffi("glfw3","*")) do glfw[k:match("^glfw(.+)") or k] = v end

or make them global

for k,v in pairs(ffi("glfw3","*")) do _G[k] = v end

and use them as glfwInit()

Argument types

When loading functions one at a time, the argument and return types can be specified explicitly:

local cosf = ffi("msvcrt.dll", "cosf", "ff")
print( cosf(math.pi / 3), math.cos(math.pi / 3) )

Then all the necessary type conversions are done inside, a number converted to float is passed to the function, and Lua gets back a value that is normal for it — a lua_Number, i.e. a double.

The C function signature is passed as an extra string, one character per argument: 'v' = void, 'i' = int, 'f' = float, 'd' = double, 'b' = boolean, 'p' = pointer, 's' = string. The first character is always the return type, then the arguments.

You could also bolt on a human-readable parser of function declarations in C, but writing a simple parser that doesn't choke on constructs like 'a function that returns a pointer to a function that returns a pointer to a function that returns ...' somehow didn't work out right away. There's no full type correspondence between Lua and C either, and it would still require some hacks; and attaching a full-fledged off-the-shelf parser seemed like overkill. So it's like this for now, and it can be layered on top later.

If the types aren't specified, argument types are converted automatically, based on the types of the arguments passed to the resulting Lua wrapper function when it is called.

However, typing in Lua is generally weak, and there is only double for floating point. So, in order to pass an actual float into a library C function, there is an extra conversion via ffi.float(x), which converts a number to float and packs it into the low 32 bits of a double. Correspondingly, if a C function returns a float, it also has to be unpacked through the same ffi.float() to convert it back to a lua_Number.

local cosf = ffi("msvcrt.dll", "cosf")
print( ffi.float( cosf( ffi.float(math.pi / 3) ) ) )

The wrapper function with automatic type conversion returns two values (the integer and the floating point one, from the rax and xmm0 registers — details below), and that is exactly the trait ffi.float uses to tell an argument that needs to be 'packed' from a return value that needs to be 'unpacked' back into a double. There are also ffi.double, ffi.string, ffi.boolean, ffi.integer and ffi.pointer functions for converting the corresponding return value types back to Lua.

Different structs can be passed either as a pointer (with ffi.userdata() you can allocate memory on the Lua side and pass that pointer to C) or as a string (Lua has string.pack for packing binary data), except when the struct fits entirely into 8 bytes — then it has to be packed into an integer (a calling convention quirk).

If a table of strings is passed as an argument, it gets converted into a pointer for the C function: first an array of const char * pointers, the size of the table, is allocated and initialized with pointers to the strings, for passing string arrays of type const char **.

Such a 'constant' array lives only while the function is being called, is never returned to the outside, and will later be destroyed by the garbage collector. If the C function tries to write something through that pointer, nothing good will probably come of it — though probably nothing terrible either, as long as you stay within the array bounds. To pass a non-constant array that the C function can write into, you first have to create it on the Lua side with ffi.userdata().

For example, the call

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);

in Lua looks like

gl.ShaderSource(shader, 2, {shader_src1, shader_src2}, 0)

Tables can be nested, to build more multi-dimensional char ****.

Callback

If instead you pass ffi a Lua function, not a library name string or a pointer (to a C function, for example one returned by some other C function, like wglGetProcAddress), then the Lua function gets converted the other way around — into a callback (a C function pointer).

To pass callbacks from Lua to C, the argument types must be specified explicitly, because someone on the outside will be calling this function, and there is no way for the function itself to find out the types of the passed arguments and, especially, of the return value.

local GLFW = {KEY_ESCAPE = 256, PRESS = 1, RELEASE = 0, TRUE = 1, FALSE = 0}

local function key_callback(window, key, scancode, action, mods)
--  print(key,scancode,action)
  if (key == GLFW.KEY_ESCAPE and action == GLFW.PRESS) then
    glfw.SetWindowShouldClose(window, GLFW.TRUE)
  end
end

key_callback = ffi(key_callback, "vpiiii")

glfw.SetKeyCallback(window, key_callback);

Named constants

Named constants are a separate sorrow when calling C libraries from other languages, but a primitive header file parser to yank out the constants you need can look quite simple:

for s in io.lines("glfw3.h") do
  k,v = s:match("#define GLFW_([%w_]+)%s+(%g+)")
  if k and tonumber(v) then print("GLFW."..k.. " = " .. tonumber(v)) end
end

It just needs a bit more work so it also parses nested expressions like #define A (B|C).

Userdata

For working with memory and pointers there is the ffi.userdata() function, which allocates memory on the Lua side; you can pass the resulting object as a pointer to a C function, and also store and retrieve values into and out of that chunk of memory from Lua. Indexing userdata as an array from Lua via [] isn't finished yet.

  • x = ffi.userdata(N) allocates N bytes.
  • x = ffi.userdata("qweqweqwe") allocates memory of the string length +1 for the extra '\0' and initializes it with the string contents.
  • x = ffi.userdata({N,K,M}) first allocates three pointers, and under each allocates the corresponding N, K, M bytes — i.e. a two-dimensional array of three elements, sized N, M, K bytes each.
  • x = ffi.userdata({"qwe", "asdfgh"}) the same thing, but initialized with pointers to the strings and a copy of the string data.
  • x:int(), x:uint(), x:float(), x:double(), x:boolean(), x:string() do *(int*)x, *(float*)x, *(double*)x and return the corresponding values to Lua.
  • x:pack(fmt, v1, v2, ...) — the same as string.pack(fmt, v1, v2, ...); writes binary data to the start of the string.
  • x:unpack(fmt [, pos]) == string.unpack(fmt, x:string() [, pos]) — unpacks binary data starting from pos.

OpenGL extensions

You can also add a bit of syntactic sugar and implement 'lazy' function loading — for OpenGL extensions, say. To do that, add a metatable to the gl table, so that when a call is attempted for a function that isn't in the gl table yet (the __index metamethod fires exactly when there is no value for the given key in the table), it gets loaded automatically from opengl32.dll; if that function happens not to be there, then via wglGetProcAddress; or the __lib_error function is called (also from the metatable, so as not to pollute the main gl table), which the user can later override so as not to die with an error when some non-essential function fails to load.

gl.GetProcAddress = ffi("opengl32", "wglGetProcAddress", "ps")
local mt = getmetatable(gl) or {}
mt.__index = function(t, k)
  t[k] = ffi("opengl32", "gl"..k) or ffi(t.GetProcAddress("gl"..k)) or getmetatable(t).__lib_error(t, "gl"..k)
  return t[k]
end
mt.__lib_error = function (t, name) error("Unable to load '"..name.."' from opengl32.dll or wglGetProcAddress") end
setmetatable(mt)

Lazy function loading

The ffi library itself behaves in a similar way. If you give it only a library name or a table with a list of libraries, without naming functions, it returns an empty table with an __index metamethod that, when asked for an unknown function, searches the listed libraries for it and loads it if found. That lets the user declare a list of libraries and then just call any function by name directly, without any extra ceremony:

libs = ffi({"user32", "msvcrt", "kernel32"})
libs.MessageBoxA(0,"Message","Caption",0)

A bit of OOP

The GLFW library has quite a few functions of the form glfwXXX(GLFWwindow * w, ...). So you can tweak glfw.CreateWindow() a little so that it returns a 'class' for the window with all these methods, where you don't have to pass GLFWwindow as the first argument.

local create_window = glfw.CreateWindow
function glfw.CreateWindow(...) return setmetatable({window = create_window(...)}, {__index = function(t,k) return function(self,...) return glfw[k](self.window,...) end end }) end

Now glfw.CreateWindow returns a table with an __index metamethod instead of a pointer, and you can write:

window = glfw.CreateWindow(...)
window:MakeContextCurrent()
window:SetKeyCallback(key_callback)

Hello glfw window

So the glfw 'hello window' example in full ends up looking something like this:

Hello window

local ffi = require("ffi")
local gl = require ("gl")
local glfw = require ("glfw")
local GL = gl.GL
local GLFW = glfw.GLFW

glfw.Init()
local window = glfw.CreateWindow(640, 480, "Simple example", 0, 0)
window:MakeContextCurrent()

local key_callback = function(window, key, scancode, action, mods)
  if (key == GLFW.KEY_ESCAPE and action == GLFW.PRESS) then glfw.SetWindowShouldClose(window, GLFW.TRUE) end
end
key_callback = ffi(key_callback, "vpiiii")
window:SetKeyCallback(key_callback)

local vs = gl.shader(GL.VERTEX_SHADER, [[#version 150
in vec2 p;
void main(void){ gl_Position = vec4(p.xy, 0.0f, 1.0f); }]])

local fs = gl.shader(GL.FRAGMENT_SHADER, [[#version 150
uniform vec2 resolution;
uniform float zoom = 1.0;
uniform vec2 pos = vec2(1.25, 0.05);
void main(void){
  vec2 uv = ((2.0 * gl_FragCoord.xy - resolution) / resolution.y) * zoom - pos;
  int i = 0;
  for (vec2 z = vec2(0.0); (dot(z, z) < 65536.0) && (i < 100); i++) z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + uv;
  gl_FragColor = vec4(vec3(sin(i * 0.05)) * vec3(0.5, 1.0, 1.3), 1.0);
}]])

local prog = gl.program(fs, vs)
local pwidth, pheight  = ffi.userdata(4), ffi.userdata(4)  --allocate 4 byte userdata in lua to pass as pointer to C function

gl.ClearColor(0.3, 0.4, 0.5, 1.0)

while window:WindowShouldClose() == GLFW.FALSE do
  window:GetFramebufferSize(pwidth, pheight)
  local w,h = pwidth:int(), pheight:int()   --dereference userdata pointer to integer, same as pwidth:upack("I4")
  gl.Viewport(0, 0, w, h)
  prog.resolution = {w, h}          --GLSL uniforms by name with __index/__newindex of prog, type convertion inside
  prog.zoom = math.exp(-5+5*math.cos(os.clock()))
  gl.Clear(GL.COLOR_BUFFER_BIT)
  gl.Rects(-1,-1,1,1)
  window:SwapBuffers()
  glfw.PollEvents()
end

window:DestroyWindow()
glfw.Terminate()

gl.lua holds the helper functions for compiling and linking shaders, plus access to uniform variables by name through an indexing metethod. https://github.com/pavel212/uffi/tree/master/example

fractal

"The teddy bear on top is all plush — I wonder what's inside?"

Usually, to call some C function from Lua, say

int cfunc(const char *, double);

you need to write the following wrapper for it, which takes a pointer to the Lua state as an argument and returns the number of values returned to Lua (how many values were pushed onto the Lua stack).

int luacfunc(lua_State * L){
  const char * arg1 = lua_tostring(L, 1);
  double       arg2 = lua_tonumber(L, 2);
  int ret = cfunc(arg1, arg2);
  lua_pushinteger(L, ret);
  return 1;
}

In other words: take the arguments off the Lua stack, call the function, then push what the function returned back onto the Lua stack, and return the number of returned values. C functions usually have a single return value.

But if you want to call some function from a .dll where the types and the number of arguments and return values are known only at runtime, the code of the function described above has to be generated on the fly.

In assembly, that same function should look roughly like this:

;int luacfunc(lua_State * L){
;prologue
  push 'nonvolatile regs'
  sub rsp, NSTACK    ;reserve and align stack

;const char * arg1 = lua_tostring(L, 1);
  mov rcx, L
  mov rdx, 1
  mov r8, 0
  call lua_tolstring         ;#define to_tostring(L,idx) lua_tolstring(L,idx,0)
  mov [rsp+ARG1], rax               ;store arg1 on stack

;double       arg2 = lua_tonumber(L, 2);
  mov rcx, L
  mov rdx, 2
  call lua_tonumber
  mov [rsp+ARG2], xmm0              ;store arg2 on stack

;int ret = cfunc(arg1, arg2);
  mov rcx, [rsp+ARG1]               ;load arg1
  mov xmm1, [rsp+ARG2]              ;load arg2
  call cfunc

;lua_pushinteger(L, ret);
  mov rcx, L
  mov rdx, rax               ;cfunc() returned integer value in rax -> as second argument for lua_push in rdx
  call lua_pushinteger

;epilogue
  add rsp, NSTACK   ;restore stack
  pop 'nonvolatile regs'

;return 1
  mov eax, 1
  ret

What this actually looks like in assembly can be seen with objdump -d, or here: godbolt.org/z/ec6q4h5dT

A few words about the function calling convention in Win_x64:

  • the first 4 arguments are passed in the registers rcx/rdx/r8/r9, or xmm0/xmm1/xmm2/xmm3 for floating point; the rest go via the stack.
  • the return value goes in rax/xmm0.
  • anything that doesn't fit entirely into an 8-byte register goes via a pointer.
  • you also have to reserve at least 32 bytes on the stack for the first four arguments in any case, even if there are fewer of them, and despite them being passed in registers.
  • the stack should be aligned to 16 bytes, because the previous call pushed an 8-byte return address onto the stack at the last moment, so the stack isn't 16-byte aligned to begin with.

All that's left is to wrap this pseudo-assembly code in majestic macros of the form:

#define _push_rbx(p)           _B(p, 0x53)
#define _pop_rbx(p)            _B(p, 0x5B)
#define _mov_rbx_rcx(p)        _B(p, 0x48, 0x89, 0xCB)
#define _sub_rsp_DW(p,x)       _B(p, 0x48, 0x81, 0xEC, _DW(x))
#define _ld_xmm0(p,x)          _B(p, 0xF3, 0x0F, 0x7E, 0x84, 0x24, _DW(x))

which write the machine code of the corresponding instructions into memory, plus check the argument types so as to call the matching lua_to* / lua_push* functions.

Overloading the _B macro by argument count to write an arbitrary number of bytes through a pointer (with hacks for msvc, which has its own ideas about macro expansion), plus the _DW and _QW macros for writing whole words, least significant byte first.

Scary, very scary

#define _ARGN(A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15,A16,A17,...) A17
#define _ARGC(...) _EXPAND_MSVC(_ARGN(__VA_ARGS__,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))
#define _EXPAND_MSVC(x) x
#define _B(x,...) _EXPAND_MSVC(_ARGN(__VA_ARGS__,B16,_B15,_B14,_B13,_B12,_B11,_B10,_B09,_B08,_B07,_B06,_B05,_B04,_B03,_B02,_B01) (x,__VA_ARGS__))
#define _B01(x,A)                               ( *(uint8_t*)x++ = (uint8_t)A, x)
#define _B02(x,A,B)                             ( _B01(x,A),                             _B01(x,B) )
#define _B03(x,A,B,C)                           ( _B02(x,A,B),                           _B01(x,C) )
#define _B04(x,A,B,C,D)                         ( _B03(x,A,B,C),                         _B01(x,D) )
#define _B05(x,A,B,C,D,E)                       ( _B04(x,A,B,C,D),                       _B01(x,E) )
#define _B06(x,A,B,C,D,E,F)                     ( _B05(x,A,B,C,D,E),                     _B01(x,F) )
#define _B07(x,A,B,C,D,E,F,G)                   ( _B06(x,A,B,C,D,E,F),                   _B01(x,G) )
#define _B08(x,A,B,C,D,E,F,G,H)                 ( _B07(x,A,B,C,D,E,F,G),                 _B01(x,H) )
#define _B09(x,A,B,C,D,E,F,G,H,I)               ( _B08(x,A,B,C,D,E,F,G,H),               _B01(x,I) )
#define _B10(x,A,B,C,D,E,F,G,H,I,J)             ( _B09(x,A,B,C,D,E,F,G,H,I),             _B01(x,J) )
#define _B11(x,A,B,C,D,E,F,G,H,I,J,K)           ( _B10(x,A,B,C,D,E,F,G,H,I,J),           _B01(x,K) )
#define _B12(x,A,B,C,D,E,F,G,H,I,J,K,L)         ( _B11(x,A,B,C,D,E,F,G,H,I,J,K),         _B01(x,L) )
#define _B13(x,A,B,C,D,E,F,G,H,I,J,K,L,M)       ( _B12(x,A,B,C,D,E,F,G,H,I,J,K,L),       _B01(x,M) )
#define _B14(x,A,B,C,D,E,F,G,H,I,J,K,L,M,N)     ( _B13(x,A,B,C,D,E,F,G,H,I,J,K,L,M),     _B01(x,N) )
#define _B15(x,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O)   ( _B14(x,A,B,C,D,E,F,G,H,I,J,K,L,M,N),   _B01(x,O) )
#define _B16(x,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ( _B15(x,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O), _B01(x,P) )

#define _DW(x) x, (x)>>8, (x)>>16, (x)>>24
#define _QW(x) x, (x)>>8, (x)>>16, (x)>>24, (x)>>32, (x)>>40, (x)>>48, (x)>>56

You probably can't do without macros in C for overloading functions by argument count, but you can make it a bit prettier and hope the compiler is sane enough not to call memcpy every single time to copy one byte.

#define _B(x,...) (memcpy(x, (const uint8_t[]){__VA_ARGS__}, sizeof((uint8_t[]){__VA_ARGS__}) ), (uint8_t*)x += sizeof((uint8_t[]){__VA_ARGS__}))

It's not as if the speed of wrapper code generation affects anything, but when writing a single byte into memory involves function calls, somewhere a kitten is sad — a kitten that doesn't yet know that premature optimization is bad. Still, msvc handled it, and even two consecutive macros

_push_rbx(p);
_mov_rbx_rcx(p);

expanded by the preprocessor into

(memcpy(p, (const uint8_t[]){0x53}, sizeof((uint8_t[]){0x53}) ), (uint8_t*)p += sizeof((uint8_t[]){0x53}));
(memcpy(p, (const uint8_t[]){0x48, 0x89, 0xCB}, sizeof((uint8_t[]){0x48, 0x89, 0xCB}) ), (uint8_t*)p += sizeof((uint8_t[]){0x48, 0x89, 0xCB}));

ended up compiled together into a single mov instruction. A small thing, but a nice one.

00037    c7 01 53 48 89 cb      mov DWORD PTR [rcx], -880195501 ; cb894853H

The result is this fine mix of C and pseudo-assembly that generates the machine code of a wrapper for calling a C function from Lua.

int make_func(char * code, void * func, const char * argt){
  int argc = strlen(argt);
  int stack = 16 * ((argc + 4)>>1);  //32 bytes of shadow space + space for function arguments multiple of 16 bytes to keep stack alignment
  char * p = code;
 {
//prologue
    _push_rbx(p);        //additional push rbx makes stack aligned to 16.
    _mov_rbx_rcx(p);         //as we are in luaCfunction: int f(lua_State * L); L is passed in rcx. store lua_State * L in rbx,
    _sub_rsp_DW(p,stack);   //reserve stack

//get arguments from lua stack and store them on stack
    for (int i = 1; i < argc; i++){  //argt[0] - return type
      _mov_rcx_rbx(p);        //first argument lua_State * L
      _mov_rdx_DW(p, i+1);  //second argument n+1 - position of argument on lua stack, [1] == self, [2] == first arg, ...
      _clr_r8(p);         //some lua_to functions could have 3rd argument, #define lua_tostring(L, i) lua_tolstring(L, i, 0)
      _call(p, lua_to(argt[i]));
      if (argt[i] == 'f') _mov_xmm0f_xmm0(p);                                                            //double -> float conversion, double lua_tonumber();
      if (argt[i] == 'f' || argt[i] == 'd') _st_xmm0(p, 24+i*8); else _st_rax(p, 24+i*8);    //store argument on stack with 32 bytes offset of shadow store
    }

    _add_rsp_DW(p, 32);    //adjust stack by shadow 32 bytes that were reserved for lua_to functions, so other (4+) arguments on stack with a proper offset

//put back first four arguments to registers
    if (argc > 1){ if (argt[1] == 'f' || argt[1] == 'd') _ld_xmm0(p, 0);  else _ld_rcx(p, 0);  }
    if (argc > 2){ if (argt[2] == 'f' || argt[2] == 'd') _ld_xmm1(p, 8);  else _ld_rdx(p, 8);  }
    if (argc > 3){ if (argt[3] == 'f' || argt[3] == 'd') _ld_xmm2(p, 16); else _ld_r8 (p, 16); }
    if (argc > 4){ if (argt[4] == 'f' || argt[4] == 'd') _ld_xmm3(p, 24); else _ld_r9 (p, 24); }

    _call(p, func);

//lua_push() function to put return value onto lua stack
    _mov_rcx_rbx(p);                                      //first argument lua_State * L
    if      (argt[0] == 'f') _mov_xmm1_xmm0f(p);          //convert float to double and mov return value(xmm0) to (xmm1) second argument  of lua_push
    else if (argt[0] == 'd') _mov_xmm1_xmm0(p);           //mov double without conversion
    else _mov_rdx_rax(p);                                 //other types returned in rax -> second argument in rdx
    _clr_r8(p);                                       //some lua_push functions could have 3rd argument
    _call(p, lua_push(argt[0]));
//return 1
    _mov_rax_DW(p, 1);                                    //return 1, even void func() returns 1 and lua_pushnil just for uniformity.
    _add_rsp_DW(p, stack-32);                              //restore stack pointer
    _pop_rbx(p);                                        //and rbx
    _ret(p);
  }
  return p - code;             //length of generated code
}

The helper functions lua_to / lua_push return a function pointer for handling an argument of the corresponding type

void * lua_to  (const char t){
  switch (t){
    case 'v': return voidfunc;        //do nothing: void voidfunc() {}
    case 'i': return lua_tointegerx;
    case 'f': return lua_tonumberx;
    case 'd': return lua_tonumberx;
    case 'b': return lua_toboolean;
    case 's': return lua_tolstring;
    case 'p': return luaF_topointer;  //lua_tointeger || lua_touserdata
    case 't': return luaF_totable;
    default : return voidfunc;
  }
  return voidfunc;
}

The chunk of code generated in memory has to be made executable:

  DWORD protect;
  VirtualProtect(code, size, PAGE_EXECUTE_READWRITE, &protect);

The code itself is stored in Lua as userdata, and to make it callable as a Lua function you add a __call metamethod to it, which takes the pointer to itself from the first argument (a metamethod always receives the object itself, self, as the first argument) and simply calls it as a function.

int __call(lua_State * L){
  int (*f)(lua_State*) = lua_touserdata(L, 1);
  return f ? f(L) : 0;
}

mmm, B — safe code: 'they handed us some pointer here; let's just jump to it and execute code' — well, at least they checked for 0.

And in the __gc 'destructor', at the moment when the garbage collector has come to collect this wrapper, you can also free the library and revoke the execute permission on that chunk of memory. And thereby plant a wonderful rake here, because lua_newuserdata allocates memory however it likes while VirtualProtect operates on pages (~4 KB): by revoking execute permission from one function at destruction time, you can accidentally forbid execution to other functions that are still alive and that unfortunately landed in the same 4 KB. So you either have to allocate memory yourself in a separate execute-enabled chunk (temporarily swap in your own allocator via lua_setallocf), or allocate one 4 KB page per function, or build reference counting over separate pages. Or, sticking to the previously adopted code safety concept, simply leave behind chunks of memory with execute permission.

On load, LoadLibrary was called for each function from the library, thereby bumping its internal counter, so a FreeLibrary call for one function won't close that library for the others.

The library pointer can be stored in the uservalue. Arbitrary values can be attached to any userdata, accessible from C via lua_getiuservalue.

__gc

int __gc(lua_State * L){
  lua_getiuservalue(L, 1, 1);
  FreeLibrary(lua_touserdata(L, -1));
  lua_pop(L, 1);
  DWORD protect;
  VirtualProtect(lua_touserdata(L, 1), lua_rawlen(L, 1), PAGE_READWRITE, &protect);
  return 0;
}

adding a metatable from C

  lua_newtable(L, 0, 2);
  lua_pushcfunction(L, __gc);
  lua_setfield(L, -2, "__gc");
  lua_pushcfunction(L, __call);
  lua_setfield(L, -2, "__call");
  lua_setmetatable(L, -2);

Automatic type detection

If the function's argument types aren't specified explicitly, you can try to guess them from the types of the Lua function's arguments. This will be somewhat slower, because the generated code already has to ask Lua about argument types at runtime rather than at 'compile' time, and it shifts the whole burden of the return value's type and converting it to a proper Lua form onto the user — just hand them both registers, the integer rax and the floating-point xmm0, one of which holds the return value of the called function (depending on the return type), and let them do whatever they want with them.

On the other hand, since nothing is known about argument types anyway, the code for this function doesn't have to be generated on the fly — the __call metamethod can just be written entirely in hand-written assembly. In pure C, with variadic functions, it apparently doesn't work, because there seems to be all sorts of misbehavior there, like automatic float->double conversion. And partially, with small bits of assembly only for shoving an arbitrary number of arguments onto the stack before the call, it doesn't work either, because msvc is a compiler that has been hacked on for over 30 years and, in the course of all the possible improvements in the move to 64-bit, suddenly forgot how to do inline assembly.

call.asm

extrn lua_pushnumber : proc
extrn lua_pushinteger: proc
extrn lua_touserdata : proc
extrn lua_gettop : proc
extrn luaF_isfloat : proc
extrn luaF_typeto : proc

.code
func__call_auto PROC
  push rbx
  push r12
  push r13
  push r14
  push r15

  sub rsp, 32

  mov rbx, rcx               ;first argument lua_State * L passed in rcx, store in rbx

  mov rdx, 1
  call lua_touserdata
  mov r15, [rax]             ;store function pointer in r15

  mov rcx, rbx
  call lua_gettop
  mov r12, rax               ;store number of arguments in r12

  shr eax, 1
  shl eax, 4
  sub rsp, rax               ;reserve stack ((n+1)/2)*16  [1] - self

;//for (int i = 1; i < argc; i++) arg[i] = arg_to(L, i+1)
  xor r13, r13
  inc r13
arg_loop:
  cmp r13, r12
  jge arg_loop_end
  inc r13

  mov rcx, rbx
  mov rdx, r13
  call luaF_isfloat
  mov r14, rax               ;r14 - arg_isfloat?

  mov rcx, rbx
  mov rdx, r13
  call luaF_typeto                ;get pointer to proper lua_to function

  mov rcx, rbx
  mov rdx, r13
  xor r8, r8
  call rax                   ;call lua_to

  cmp r14, 0
  jz store_int
store_float:
  movq  QWORD PTR [rsp + r13*8 + 16], xmm0  ;store floating point arg
  jmp arg_loop
store_int:
  mov [rsp + r13*8 + 16], rax               ;store integer arg
  jmp arg_loop
arg_loop_end:

  add rsp, 32

;load args to registers, both r*x & xmm*, should be faster than checks for floating point / integer type
;and loading all 4 args (garbage if <4) probably also faster than checks for actual number of arguments

;  mov r13, r12

;  dec r13
;  jz call_cfunc
  mov rcx, [rsp]
  movq xmm0, rcx

;  dec r13
;  je call_cfunc
  mov rdx, [rsp+8]
  movq xmm1, rdx

;  dec r13
;  jz call_cfunc
  mov r8, [rsp+16]
  movq xmm2, r8

;  dec r13
;  jz call_cfunc
  mov r9, [rsp+24]
  movq xmm3, r9

call_cfunc:
  call r15
  movq r14, xmm0

  mov rcx, rbx
  mov rdx, rax
  call lua_pushinteger         ;first return integer

  mov rcx, rbx
  movq xmm1, r14
  call lua_pushnumber          ;then return floating point

  shr r12, 1
  shl r12, 4
  add rsp, r12                 ;restore stack

  pop r15
  pop r14
  pop r13
  pop r12
  pop rbx

  mov eax, 2
  ret
func__call_auto ENDP
END

The helper function arg_to, like lua_to earlier, returns a pointer to the corresponding function for reading arguments off the Lua stack, but based on lua_type(L,idx).

Library function list

After a .dll is loaded via LoadLibrary, its contents end up in memory at the address that LoadLibrary itself returns. The dllfuncname function lightly checks the signature and type and, walking the PE headers, finds and returns a pointer to the function name.

Here is an illustrated description of the PE headers, but for x64 some address fields are 8 bytes, and accordingly the EXPORT_DIRECTORY offset is 136 bytes rather than the 120 shown in the picture.

Another fine example of how not to use magic constants:

const char * dllfuncname(const uint32_t * lib, int idx){
  if (lib == 0) return 0;
  if (idx < 0) return 0;
  const uint32_t * p = lib;
  if ((p[0] & 0xFFFF) != 0x5A4D) return 0; //MZ
  p = &lib[p[15] >> 2];                    //e_lfanew
  if (p[0] != 0x00004550) return 0;        //signature
  if ((p[1] & 0xFFFF) != 0x8664) return 0; //machine
  p = &lib[p[34] >> 2];                    //EXPORT_DIRECTORY
  if (idx >= p[6]) return 0;               //NumberOfNames
  p = &lib[(p[8] >> 2) + idx];             //AddressOfNames[idx]
  return &((char*)lib)[*p];
}

Example use: print a list of all library functions

void main(int argc, char ** argv){
  const char * name = argv[1];
  char path[260];
  if (SearchPath(NULL, name, ".dll", sizeof(path), path, 0)) name = path;
  HMODULE lib = LoadLibrary(name);
  int idx = 0;
  while (0 != (name = dllfuncname(lib, idx))) printf("%d: %s\n", idx++, name);
  FreeLibrary(lib);
}

Callback

To convert a Lua function back into a C callback, you first push the function itself onto the Lua stack, then its arguments, and call lua_pcall, which takes the function and its arguments off the stack, runs it, and pushes the return values back — which then have to be taken off the Lua stack and returned to C.

The Lua function itself (and the lua_State * L pointer) has to be passed somehow into the C callback function; for that you can use LUA_REGISTRYINDEX — a global table for storing references — and then hardcode that reference, along with lua_State * L, into the generated code. This also prevents a situation where the garbage collector happens to collect the Lua function itself (as no longer needed) while the still-alive callback wrapper survives and tries to call it, not knowing the function is gone.

When generating the wrapper code you save a reference to the function with int funcref = luaL_ref(L, LUA_REGISTRYINDEX), and inside the wrapper at runtime you fetch and push that Lua function onto the Lua stack with lua_rawgeti(L, LUA_REGISTRYINDEX, funcref). You can also add a __gc metamethod to the generated code (for Lua it is still userdata), which when the wrapper is destroyed also cleans up the reference via luaL_unref(L, LUA_REGISTRYINDEX, funcref). And a __call metamethod that simply calls the original Lua function. So you need to generate code for:

int callback(const char * arg1, double arg2){
  lua_rawgeti(L, LUA_REGISTRYINDEX, funcref)
  lua_pushstring(L, arg1);
  lua_pushnumber(L, arg2);
  lua_pcall(L, 2, 1);
  return lua_tointeger(L, -1);
}

In principle this is no different from wrapping a C function in Lua directly — the same thing, only in the other direction.

int make_cb(char * code, int size, lua_State * L, int ref, const char * argt){
  int argc = strlen(argt);
  char * p = code;
{
    _push_rbx(p);     //push rbx to align stack to 16 and store lua_State * L in rbx
    _mov_rbx_QW(p, (uintptr_t)L);   //as we are generating ordinary C function, lua_State * L is passed as argument from ffi and stored as immediate constant in code.

    if (argc > 1) if (argt[1] == 'f' || argt[1] == 'd') _st_xmm0(p, 16); else _st_rcx(p, 16);  //store 4 arguments in shadow
    if (argc > 2) if (argt[2] == 'f' || argt[2] == 'd') _st_xmm1(p, 24); else _st_rdx(p, 24);
    if (argc > 3) if (argt[3] == 'f' || argt[3] == 'd') _st_xmm2(p, 32); else _st_r8 (p, 32);
    if (argc > 4) if (argt[4] == 'f' || argt[4] == 'd') _st_xmm3(p, 40); else _st_r9 (p, 40);

    _sub_rsp_DW(p, 48); //32 shadow space + additional 8 bytes for 5th argument of lua_callk + 8 bytes to keep 16 bytes alignment

    _mov_rcx_rbx(p);
    _mov_rdx_DW(p, LUA_REGISTRYINDEX);
    _mov_r8_DW(p, ref);        //lua function we are going to wrap (ref) as C callback is stored in lua registry, so it is not garbage collected accidentally
    _call(p, lua_rawgeti);   //get lua function on stack from registry lua_rawgeti(L, REIGSTRYINDEX, funcref)

    for (int i = 1; i < argc; i++){
      _mov_rcx_rbx(p);   //first argument lua_State * L
      if (argt[i] == 'f' || argt[i] == 'd') _ld_xmm1(p, 56+i*8); else _ld_rdx(p, 56+i*8);           //get second argument for lua_push from stack
      if (argt[i] == 'f') _mov_xmm1_xmm1f(p);                                                           //float -> double conversion for lua_pushnumber
      _clr_r8(p);     //some lua_push functions have 3rd argument
      _call(p, lua_push(argt[i]));
    }

    _mov_rcx_rbx(p);
    _mov_rdx_DW(p, argc-1);
    _mov_r8_DW(p, 1);
    _clr_r9(p);
    _st_r9(p, 32);
    _call(p, lua_callk);   //lua_callk(L, argc-1, 1, 0, 0); lua function always return 1 value, nil for void f() callback

    _mov_rcx_rbx(p);        //first argument lua_State * L
    _mov_rdx_DW(p, -1);   //return value is on top of the lua stack after lua_call
    _clr_r8(p);         //some functions have 3rd argument, lua_tostring == lua_tolstring(L, idx, 0);
    _call(p, lua_to(argt[0]));

    if (argt[0] == 'f' || argt[0] == 'd') _st_xmm0(p, 32); else _st_rax(p, 32); //store return value on stack to call lua_pop(L, 1)

    _mov_rcx_rbx(p);
    _mov_rdx_DW(p, -2);
    _call(p, lua_settop); //lua_pop(L, 1) == lua_settop(L, -2);

    if (argt[0] == 'f' || argt[0] == 'd') _ld_xmm0(p, 32); else _ld_rax(p, 32); //return value in rax/xmm0

    _add_rsp_DW(p, 48);    //restore stack
    _pop_rbx(p);        //and rbx
    _ret(p);
  }
  return p - code;
}

Conclusion

The result is a small dependency-free library that, if it doesn't completely eliminate the extra wrapper code for calling external libraries from Lua, at least lets you write that code directly in Lua.

github.com/pavel212/uffi