我正在寫一個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;
}
這裏的關鍵方法newLine
和lineNewIndex
。在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
對於我的測試程序,我創建了一個應用程序使用單個視圖模板。我修改對AppDelegate
的applicationDidFinishLaunching
方法來調用一個測試腳本如下:
-(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);
}
您是否嘗試啓用殭屍? – 2012-04-05 01:44:42