Click here to Skip to main content
15,895,833 members
Articles / Programming Languages / Lua

Extending a C++ Application with Lua 5.2

Rate me:
Please Sign up or sign in to vote.
4.69/5 (15 votes)
4 Aug 2013CPOL13 min read 56.9K   1.5K   66  
Using a subset of the available Lua 5.2 C interface to extend a C++ application with Lua.
#include "stdafx.h"

#include <iostream>
#include <string.h>
#include <stdarg.h>

using namespace std;

#include "UtilityFunctions.h"

//-------    Helper functions for Lua table variables
// The following functions are used to push table entries onto
// the stack so a table can be created.
// keys are of two types, integer keys being numbers starting with 1 or
// string keys being strings for an association between the key and some value.

void LuaSimpleWrapper::l_pushtablestring(lua_State *lua, char* key , char* value) {
    lua_pushstring(lua, key);
	// standard char zero terminated string so Lua can figure out the size
    lua_pushstring(lua, value);
    lua_settable(lua, -3);
}

void LuaSimpleWrapper::l_pushtablestringW(lua_State *lua, char* key , wchar_t *value) {
	size_t iSize = 0;
	for (iSize = 0; value[iSize]; iSize++);
	iSize++;   // include the end of string terminator
	iSize *= sizeof(wchar_t);  // change from number of characters to number of bytes
    lua_pushstring(lua, key);
	// non-standard multi-byte string so we must specify the size of the string of bytes
    lua_pushlstring(lua, (const char *)value, iSize);
    lua_settable(lua, -3);
}

void LuaSimpleWrapper::l_pushtablenumber(lua_State *lua, char* key, double value) {
    lua_pushstring(lua, key);
    lua_pushnumber(lua, value);
    lua_settable(lua, -3);
}

void LuaSimpleWrapper::l_pushtableinteger(lua_State *lua, char* key, lua_Integer value) {
    lua_pushstring(lua, key);
    lua_pushinteger(lua, value);
    lua_settable(lua, -3);
}

void LuaSimpleWrapper::l_pushtablestring(lua_State *lua, lua_Integer key, char* value) {
    lua_pushinteger(lua, key);
    lua_pushstring(lua, value);
    lua_settable(lua, -3);
}

void LuaSimpleWrapper::l_pushtablefunctionCharKey(lua_State *lua, char* key, lua_CFunction value) {
    lua_pushstring(lua, key);
	lua_pushcclosure (lua, value, 0);
    lua_settable(lua, -3);
}

int LuaSimpleWrapper::PutObjectFrameData (const lua_Integer objectindex, const lua_Integer frameindex, const char *aszTableName)
{
	m_ListOfObjects[objectindex]->m_ListOfFrames[frameindex].iValue = (int)frameindex;
	strcpy_s (m_ListOfObjects[objectindex]->m_ListOfFrames[frameindex].aszName, sizeof(m_ListOfObjects[objectindex]->m_ListOfFrames[frameindex].aszName), aszTableName);

	return 0;
}


//------------------------------------------------------

extern "C" {
	static int  ParserLuaCreateGlobalFrame (lua_State *luaState = 0);
};

LuaSimpleWrapper::LuaSimpleWrapper (bool newState) : m_luaState(0), m_iStartNewStateRetStatus(0), m_lastState(LuaNewInit)
{
	m_lastLuaError[0] = 0;
	m_MyObjectCount = LuaSimpleWrapper::m_ObjectCount++;
	LuaSimpleWrapper::m_ListOfObjects[m_MyObjectCount] = this;
	if (newState) {
		StartNewState (true);
	}
}

LuaSimpleWrapper::~LuaSimpleWrapper ()
{
	if (m_luaState) {
		lua_close(m_luaState);
		m_luaState = 0;
	}
}

int LuaSimpleWrapper::StartNewState (bool openLibs)
{
	// initialize the lua interpreter for this thread
	// we could use lua_State *L = luaL_newstate(); instead
	// however with lua_newstate() we have additional options.
	// when we are done we will use lua_close()
	if (m_luaState == 0) {
		m_luaState = lua_newstate(ParserLuaAlloc, 0);
		if (m_luaState) {
			m_iStartNewStateRetStatus = 1;
			if (openLibs) {
				m_iStartNewStateRetStatus = 2;
				// open up the standard library for use
				luaL_openlibs (m_luaState);
			}
		}
	}

	return m_iStartNewStateRetStatus;
}

int LuaSimpleWrapper::LoadLuaFile (char *aszFilePath)
{
	int iRetStatus = 0;

	if (m_luaState) {
		// should return 0 if successful otherwise will return the following:
		//   LUA_ERRFILE  unable to open the file from luaL_loadfile()
		//   LUA_ERRSYNTAX  syntax error in the lua code in the file from lua_load()
		//   LUA_ERRMEM  if there is a memory allocation error from lua_load()
		// the loadfile function loads a lua script chunk or section of source
		// into the lua script engine along with any other pieces that are there.
		// it does not execute any script, it only load the chunk.
		int iStatus = luaL_loadfile(m_luaState, aszFilePath);
		if (iStatus != 0) {
			strncpy_s (m_lastLuaError, sizeof(m_lastLuaError), lua_tostring(m_luaState, -1), sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]));
			m_lastLuaError[sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]) - 1] = 0;
			m_lastState = LuaLoadFile;
			iRetStatus = -1;
		}
	} else {
		strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Lua state not initialized.");
		m_lastState = LuaLoadFile;
		iRetStatus = -2;
	}

	return iRetStatus;
}

int LuaSimpleWrapper::LoadLuaFile (wchar_t *wszFilePath)
{
	char tempFilePath[2048];
	int  i;

	for (i = 0; i < 2040 && wszFilePath[i]; i++) tempFilePath[i] = (char) wszFilePath[i];
	tempFilePath[i] = 0;
	return LoadLuaFile (tempFilePath);
}

char const *LuaSimpleWrapper::GetLastErrorString(char *aszError, size_t bufLen)
{
	if (aszError && bufLen > 0) {
		strncpy_s (aszError, bufLen, m_lastLuaError, bufLen);
		aszError[bufLen - 1] = 0;
	}

	return &m_lastLuaError[0];
}

lua_State const *LuaSimpleWrapper::GetLuaState (void)
{
	return m_luaState;
}

int LuaSimpleWrapper::m_ObjectCount = 0;
LuaSimpleWrapper *LuaSimpleWrapper::m_ListOfObjects[20] = {0};

int LuaSimpleWrapper::InitLuaEnvironment (int (*pFunc)(LuaSimpleWrapper *luaWrapper, lua_State *luaState))
{
	int iRetStatus = 0;

	if (m_luaState) {
		// CreateFrame() function that will create a frame with an index
		// This function uses two variables which are used to store the
		// frame data allowing it to be used by the application in order to
		// send events to a specific frame object in the Lua code.
		// we access the array of the list of objects, m_ListOfObjects[], with objectindex
		// and we access the specific frame for the object with frameindex.
		lua_pushnumber(m_luaState, 0);                // frameindex, count of frames for this Lua state object, init to zero
		lua_pushnumber(m_luaState, m_MyObjectCount);  // objectindex, which Lua state object am I?
		// create the C closure with the above two arguments, 
		lua_pushcclosure (m_luaState, ParserLuaCreateGlobalFrame, 2);
		lua_setglobal (m_luaState, "CreateFrame");

		if (pFunc) {
			iRetStatus = pFunc (this, m_luaState);
		}
		if (iRetStatus >= 0) {
			// should return 0 if successful otherwise will return the following:
			//   LUA_ERRRUN unable to run the script.
			// this initial call will run the script loaded and prep the various functions
			// and variables.  It is necessary to call this once in order to run the script
			// so that all the global variables for functions and table names will be set.
			// after this call, we can perform various actions by setting up the Lua stack
			// and then using lua_pcall() again to perform the action.
			int iStatus = lua_pcall(m_luaState, 0, 0, 0);
			if (iStatus != 0) {
				strncpy_s (m_lastLuaError, sizeof(m_lastLuaError), lua_tostring(m_luaState, -1), sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]));
				m_lastLuaError[sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]) - 1] = 0;
				m_lastState = LuaInitLoad;
				iRetStatus = -1;
			}
		}
	} else {
		strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Lua state not initialized.");
		m_lastState = LuaInitLoad;
		iRetStatus = -2;
	}

	return iRetStatus;
}

// TriggerGlobalCall() is a method to allow an application program to invoke
// a function in a global variable.  this function uses a variable argument
// list to allow the application to specify as many or as few arguments as
// needed.  to use this method you must specify a description of the call
// along with a list of the parameters.
//
// the description has the following format.
//   "global.item:d,s,i"
//   global -> name of a global variable
//   key    -> name of a table key (optional)
//   d -> double number value
//   s -> zero terminated char type string
//   i -> integer number value
int LuaSimpleWrapper::TriggerGlobalCall (char *aszDescription, ...)
{
	char  tempStringBuffer[2048];
	char  *globalName = 0;
	char  *globalKey = 0;
	char  *pString = tempStringBuffer;
	int   iStatus = 0;
	int   iState = 1;
	int   nPushCount = 0;
	va_list argList;

	va_start (argList, aszDescription);

	strncpy_s (tempStringBuffer, sizeof(tempStringBuffer), aszDescription, sizeof(tempStringBuffer)/sizeof(tempStringBuffer[0]));
	tempStringBuffer[sizeof(tempStringBuffer)/sizeof(tempStringBuffer[0]) - 1] = 0;
	while (*pString) {
		switch (iState) {
			case 1:
				if (*pString != ' ')
					iState = 2;
				else
					break;
				// if we have found a non-space then fall through to state 2
				// so that we can process the first non-space character.
			case 2:
				if (*pString == ':') {
					*pString = 0;
					iState = 3;
					if (globalName && *globalName) {
						// get the global onto the Lua virtual stack by querying the Lua engine
						// then we check to see if the value provided is nil indicating that the
						// global does not exist.  if so we error out.
						lua_getglobal (m_luaState, globalName);
						if (lua_type(m_luaState, lua_gettop(m_luaState)) == LUA_TNIL) {
							// if the global variable does not exist then we will bail out with an error.
							strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Global variable not found: ");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalName);
							m_lastState = LuaDescripParse;
							// error so we will just clear the Lua virtual stack and then return
							// if we do not clear the Lua stack, we leave garbage that will cause
							// problems with later function calls from the application.
							// we do this rather than use lua_error() because this function is called
							// from the application and not through Lua.
							lua_settop (m_luaState, 0);
							return -1;
						}
					}
					break;
				} else if (*pString == '.') {
					*pString = 0;
					iState = 12;
					if (globalName && *globalName) {
						// get the global onto the Lua virtual stack by querying the Lua engine
						// then we check to see if the value provided is nil indicating that the
						// global does not exist.  if so we error out.
						lua_getglobal (m_luaState, globalName);
						if (lua_type(m_luaState, lua_gettop(m_luaState)) == LUA_TNIL) {
							// if the global variable does not exist then we will bail out with an error.
							strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Global variable not found: ");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalName);
							m_lastState = LuaDescripParse;
							// error so we will just clear the Lua virtual stack and then return
							// if we do not clear the Lua stack, we leave garbage that will cause
							// problems with later function calls from the application.
							// we do this rather than use lua_error() because this function is called
							// from the application and not through Lua.
							lua_settop (m_luaState, 0);
							return -1;
						}
					}
					break;
				}
				if (globalName == 0) globalName = pString;
				break;
			case 12:
				if (*pString == ':') {
					*pString = 0;
					iState = 3;
					if (globalKey) {
						// the global is already on the Lua virtual stack so lets do a
						// query of the Lua engine to see if this is a table and the key exists.
						// if the value provided by the Lua engine is nil the then the key
						// specified does not exist in the global table so we error out.
						if (lua_type(m_luaState, lua_gettop(m_luaState)) != LUA_TTABLE) {
							// if the global table does not exist then we will bail out with an error.
							strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Global table not found: ");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalName);
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), ".");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalKey);
							m_lastState = LuaDescripParse;
							// error so we will just clear the Lua virtual stack and then return
							// if we do not clear the Lua stack, we leave garbage that will cause
							// problems with later function calls from the application.
							// we do this rather than use lua_error() because this function is called
							// from the application and not through Lua.
							lua_settop (m_luaState, 0);
							return -1;
						}
						lua_pushstring (m_luaState, globalKey);
						lua_gettable (m_luaState, 1);
						if (lua_type(m_luaState, lua_gettop(m_luaState)) == LUA_TNIL) {
							// if the table key does not exist then we will bail out with an error.
							strcpy_s (m_lastLuaError, sizeof(m_lastLuaError), "Global variable key not found: ");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalName);
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), ".");
							strcat_s (m_lastLuaError, sizeof(m_lastLuaError), globalKey);
							m_lastState = LuaDescripParse;
							// error so we will just clear the Lua virtual stack and then return
							// if we do not clear the Lua stack, we leave garbage that will cause
							// problems with later function calls from the application.
							// we do this rather than use lua_error() because this function is called
							// from the application and not through Lua.
							lua_settop (m_luaState, 0);
							return -1;
						}
					}
					lua_insert (m_luaState, 1);
					nPushCount++;
				}
				if (globalKey == 0 && *pString != ' ') globalKey = pString;
				break;
			case 3:
				// process the parameter list stepping through the descriptor
				// and processing the function parameter list.  this is handling
				// the variable part of the parameter list.
				switch (*pString) {
					case 'd':  // the parameter being passed is a double
					case 'D':
						{
							double dVal = va_arg(argList, double);
							lua_pushnumber (m_luaState, dVal);
							nPushCount++;
						}
						break;
					case 'f':  // the parameter being passed is a C function
					case 'F':
						{
							lua_CFunction fVal = va_arg(argList, lua_CFunction);
							lua_pushcclosure (m_luaState, fVal, 0);
							nPushCount++;
						}
						break;
					case 'i':  // the parameter being passed is an integer
					case 'I':
						{
							int iVal = va_arg(argList, int);
							lua_pushinteger (m_luaState, iVal);
							nPushCount++;
						}
						break;
					case 's':  // the parameter being passed is a zero terminated string
					case 'S':
						{
							char *asz = va_arg(argList, char *);
							lua_pushstring (m_luaState, asz);
							nPushCount++;
						}
						break;
					case 'w':  // the parameter being passed is a string of bytes
					case 'W':
						{
							wchar_t    *asz = va_arg(argList, wchar_t *);
							size_t  iLen = wcslen(asz);
							iLen += 1;  // add one to include the zero termination character
							iLen *= sizeof(wchar_t);   // figure number of bytes in the string.
							lua_pushlstring (m_luaState, (char *)asz, iLen);
							nPushCount++;
						}
						break;
					default:
						break;
				}
				break;
			default:
				break;
		}
		pString++;
	}
	iStatus = lua_pcall(m_luaState, nPushCount, 0, 0);
	if (iStatus != 0) {
			strncpy_s (m_lastLuaError, sizeof(m_lastLuaError), lua_tostring(m_luaState, -1), sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]));
			m_lastLuaError[sizeof(m_lastLuaError)/sizeof(m_lastLuaError[0]) - 1] = 0;
			m_lastState = LuaGlobalCall;
			iStatus = -2;
	}

	return iStatus;
}

static int UifLuaOnEvent (lua_State *lua)
{
	int  nPushCount = 0;
	int argc = lua_gettop(lua);
	const char *msg = lua_tostring (lua, 1);

	cout << "UifLuaOnEvent() called. " << msg << endl;
	return  0;
}

// RegisterEvent is called by specifying two arguments
//   self -> the name of the table created by UifLuaCreateFrame()
//   key  -> the name of the event which is to be observed
static int UifLuaRegisterEvent (lua_State *lua)
{
	int  nPushCount = 0;
	int argc = lua_gettop(lua);
	const char *eventKey = lua_tostring (lua, 2);
	int result = 0;

	cout << "UifLuaRegisterEvent() called. " << eventKey << endl;
	return  0;
}

static int ParserLuaCreateGlobalFrame (lua_State *luaState)
{
	int  nPushCount = 0;

	if (luaState == 0)
		return nPushCount;

	int argc = lua_gettop(luaState);
	const char *tableKey = lua_tostring (luaState, 1);

	// get the two integer values that are part of the C closure for this function.
	// the first integer value is the frame count which is incremented for each frame created.
	// the second integer value is the object count which is increment for each object created.
	// an object may create multiple frames as requested by the Lua script it is executing.
    lua_Integer    frameindex = lua_tointeger(luaState, lua_upvalueindex(1));
    lua_Integer    objectindex = lua_tointeger(luaState, lua_upvalueindex(2));

	// the CreateFrame closure contains an upvalue or local variable
	// associated with the CreateFrame closure which contains a unique
	// identifier for each frame that is created.
	lua_pushinteger(luaState, (frameindex+1));  /* new value */
	lua_pushvalue(luaState, -1);  /* duplicate it */
	lua_replace(luaState, lua_upvalueindex(1));  /* update upvalue */
 
	// create the Frame table with the necessary set of fields
	// that are standard for a Frame table.
	// we create the new table on the Lua virtual stack and then
	// set the specific keys that we want to set with the values for
	// those keys.  Since there is only one value returned, the table,
	// we increment the number of arguments in nPushCount by one.
	lua_newtable(luaState);
	LuaSimpleWrapper::l_pushtableinteger(luaState, "FrameIndex", frameindex);
	LuaSimpleWrapper::l_pushtablefunctionCharKey(luaState, "OnEvent", UifLuaOnEvent);
	LuaSimpleWrapper::l_pushtablefunctionCharKey(luaState, "RegisterEvent", UifLuaRegisterEvent);

	// now call setglobal to create a global variable in the Lua environment
	lua_setglobal (luaState, tableKey);

	// now get the global so that we can return it in case the user expects the handle as
	// part of calling the function to create the frame.
	// for instance in Lua:  myNewFrame = CreateFrame ("myNewFrame")
	lua_getglobal (luaState, tableKey);

	nPushCount++;
	LuaSimpleWrapper::PutObjectFrameData (objectindex, frameindex, tableKey);
	return nPushCount;
}

//  The type of the memory-allocation function used by Lua states. The allocator function
//  must provide a functionality similar to realloc, but not exactly the same. Its arguments
//  are:
//     ud, an opaque pointer passed to lua_newstate;
//     ptr, a pointer to the block being allocated/reallocated/freed;
//     osize, the original size of the block;
//     nsize, the new size of the block.
//
//  ptr is NULL if and only if osize is zero. When nsize is zero, the allocator
//  must return NULL; if osize is not zero, it should free the block pointed to by ptr. When nsize
//  is not zero, the allocator returns NULL if and only if it cannot fill the request. When nsize
//  is not zero and osize is zero, the allocator should behave like malloc. When nsize and osize
//  are not zero, the allocator behaves like realloc().
//
//  Lua assumes that the allocator never fails when osize >= nsize.
//
//  See also this documentation http://www.lua.org/manual/5.2/manual.html#lua_Alloc
//
void *ParserLuaAlloc (void *ud, void *ptr, size_t osize, size_t nsize)
{
	void *pMalloc = NULL;

	if (osize && nsize && ptr) {
		if (osize < nsize) {
			pMalloc = realloc (ptr, nsize);
		} else {
			pMalloc = ptr;
		}
	} else if (nsize) {
		pMalloc = malloc (nsize);
	} else if (nsize == 0) {
		free (ptr);
		pMalloc = NULL;
	}

	return pMalloc;
}

void LuaSimpleWrapper::ParserLuaSimpleStackDump (lua_State *L)
{
	int i;
	int top = lua_gettop(L);

	cout << "  Stack dump from 0 to top (" << top << ")\n  ";
	for (i = 0; i <= top; i++) {  /* repeat for each level */
		int t = lua_type(L, i);
		switch (t) {
			case LUA_TSTRING:  /* strings */
				cout << "`" << lua_tostring(L, i) << "'";
				break;

			case LUA_TBOOLEAN:  /* booleans */
				cout << (lua_toboolean(L, i) ? "true" : "false");
				break;

			case LUA_TNUMBER:  /* numbers */
				cout << lua_tonumber(L, i);
				break;

			case LUA_TTABLE:
				cout << lua_typename(L, t);
				break;

			default:  /* other values */
				cout << lua_typename(L, t);
				break;
		}
		cout << "  ";  /* put a separator */
	}
	cout << endl;  /* end the listing */
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired Georgia Southern University
United States United States
Team lead for a point of sale software application written in C and C++. Previous experience with Nortel Networks on software for telecommunications products as well as Program Management.

Education:
BS Computer Science
MBA
Masters in Project Management

Comments and Discussions