2012-04-03 17 views
2

Ich schreibe eine iOS-Bibliothek, um Lua in Spiele einzubetten und habe ein Problem in Bezug auf Benutzerdaten festgestellt. Ich möchte, dass Benutzer meine Bibliotheksobjekte als normale Tabellen (in Lua-Skripten) behandeln können, um Attribute festzulegen und diese Attribute für die Basisobjekte in der Bibliothek verfügbar zu machen. Für Fälle kann ein Benutzer SkriptEXC_BAD_ACCESS Fehler beim Zugriff auf Lua-Benutzerdaten in iOS

line = display.newLine 
line.width = 3 

Dann wird das width Feld aus der Bibliothek (Objective-C/C) Code zugänglich sein sollte.

Ich habe dies funktioniert, eine Art, aber nach ein paar Sekunden ausgeführt bekomme ich einen EXC_BAD_ACCESS Fehler, so offensichtlich ich auf ein freigegebenes Objekt zugreifen oder eine andere Art von Speicherbeschädigung haben, aber ich kann nicht scheinen scheinen Warum?

Ich habe meinen Code auf nur ein Beispiel reduziert, um den Fehler zu reproduzieren. Zuerst habe ich ein Objective-C-Basisobjekt, das die Bibliotheksfunktion implementiert. Der Header ist unten gezeigt:

#import "lua.h" 
#include "lualib.h" 
#include "lauxlib.h" 


@interface GeminiLine : NSObject { 
    int selfRef; 
    int propertyTableRef; 
    lua_State *L; 
} 

@property (nonatomic) int propertyTableRef; 

-(id)initWithLuaState:(lua_State *)luaStat; 
-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt; 

@end 

Diese Klasse hält eine Referenz auf das Objekt und ganzzahligen lua_State Lua Verweise auf es Lua Benutzerdaten und entsprechende Uservalue (die Tabellen mit den Benutzerdaten verbunden ist). Die Referenz propertyTableRef wird verwendet, um auf die Objektattribute zuzugreifen (Benutzerwerttabelle).

Die Implementierung ist unten angegeben:

#import "GeminiLine.h" 

@implementation GeminiLine 

@synthesize propertyTableRef; 

-(id)initWithLuaState:(lua_State *)luaState { 
    self = [super init]; 
    if (self) { 
     L = luaState; 
    } 

    return self; 

} 

-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt { 

    lua_rawgeti(L, LUA_REGISTRYINDEX, propertyTableRef); 
    //lua_pushstring(L, key); 
    //lua_gettable(L, -2); 
    lua_getfield(L, -1, key); 
    if (lua_isnil(L, -1)) { 
     return dflt; 
    } 
    return lua_tonumber(L, -1); 
} 

-(void)dealloc { 
    luaL_unref(L, LUA_REGISTRYINDEX, propertyTableRef); 
    [super dealloc]; 
} 

@end 

Der einzige Nicht-Lifecycle-Methode ist hier die getDoubleForKey Methode, die die Lua Uservalue mit dem Benutzerdaten für das Objekt zugeordnet zugreift.

Der C-Code das Objekt Lua binden die hier angegeben werden:

hier
///////////// lines /////////////////////////// 
static int newLine(lua_State *L){ 
    NSLog(@"Creating new line..."); 
    GeminiLine *line = [[GeminiLine alloc] initWithLuaState:L]; 
    GeminiLine **lLine = (GeminiLine **)lua_newuserdata(L, sizeof(GeminiLine *)); 
    *lLine = line; 

    [Gemini shared].line = line; 

    luaL_getmetatable(L, GEMINI_LINE_LUA_KEY); 
    lua_setmetatable(L, -2); 

    // append a lua table to this user data to allow the user to store values in it 
    lua_newtable(L); 
    lua_pushvalue(L, -1); // make a copy of the table becaue the next line pops the top value 
    // store a reference to this table so we can access it later 
    line.propertyTableRef = luaL_ref(L, LUA_REGISTRYINDEX); 
    // set the table as the user value for the Lua object 
    lua_setuservalue(L, -2); 

    NSLog(@"New line created."); 

    return 1; 
} 

static int lineGC (lua_State *L){ 
    NSLog(@"lineGC called"); 
    GeminiLine **line = (GeminiLine **)luaL_checkudata(L, 1, GEMINI_LINE_LUA_KEY); 

    [*line release]; 

    return 0; 
} 

static int lineIndex(lua_State* L) 
{ 
    NSLog(@"Calling lineIndex()"); 
    /* object, key */ 
    /* first check the environment */ 
    lua_getuservalue(L, -2); 
    if(lua_isnil(L,-1)){ 
     NSLog(@"user value for user data is nil"); 
    } 
    lua_pushvalue(L, -2); 

    lua_rawget(L, -2); 
    if(lua_isnoneornil(L, -1) == 0) 
    { 
     return 1; 
    } 

    lua_pop(L, 2); 

    /* second check the metatable */  
    lua_getmetatable(L, -2); 
    lua_pushvalue(L, -2); 
    lua_rawget(L, -2); 

    /* nil or otherwise, we return 1 here */ 
    return 1; 
} 

// this function gets called with the table on the bottom of the stack, the index to assign to next, 
// and the value to be assigned on top 
static int lineNewIndex(lua_State* L) 
    { 
    NSLog(@"Calling lineNewIndex()"); 
    int top = lua_gettop(L); 
    NSLog(@"stack has %d values", top); 
    lua_getuservalue(L, -3); 
    /* object, key, value */ 
    lua_pushvalue(L, -3); 
    lua_pushvalue(L,-3); 
    lua_rawset(L, -3); 

    NSLog(@"Finished lineNewIndex()"); 

    return 0; 
} 


// the mappings for the library functions 
static const struct luaL_Reg displayLib_f [] = { 
    {"newLine", newLine}, 
    {NULL, NULL} 
}; 

// mappings for the line methods 
static const struct luaL_Reg line_m [] = { 
    {"__gc", lineGC}, 
    {"__index", lineIndex}, 
    {"__newindex", lineNewIndex}, 
    {NULL, NULL} 
}; 


int luaopen_display_lib (lua_State *L){ 
    // create meta tables for our various types ///////// 

    // lines 
    luaL_newmetatable(L, GEMINI_LINE_LUA_KEY); 
    lua_pushvalue(L, -1); 
    luaL_setfuncs(L, line_m, 0); 


    /////// finished with metatables /////////// 

    // create the table for this library and popuplate it with our functions 
    luaL_newlib(L, displayLib_f); 

    return 1; 
} 

Die wichtigsten Methoden sind newLine und lineNewIndex. In newLine erstelle ich das objektive C GeminiLine Objekt, das dem Lua-Objekt entspricht, und speichere einen Zeiger darauf in Lua userdata. Ich speichere auch einen Zeiger auf das neue Objekt in einem Singleton Gemini Objekt, das das Hauptprogramm mit Zugriff auf die Ausführung von Lua-Skripts ist. Diese Klasse ist hier angegeben:

Gemini *singleton = nil; 

@interface Gemini() { 
@private 
    lua_State *L; 
} 
@end 


@implementation Gemini 

@synthesize line; 

- (id)init 
{ 

    self = [super init]; 
    if (self) { 
     L = luaL_newstate(); 
     luaL_openlibs(L); 

    } 

    return self; 
} 

+(Gemini *)shared { 

    if (singleton == nil) { 
     singleton = [[Gemini alloc] init]; 
    } 

    return singleton; 
} 


-(void)execute:(NSString *)filename { 
    int err; 

lua_settop(L, 0); 

    NSString *luaFilePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"lua"]; 

    setLuaPath(L, [luaFilePath stringByDeletingLastPathComponent]); 

    err = luaL_loadfile(L, [luaFilePath cStringUsingEncoding:[NSString defaultCStringEncoding]]); 

if (0 != err) { 
     luaL_error(L, "cannot compile lua file: %s", 
     lua_tostring(L, -1)); 
     return; 
} 


    err = lua_pcall(L, 0, 0, 0); 
if (0 != err) { 
     luaL_error(L, "cannot run lua file: %s", 
     lua_tostring(L, -1)); 
     return; 
} 

} 
@end 

Für mein Testprogramm habe ich eine Anwendung mit der Single-View-Vorlage erstellt. Ich veränderte die applicationDidFinishLaunching Methode auf den AppDelegate ein Testskript zu nennen wie folgt:

-(void) update { 

    double width = [[Gemini shared].line getDoubleForKey:"width" withDefault:5.0]; 
    NSLog(@"width = %f", width); 
} 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 
    // Override point for customization after application launch. 

    [[Gemini shared] execute:@"test"]; 

    timer = [NSTimer scheduledTimerWithTimeInterval:0.01 
           target:self 
           selector:@selector(update) 
           userInfo:nil 
           repeats:YES]; 
.... 

Ich habe auch einen Timer enthalten, die 100-mal pro Sekunde und eine update Methode als Ziel abfeuert. Die update-Methode ruft das Attribut ab, das im lua-Skript width festgelegt ist, und protokolliert es mit .

Das test.lua Skript I unten bin mit gegeben:

display = require('display') 

line = display.newLine() 
line.width = 3; 

Nun, wenn ich diesen Code ausführen, ist es richtig für einige Sekunden ausgeführt wird, die richtige Nachricht und entsprechende Linienbreite Druck, aber es scheitert dann mit ein EXC_BAD_ACCESS-Fehler in der NSLog(@"width = %f", width); Zeile der update-Methode.Zuerst dachte ich, dass vielleicht das line Objekt Müll gesammelt wurde, aber die lineGC Methode würde dies protokollieren und es tut es nicht. Ich bin davon überzeugt, dass das Problem in der Art und Weise liegt, wie ich den Benutzerwert meiner Lua-Benutzerdaten verwende, entweder beim Setup oder beim Zugriff.

Kann jemand einen Fehler in der Art, wie ich das implementiert habe, sehen?

EDIT

Um zu bestätigen, dass meine Benutzerdaten nicht wird Müll gesammelt, ich die GC noch bevor das Laden des Skripts lua_gc(L, LUA_GCSTOP, 0); deaktiviert. Immer noch genau das gleiche Problem.

Ich habe vergessen zu erwähnen, dass ich Lua 5.2 verwende.

auf jedem Speicher-Debugging-Flag Drehen „Edit Schema“ verwendet zeigt an, dass der Fehler in der folgenden Lua Code-Basis-Funktion auf den Aufruf von setsvalue2s, das ist eigentlich ein Makro passiert:

LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { 
    StkId t; 
    lua_lock(L); 
    t = index2addr(L, idx); 
    api_checkvalidindex(L, t); 
    setsvalue2s(L, L->top, luaS_new(L, k)); 
    api_incr_top(L); 
    luaV_gettable(L, t, L->top - 1, L->top - 1); 
    lua_unlock(L); 
} 
+0

Haben Sie versucht, Zombie zu aktivieren? –

Antwort

0

Ich bin mir ziemlich sicher, dass ich jetzt die Antwort auf meine eigene Frage habe. Das Problem ist in der getDoubleForKey Methode:

-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt { 

    lua_rawgeti(L, LUA_REGISTRYINDEX, propertyTableRef); 
    //lua_pushstring(L, key); 
    //lua_gettable(L, -2); 
    lua_getfield(L, -1, key); 
    if (lua_isnil(L, -1)) { 
     return dflt; 
    } 
    return lua_tonumber(L, -1); 
} 

ich Lua bin neu und hatte nicht bemerkt, dass ich nach dem Telefonieren wie diese den Stapel leeren benötigt. Wenn meine Bibliotheksfunktionen von Lua aufgerufen werden, ist das nicht nötig, aber hier mache ich den Anruf, damit Lua mir nicht aus dem Weg geht.

Ich fand das heraus, indem ich die Stapelgröße an der Oberseite der Methode druckte und sah, dass sie mit jedem Anruf anstieg. Schließlich wurde der Stapel so groß, dass schlimme Dinge passierten. Die Lösung ist, den Stapel vor dem Beenden der Methode zu leeren:

-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt { 

    lua_rawgeti(L, LUA_REGISTRYINDEX, propertyTableRef); 
    lua_getfield(L, -1, key); 
    if (lua_isnil(L, -1)) { 
     lua_pop(L,2); 
     return dflt; 
    } 
    double rval = lua_tonumber(L, -1); 

    lua_pop(L, 2); 

    return rval; 
} 
0

Ich habe auf ähnliche Probleme stoßen. Meine Vermutung ist, dass der Lua-Speichermanager (oder ObjC-Manager) das Objekt freigibt. Es funktioniert für ein paar Sekunden richtig, weil es nicht Müll gesammelt worden ist.

+0

Ich dachte darüber nach, aber wie ich oben erwähnt habe, habe ich die __gc-Methode für das Objekt definiert und dies wird nie aufgerufen. – James

Verwandte Themen