Sign in to follow this  

Pass Lua State to Plugin SDK

This topic is 2790 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have taken a brake from programming my game engine for awhile to work on my site but I've been trying to think about how to fix the last problem I ran into, the idea is to allow developers who use my game engine to include plugins so that they can extend the capabilities of the engine to do anything they need it to. Since signature information from DLL functions and classes can not be retrieved I have to bind the functions and classes from inside the DLL. To do this I would have to include Lua, Luabind and Boost in the Plugin and bind functions and classes to the engine's lua state. My idea to simplify this process is to create a wrapper around Lua, Luabind and Boost so that the plugin developer will only need to include one library. This should greatly simplify the process of creating a plugin for my game engine but I can't seem to figure out how I should pass the Luastate from my engine to the Plugin SDK, here is a diagram: Any ideas?

Share this post


Link to post
Share on other sites
The real problem you have to solve is how you are going to implement your basic plugin SDK.

Forget a moment about Lua, Boost, and everything else in your project and focus solely on this question: "How am I going to load and unload DLLs at runtime?"

Once you know the answer to that question, then you focus on this next question: "How am I going to export functions from the DLL and load them at runtime in my host program?"

When you work out those two questions, you have your basic plugin SDK implemented. From there, you will see that you don't really have a problem of trying to figure out how to pass a Lua State to a plugin because that functionality is already implemented in your SDK.

Before you rush to add plugins to your project though, I'd suggest doing some more research on the topic. You really need to understand how DLLs work and some of the issues that you will come across when using them. In addition, you need to consider the implications of having code executing in the DLL and what that means for any threading you might want to do.

I'll leave doing more research up to you, but here's a quick example of a bare minimal Plugin SDK for the sake of completeness.

Plugin.cpp

#include <windows.h>
#include <stdio.h>

HINSTANCE globalDLL;

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fwdReason, LPVOID lpvReserved)
{
if(fwdReason == DLL_PROCESS_ATTACH)
{
globalDLL = hInstance;
DisableThreadLibraryCalls(hInstance);
}
return TRUE;
}

extern "C" __declspec( dllexport ) int __stdcall Load(void * userdata, size_t datasize)
{
printf("%s\n", __FUNCTION__);
return 0;
}

extern "C" __declspec( dllexport ) int __stdcall Unload(void)
{
printf("%s\n", __FUNCTION__);
return 0;
}




Engine.cpp

#include <windows.h>
#include <stdio.h>

typedef int (__stdcall *typedef_Load)(void * /*userdata*/, size_t /*datasize*/);
typedef int (__stdcall *typedef_Unload)(void);

class Plugin
{
private:
bool bLoaded;
HMODULE dll;
typedef_Load func_Load;
typedef_Unload func_Unload;

public:
Plugin()
{
printf("%s\n", __FUNCTION__);
bLoaded = false;
dll = NULL;
func_Load = NULL;
func_Unload = NULL;
}

~Plugin()
{
printf("%s\n", __FUNCTION__);
Unload(); // Or warn if still loaded, depending on your design
}

bool Load(const char * filename, void * ud, size_t sze)
{
char * name;
int error = 0;

dll = LoadLibraryA(filename);
if(dll == NULL)
{
return false;
}

name = "_Load@8"; // Write a .def to remove decoration
func_Load = (typedef_Load)GetProcAddress(dll, name);
if(func_Load == NULL)
{
printf("Could not import the function: %s.\n", name);
error = 1;
}

name = "_Unload@0"; // Write a .def to remove decoration
func_Unload = (typedef_Unload)GetProcAddress(dll, name);
if(func_Unload == NULL)
{
printf("Could not import the function: %s.\n", name);
error = 1;
}

if(error)
{
Unload();
return false;
}

error = (*func_Load)(ud, sze);
if(error)
{
printf("The plugin's \"Load\" function failed with error %i.\n", error);
Unload();
return false;
}

bLoaded = true;
return true;
}

void Unload()
{
if(bLoaded)
{
(*func_Unload)();
}
if(dll)
{
FreeLibrary(dll);
dll = NULL;
}
func_Load = NULL;
func_Unload = NULL;
bLoaded = false;
}
};

int main(int argc, char * argvp[])
{
Plugin newPlugin;
if(newPlugin.Load("plugin.dll", 0, 0) == false)
return EXIT_FAILURE;
newPlugin.Unload();
return EXIT_SUCCESS;
}




Output:
Plugin::Plugin
Load
Unload
Plugin::~Plugin
Press any key to continue . . .
As you can see, you would solve the problem simply by passing the lua_State pointer through the Load function (either directly or via a struct) or through a new function that you call to set it manually. Pretty much all Plugin interactions involve such mechanics of loading an exported function and calling it, passing data as needed.

You can use a more type strong approach if you wish, but there are certain caveats when trying to use C++ objects across DLL/EXE boundaries (mostly involving memory, calling conventions, and CRT versions). This is why it is vital to have a solid understanding of DLLs first and how the code you write might behave differently if implemented in a DLL.

Good luck!

Share this post


Link to post
Share on other sites
Don't need a .def file at all. You're using stdcall for your calling convention, which dictates that sort of name mangling will happen. It's not necessary. Using extern "C" will inheritly use __cdecl calling convention, whose only name mangling is prepending an underscore to the name, since GetProcAddress is already coded to deal with that... you don't need to mangle the name at all:

Also, your call to DisableThreadLibraryCalls may have unexpected side effects depending on how he's linking to the CRT (static linkage requires thread attach notifications), and so you should omit that, and really you should omit the entire DllMain, as he'll get the wrong idea of what it can be used for and try something stupid (like every other n00b does when they encounter DllMain).

#include <windows.h>
#include <iostream>

extern "C" __declspec( dllexport ) int Load(void * userdata, size_t datasize)
{
std::cout<<__FUNCTION__<<std::endl;
return 0;
}

extern "C" __declspec( dllexport ) int Unload(void)
{
std::cout<<__FUNCTION__<<std::endl;
return 0;
}





#include <windows.h>
#include <iostream>
#include <string>
#include <stdexcept>

typedef int (*LoadFunctionSig)(void*, std::size_t);
typedef int (*UnloadFunctionSig)(void);

class Plugin
{
private:
HMODULE dll;
LoadFunctionSig func_Load;
UnloadFunctionSig func_Unload;

public:
Plugin(std::string const& filename, void* userdata, std::size_t datasize) : dll(nullptr), func_Load(nullptr), func_Unload(nullptr)
{
std::cout<<__FUNCTION__<<std::endl;
Load(filename, userdata, datasize);
}

~Plugin()
{
Unload();
std::cout<<__FUNCTION__<<std::endl;
}

private:
void Load(std::string const& filename, void * ud, size_t sze)
{
dll = LoadLibraryA(filename.c_str());
if(dll == nullptr)
{
throw std::runtime_error("Unable to load plugin " + filename);
}

func_Load = GetLibraryFunction<LoadFunctionSig>("Load");
func_Unload = GetLibraryFunction<UnloadFunctionSig>("Unload");

int error = func_Load(ud, sze);
if(error)
{
std::cout<<"The plugin's \"Load\" function failed with error "<<error<<std::endl;
throw std::runtime_error("Plugin failed to load.");
}
}

template<class Sig>
Sig GetLibraryFunction(std::string const& name) {
Sig proc = reinterpret_cast<Sig>(GetProcAddress(dll, name.c_str()));
if(proc == nullptr)
throw std::runtime_error("Unable to import function " + name);

return proc;
}

void Unload()
{
if(func_Unload != nullptr)
func_Unload();

if(dll)
FreeLibrary(dll);
}
};

int main(int argc, char * argv[])
{
if(argc < 2)
return -1;

try {
Plugin newPlugin(argv[1], 0, 0);
} catch(std::runtime_error& error) {
std::cout<<"Failed to load plugin DLL: "<<argv[1]<<std::endl;
std::cout<<"Error: "<<error.what()<<std::endl;
}

return 0;
}



To use C++ objects, it's best to use a COM/pseudo COM style API, with factory functions (see Direct3D for an example). This helps to eliminate most of the memory handling and other issues that crop up otherwise. Something on the order of:
struct IMyThong {
virtual void Release() = 0;
virtual void TakeOffThong() = 0;
};

extern "C" __declspec(dllexport) IMyThong* CreateThong();

class Thong : public IMyThong {
public:
virtual void Release() { delete this; }
virtual void TakeOffThong() { }
};

extern "C" __declspec(dllexport) IMyThong* CreateThong() {
return new Thong;
}

Share this post


Link to post
Share on other sites

This topic is 2790 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this