Sonntag, 15. Juli 2012

Lua C API Tutorial (part 2) C Functions in Lua

Hi, in this tutorial I'll show how to make C functions available in Lua. We'll start small first and expose a function that does not take any arguments or return any values and then cover step by step things like multiple return values, function arguments, namespaces etc. .

I. Simple 'greet' Function
So,  in this first example we'll create a greet function that will just print "Hello!" to the standard output. Here's the source code:

#include "iostream"
#include "lua5.1/lua.hpp"

int greet(lua_State* L)
{
    std::cout << "Hello!" << std::endl;
    return 0;
}

int main(int argc, char** argv)
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    luaL_Reg module[] = {
                                {"greet", &greet},
                                {NULL, NULL}
                        };
    luaL_register(L, "_G", module);
    
    luaL_dostring(L, "greet()");
    
    lua_close(L);
    return 0;
}

Many things should be familiar to you from the previous tutorial however there are (obviously) a few changes. We first define the function greet function that we will expose to Lua. The function expects a lua_State* as argument and returns an integer value. At this point, you may ask yourself how you pass a pointer to lua_State from within Lua, and I know you'll like the answer: you don't have to. Lua does this for you. The passed pointer points to the state that calls the function. The return value of the functions specifies how many values the function returns (0 - in our case).
Note that all functions that you want to expose to Lua have to return an int and expect lua_State* as its only argument (I'll explain it later).

These lines:

luaL_Reg module[] = {
                                {"greet", &greet},
                                {NULL, NULL}
                    };
luaL_register(L, "_G", module);

...create a table of functions that you wish to make available in Lua, terminated by {NULL, NULL} (a value that indicates the end of the table). The first value in a table entry is always the name of the function in Lua. The second one is the pointer to the function itself.
After the creation of the table we finally expose it to our Lua state (first argument). The second argument is the name of the table that will hold the function. I've entered "_G" (Lua's global table) here because I want the function to be accessible globally, like that:

greet()

If we want to put our function into a namespace-table then we have to enter the name of the table. For example:

luaL_register(L, "foo", module);

After that we will have to call our function like that:

foo.greet()

Everything else should be self explanatory. Now let's move on and make a function that returns values and expects arguments.

II. Return Values and Arguments
In this example we will write and expose a function that calculates the average of arguments and returns the result then. The implementation will require us to do some stack trickery and here's what it looks like:


#include "lua5.1/lua.hpp"

int average(lua_State* L)
{
    double sum = 0;
    int i = 0;
    for(;lua_gettop(L) > 0; ++i)
    {
        sum += luaL_checknumber(L, -1);
        lua_remove(L, -1);
    }
    
    lua_pushnumber(L, sum/i);
    return 1;
}

int main(int argc, char** argv)
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    luaL_Reg module[] = {
                                {"average", &average},
                                {NULL, NULL}
                        };
    luaL_register(L, "_G", module);
    
    luaL_dostring(L, "print(average(5, 4, 5))");
    
    lua_close(L);
    return 0;
}

While you should understand what's going on in the main function, the average function deserves a closer look. So what we do there is defining two variables first: sum to store the sum of the arguments passed to the function and the counter variable i that will contain the number of valid arguments in the end.
In order to understand what happens after that, one has to understand the lua stack. So we already know that the exchange of values between Lua and C/C++ works through the stack. That means that the arguments we pass to the average function will land on our Lua stack, and now we need to get them out of there. How that works? We can access values using indices (just like when we use arrays). Indices are integer values that can be positive as well as negative (opposed to C arrays that accept only positive values). Please note that in the case of a stack the indices start with 1 and not with 0.
Positive indices specify an absolute position within the stack (1 - is the element at the bottom, 2 - element above 1 etc.) while negative indices are relative to the top of the stack (-1 is the very top of a stack, -2 - the value below -1 etc.).
So that means that when we call the average function with 2, 5 and 9 (in exactly that order) as arguments then our stack will look like that:


Value Absolute Index Relative Index Comment
9 3 -1 It was the last argument so it's on the top of the stack now.
5 2 -2
2 1 -3 Is on the bottom of the stack

What we have to do now, to get all the values is to make a loop and let it run until the stack is empty. Inside of the loop we will check whether the element on the top is a number and add it to the sum variable. After that we will have to remove the element from the top, so the element below it becomes the 'top'. We will empty the stack step by step until the loop terminates. Then we finally calculate the average, push it onto the stack and return 1 so Lua knows that there is one result to be fetched from the stack. To cut it short, here are the functions we call with their descriptions:


for(;lua_gettop(L) > 0; ++i)

We let the loop iterate for as long as the index of the top is greater then zero. Because a stack is empty when it's greatest absolute index is zero.


sum += luaL_checknumber(L, -1);

We check whether the argument on the top of the stack is a number, extract it and finally add it to the sum variable.


lua_remove(L, -1);

This call removes the value we just got, from the stack, so we can process the value below it on the next iteration.


lua_pushnumber(L, sum/i);

All done! We finally push our result onto the stack.

So there's one more thing that I should mention before I go to bed:

III. Multiple Return Values
There isn't really much to explain here. Just take a look at this piece of code:


#include "lua5.1/lua.hpp"

int get_info(lua_State* L)
{
    lua_pushstring(L, "John Doe"); // push name (first return value)
    lua_pushnumber(L, 40); // push age (second return value)
    return 2; // return 2 values from the top of the stack
}

int main(int argc, char** argv)
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    luaL_Reg module[] = {
                                {"get_info", &get_info},
                                {NULL, NULL}
                        };
    luaL_register(L, "_G", module);
    
    luaL_dostring(L, "name, age = get_info(); print(name .. '(' .. age .. ')' )");
    
    lua_close(L);
    return 0;
}


Output:
John Doe(40)


I hope you're still brave and ready for the next tutorial where I will show you how to create userdata. Good night!

Keine Kommentare:

Kommentar veröffentlichen