2012-04-03 27 views
2

我正在寫一個iOS庫來在遊戲中嵌入Lua,並且遇到了有關userdata的問題。我希望用戶能夠將我的庫對象視爲普通表(在Lua腳本中)來設置屬性,並使這些屬性可供庫中的基礎對象使用。對於情況下,用戶的腳本可具有當訪問iOS中的Lua用戶數據時出現EXC_BAD_ACCESS錯誤

line = display.newLine 
line.width = 3 

然後width字段應該是從庫中(目標C/C)代碼內訪問。

我有這個工作,有點,但運行幾秒鐘後,我得到一個EXC_BAD_ACCESS錯誤,所以顯然我訪問一個釋放的對象或有一些其他類型的內存損壞,但我似乎無法圖爲什麼。

我已經將我的代碼修剪成僅僅一個例子來重現錯誤。首先,我有一個實現庫功能的基礎Objective C對象。頭如下所示:

#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 

該類保持到它的對應的Lua用戶數據和uservalue(與用戶數據相關聯的表)至lua_State對象和整數的Lua引用的參考。參考propertyTableRef用於訪問對象屬性(用戶值表)。

的實現如下:

#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 

這裏唯一的非生命週期方法是getDoubleForKey方法,其中訪問與用戶數據的對象相關聯的的Lua uservalue。

C代碼綁定對象到Lua這裏給出:

///////////// 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; 
} 

這裏的關鍵方法newLinelineNewIndex。在newLine中,我創建了與Lua對象相對應的目標C GeminiLine,並在Lua userdata中存儲了一個指向它的指針。我還將一個指向新對象的指針存儲在一個單例對象中,這是一個爲主程序提供執行Lua腳本訪問權限的對象。這個類在這裏給出:

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 

對於我的測試程序,我創建了一個應用程序使用單個視圖模板。我修改對AppDelegateapplicationDidFinishLaunching方法來調用一個測試腳本如下:

-(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]; 
.... 

我也包括一個觸發每秒100次和update方法作爲其目標的定時器。方法update檢索在lua腳本中設置的屬性width,並將其記錄爲NSLog

test.lua腳本我現在用的就是如下:

display = require('display') 

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

現在,當我運行這段代碼,它正確地執行了幾秒鐘,打印出正確的信息和相應的線寬,但隨後失敗在update方法的NSLog(@"width = %f", width);行上出現EXC_BAD_ACCESS錯誤。起初,我想也許line對象正在垃圾收集,但lineGC方法會記錄這一點,它不會。所以我確信問題在於我使用我的Lua userdata的用戶值,無論是在設置還是訪問中。

任何人都可以看到一個錯誤,我已經實現了這一點?

編輯

要確認我的用戶數據不被垃圾收集,我甚至加載使用lua_gc(L, LUA_GCSTOP, 0);腳本之前禁用GC。仍然有完全相同的問題。

我忘記提前說我正在使用Lua 5.2。

打開使用「編輯計劃」每一個內存調試標誌指示錯誤在以下Lua代碼基礎功能上發生的呼叫setsvalue2s,這實際上是一個宏:

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

您是否嘗試啓用殭屍? – 2012-04-05 01:44:42

回答

0

我很確定我現在有自己的問題的答案。問題是在getDoubleForKey方法:

-(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); 
} 

我是新來的Lua,並沒有意識到我需要做這樣的電話後,清空堆棧。當我的庫函數被Lua調用時,沒有必要,但是在這裏我正在進行調用,所以Lua不會將我保釋出來。

我發現這樣做的方法是在方法頂部打印堆棧大小,並在每次調用時都看到堆棧大小增加。最終,堆棧變得如此之大以至於發生了不好的事情。解決方法是在退出該方法之前清空堆棧:

-(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

我已經遇到類似的問題。我的猜測是,Lua內存管理器(或ObjC管理器)正在釋放該對象。它可以正常工作幾秒鐘,因爲它不會被垃圾收集。

+0

我想過,但正如我上面提到的,我已經定義了__gc方法的對象,這永遠不會被調用。 – James 2012-04-03 17:41:22

相關問題