2016-07-09 11 views
2

Ich versuche Lua Bindungen zu schreiben, so dass man beliebige Funktionen auf einem userdata aufrufen kann. Ein MCV-Beispiel, an dem ich gearbeitet habe, ist unten.Lua: Aufruf von __index Funktion funktioniert dann löst Fehler

Zusammenfassend: Wir haben die C-Funktion newarray an eine Tabelle in der Lua globals geschoben, so dass man ein neues Array-Objekt erstellen kann. Angenommen, das Array ist ein Datenbankeintrag. Ich habe zwei Arten von Operationen, die ich ausführen möchte, nachdem ich sie mit newarray erzeugt habe (für dieses schlechte Beispiel): auf ein Element zugreifen und das Objekt zerstören.

Da ich nicht weiß, wie viele Elemente es gibt (in einem realen Beispiel), entscheide ich mich, __index eine Funktion zu machen und eine if-Anweisung zu verwenden, um zu bestimmen, ob die Funktion "destroy" war oder irgendetwas anderes (dh "gib mir dieses Element"). Wenn es "zerstören" war, lösche das Objekt; Andernfalls geben Sie das angeforderte Element zurück.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <assert.h> 

#include <lua.h> 
#include <lauxlib.h> 
#include <lualib.h> 

#define TEST_METATABLE "_test_mt" 

typedef struct 
{ 
    int* array; 
} array_t; 

int newArray(lua_State* L) 
{ 
    assert(lua_gettop(L) == 0); 
    array_t* array = lua_newuserdata(L, sizeof(array_t)); 
    array->array = malloc(sizeof(int) * 10); 

    for (int i = 0; i < 10; i++) 
     array->array[i] = i; 

    /* Set metatable */ 

    lua_getfield(L, LUA_REGISTRYINDEX, TEST_METATABLE); 
    lua_setmetatable(L, -2); 

    return 1; 
} 

int indexFunc(lua_State* L) 
{ 
    int argc = lua_gettop(L); 
    array_t* array = luaL_checkudata(L, 1, TEST_METATABLE); 
    const char* key = luaL_checkstring(L, 2); 
    int ret = 0; 

    if (!strcmp(key, "destroy")) 
    { 
     if (argc != 2) 
     { 
      lua_settop(L, 0); 
      luaL_error(L, "Invalid arguments"); 
     } 

     if (array->array) 
     { 
      free(array->array); 
      array->array = NULL; 
     } 

     printf("Finished destroy\n"); 

     lua_settop(L, 0); 
    } 
    else 
    { 
     if (argc != 2) 
     { 
      lua_settop(L, 0); 
      luaL_error(L, "Invalid arguments"); 
     } 

     if (lua_tointeger(L, 2)) 
     { 
      lua_pushinteger(L, array->array[lua_tointeger(L, 2)]); 
     } 
     else 
     { 
      lua_settop(L, 0); 
      luaL_error(L, "Bad index supplied"); 
     } 

     lua_remove(L, 2); 
     lua_remove(L, 1); 
     ret = 1; 
    } 

    return ret; 
} 

int luaopen_TestArray(lua_State* L) 
{ 
    /* Set up metatable */ 

    lua_newtable(L); 

    lua_pushliteral(L, "__index"); 
    lua_pushcfunction(L, indexFunc); 
    lua_settable(L, -3); 

    lua_setfield(L, LUA_REGISTRYINDEX, TEST_METATABLE); 

    /* Set up 'static' stuff */ 

    lua_newtable(L); 

    lua_pushliteral(L, "newarray"); 
    lua_pushcfunction(L, newArray); 
    lua_settable(L, -3); 

    lua_setglobal(L, "TestArray"); 

    return 0; 
} 

ich mit kompiliert:

gcc -std=c99 -Wall -fPIC -shared -o TestArray.so test.c -llua 

Der Lua-Testprogramm wird wie folgt:

require("TestArray") 

a = TestArray.newarray() 

print(a[5]) 

a:destroy() 

Der Ausgang:

$ lua test.lua 
5 
Finished destroy 
lua: test.lua:7: attempt to call method 'destroy' (a nil value) 
stack traceback: 
     test.lua:7: in main chunk 
     [C]: ? 
$ 

Das Gleiche gilt für Lua, was es soll durch Abrufen des v alue (in Bezug auf C) und Drucken es (wie es sicherlich durch indexFunc). Dann fährt es fort, den destroy-spezifischen Code in indexFunc, auszuführen, dann versucht nach einer Funktion zu suchen, die destroy genannt wird, und ich habe keine Idee warum. Es hat das 0metamethod __index gefunden, also verstehe ich nicht, warum es an anderer Stelle danach aussah. Warum tut es das und was mache ich falsch?

Lua-Version: 5.1.4.

+2

'__index' metamethod sollte nur den Wert für den Schlüssel" destroy "abrufen, das heißt," indexFunc "muss nur einen Wert zurückgeben (Funktion" destroy "), ohne diese Funktion auszuführen. Destruktor sollte als separate Funktion 'int destroyFunc (lua_State * L)' implementiert werden. Schnelle Lösung: Ersetzen Sie einfach 'a: destroy()' durch 'local _ = a.destroy' :-) –

+0

@EgorSriptounoff Ohh, ich verstehe, also liegt es an den Klammern nach 'destroy', die im Grunde bedeuten:" rufen Sie das Ergebnis von "zerstören" als eine Funktion abrufen "? Wenn Sie es als Antwort einreichen möchten, werde ich akzeptieren, weil AFAIK Sie nicht einen verdienten Ruf von einem Kommentar erhalten. :) – Doddy

Antwort

1

__index wird voraussichtlich einen Wert zurückgeben. Deines nicht.

Insbesondere dann, wenn Sie schreiben:

a:destroy() 

Das äquivalent ist:

getmetatable(a).__index(a, "destroy")(a) 

das heißt die __index metamethod nennen, dann nennen, was es gibt sie a als Argument übergeben.

Aber wenn wir an Ihrer __index Umsetzung aussehen, ist es nicht, diesen Vertrag respektieren:

int indexFunc(lua_State* L) 
{ 
    int argc = lua_gettop(L); 
    array_t* array = luaL_checkudata(L, 1, TEST_METATABLE); 
    const char* key = luaL_checkstring(L, 2); 
    int ret = 0; 

    if (!strcmp(key, "destroy")) 
    { 
    /* ... delete the array ... */ 
    lua_settop(L, 0); 
    } 
    else 
    { 
    /* ... push the value ... */ 
    } 

    return ret; /* since key == "destroy", ret == 0 here */ 
} 

Wenn der Schlüssel "destroy" ist, ist es nicht eine Funktion zurückgeben; stattdessen zerstört es das Array sofort und gibt nichts zurück, was in diesem Fall gleichbedeutend mit dem Zurückgeben von Nil ist. Dann versucht der Lua-Code, das zurückgegebene Nil aufzurufen und zu explodieren.

Stattdessen müssen Sie eine separate Funktion, die die Zerstörung, z.

int destroyFunc(lua_State * L) { 
    array_t array = luaL_checkudata(L, 1, TEST_METATABLE); 
    free(array->array); 
    array->array = NULL; 
    return 0; 
} 

Und dann __index Rückkehr haben, die eher funktionieren als Aufruf:

lua_pushcfunction(L, destroyFunc); 
return 1; 

an welchem ​​Punkt der Lua Code in der Lage sein wird, diese Funktion aufzurufen.