Aus Linux-Magazin 03/2015

Lua- und C-Code mischen

© Iurii Kovalenko, 123RF

Lua ist klein, schlank und vor allem schnell. Nicht zuletzt deshalb dient die Skriptsprache in vielen Anwendungen und Spielen als Vorlage und Einstieg. Dank der mitgebrachten C-Schnittstelle lässt sich der portugiesische Mond spielend leicht auch in eigene C-Programme integrieren.

Um von einemC-Programm aus ein Lua-Skript anzuwerfen, binden Entwickler zunächst die im Lua-Paket mitgelieferten Header »lua.h« und »lauxlib.h« ein, »lua.h« enthält dabei die von Lua bereitgestellten C-Funktionen. Deren Namen beginnen grundsätzlich mit »lua_« . Der Header »lauxlib.h« holt ein paar Hilfsfunktionen hinzu, die dem Programmierer etwas Schreibarbeit abnehmen. Die Namen aller Hilfsfunktionen beginnen mit dem Kürzel »luaL_« .

Sind die passenden »#include« -Anweisungen geschrieben, erstellt der C-Entwickler einen Lua-Interpreter:

lua_State *mystate = luaL_newstate();

Die Datenstruktur »lua_State« kapselt den kompletten Zustand (State) eines Lua-Interpreters. Auf diese Weise lassen sich mehrere unabhängige Lua-Interpreter gleichzeitig anwerfen. Wenn die Funktion jedoch den Lua-State nicht erstellen konnte, gibt sie »NULL« zurück – etwa dann, wenn nicht genügend Speicher vorhanden war.

Panik? Mach einfach!

Die Hilfsfunktion »luaL_newstate()« richtet zudem im Hintergrund eine Panik-Funktion ein, die bei einem Fehler eine entsprechende Meldung auf die Standardausgabe schreibt. Ein Lua-Skript führt jetzt die Funktion »luaL_dofile()« aus, die neben einem Zeiger auf den Lua-State lediglich den Dateinamen des Skripts erwartet:

luaL_dofile(mystate, "./beispiel.lua");

Alternativ legt der C-Programmierer das Lua-Skript in einem String ab. Das ist besonders dann nützlich, wenn es nur einen einzigen Lua-Befehl auszuführen gilt. Die Ausführung übernimmt die Funktion »luaL_dostring()« , die einfach den entsprechenden String mit dem Lua-Code übergeben bekommt:

luaL_dostring(mystate, "print(\"Hallo\")");

Dieser Beispiel-Befehl hätte aber keine Folgen, hier also kein »Hallo« ausgegeben. Der simple Grund: Der neu erzeugte Lua-Interpreter enthält standardmäßig keine Bibliotheken, womit aber auch das viel genutzte »print« fehlt.

Alle Standardbibliotheken auf einen Schlag lädt die Hilfsfunktion »luaL_openlibs(mystate);« . Trotz ihres Namens ist sie nicht im Header »lauxlib.h« definiert, sondern in einem eigenen namens »lualib.h« . Diesen dritten Header müssen C-Entwickler folglich ebenfalls noch einbinden.

Listing 1 zeigt ein Beispielprogramm, das einen Lua-Interpreter erstellt und dann das Lua-Skript in der Datei »beispiel.lua« ausführt. Bei der Übersetzung des C-Programms muss man lediglich die Lua-Bibliothek hinzulinken (Abbildung 1).

Listing 1

Lua-Skript aus C-Programm starten

01 #include "lua.h"
02 #include "lauxlib.h"
03
04 main()
05 {
06   lua_State *mystate = luaL_newstate();
07   luaL_openlibs(mystate);
08   luaL_dofile(mystate, "./beispiel.lua");
09   lua_close(mystate);
10 }
Abbildung 1: Bei der Übersetzung muss der Programmierer lediglich die (statische) Lua- und die Mathematik-Bibliothek hinzulinken. Sofern er die Lua-Bibliotheken nutzt, kommt noch die »libdl« hinzu.

Abbildung 1: Bei der Übersetzung muss der Programmierer lediglich die (statische) Lua- und die Mathematik-Bibliothek hinzulinken. Sofern er die Lua-Bibliotheken nutzt, kommt noch die »libdl« hinzu.

Stapelweise

Die Kommunikation zwischen Lua-Skript und C-Programm erfolgt über einen Stack. Möchte der C-Entwickler eine Funktion in einem Lua-Skript aufrufen, legt er zunächst den Namen der Lua-Funktion auf den Stack und packt dann nacheinander die von der Funktion benötigten Parameter obendrauf. Anschließend bittet der Entwickler den Lua-Interpreter um die Ausführung. Listing 2 zeigt dafür ein Beispiel: Es ruft die im Lua-Skript »plus.lua« definierte Funktion »plus()« auf, die einfach zwei Zahlen addiert.

Listing 2

Neuen Interpreter anlegen

01 #include <stdio.h>
02 #include "lua.h"
03 #include "lauxlib.h"
04 #include "lualib.h"
05
06 int main (int argc, char *argv[])
07 {
08    lua_State *mystate = luaL_newstate(); // Neuer Interpreter
09    luaL_openlibs(mystate); // Bibliotheken laden
10    luaL_dofile(mystate, "./plus.lua"); // Skript laden
11
12    lua_getglobal(mystate, "plus"); // Name der Funktion auf den Stack
13   lua_pushinteger(mystate, 7); // Erster Parameter
14   lua_pushinteger(mystate, 5); // Zweiter Parameter
15
16   lua_pcall(mystate, 2, 1, 0); // Funktion ausführen
17   int ergebnis = lua_tointeger(mystate, -1); // Ergebnis vom Stack
18
19   printf("Ergebnis: %d\n", ergebnis);
20   lua_close(mystate);
21 }

Nachdem Zeile 8 einen neuen Interpreter angelegt hat, lädt die Funktion »luaL_dofile()« das Skript. Da das C-Programm die Funktion »plus()« aufrufen möchte, schiebt danach »lua_getglobal()« diesen Namen auf den Stack.

Die Funktion »plus()« erwartet zwei Zahlen. Beide Zahlen packt »lua_pushinteger()« nacheinander auf den Stack, im Beispiel sind das die Zahlen »5« und »7« . Anschließend ruft «lua_pcall()» die Lua-Funktion auf. Dazu benötigt « lua_pcall()» den Lua-State und die Anzahl der auf den Stack geschobenen Werte, im Beispiel waren das die beiden zu addierenden Zahlen.

Rückgabe

Der dritte Parameter von »lua_pcall()« gibt an, wie viele Werte die Lua-Funktion zurückliefert. In Listing 2 ist dies das Ergebnis der Addition und somit genau ein Wert. Sollte bei der Ausführung der Funktion ein Fehler auftreten, schiebt »lua_pcall()« eine Fehlermeldung auf den Stack und liefert einen Fehlercode zurück. Wenn der letzte Wert von »lua_pcall()« die »0« ist, liegt auf dem Stack die Fehlermeldung im Klartext.

Nach der Ausführung der Lua-Funktion »plus()« entfernt »lua_pcall()« die drei vom C-Programm auf den Stack gelegten Werte und wirft das von »plus()« zurückgelieferte Ergebnis auf den Stack. Von dort holt es dann eine entsprechende C-Funktion ab.

Welche dabei zum Einsatz kommt, hängt vom Datentyp ab. In Listing 2 gibt »plus()« eine Ganzzahl zurück, diese liefert die Funktion »lua_tointeger()« vom Stack. Neben »lua_tointeger()« gibt es unter anderem noch »lua_toboolean()« und »lua_tolstring()« . Der zweite Parameter dieser Funktionen gibt immer den Index auf dem Stack an, »-1« bezeichnet das oberste Element.

Global

Über den Stack kommt der Programmierer auch an die Werte der globalen Variablen im Lua-Skript. Dazu genügt ein Aufruf von:

lua_getglobal(mystate, "name");

Anschließend liegt auf dem Stack der Wert der globalen Variablen »name« , wo man ihn mit den bekannten Funktionen abholen kann, bei einem String etwa so:

printf("name enthält den String: %s\n",lua_tostring(mystate, -1));

Auf diese Weise lassen sich übrigens auch Lua-Skripte als Konfigurationsdateien missbrauchen. So genügt ein Lua-Skript mit dem Inhalt:

pfad="/usr/bin"
cache=64

Da dies bereits einem vollständigen Lua-Skript entspricht, muss man es lediglich im C-Programm einlesen und dann über »lua_global()« die Werte der globalen Variablen »pfad« und »cache« auslesen.

Lua ruft C

Funktionsaufrufe gelingen natürlich auch in der anderen Richtung: Wenn ein Lua-Skript eine C-Funktion aufruft, schiebt Lua die von der C-Funktion benötigten Parameter auf den Stack. Die C-Funktion muss sich diese Werte dann vom Stack holen und die von ihr berechneten Ergebnisse wieder auf den Stack packen. Wie viele Ergebniswerte auf dem Stack liegen, verrät die C-Funktion über ihren Rückgabewert.

Listing 3 zeigt das Verfahren an einem kleinen Beispiel: Die C-Funktion »lf_minus()« subtrahiert zwei Zahlen und soll diesen Dienst in Lua-Skripten anbieten. Als einzigen Parameter bekommt sie einen Zeiger auf einen Lua-State übergeben. Mit seiner Hilfe gelangt »lf_minus()« an den Stack. Von ihm holt sie sich mit »lua_tointeger()« die dort von Lua abgelegten Zahlen. »lua_tointeger()« benötigt dazu den Zeiger auf den »lua_State« sowie die Position auf dem Stack.

Listing 3

Zwei Zahlen subtrahieren

01 static int lf_minus(lua_State* s) {
02   int a, b, ergebnis;
03   a=lua_tointeger(s, -2);
04   b=lua_tointeger(s, -1);
05   ergebnis=a-b;
06   lua_pushinteger(s, ergebnis);
07   return 1;
08 }

Der erste Parameter landet zuerst auf dem Stack, dann folgt der zweite und so weiter. Ganz oben auf dem Stack liegt somit der letzte Parameter. Im Beispiel findet sich die erste Zahl somit auf der Position »-2« , die davon zu subtrahierende Zahl an der Position »-1« . Das Ergebnis der Berechnung packt »lf_minus()« mit dem bereits bekannten »lua_pushinteger()« wieder auf den Stack. Dann teilt sie Lua noch über ihren Rückgabewert mit, dass auf dem Stack ein Ergebnis liegt (»return 1;« ). Alle vom Lua-Skript erreichbaren Funktionen müssen sich an dieses Vorgehen halten und die Form »static int name(lua_State*)« besitzen.

Abschließend meldet der Entwickler die neue C-Funktion noch bei Lua an. Das geschieht über »lua_register()« :

lua_register(mystate, "minus", lf_minus);

Damit kennen Lua-Skripte die C-Funktion »lf_minus()« unter dem Namen »minus()« . Die Anweisung

minus(5,3);

im Lua-Skript ruft die Funktion »lf_minus()« im C-Programm auf. Damit das klappt, ist die Funktion »lua_register()« nach »luaL_newstate()« , aber noch vor »luaL_dofile()« aufzurufen:

lua_State *mystate = luaL_newstate();
luaL_openlibs(mystate);
lua_register(mystate, "minus", lf_minus);
luaL_dofile(mystate, "./beispiel.lua");

Die Listings 4 und 5 zeigen den Aufruf noch einmal vollständig in beide Richtungen, einmal (Listing 4) in Lua und einmal (Listing 5) in C.

Listing 5

beispiel.c

01 #include <stdio.h>
02 #include "lua.h"
03 #include "lauxlib.h"
04 #include "lualib.h"
05
06
07 static int lf_minus(lua_State* s) {
08   int a, b;
09   // Werte vom Stack holen:
10   a=lua_tointeger(s, -2);
11   b=lua_tointeger(s, -1);
12   // Ergebnis auf Stack legen:
13   lua_pushinteger(s, a-b);
14   // Auf dem Stack wurde ein Ergeniswert abgelegt:
15   return 1;
16 }
17
18 int main (int argc, char *argv[])
19 {
20   // Lua-Funktion aufrufen:
21   lua_State *mystate = luaL_newstate();
22   // Lua-Bibliotheken laden:
23   luaL_openlibs(mystate);
24
25   // C-Funktion lf_minus() als minus() registrieren:
26   lua_register(mystate, "minus", lf_minus);
27
28   // Skript ausführen:
29   luaL_dofile(mystate, "./beispiel.lua");
30
31   // Funktion "plus" im Lua-Skript aufrufen:
32   lua_getglobal(mystate, "plus");
33   lua_pushinteger(mystate, 7);
34   lua_pushinteger(mystate, 5);
35   lua_pcall(mystate, 2, 1, 0);
36   printf("Addiere im Lua-Skript 7+5 = %d\n", (int) lua_tointeger(mystate, -1));
37
38   // Im Lua-Skript definierte, globale Variablen auslesen:
39   lua_getglobal(mystate, "name");
40   printf("Inhalt der globalen Variable 'name': %s\n",lua_tostring(mystate, -1));
41
42   // Aufräumen:
43   lua_close(mystate);
44 }

Listing 4

beispiel.lua

01 -- Globale Variable:
02 name="klaus"
03
04 -- Addition:
05 function plus(a,b)
06   return a+b
07 end
08
09 -- Rufe minus() aus C-Programm auf,
10 -- berechne 5-3 und gibt Ergebnis aus:
11 e=minus(5,3)
12 print("Subtrahiere im C-Programm 5-3 =")
13 print(e)

Fazit

Dank der serienmäßigen C-Schnittstelle lassen sich Lua-Skripte recht einfach mit C-Programmen mischen. Der Datenaustausch über den Stack ist zwar etwas umständlich, die Arbeit mit dem Stapel geht aber nach kurzer Eingewöhnung recht einfach von der Hand.

Neben den hier vorgestellten Funktionen bietet das C-API noch viele weitere, mit denen sich unter anderem Lua-Tabellen behandeln lassen oder sogar Eingriffe in die Garbage Collection möglich sind. Eine Dokumentation finden Programmierer unter [1] in den Abschnitten “The Application Program Interface” und “The Auxiliary Library”.

Infos

  1. Lua 5.3 Reference Manual: http://www.lua.org/manual/5.3/
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben