sepisoad.com

index
blog

how to lua and c - a short novel

published on 2018-12-27 14:42:26.127190 by Sepehr Aryani
lua c tutorial programming

enter image description here

Objectives

  1. Write a C program (cool)
  2. Embed Lua in the C program
  3. Write some C functions
  4. Expose C functions to Lua
  5. Have Lua call those C functions (dope)

Presumptions

  1. You are OK with C
  2. you are familiar with Lua

Project Structure

enter image description here

cautions

To keep the code simple and easier to read I had to intentionally remove some checking and extra conditions.

The Code

main.c


#include <lua.h> // Lua basic definitions
#include <lualib.h> // Lua standard libraries
#include <lauxlib.h> // Lua auxiliary functions
#include "libcrap_lua.h" // our Lua exported functions

int main(int argc, char *argv[]){
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);
    glue(lua); //WTF?
    luaL_dofile(lua, "./app.lua")
    lua_close(lua);
}

the source code is fairly simple, but some explanation is required.


#include <lua.h>

this line includes basic Lua interpreter definitions. for example, lua_State type is defined there.


#include <lualib.h>

This header holds definitions of the Lua standard library. Without including this one our final Lua program will not be that useful. This header adds many features to our little embedded Lua interpreter including:


#include  <lauxlib.h>

This header is useful when you want to bridge Lua and C. Using this library we can make wrappers for existing C libraries. In our case, we want to export our crappy C functions to Lua.


lua_State* lua = luaL_newstate();

This line creates a Lua state object. From now on this object is used everywhere we need to talk to Lua. We can safely say that it represents the Lua interpreter that is embedded in our program.


luaL_openlibs(lua);

This line adds some features to our Lua interpreter, including math and I/O (standard library).


glue(lua);  //WTF?

This line adds our glue functions to Lua (more on this in "libcrap_lua.h" section)


luaL_dofile(lua,  "./app.lua")

Loads and executes "./app.lua" file.


lua_close(lua);

Closes our Lua interpreter.

libcrap.h


#pragma once

#include <string.h> // strcmp()

int crap_add(int a, int b){
    return a + b;
}

int crap_isstreq(const char* a, const char* b){
    if(0 == strcmpy(a, b)){
        return 1;
    }
    return 0;
}

Again, for the sake of simplicity, I have not separated functions' definition from the declaration and put them all in header files. However these days I do this a lot and I am pretty happy with it. Anyways, libcrap.h is where our shiny sophisticated C functions are. These two functions crap_add and crap_isstreq, we want them to be exposed to our little Lua side of the story.

The code is self-explanatory so let us move on to the next source file.

app.lua

I know that by now you expected me to show you the source code of libcrap_lua.h file, don't hurry, we'll get to that, but first let us see how our Lua code looks like, afterward I will tell you how to glue things together!


function my_app()
    local num_a = 1980
    local num_b = 7
    local str_a = 'sepi'
    local str_b = 'soad'
    
    local res = c_crap_add(num_a, num_b)
    print(res) -- 1987
    
    res = c_crap_isstreq(str_a .. str_b, 'sepisoad')
    print(res) -- true!
end
my_app()

If you can somehow convince your favorite C compiler to build this half-assed mix of C and Lua code, the resulting binary will be of no use. As we know, our generated executable binary has a Lua interpreter at its heart. If you manage to run the binary, it will look for app.lua, and if it finds it tries to run the script, no matter what is written in it.

Well actually my last sentence is not totally correct, in fact, Lua tries its best to make sense of the script we gave it, However, what basically happens in our case is as follows:

  1. Loads the script
  2. Starts executing the script by following my_app() function call
  3. When inside my_app() function, creates a bunch of variables and gives them their values. (Pretty straightforward so far, right?)
  4. Reaches to local res = c_crap_add(num_a, num_b) line
  5. Understands that it has to call a function named clike_crap_add
  6. Asks itself (its core and standard library) a bunch of questions including:
    • What the hell is this function
    • Where is it defined
  7. Finds out nothing
  8. Freaks out
  9. Goes nuts
  10. Kills himself

libcrap_lua.h

In the last section, we saw that Lua wasn't happy about those two mysterious functions however he could not do away with them so he sadly decided to kill himself.

We want to let Lua know how to handle this situation from now on. We want to use the facilities defined in lauxlib.h file and write glue for our C functions and bridge the two worlds. Writing glues Is a simple task as long as you know how the two worlds talk to each other. Knowing that is a little tricky.

enter image description here

Lua and C can exchange goods using an abstract stack, which is an isolated piece of memory. As the name suggests the behavior of this piece of memory is like a stack data structure, first in last out or put it another way, last in first out. Bellow is the code for wrapper functions and the glue functions.


#include "libcrap.h"

int crap_add_wrapper(lua_State* lua) {
    int top = lua_gettop(lua);
    if (2 != top) return -1;  
    if (!lua_isnumber(lua, 1)) return -1;
    if (!lua_isnumber(lua, 2)) return -1;    
    
    int num_a = lua_tonumber(lua, 1);
    int num_b = lua_tonumber(lua, 2);
    
    int res = crap_add(num_a, num_b);
    
    lua_settop(lua, 0);  
    lua_pushnumber(lua, res);
    return 1;
}

int crap_isstreq_wrapper(lua_State* lua) {
    int top = lua_gettop(lua);
    if (2 != top) return -1;  
    if (!lua_isstring(lua, 1)) return -1;
    if (!lua_isstring(lua, 2)) return -1;    
    
    const char* str_a = lua_tostring(lua, 1);
    const char* str_b = lua_tostring(lua, 2);
    
    int res = crap_isstreq(str_a , str_b);
        
    lua_settop(lua, 0);  
    lua_pushboolean(lua, res);
    return 1;
}

void glue(lua_State* lua) {
    lua_pushcfunction(lua, crap_add_wrapper);
    lua_setglobal(lua, "c_crap_add");
    
    lua_pushcfunction(lua, crap_isstreq_wrapper);
    lua_setglobal(lua, "c_crap_isstreq");
}

Ok, don't feel bad if you do not understand this one. Let us first see what this glue function does. glue function receives a lua_State object which was created in the main function and then was passed into it. We can see two pairs of lua_pushcfunction and lua_setglobal functions. It is easy to tell that the first pair tries to export crap_add function and the second pair does the same thing for crap_isstreq function.

Now you might ask what are these lua_pushcfunction and lua_setglobal. Well, lua_pushcfunction pushes a C function onto the Lua stack (the wrapper), However, the function should follow a specific protocol, It should accept a single lua_State* object and return an int, as you can see :


int foo (lua_State *L)

Now that we have pushed our wrapper function onto the stack we must tell Lua to register it and give it a name so that later on when Lua encountered the function it knows what to do. This is done using lua_setglobal function, It actually takes any item stored on top of the stack, registers it to Lua side and pops it from the stack.

Lets recap:


lua_pushcfunction(lua, crap_add_wrapper);
lua_setglobal(lua,  "c_crap_add");

We pushed the wrapper function (crap_add_wrapper) onto the stack using lua_pushcfunction, then we asked Lua to save it or register it giving it a name ("c_crap_add") using lua_setglobal function.

Now that we covered glue function It's time to learn how a wrapper function is implemented. crap_add_wrapper is a simple wrapper and is fairly easy to understand. Let us take a look at crap_add_wrapper function one more time:


int  crap_add_wrapper(lua_State* lua)  {  
    int top =  lua_gettop(lua);  
    
    if  (2  != top)  return  -1;  
    if  (!lua_isnumber(lua,  1))  return  -1;  
    if  (!lua_isnumber(lua,  2))  return  -1;  
    
    int num_a =  lua_tonumber(lua,  1);  
    int num_b =  lua_tonumber(lua,  2);  
    int res =  crap_add(num_a, num_b);  
    
    lua_settop(lua,  0);  
    lua_pushnumber(lua, res);  return  1;  
}

Now let's cover it line by line:


int top =  lua_gettop(lua);

The first line makes a call tolua_gettop function, this function returns the current size of the stack. Remember this line from app.lua script?

 
local res =  c_crap_add(num_a, num_b)

When Lua reaches this line in the script file it already knows that this is an exported C function, so it takes those two parameters (num_a and num_b) and pushes them onto the stack, then calls crap_add_wrapper C wrapper function.

Inside crap_add_wrapper we expect to have a stack that has two items. As mentioned before we can query the stack size by calling lua_gettop(lua); function.


if  (2  != top)  return  -1;

This makes sure that we have received the correct amount arguments. Otherwise returns -1 which means failure.


if  (!lua_isnumber(lua,  1))  return  -1;
if  (!lua_isnumber(lua,  2))  return  -1;

These two similar lines query stack value types at index 1 and 2. They make sure that we have received proper values. In our case the expected value type for both first and second arguments is number, anything other than the number type causes our function to fail.


int num_a =  lua_tonumber(lua,  1);
int num_b =  lua_tonumber(lua,  2);
int res =  crap_add(num_a, num_b);

After making sure that we have received the correct amount of arguments and type are also valid, we can convert values stored onto the stack to their corresponding C types. Then we call the real C function crap_add and store the return value into res variable.


lua_settop(lua,  0);  
lua_pushnumber(lua, res);  
return  1;

One last thing to do is to give the crap_add function return value back to Lua, but how? Correct, through the stack. As you can see stack plays the bridge role between C and Lua worlds.

lua_settop(lua, 0); function resets the stack and give 0 size. Then lua_pushnumber(lua, res); function pushed the value stored in res onto the stack. We call return 1; to tell Lua that it has to take only one value from the stack, in Lua functions can return more than one values, so here we are basically telling Lua we have only one return value for you.

Yup, that's it, thanks for the time you spent reading this crap.

Send
Share
©️ 2018 Sepehr Aryani
made with zaart