r/lua 1d ago

Calling an existing C function from Lua

This is a follow-on question from this thread.

Apparently calling C (any external FFI function really) from Lua isn't as simple as it is when using LuaJIT's FFI.

However, I haven't been able to find any definitive way of doing it. lua.org articles are rather waffly and are more concerned with writing C that can interact with Lua.

I'm not interested in that right now; I just want to know how to call functions in existing external libraries.

For example, the C runtime exports a function puts, which takes a pointer to a zero-terminated string, and returns a 32-bit count of the number of characters output.

Its signature in C syntax is int puts(char*). How would I define that in Lua, and how would I call it?

I'm mainly curious as I've heard that Lua is supposedly good at C FFI, and I want to see how good. Unless people were again talking about LuaJIT!

9 Upvotes

17 comments sorted by

6

u/didntplaymysummercar 1d ago

This is covered in the manual.

You'd write a C function that takes lua_State and returns int and call puts inside it, taking argument from Lua (using lua_tostring, or luaL_checkstring , etc.) and returning something or nothing by pushing these values and return their amount.

All Lua built in libraries are implemented like that too so you can read those.

An int returning lua_State taking function is the only type you can register to Lua. This is usually not called "ffi" by most BTW.

The LuaJIT has own ffi and for stock Lua ffi libs exist too, and (especially in C++) people use wrappers that automatically wrap functions and structs for you too.

1

u/sal1303 1d ago

So if I understand correctly:

  • Standard Lua doesn't have a general purpose FFI built-in
  • It can only call specially written C functions that handle the necessary machinery
  • Such a function can itself call a regular C function so acts as a wrapper between Lua and arbitrary external functions
  • Add-on products exist that automate some of this to provide a more streamlined FFI experience, perhaps similar to or compatible with LuaJIT built-in FFI scheme

2

u/didntplaymysummercar 1d ago

The first three, yes. It can call C++ too, of course. That's how classes (can) get wrapped one to one, using userdata and functions that call it's member functions.

And third party libraries (products as you call them but they're almost all Foss) that automate the boilerplate usually people don't call ffi. You still need a C or C++ compiler then.

By ffi people usually (terminology is eh...) call when from scripting language itself, using some library (written in native code or that will generate some native code at runtime) you can call native code without writing any yourself, without needing your own C or C++ compiler (which massively simplifies things, you need to only bring along the ffi library and in LuaJIT and I think Python too, they're built in), you just say the function name, type, arguments, calling convention, maybe say what all/so to load, etc. and it gets done for you.

-3

u/Compux72 1d ago

Btw you should be able to create your own wrapper of dlsym and use that from lua. Claude made this example up, didnt test it as im on the phone rn:

```

include <lua.h>

include <lauxlib.h>

include <dlfcn.h>

include <ffi.h>

include <string.h>

include <stdlib.h>

/* Supported type tags: "i" int, "d" double, "s" const char, "p" void, "v" void (return only) */

static ffi_type *tag_to_ffi(char tag) {     switch (tag) {         case 'i': return &ffi_type_sint;         case 'd': return &ffi_type_double;         case 's': return &ffi_type_pointer;         case 'p': return &ffi_type_pointer;         case 'v': return &ffi_type_void;         default:  return NULL;     } }

/*  * cfunc.call(symbol_name, "ret_tag", "arg_tags", arg1, arg2, ...)  * e.g. cfunc.call("printf", "i", "s", "hello %s\n", "world")  */ static int l_cfunc_call(lua_State *L) {     const char *symname = luaL_checkstring(L, 1);     const char *rettag  = luaL_checkstring(L, 2);     const char *argtags = luaL_checkstring(L, 3);     int nargs = (int)strlen(argtags);

    void *sym = dlsym(RTLD_DEFAULT, symname);     if (!sym) {         lua_pushnil(L);         lua_pushfstring(L, "dlsym failed: %s", dlerror());         return 2;     }

    ffi_cif cif;     ffi_type *arg_types[nargs > 0 ? nargs : 1];     void *arg_values[nargs > 0 ? nargs : 1];

    /* storage for converted args */     int    int_store[nargs];     double dbl_store[nargs];     const char *str_store[nargs];

    for (int i = 0; i < nargs; i++) {         char tag = argtags[i];         arg_types[i] = tag_to_ffi(tag);         int luaidx = 4 + i;         switch (tag) {             case 'i':                 int_store[i] = (int)luaL_checkinteger(L, luaidx);                 arg_values[i] = &int_store[i];                 break;             case 'd':                 dbl_store[i] = luaL_checknumber(L, luaidx);                 arg_values[i] = &dbl_store[i];                 break;             case 's':                 str_store[i] = luaL_checkstring(L, luaidx);                 arg_values[i] = &str_store[i];                 break;             case 'p':                 str_store[i] = lua_touserdata(L, luaidx);                 arg_values[i] = &str_store[i];                 break;             default:                 return luaL_error(L, "unsupported arg tag '%c'", tag);         }     }

    ffi_type *ret_type = tag_to_ffi(rettag[0]);     if (!ret_type) return luaL_error(L, "unsupported return tag");

    if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, nargs, ret_type, arg_types) != FFI_OK)         return luaL_error(L, "ffi_prep_cif failed");

    ffi_arg  int_ret;     double   dbl_ret;     void    ptr_ret;     void    rc = &int_ret;     if (rettag[0] == 'd') rc = &dbl_ret;     if (rettag[0] == 'p' || rettag[0] == 's') rc = &ptr_ret;

    ffi_call(&cif, FFI_FN(sym), rc, nargs ? arg_values : NULL);

    switch (rettag[0]) {         case 'i': lua_pushinteger(L, (lua_Integer)int_ret); break;         case 'd': lua_pushnumber(L, dbl_ret); break;         case 's': lua_pushstring(L, (const char*)ptr_ret); break;         case 'p': lua_pushlightuserdata(L, ptr_ret); break;         case 'v': lua_pushnil(L); break;     }     return 1; }

static const luaL_Reg cfunc_funcs[] = {     {"call", l_cfunc_call},     {NULL, NULL} };

int luaopen_cfunc(lua_State *L) {     luaL_newlib(L, cfunc_funcs);     return 1; } ```

``` local cfunc = require("cfunc")

-- int abs(int) print(cfunc.call("abs", "i", "i", -42))          -- 42

-- double sqrt(double) print(cfunc.call("sqrt", "d", "d", 2.0))          -- 1.4142...

-- int printf(const char*, ...) -- note: varargs need special ffi_prep_cif_var, not shown here print(cfunc.call("puts", "i", "s", "hi from libffi")) ```

1

u/AutoModerator 1d ago

Hi! Your code block was formatted using triple backticks in Reddit's Markdown mode, which unfortunately does not display properly for users viewing via old.reddit.com and some third-party readers. This means your code will look mangled for those users, but it's easy to fix. If you edit your comment, choose "Switch to fancy pants editor", and click "Save edits" it should automatically convert the code block into Reddit's original four-spaces code block format for you.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

-3

u/Compux72 1d ago

Yea no thank you

5

u/revereddesecration 1d ago

What’s your goal? Or is this just curiosity?

1

u/sal1303 1d ago

As I mentioned in my other thread, I maintain a scripting language of my own.

One feature is a built-in FFI, which I considered unusual among scripting languages, where calling external functions tends to be a clunky process. But I'd heard that Lua was designed for easy interop with C and started investigating.

The first FFI I saw was LuaJIT's, which looked decent and with similar abilities to mine. But it turned out that regular Lua doesn't have a built-in FFI of that standard, and requires extensions to simplify the process.

1

u/immortalx74 1d ago

The way I understand it, is that the claim of easy integration with C of vanilla Lua, is to write programs/libs tailored to it and not the other way around. For existing C code I suppose you could write a C wrapper, but since LuaJIT + FFI exists, that's the easiest path.

1

u/Difficult-Value-3145 1d ago

I have never gotten luajit ffi or lua++ or any of that to work I found it easier to just write the function takes lua state as a variable returns the number of results you want to pass back. Now I learn kinda weird some times and I miss random stuff I really should get but never got any working results from any third party designed to turn a function from a header file of a club into callable lua. I found writing the wrappers fairly easy with lua c api I keep saying to myself I'm going to try doing it for python or something for comparison at least haven't thou. Bash dosnt count there is a reason bash is so non intuitive and part of that is so you can call programs from other languages seemingly natively if they are written for it without any special bash c api

1

u/immortalx74 1d ago

What I've done successfully numerous times was call Windows API or other external libraries code from Lua via LuaJIT's FFI. You just write the function signatures in a file and pass it to the ffi.cdef function. It's very straightforward.

1

u/Kritzel-Kratzel 1d ago

OneLuaPro provides https://github.com/OneLuaPro/lua-ffi for instant DLL access. However, Windows only at the moment.

1

u/xoner2 18h ago

Like I said in your previous thread, luaffi library works for PUC Lua 5.1 and 5.2. It uses the dynasm from LuaJIT. There's no reason it can't be compiled for 5.3 to 5.5.

Here's an example, I use this from both 5.1 and LuaJIT:

local cdefs = [[
typedef unsigned long DWORD;
typedef long LONG;
typedef long long LONGLONG;
typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER;
typedef int BOOL;

BOOL QueryPerformanceCounter(
  LARGE_INTEGER *lpPerformanceCount
);

BOOL QueryPerformanceFrequency(
  LARGE_INTEGER *lpFrequency
);
]]

local ffi = require 'ffi'
ffi.cdef (cdefs)

local freq = ffi.new ('LARGE_INTEGER[1]')
local ticks = ffi.new ('LARGE_INTEGER[1]')

local function GetSeconds ()
  ffi.C.QueryPerformanceFrequency(freq)
  ffi.C.QueryPerformanceCounter(ticks)

  return tonumber (ticks[0].QuadPart) / tonumber (freq[0].QuadPart)
end

return GetSeconds

1

u/sal1303 12h ago

I looked at that link. I'm on Windows, and it says:

On windows use msvcbuild.bat in a visual studio cmd prompt

VS, and MS tools in general are huge, so I'm not going to do that. (I haven't managed to get it to work for years anyway). I guess there is no simple way of making this available?

(I develop my own small-scale language products which are the antithesis of tools like VS/MSVC, gcc, clang and LLVM. Or, for scripting languages, compared with ones like CPython.

Lua is different in being on a similar scale to my stuff, and its C source code, especially in its one-file version, is a test input to my C compiler project.)

1

u/xoner2 10h ago

(I haven't managed to get it to work for years anyway). I guess there is no simple way of making this available?

MSVC works now.

Which C runtime are you on?

Are you able to compile Lua with your C compiler project? If yes, try compiling luaffi too. It requires very few functions: memmove, memset, memchr, memcpy, strchr. The Lua dll/exe must be compiled with dlopen support.

1

u/smog_alado 10h ago

The Lua–C API looks a bit unusual at first, because of the Lua Stack, but it's actually a clever design. Compared to other programming languages:

  • The API surface is quite small
  • The API is comprehensive, covering everything that Lua can do.
  • The API is safe: it is very hard to leak or corrupt memory.

To call an external C function you need to create a wrapper function that translate between the Lua world and the C world. You can find examples of this in Lua's own standard library. At the end of these files you should be able to find a luaL_Reg array with a list of functions in the library. Each of the functions listed in this array should be one of those wrapper functions I just mentioned, with a signature that receives lua_State * and returns int.