Editor DLL modules cheat sheet – ShiVa Engine

Editor DLL modules cheat sheet

Visual Studio preparations

Open Visual Studio 2012+ and create a new project. Choose the “Win32” DLL template.

Copy your header and lib files into the project directory tree. We recommend a folder structure like this:

projectroot/luaheaders/x86/*.h
projectroot/luaheaders/x64/*.h
projectroot/lualibs/x86/lua52.lib
projectroot/lualibs/x64/lua52.lib

Tell Visual Studio about the location of the headers (Project Property Pages -> C++ -> General -> Additional Include Directories) as well as the lib path (Project Property Pages -> Linker -> General -> Additional Library Directories) and lib name (Project Property Pages -> Linker -> Input -> Additional Dependencies).

C++ boiler plate

Include the main Lua header:

#include "lua.hpp"

You need the following function to declare all the names of your Lua callbacks:

extern "C" {
	int __declspec(dllexport) libinit(lua_State* L) {
		// register function callbacks, like so:
		lua_register(L, "myFunc", lua_myFunc);
		return 0;
	}
}

In the example above, “myFunc” is the name of the Lua function, and lua_myFunc the corresponding C++ function.

Lua to C++ and back

Arguments and Returns

Argument and return handling is done by pushing and popping values to and off the lua_State. A Lua function like this…

local nSum = DLLcalulateSum(nArg1, nArg2)

… translates into this:

int lua_calcsum(lua_State* L) {
	// taking 2 arguments off the stack
	auto num1 = luaL_checknumber(L, 1);
	auto num2 = luaL_checknumber(L, 2);
	// actual work
	auto sum = num1 + num2;
	// pushing result back
	lua_pushnumber(L, sum);
	// tell Lua that we pushed 1 result
	return 1;
}

Note that all luaL_check* functions require you to number the arguments in the 2nd parameter.
If you want to push multiple returns, you have to increment the return value. This function with 3 returns…

local hours, minutes, seconds = DLLtime()

… translates into this:

int lua_multitime(lua_State* L) {
	// not getting any arguments
	// do work
	auto t = time(0);
	struct tm now;
	localtime_s(&now, &t);
	// push 3 return values
	lua_pushnumber(L, now.tm_hour);
	lua_pushnumber(L, now.tm_min);
	lua_pushnumber(L, now.tm_sec);
	// tell Lua that we pushed 3 results
	return 3;
}

luaL_check* are used to retrieve arguments, and lua_push* functions are used to return them. There are functions for every Lua type, such as numbers, strings, tables etc – choose the appropriate type for your use case.

Checking and Error handling

For every Lua variable type, there is a corresponding type check function called lua_is*. If you detect an unrecoverable error, you can stop the interpreter with lua_error:

if (!lua_isnumber(L, i)) {	/* make sure we only accept numbers */
	lua_pushstring(L, "incorrect argument");
	lua_error(L);		/* stop interpreter, exit with error */
}

Tables

Tables use the same lua_push* functions, although you need to push in key->value pairs and reset the stack pointer (lua_settable to -3) every time you push a new set. For convenience, you should move this repetitive task into its own function:

void setfieldNumber(lua_State* L, const char *index, float value) {
	lua_pushstring(L, index);
	lua_pushnumber(L, value);
	lua_settable(L, -3);
}
int lua_multitimeAssocTable(lua_State* L) {
	// do work
	auto t = time(0);
	struct tm now;
	localtime_s(&now, &t);
	// create temporary table
	lua_newtable(L);
	// add 3 table entries and reset stack pointer
	// via custom function from above
	setfieldNumber(L, "h", now.tm_hour);
	setfieldNumber(L, "m", now.tm_min);
	setfieldNumber(L, "s", now.tm_sec);
	// we only return 1 table
	return 1;
}

C++ to Lua – calling module functions

You can also call Lua functions in your module directly from the DLL. No prior registration is necessary, but if a function does not exist, you will get warnings in the ShiVa log.
Commands must be written as strings using module events, which is the executed by luaL_dostring:

int lua_runLuaComand ( lua_State* pLua ) {
	// the command string
	const char * sCommand = "module.sendEvent ( this.getModule(), 'onFinishTheJob', 123456 )"
	// execute string
	bool bOk = luaL_dostring ( pLua, sCommand ) == 0 ;
	// optional return bOK
	lua_pushboolean ( pLua, bOk );
	return 1;
}

Mudule-specific identifiers like this.getModule() do work through module.sendEvent, however the ShiVa API is not directly accessible. A direct call to log.message for instance will fail. In the example above, onFinishTheJob is a function/handler in one of the module lua scripts, followed by the argument for the function.

Loading the DLL

If you store the DLL in the module resources folder, you can access it at the path module.getRootDirectory ( this.getModule() ) ..”resources/myDLL.dll”.
Use package.loadlib to load the DLL. If the first return is not nil, then simply call the initialization function. You should load the DLL as soon as the module initializes, which typically happens in the predefined onMainViewInit handler:

function onMainViewInit ( )
	local libinit, libmsg = package.loadlib( module.getRootDirectory ( this.getModule() ) .."resources/myDLL.dll", "libinit" )
	if libinit == nil then
		log.warning ( "DLL error: " ..libmsg )
	else
		libinit()
	end
end

Now, all functions you defined in libinit will be accessible to your Editor Module.




Need more answers?

  • slackBanner