Skip to main content
GameDev.net gamedev.net
🔒 Locked

Lua problem

Started by Eldritch Dec 12, 2008 at 2:49 PM 2 replies 2k views
Original Post
Eldritch
Eldritch
I am using the following function to call a Lua function from C++. It was taken from the official Lua site (somewhere in the manual, cannot say where exactly). Whenever I call it, it crashes on the marked lines.

bool ScriptManager::CallFunc(const std::string& sFuncName, const std::string& sFuncSignature, ...)
{
	/*
	note:

	Because the function may return strings, call_va cannot pop the results from the stack.
	It is up to the caller to pop them, after it finishes using occasional string results
	(or after copying them to other buffers). 
	*/

	va_list args;
	int nArg, nRes;
	const char* func = sFuncName.c_str();
	const char* sig = sFuncSignature.c_str();

	va_start(args, sig);
	
	// Get Lua function.
	lua_getglobal(m_pLua, func);

	// Push arguments.
	nArg = 0;
	while (*sig)
	{
		switch (*sig++)
		{
		case 'd':
			// Number type (double).
			lua_pushnumber(m_pLua, va_arg(args, double));
			break;

		case 'i':
			// Number type (int).
			lua_pushnumber(m_pLua, va_arg(args, int));
			break;

		case 's':
			// String type.
			lua_pushstring(m_pLua, va_arg(args, char*));
			break;

		case 'b':
			// Boolean type.
			lua_pushboolean(m_pLua, va_arg(args, bool));
			break;

		// TODO: 'c' for user data (classes)

		case ':':
			// No arguments.
			goto _end_while;

		default:
			return Logger::Error(L"Attempting to push invalid argument to Lua function: %c.", *(sig - 1));
		}
		nArg++;
		luaL_checkstack(m_pLua, 1, "too many arguments!");
	}

_end_while:

	// Calculate number of expected results.
	nRes = (int)strlen(sig);

	// Call the function.
	if (lua_pcall(m_pLua, nArg, nRes, 0) != 0)
	{
		return Logger::Error(L"Failed to run Lua function %s: %s.", func, lua_tostring(m_pLua, -1));
	}

	// Stack index of the first result.
	nRes = -nRes; 

	// Get results.
	while (*sig)
	{
		switch (*sig++)
		{
		case 'd':
			// Number type (double).
			if (!lua_isnumber(m_pLua, nRes))
			{
				return Logger::Error(L"Wrong return type from CallFunc! Expected 'double'.");
			}
			*va_arg(args, double*) = lua_tonumber(m_pLua, nRes); // <--- here
			break;

		case 'i':
			{
			// Number type (int).
			if (!lua_isnumber(m_pLua, nRes))
			{
				return Logger::Error(L"Wrong return type from CallFunc! Expected 'int'.");
			}
			*va_arg(args, int*) = (int)lua_tonumber(m_pLua, nRes);  // <--- here
			}
			break;

		case 's':
			// String type.
			if (!lua_isstring(m_pLua, nRes))
			{
				return Logger::Error(L"Wrong return type from CallFunc! Expected 'string'.");
			}
			*va_arg(args, const char**) = lua_tostring(m_pLua, nRes);  // <--- here
			break;

		case 'b':
			// Boolean type.
			if (!lua_isboolean(m_pLua, nRes))
			{
				return Logger::Error(L"Wrong return type from CallFunc! Expected 'bool'.");
			}
			*va_arg(args, bool*) = lua_toboolean(m_pLua, nRes) == 0 ? false : true;  // <--- here
			break;

		default:
			return Logger::Error(L"Attempting to get invalid result type from Lua function: %c.", *(sig - 1));
		}
		nRes++;
	}

	va_end(args);

	return true;
}

The debugger say the following: First-chance exception at 0x00415d35 in TestSuite.exe: 0xC0000005: Access violation writing location 0xcccccccc. Unhandled exception at 0x00415d35 in TestSuite.exe: 0xC0000005: Access violation writing location 0xcccccccc. The function is called like this:

int res = 0;
scriptmgr->CallFunction("testfunc", ":i", &res);

scriptmgr is valid, so don't worry about that.
ddn3
ddn3
There are several things possibly going on. First, the code doesn't pop errors or return values from the stack, that's because as mentioned in the chapter, its a C library and doesn't directly manage the strings. You'll have to manually pop the elements off the stack, or implement a better return value scheme than using arg-lists. Otherwise you'll get a stack overflow eventually from using this function.

Also you'll need to pop the error string from the stack if the script execution fails, in the line,

// Call the function.	if (lua_pcall(m_pLua, nArg, nRes, 0) != 0)	{		return Logger::Error(L"Failed to run Lua function %s: %s.", func, lua_tostring(m_pLua, -1));	}


should look something like this
// Call the function.	if (lua_pcall(m_pLua, nArg, nRes, 0) != 0)	{        const char* msg = lua_tostring(L, -1);	if (msg == NULL) msg = "(error with no message)";	lua_pop(L, 1);	return Logger::Error(L"Failed to run Lua function %s: %s.", func, msg);	}


I suggest using a more advance binding library like MLuabind, Luabind or toLua. Doing raw function calls like this is pretty errror prone and once you start moving to more advance data structures like biniding classes and structs, this method becomes more trouble than its worth.

Good Luck!

-ddn
Eldritch
Eldritch
I know all that, but that is not the current problem. I cannot even get any value because it fails to write to the target argument for some reason. Since there is no error and no return value is even read, then I don't have anything to pop. I wanted to solve the pop'ing thing later.
ddn3
ddn3
The code works as is just fine in the original. In the original, if there is an error it bombs out and exits the execution at that point.

I have nearly the same code in another project ( I've not yet ported Luabind too it yet, so im using low level calls ). Here is what I do.

//error handler catches and dumps callstack upon errorint luaErrorHandler(lua_State *L) 	{		lua_getfield(L, LUA_GLOBALSINDEX, "debug");		if (!lua_istable(L, -1)) {			lua_pop(L, 1);			return 1;		}		lua_getfield(L, -1, "traceback");		if (!lua_isfunction(L, -1)) {			lua_pop(L, 2);			return 1;		}		lua_pushvalue(L, 1);		lua_pushinteger(L, 0);		lua_call(L, 2, 1);		return 1;	}	//exracts Lua error string on the stack and outputs them	int report (lua_State *L, int status) 	{		const char *msg;				if (status) 		{			msg = lua_tostring(L, -1);			if (msg == NULL) msg = "(error with no message)";			NSLog(@"status=%d, %s\n", status, msg);			lua_pop(L, 1);		}		return status;	}		//taken from Programming in  Lua 24.1    void error (lua_State *L, const char *fmt, ...) {		va_list argp;		va_start(argp, fmt);		//vfprintf(stderr, argp);		va_end(argp);		lua_close(L);		exit(EXIT_FAILURE);    }		int safe_call(lua_State*luaState, int nParams, int nReturn)	{		int size0 = lua_gettop(luaState);		int error_index = lua_gettop(luaState) - nParams;				//push error handler onto stack..		lua_pushcfunction(luaState, luaErrorHandler);		lua_insert(luaState, error_index);				//call the function		int status = lua_pcall(luaState, nParams, nReturn, error_index);				//report any errors		if (status!=0)		{			report(luaState,status);		}				//pop the error function		lua_remove(luaState, error_index);				return status;	}		//calls a function in Lua with arbitary paramters ( taken from Programming in  Lua 25.3 )	//ex: call_va("f", "dd>d", x, y, &z);	void call_va (lua_State* L, const char *func, const char *sig, ...) {		va_list vl;		int narg, nres;  /* number of arguments and results */				va_start(vl, sig);		lua_getglobal(L, func);  /* get function */				/* push arguments */		narg = 0;		while (*sig) {  /* push arguments */			switch (*sig++) {									case 'd':  /* double argument */					lua_pushnumber(L, va_arg(vl, double));					break;									case 'i':  /* int argument */					lua_pushnumber(L, va_arg(vl, int));					break;									case 's':  /* string argument */					lua_pushstring(L, va_arg(vl, char *));					break;									case '>':					goto endwhile;									default:					error(L, "invalid option (%c)", *(sig - 1));			}			narg++;			luaL_checkstack(L, 1, "too many arguments");		} endwhile:				/* do the call */		nres = strlen(sig);  /* number of expected results */				if (safe_call(L, narg, nres) != 0)  /* do the call */		{			NSLog(@"error running functions  %s",func);			return;		}				/* retrieve results */		nres = -nres;  /* stack index of first result */		while (*sig) {  /* get results */			switch (*sig++) {									case 'd':  /* double result */					if (!lua_isnumber(L, nres))						error(L, "wrong result type");					*va_arg(vl, double *) = lua_tonumber(L, nres);					break;									case 'i':  /* int result */					if (!lua_isnumber(L, nres))						error(L, "wrong result type");					*va_arg(vl, int *) = (int)lua_tonumber(L, nres);					break;									case 's':  /* string result */					if (!lua_isstring(L, nres))						error(L, "wrong result type");					*va_arg(vl, const char **) = lua_tostring(L, nres);					break;									default:					error(L, "invalid option (%c)", *(sig - 1));			}			nres++;		}		va_end(vl);    }	


Several things to note, when it encounters an error it will dump out both error and the associated call stack. Also it will not try to extract params from a failed call, which will result in the error your getting. Also I'm not using this code to return any values from Lua so I'm not bothering to clean up the stack, which was mentioned before, but I did verify that the code does properly return values from Lua.

Good Luck!

-ddn

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.