Ошибка EXC_BAD_ACCESS при доступе к пользовательским данным Lua в iOS - PullRequest
2 голосов
/ 03 апреля 2012

Я пишу библиотеку iOS для встраивания Lua в игры и столкнулся с проблемой, связанной с пользовательскими данными.Я хочу, чтобы пользователи могли обрабатывать объекты моей библиотеки как обычные таблицы (в сценариях Lua) для установки атрибутов и для того, чтобы эти атрибуты были доступны базовым объектам в библиотеке.Для случаев пользовательский сценарий может иметь

line = display.newLine
line.width = 3

Тогда поле width должно быть доступно из кода библиотеки (Objective 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_State и целочисленные ссылки Lua на его соответствующие пользовательские данные 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, связанному с пользовательскими данными для объекта.

Код 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 и lineNewIndexnewLine я создаю объект C GeminiLine, соответствующий объекту Lua, и сохраняю указатель на него в пользовательских данных Lua.Я также храню указатель на новый объект в одноэлементном объекте Gemini, который предоставляет основной программе доступ к выполнению сценариев 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

Для моей тестовой программы я создал приложение, используя шаблон единого представления.Я изменил метод applicationDidFinishLaunching на AppDelegate для вызова тестового сценария следующим образом:

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

Теперь, когда я запускаю этот код, он выполняется правильно в течение нескольких секунд, распечатывая правильное сообщение и соответствующую ширину строки, но затем происходит сбой с ошибкой EXC_BAD_ACCESS в строке NSLog(@"width = %f", width); метода update.Сначала я подумал, что, возможно, объект line собирался мусором, но метод lineGC регистрирует это, а это не так.Поэтому я убежден, что проблема заключается в том, как я использую значение пользователя в моих пользовательских данных Lua, либо при настройке, либо при доступе.

Может кто-нибудь увидеть ошибку в том, как я это реализовал?

РЕДАКТИРОВАТЬ

Чтобы подтвердить, что мои пользовательские данные не являются сборщиком мусора, я отключил сборщик мусора еще до загрузки скрипта с помощью lua_gc(L, LUA_GCSTOP, 0);.Тем не менее получаю точно такую ​​же проблему.

Я забыл упомянуть ранее, что я использую 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);
}

Ответы [ 2 ]

0 голосов
/ 05 апреля 2012

Я почти уверен, что теперь у меня есть ответ на свой вопрос. Проблема в методе 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 голосов
/ 03 апреля 2012

Я столкнулся с похожими проблемами. Я предполагаю, что менеджер памяти Lua (или менеджер ObjC) освобождает объект. Он работает правильно в течение нескольких секунд, потому что он не будет собирать мусор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...