[DLL/LUA] Check if your DLL is loaded from LUA

Castar

Warlord
Joined
May 26, 2007
Messages
100
While working on a mod, I found that I had a need to check in LUA whether my DLL was loaded, and only if it wasn't, run some (inefficiënt) workaround LUA code to approximate the behavior of my DLL changes. Thus, a user who does not want to use my DLL can still use the mod. It may be slower, it may not be fully functional, but it won't outright break the mod, as I now accounted for the possibility of the DLL not being there. This makes the DLL "Recommended", instead of "Required", which is a bit more pick'n'mix-friendly ;)

Speaking of pick'n'mix, this little trick assumes you have read whoward's excellent [DLL/C++] Replacing hard-coded constants with database values and [DLL/C++] Extending the Lua API tutorials. Seriously, they are mandatory, not just simply for this thread. Read them if you are interested in DLL modding.

So how do we go about this? When LUA calls a DLL method that does not exist, it fails as it tries to call a value that is nil. We could simply use LUA error handling whenever we call a DLL function, but there are two problems with this: 1) LUA error handling is somewhat cumbersome (no try-catch) and 2) the code we might want to (not) run based on whether the DLL is loaded or not, might not involve actual calls to the DLL. We might simply want to make sure that work is not done twice, once in the DLL and once in LUA. So we need something different.

What we actually want to be able to do, in LUA, is something like this:

Code:
-- Only needed when the DLL is not loaded
if (not DllCheck()) then 
     GameEvents.PlayerDoTurn.Add(DoLUAApproximation); 
end

We have some code in our DLL that does something, and if our DLL isn't there, we're going to hook some LUA code into the GameEvents to approximate the DLL behavior.

For the DllCheck() LUA function, we might naively do something like this:

First, we add a LUAEvent in our DLL. There's no ideal place for something like this, but the global Game instance seems appropriate enough. So, in our mod header file:

Code:
#define CVX_LUA_DLL_CHECK

(CVX is my mod prefix, use whatever you like, and if you don't get this, read whoward's tutorials I mentioned above).

In CvLuaGame.h (using whoward's handy LUAAPIEXTN macro):

Code:
protected:

#if defined(CVX_LUA_DLL_CHECK)
	LUAAPIEXTN(DllCheck);
#endif
	static int lCanHandleAction(lua_State* L);
	static int lHandleAction(lua_State* L);
        ...

In CvLuaGame.cpp:

Code:
void CvLuaGame::RegisterMembers(lua_State* L)
{
#if defined(CVX_LUA_DLL_CHECK)
	Method(DllCheck);
#endif
	Method(CanHandleAction);
	Method(HandleAction);
        ...

and the method itself (also in CvLuaGame.cpp)

Code:
#if defined(CVX_LUA_DLL_CHECK)
// bool DllCheck()
int CvLuaGame::lDllCheck(lua_State* L)
{
	lua_pushboolean(L, true);
	return 1;
}
#endif

Aside from "return 0", this might well be the simplest LUA API call imaginable :) Now for our LUA code we can write:

Code:
function DllCheck()
	return Game.DllCheck();
end

And now we know whether our DLL is loaded or not! But of course, as you are undoubtedly already aware, there are some problems with this. The first is obvious. Our above-mentioned LUA code will return "true" when our DLL is loaded, but it will crash and burn horribly when it isn't, which is precisely what we want to avoid. We could use error handling to check this error, but in fact, we can use a simple LUA trick to check if the function exists before calling it:

Code:
function DllCheck()
	return Game.DllCheck and Game.DllCheck();
end

What is this? Notice the lack of brackets in the first "Game.DllCheck". If the function exists, this will return some internal LUA representation of the function. We don't really care what it is. What is important is that if the function does not exist, it will return "nil" (thanks whoward for this trick). But that might still leave you with some questions as to why this works.

It works because of how LUA handles boolean expressions. First of all, there are two things that are "false" in LUA: any logical expression that is actually false, and anything that is "nil". Everything else, when used in a boolean expression, is "true". The number 1 is true. The number 0 is true. The string "Your father smelled of elderberries" is true. And whatever LUA returns when we access a function without the brackets is also true, because it is not nil or false.

That still might leave you confused as to how this does not still crash? After all, we are still calling the function in our return statement, aren't we? So if it doesn't exist, it will still crash? Nope! It works because LUA uses something called Short-circuit evaluation.

What this means is that, if the result of a boolean expression becomes fixed at any point during the evaluation of that expression, LUA will stop the evaluation and just return this guaranteed result. In our case, we have an "and" expression. The expression "false and <whatever>" will always return "false", regardless of the value of <whatever>. LUA won't even look at <whatever>. Conversely, the expression "true and <whatever>", will always return true if <whatever> is true, and false if <whatever> is false. Now to apply that to the return value of our LUA function:
  • If the function does not exist (it's nil), Game.DllCheck is false, and LUA won't even look at the rest of the statement
  • If the function does exist, Game.DllCheck is true, so LUA will continue evaluating the "and" by going to Game.DllCheck(), which we know can now be safely called. The return value will then be whatever Game.DllCheck() returns.

Are we done now? Not quite. There is another, slightly less obvious problem with this. "DllCheck()" is a rather obvious name for this kind of function, and especially now with this tutorial, there's a good possibility that other people use this in their DLL, meaning our function would return true if someone else's custom DLL was loaded. This could obviously lead to all kinds of nasty and hard to debug problems.

So, we don't just want to make sure that a custom DLL with the DllCheck LUA call is loaded, we want to make sure it's our DLL. Now, since you've all read whoward's tutorials, you know about how each mod requires a unique GUID in CvDllVersion.h, so we've got something that distinguishes our DLL from every other DLL out there. Let's put that to good use.

Say we've got our unique GUID in CvDllVersion.h:

Code:
// {C0915E44-C56D-4BFC-B7CA-ED7A53EA955A}
static const GUID CIV5_XP2_DLL_GUID = 
{ 0xc0915e44, 0xc56d, 0x4bfc, { 0xb7, 0xca, 0xed, 0x7a, 0x53, 0xea, 0x95, 0x5a } };

We're not really going to bother with comparing the actual GUID to something. For our purposes, the string representation (which Visual Studio generates for us in the comment line) is unique enough.

So let's say we change our LUA function to

Code:
function DllCheck()
	return Game.DllCheck and Game.DllCheck("C0915E44-C56D-4BFC-B7CA-ED7A53EA955A");
end

And our CvLuaGame.cpp function to

Code:
#if defined(CVX_LUA_DLL_CHECK)
// bool DllCheck()
int CvLuaGame::lDllCheck(lua_State* L)
{
	const char* szDllModGuid = lua_tostring(L,1);
	bool bResult = strcmp(szDllModGuid, "C0915E44-C56D-4BFC-B7CA-ED7A53EA955A") == 0;
	lua_pushboolean(L, bResult);
	return 1;
}
#endif

This gets our LUA call's argument out of our lua_State, then compares that argument with our GUID, and returns a boolean representing whether they're equal (true) or not (false). NOTE: in case you're unaware of this: In C++, never compare strings with the "==" operator. Use a string-compare function like strcmp (which returns 0 if all characters one string match all characters in the other).

There, all done? HOLD UP. We've got ourselves quite an insidious problem here. I initially missed this myself, but whoward was kind enough to point it out to me: suppose someone has a DLL which uses our first implementation of the CvLuaGame.cpp API function, which simply returns "true" without looking at the arguments passed by our LUA call. If we call that function with our GUID as argument, nothing "bad" will happen. The way the LUA C++ API works means the API method won't care there are too many arguments. It will simply return "true", meaning, back in LUA-land, we will think our DLL is correcly loaded, when in fact it's someone else's vastly inferior DLL.

So, in order for this to finally work, we need to change things one last time so that instead of passing the GUID to our DLL and comparing it over there, our DLL will return the GUID, and we do the comparison LUA-side.

So the LUA function becomes:

Code:
function DllCheck()
	return Game.DllCheck and Game.DllCheck() == "C0915E44-C56D-4BFC-B7CA-ED7A53EA955A";
end

and the CvLuaGame.cpp method:

Code:
#if defined(CVX_LUA_DLL_CHECK)
// bool DllCheck()
int CvLuaGame::lDllCheck(lua_State* L)
{
	lua_pushstring(L, "C0915E44-C56D-4BFC-B7CA-ED7A53EA955A");
	return 1;
}
#endif

There you go (finally)! It's a little trick with a big explanation, and more pitfalls than one would assume at first glance. I hope both the function and the explanation can be somewhat helpful to the beginning DLL coders out there :)

Credits: if it wasn't obvious by now, whoward69 for his excellent DLL tutorials, and for pointing out some things I missed in this tutorial :)

Oh, and a merry christmas to all of you ;)
 
Top Bottom