Related articles in this series:
Flutter thermal update and dynamic UI generation
Lua 15 Minutes quick Start (PART 1)
Lua 15 minutes quick Start (II)
Lua and C language call each other
Dart and Lua invocation in LuaDardo
The underlying Lua is developed in C and was designed with C interoperability in mind. Lua can be used as a standalone language or as an embedded scripting language. There are many games and other applications that use Lua as a scripting language.
In this article, we’ll focus on how Lua can be embedded in C or C++ applications as glue scripts. While this article focuses only on THE C API, in the LuaDardo project, no fundamental changes were made to these C apis, just a naming convention to accommodate the Dart language change (the hump nomenclature). In short, LuaDardo is basically compatible with these apis.
Use the C API
Lua’s C API is efficient and lightweight. Before writing any C code, there are a few important header files to familiarize yourself with:
-
Lua.h: Provides all the functions needed to use Lua. All functions in this file are prefixed with lua_.
-
Luaxlib.h: Use public apis exposed in lua.h to provide a higher level of abstraction for common tasks. All functions in this file are prefixed with luaL_.
-
Lualib. h: Provides the standard Lua library. All functions in this file are also prefixed with luaL_.
Lua does not allocate any global memory. Instead, it stores all the state in a structure called lua_State. This structure contains everything you need for Lua runtime. Placing a mutex around a lua_State object is a quick and easy way to ensure that any Lua instance is thread-safe. It is perfectly valid to create multiple states in an application and therefore multiple Lua runtimes, although there are few use cases to do so.
To create a new Lua state, call the luaL_newstate() function, which returns a pointer to the lua_State structure. This pointer needs to be passed to all future Lua API calls — that’s how Lua knows what runtime it’s using. After your program is finished, destroy the state with the lua_close(lua_State*) function.
When you create a new Lua state, the standard Lua library is not automatically loaded. This can be a problem because Lua programmers at least expect quasi-Lua libraries to be available. You can load the library with the luaL_openlibs(lua_State*) function.
The following code demonstrates how to set up and destroy the embedded Lua runtime:
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
int main(int argc, char** argv) {
// First, create a new lua state
lua_State *L = luaL_newstate();
// Next, load all the standard libraries
luaL_openlibs(L);
// Write the code that interacts with the Lua runtime here
// Finally, destroy lua state
lua_close(L);
return 0;
}
Copy the code
The stack
Lua and C are fundamentally different languages. They handle everything differently, such as memory management, types, and even function calls. This raises a question when trying to integrate the two languages: how do we communicate between them? This is where the Lua stack comes in.
The Lua stack is an abstract stack that sits between the C language and the Lua runtime. It is a last in first out (LIFO) stack. The idea is that C and Lua both know the rules of the stack, and as long as they both follow those rules, they can coexist and communicate.
In general, you can think of a stack as a shared data storage mechanism. The way it usually works is you push some values on the stack in C, and then you call a Lua function and give control to the Lua runtime. The runtime pops these values off the stack, and the related function does its job and pushes the returned value back onto the stack. Control is then given back to C, who pops the return value off the stack.
The following diagram shows the flow:
Push the stack
The first step in using the Lua stack is usually to push some data to it. The Lua C API provides several functions to push different types of data onto the stack. The following functions can be used to push data:
-
Lua_pushnil (lua_State*) : Pushes nil onto the stack
-
Lua_pushboolean (lua_State*, bool) : Pushes a Boolean value onto the stack
-
Lua_pushnumber (lua_State*, lua_Number) : pushes double precision values onto the stack
-
Lua_pushinteger (lua_State*, lua_Integer) : Pushes a signed integer onto the stack
-
Lua_pushstring (lua_State*, const char*) : Pushes a null-terminated string onto the stack
When you push a string onto the stack, Lua creates its own copy of the string. Once the push operation is complete, you can modify or even release your own copy of the string.
The stack is not infinite. Before pushing data onto the stack, it’s a good idea to check that there really is room for it. To check how much space the stack has, you can use the int lua_checkStack (lua_State*, int) function. This function takes two arguments: Lua status and the number of items you want to add to the stack. This function returns true (1) if there is enough space. This function returns false (0) if there is not enough space. This function can actually increase the stack if needed.
Query the stack
Lua uses indexes to refer to elements in the stack. The index of the bottom element of the stack is 1; The index grows to the top of the stack where the last element is added. Lua can also reverse index the stack. Index -1 denotes the top of the stack, -2 denotes the element directly below the top, and so on:
Using indexes, you can check the type of any element on the stack. The functions listed here can be used to query the types of elements in the stack. They all return true (1) or false (0). The first argument to each function is a Lua state, and the second argument is a stack index:
-
Int luA_isNumber (lua_State*, int) : checks whether the element at the supplied index is a number
-
Int luA_ISString (lua_State*, int) : Checks whether the element at the supplied index is a string
-
Int luA_ISBoolean (lua_State*, int) : Checks whether the element at the supplied index is a Boolean
-
Int luA_istable (lua_State*, int) : checks whether the element at the given index is a table
-
Int lua_isnil(lua_State*, int) : Checks if the element at the given index isnil
There is a similar function, int lua_type(lua_State*, int), which returns an enumerated value with an object type. This function is useful when used in switch statements and the like. Here are the valid enumerated values that this function can return:
-
LUA_TNUMBER: indicates a Lua number type
-
LUA_TSTRING: Represents a Lua string type
-
LUA_TBOOLEAN: Represents a Lua Boolean value type
-
LUA_TTABLE: represents a Lua table type
-
LUA_TNIL: indicates the nil type
Read from the stack
Lua provides several functions to retrieve values in the stack. The most common functions are listed here. The first argument to each function is the Lua state, and the second argument is an integer, the index of the element being read.
-
Int luA_toBoolean (lua_State, int) : returns true (1) or false (0)
-
Lua_Number lua_tonumber(lua_State*, int) : Returns a double value
-
Lua_Integer Lua_toINTEGER (lua_State*, int) : Returns an integer value
-
Const char* lua_tostring(lua_State*, int, size_t*) : Returns a pointer to an internal Lua string. The last parameter is optional; If it is not NULL, the size of the string is written to it
-
Size_t lua_objlen(lua_State*, int) : returns the same value as the # operator in Lua
When called lua_toString, this function returns a pointer to the internal string. The return value is const to remind you that you should not change this value! Once the value is popped off the stack, the string may no longer exist. Keeping the return value of this function is a bad idea — it should be copied and stored instead.
The size of the stack
You can get the index of the top element of the Lua stack by using int lua_getTop (lua_State*). The value returned by this function will be the index of the top element on the Lua stack.
You can set the top element of the stack (size) with lua_setTop (lua_State*,int). This function does not return anything. The second argument is the element that should be the index of the new top element on the stack. This function effectively resizes the stack.
If the stack size requested with lua_settop is smaller than the previous size, all elements at the top of the new stack are simply discarded. If the required stack size is greater than the previous size, all new elements will be filled to nil.
A lua_POP (lua_State*, int) macro is defined in lua.h, which is a shortcut. This function pops some elements from the stack and simply discards them. The second argument to this function is how many elements to remove.
Read Lua variables from C
Reading and writing Lua variables from C is easy. Because of this, Lua is often used as a configuration language or to save and load program state. The Lua runtime will parse and interpret files for you, eliminating the need for manual parsing. Lua’s syntax is perfect for this kind of task. In this section, we explore how to read Lua variables as configuration data.
Load the Lua file
To read a Lua variable in C, you need to load the Lua file with int luaL_loadfile(lua_State*, const char*). You then need to execute the generated block with lua_pCall (lua_State*, int, int, int, int). After Lua Chunk is loaded and executed, any variables it declares can be read. The lua_pcall function is described in detail in the “Calling Lua functions from C” section of this article.
The luaL_loadfile function returns 0 on success or one of the following enumerations on failure. Lua_errsyntax lua_errmem, lua_errfile. The first argument to this function is the Lua state to process, and the second argument is the file path to load. The resulting Lua block is added to the Lua stack.
The lua_pcall function is used to execute Lua blocks, which are usually functions. This function returns 0 on success and an error code on failure. In addition, if the function fails, an error message will be pushed onto the stack.
The first argument to lua_pcall is the Lua state to operate on. The second argument is the number of arguments expected by the called block. This parameter is only used when calling Lua functions and should correspond to the number of function arguments. This parameter should be 0 when executing a chunk loaded from a file (or string).
The third parameter to lua_pcall is the number of results that chunk is expected to return. The number specified here is the number of items that luA_pcall will leave on the stack. If there is a number here, you are responsible for reading and removing those values from the stack. This value can be 0 when loading a simple file block. Some files, such as modules, return a table or multiple values. In these cases, the value should not be 0. The fourth and final argument to lua_pcall is error handling. The argument with a value of 0 tells Lua to push an error message to the stack if lua_pcall fails.
Reading global variables
Reading a global variable is done in three steps. You must use the name of the variable to push its value onto the stack. Once on the stack, the value can be read by C. Once you get this value in C, you clear the stack by removing the old copy.
You can push a global variable onto the stack by name using the lua_getGlobal (lua_State*, const char *) function. The first argument to lua_getGlobal is the Lua state. The second argument is the name of the variable to push. This function returns nothing.
The lua_getGlobal function takes the name of the global variable and leaves its value on the stack. To read this value in C, you must use one of the lua_to functions, such as lua_tonumber or lua_tostring. These functions are discussed in the “Read from the stack” section of this article. Once you have the value of the variable in C, clean up the stack by calling lua_pop.
example
Let’s look at a simple example of how to read a variable from Lua into C. Consider a use case where we create a game character. We want to store the attributes of this role in an external configuration file for easy editing. The properties file will look like this:
class = "Warrior"
attack = 56
defense = 43
Copy the code
Reading configuration values in C involves creating a new Lua state, loading Lua files, pushing each variable onto the stack, reading and storing the values, and finally popping all the values off the stack:
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include <string.h>
int main(int argc, char** argv) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Load the file "hero.lua"
int result = luaL_loadfile(L, "hero.lua");
if(result ! =0) {
printf("Could not load hero.lua, exiting");
lua_close(L);
return - 1;
}
// Execute the loaded Lua block
result = lua_pcall(L, 0.0.0);
if(result ! =0) {
const char* error = lua_tostring(L, - 1);
printf("Error loading hero.lua, exiting.\n");
printf("Error message %s", error);
lua_close(L);
return - 1;
}
// Get the base index of the stack
int stack_base = lua_gettop(L);
// Push character attributes onto the stack
lua_getglobal(L, "class"); // Index 1
lua_getglobal(L, "attack"); // Index 2
lua_getglobal(L, "defense"); // Index 3
// Reads the value of each newly pushed element on the stack
const char* class_p = lua_tostring(L, stack_base + 1);
char class_sz[32];
strcpy(class_sz, class_p);
int attack = lua_tointeger(L, stack_base + 2);
int defense = lua_tointeger(L, stack_base + 3);
/ / clean up the stack
lua_pop(L, 3);
// Do something with these values
printf("Character is %s with %d attack and %d defense\n", class_sz, attack,defense);
// Close Lua and clean up
lua_close(L);
return 0;
}
Copy the code
The sample code above gets the base index of the stack before anything is pushed onto the stack, and then uses the offset of that base index to read the value. You should never assume that the stack is empty — don’t hard-code the index. Instead, always use an index relative to a known offset.
Create Lua variables from C
Communicating with Lua is one way. In addition to reading Lua variables from C, you can also create Lua variables from C. The process is simple: you push a value onto the stack and tell the Lua runtime to assign that value to a variable by name.
To create a variable, use the lua_setGlobal (lua_State*, const char*) function. This function returns nothing. Its first argument is the Lua state to process, and its second argument is the name of the global variable to assign. This function pops the top value from the stack and assigns it to the specified variable name.
Let’s reverse the example above. This time, the variables class, attack, and defense are created in C and printed out in Lua. The C code will push all the values onto the stack and assign them to variables using lua_setGlobal. Once the variables are set, a Lua file should be loaded and executed:
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
int main(int argc, char** argv) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
/ / pressure into the value
lua_pushstring(L, "Warrior");
lua_pushnumber(L, 56);
lua_pushnumber(L, 43);
// Assign from top of stack, IE reverse order
lua_setglobal(L, "defense");
lua_setglobal(L, "attack");
lua_setglobal(L, "class");
// Load the file "printinfo.lua"
int result = luaL_loadfile(L, "printinfo.lua");
if(result ! =0) {
printf("Could not load hero.lua, exiting");
lua_close(L);
return - 1;
}
// Execute the loaded Lua block
result = lua_pcall(L, 0.0.0);
if(result ! =0) {
const char* error = lua_tostring(L, - 1);
printf("Error loading hero.lua, exiting.\n");
printf("Error message %s", error);
lua_close(L);
return - 1;
}
lua_close(L);
return 0;
}
Copy the code
Next, the printInfo.lua file takes care of printing out all of these values. Note that the variables class, attack, and defense are never created in Lua. But they can be referenced because they were created in C:
print("Charater is ". class .." with ". attack .." attack and ". defense .." defense");
Copy the code
Call Lua functions from C
The method for calling Lua functions from C, described in the Lua file loading section of this article, is lua_pcall. This time, we will use the second and third arguments of the function. As a reminder, the second parameter is the number of arguments on the stack for Lua to consume, and the third parameter is the number of values we expect Lua to leave on the stack for us.
Let’s create an example function in Lua that takes two numbers and returns a linear index of a matrix. Only Lua code will know the width of the matrix. The Lua code for finding this linear index looks like this:
num_columns = 7
function GetIndex(row, col)
return row * num_columns + col
end
Copy the code
The Lua function above expects two variables on the stack: row and col. It will leave a value on the stack. Next, let’s create a C wrapper function with a similar name and signature. The wrapper function is expected to run in a valid Lua context, where Lua files have been loaded, as defined in the previous code:
int LinearIndex(lua_State*L, int row, int col) {
// push GetIndex on the stack
lua_getglobal(L, "GetIndex");
// Stack: function (GetIndex)
// Push the row variable onto the stack
lua_pushnumber(L, row);
// Stack: function (GetIndex), int (row)
// Push the col variable onto the stack
lua_pushnumber(L, col);
// Stack: function (GetIndex), int (row), int (col)
// Pop two arguments from stack (row & col)
// Call top of stack function (GetIndex)
// Leave a value on the stack
lua_pcall(L, 2.1.0);
// Stack: int (return value of GetIndex)
// Fetch the result of GetIndex from the stack
int result = lua_tointeger(L, - 1);
lua_pop(L, 1);
// Stack: empty
return result;
}
Copy the code
Call C functions from Lua
Because functions in C and Lua work differently, exposing C functions to Lua can get a little tricky. All C functions that Lua can call must follow the signature of lua_CFunction, which is defined in lua.h as follows.
typedef int (*lua_CFunction) (lua_State *L);
Copy the code
This function takes only one argument, lua_State. The return value of this function is an integer. This integer is the number of elements pushed on the stack by the function as a return value.
Lua has multiple stacks — each C function called from Lua has its own stack and does not share the global stack.
Let’s take a simple C function that returns the size of a 3-dimensional vector. In C, the code is as follows:
double Vec3Magnitude(double x, double y, double z) {
double dot = x * x + y * y + z * z;
if (dot == 0.0) {
return 0.0;
}
return sqrt(dot);
}
Copy the code
The above function cannot be exposed directly to Lua because it does not follow the signature of lua_CFunction. There are two ways to expose this function: either rewrite it or write a wrapper function for it. Both approaches are similar. Here is an example of a rewrite:
int LuaVec3Magnitude(lua_State* L) {
double x = lua_tonumber(L, 3);
double y = lua_tonumber(L, 2);
double z = lua_tonumber(L, 1);
lua_pop(L, 3);
double dot = x * x + y * y + z * z;
if (dot == 0.0) {
lua_pushnil(L);
}else {
lua_pushnumber(L, sqrt(dot));
}
return 1;
}
Copy the code
The above functions can be called from Lua. It must be registered before it can be invoked. Registering a function means that it first needs to be pushed onto the stack by the lua_pushcfunction function. Next, you need to assign the stack function to a variable using lua_setGlobal. The following code registers the LuaVec3Magnitude function to make it available in Lua:
lua_pushcfunction(L, LuaVec3Magnitude);
lua_setglobal(L, "Vec3Magnitude");
Copy the code
The LuaVec3Magnitude function can be called at any time after being registered as Vec3Magnitude in Lua.
It’s not always possible to rewrite a function, but you can write a wrapper function. For example, we can create a function called LuaWrapperVec3Magnitude, which doesn’t do the work of Vec3Magnitude, just calls the Vec3Magnitude function. We can then expose LuaWrapperVec3Magnitude as a Vec3Magnitude to Lua.
The following code demonstrates this:
int LuaWrapperVec3Magnitude(lua_State* L) {
double x = lua_tonumber(L, 3);
double y = lua_tonumber(L, 2);
double z = lua_tonumber(L, 1);
lua_pop(L, 3);
// Call the original function and let it do the actual work
double result = Vec3Magnitude(x, y, z);
if (dot == 0.0) {
lua_pushnil(L);
}else {
lua_pushnumber(L, result);
}
return 1;
}
// Expose the wrapper function code.
lua_pushcfunction(L, LuaWrapperVec3Magnitude);
lua_setglobal(L, "Vec3Magnitude");
Copy the code
Use tables in C
So far, we’ve only been using basic Lua types and functions. Lua’s C API also allows us to work with tables. A newtable can be created using the lua_newTABLE (lua_State*) function. This function returns nothing but the Lua state as an argument. The lua_newTABLE function creates an empty table and leaves it at the top of the stack. Once the table is on the stack, it’s up to you to assign it to a variable. For example, the following code creates a globally scoped table named “vector” :
lua_newtable(L);
lua_setglobal(L, "vector");
Copy the code
After you have created the table, you will be able to get and set values from the table. However, to do this, the table needs to be on the stack. You can use Lua_getGlobal to retrieve tables on the stack just like any other variable type.
Read the value from the table
Fields in tables can be retrieved using the lua_gettable (lua_State*, int) function. This function does not return anything; Its first argument is the Lua state to work on. Typically, accessing a table in Lua involves the table and a key, such as TBL [key]. With luA_gettable, tables (TBL) should be at the index specified in the second variable. The key should be at the top of the stack. The following code shows how to retrieve the value of key x from a table called vector:
lua_getglobal(L, "vector");
lua_pushstring(L, "x");
lua_gettable(L, 2 -);
Copy the code
Because retrieving variables is so common, Lua provides a handy shortcut function, lua_getField (lua_State*, int, const char*). This helper function avoids pushing the name of the key onto the stack and takes it as the third argument. The second argument is again the index of the table on the stack. The previous example can be rewritten with lua_getField as follows:
// Push vector to the top of the stack
lua_getglobal(L, "vector");
// index -1 refers to vector, which is at the top of the stack
// leave x at the top of the stack
lua_getfield(L, - 1."x");
// There are two new values on the stack (vector and x) that need to be cleaned up at the following locations
some point
Copy the code
You may have noticed that the previous code passes a negative index to lua_getField. Recalling the query stack section of this article, positive numbers are indexed from the bottom up, while negative numbers are indexed from the top down.
Passing -1 in the previous code is valid because the lua_getGlobal function call leaves the “vector” table at the top of the stack. At this point, we don’t know (or care) how big the stack is, except that the uppermost element is a “vector” table. When lua_getField is called, the value of “x” is at the top of the stack.
Writes values to a table
Lua provides the lua_settable (lua_State*, int) function to set a field in a table. This function does not return anything. Its first argument is the Lua state to process, and its second argument is the index of a table in the stack.
The value to be set should be at the top of the stack, and the key to be set should be just below it. Lua_settable pops both keys and values off the stack, but it leaves the table on the stack.
For example, the Lua code vector[“y”] = 7 could be written using the API as follows:
// push the vector onto the stack
lua_gettable(L, "vector");
// push y onto the stack
lua_pushstring(L, "y");
// push 7 onto the stack
lua_pushnumber(L, 7);
// There are three new variables on the stack
// The index of 7 is -1
// the index of "y" is -2
// The index of "vector" is -3
Call lua_settable on the "vector" table at index-3
lua_settable(L, - 3);
// lua_settable pops the key ("y") and value (7) from the stack
// There is only one item left in the stack, namely the "vector" table
// Items left on the stack should be purged at some point
Copy the code
Lua also provides the lua_setfield (lua_State*, int, const char*) function, which avoids the need to push keys onto the stack. The first two arguments are the same as lua_settable. The third parameter is the key to be set.
The value being set should be at the top of the stack. The lua_setfield function pops the value from the stack, just as lua_settable does.
You can rewrite the previous code example with lua_setField, as follows:
// push the vector onto the stack
lua_gettable(L, "vector");
// push 7 onto the stack
lua_pushnumber(L, 7);
// Call lua_setfield on the "vector" table at index-2
lua_setfield(L, 2 -."y");
// lua_setField pops the value (7) from the stack
// There is only one item left in the stack, namely the "vector" table
Copy the code
Yuan table
You can test whether atable has metatable and retrieve the metatable by using the int lua_getMetatable (lua_State*, int) function. The first argument to this function is the Lua state it affects, and the second argument is the index of a table on the stack. If the table at the specified index has no metatable, the lua_getMetatable function returns 0 and does not push anything to the stack. If the table at the specified index does have a metatable, the lua_getMetatable function returns 1 and pushes the metatable onto the stack.
You can assign a metatable to an existing table using int lua_setMetatable (lua_State*, int). This function takes the Lua state it affects as its first argument and the index of the table on the stack as its second argument. It expects the meta table to be at the top of the stack and pops it out of the stack. This function returns 1 if it can allocate mettables. Otherwise, the function returns 0 if an error occurs.
User data
Lua has a special data type called userData. Userdata can store any C data structure as Lua data — it’s just some arbitrary memory. Userdata can have meta-tables, which allows us to extend the type using the same mechanism that extends tables. Like tables, Userdata is compared by reference, not by value.
To create a newuserdata memory block, use the void* lua_newUserData (lua_State*, size_t) function. The first argument to this function is the Lua state to process, and the second argument is the number of bytes of data to reserve for the user. This function returns a pointer to the block of memory Lua reserves for this user data.
A three-dimensional vector might be stored in userData as follows:
struck Vec3 {
float x, y, z;
}
int make_up_vector(lua_State *L) {
Vec3* newVec = (Vev3*)lua_newuserdata(L, sizeof(Vec3));
newVec->x = 0;
newVec->y = 1;
newVec->z = 0;
// New user data on the stack
return 1;
}
Copy the code
User data can be retrieved using the lua_TOUserData function. This function returns a pointer to the user’s data store. Its first argument is the Lua state to process, and its second argument is the index on the stack where the user data should be. If you modify the pointer returned by user data, you modify the actual value of user data. The following code example shows how touse the lua_touserData function:
int lua_vec3_cross (lua_State *L) {
Vec3* a = (Vec3*)lua_touserdata(L, 2 -);
Vec3* b = (Vec3*)lua_touserdata(L, - 1);
float dot = a->x * b->x + a->y * b->y + a->z * b->z;
lua_pushnumber(L, dot);
return 1;
}
Copy the code
For more detailed information about the Lua C API, seeLua API manual
This article introduces the basic use of Lua’s C API, and the next article will focus on examples in LuaLuaDardoHow to use a similar API to implement Lua and Dart calls to each other.
Follow the public account: the path of programming from 0 to 1
Or follow a blogger’s video school