Sign in to follow this  
Commodore

Access functions in main program from DLL

Recommended Posts

Hi, I'm working on a Super-Mario-like game. I want other people to be able to make modifications to my game just like Half-Life works (without giving them the complete source code). For example: I figured that I could have a general CCharacter-class in the game exe which loads a .DLL dynamicly and calls functions in the DLL. Imagine having an array of CCharacter*'s in the game engine and then loop through them and call Render(). Each one of them calls Render() in their respective loaded .DLL-files. Is this a good idea? A problem with this is that in order to render them I need access to functions in the game from within the DLL-file. The first thing I do when I've loaded a DLL is to call an Init()-function in the DLL which then gets a pointer to the whole game engine. My intention is that the DLL would be able to call pEngine->Drawer->Render(sprite);. This isn't compiling though, I get linker errors ("unresolved external symbol") when trying to call the functions. Some code: - DLL file (player.h) - #include "..\..\includes.h" // Includes the header files for the entire engine CSprite* pSprite; - DLL file (player.cpp) - pSprite = new CSprite("player.bmp",2,60,2); Error: Unresolved external symbol "public: __thiscall CSprite::CSprite(cl [...]" Any ideas? I'd also appreciate good links to tutorials/articles discussing game engine design and using DLL files to make the game "modable". Thanks in advance.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
What you may want to do is put your engine in a DLL and have the game link to that DLL. That way, the mods can also link to it and it's all good :)

Another, more obscure way I read about (can't find a link right now, sorry) was to take advantage of the fact that .exe and .dll have basically the same file format: it's possible to export functions from a .exe in the same way as from a .dll and have DLLs link to normal programs instead of the other way around.

Share this post


Link to post
Share on other sites
It's possible to export functions from the EXE like DLLs. (Example) . In general I recommend against doing it; it's usually not worth it. A better solution may be to move the function you want exported from the EXE to a seperate DLL and link both the EXE and the other DLL against it.

Share this post


Link to post
Share on other sites
If you were just accessing functions, all you need to is set up a struct containing pointers to the functions you would like to expose, and pass that struct to an init function in the dll:


// header shared by dll and executable
typedef void (*funcTypeOne)();
typedef int (*funcTypeTwo)();

struct EngineFuncs
{
funcTypeOne funcOne;
funcTypeTwo funcTwo;
};

typedef bool (*dllInitFunc)(EngineFuncs*);

// executable source module
extern void funcOne();
extern int funcTwo();

void loadDLL()
{
// load dll - LoadLibrary on Windows
MyDLL dll= MyDLL.Load("myDLL");

// load the dll init func - GetProcAddress on Windows
dllInitFunc dllInit = MyDLL.loadSymbol("myDLLInit");

// set up your functions
EngineFuncs funcs;
funcs.funcOne = funcOne;
funcs.funcTwo = funcTwo;

// pass it to the dll
dllInit(funcs);
}



For C++ classes (opposed to 'functions' as suggested by the thread title), you need to do two things.

1) When compiling code that contains classes, and the class is not declared as pure virtual the linker needs the object file. Without it, you can't link and you will get 'Unresolved external symbol' errors. This is why you can compile, link, and run a program when statically linking to a class lubrary - the object files are all included in the library (known as an 'object archive' in the Unix world). Pure virtual classes can get around this because they are abstract and have no implementation. The exception to this is inline methods, as the implementation is there in the header file where the compiler to find it.

So when sharing classes across mulitple projects without linking them into each project (either as object files or libraries), then your best bet is to declare each class intended to be used this way as pure virtual. Then the client (the main executable or, in this case, a dll) need know nothing about the implementation during link time. But you still have the problem of 'newing' instances of your classes. Since the interfaces are pure virtual, you cannot new them. And if you had to know the implementation, you wouldn't be able to new it anyway because it puts you right back in the same boat you were in without the pure virtual interfaces. That brings us to step 2.

2) For each class you wish to expose to the dll, you will need to provide a function (not a class method) the creates and returns an instance of an interface implementaion. You can add a pointer to each function to the EngineFuncs struct above, just like a normal function. This gives you 2 additional benefits: since your classes are purevirtual, the function can return an instance of any implementation - you could even swap things out at runtime if the function takes some sort of arg determining which implementation of an interface to instantiate; if you have a custom memory management system implemented, you can easily track all allocations through these functions (overloading new wouldn't work across the exe/dll boundary - you'd have to do it in each project and expose a central allocation function through a pointer somewhere anyway). Of course, for each allocation function you provide you'd need to implement a matching deallocation function.

So now you end up with something like this:


// sprite.h
class Sprite
{
public:
// always provide a virtual destructor for pure virtual classes
virtual ~this() {};

virtual void one() = 0;
virtual void two(int) = 0;
...
};

// implementation header used only by the executable
class SpriteImpl : Sprite
{
public:
void one();
void two(int);
};

// SpriteImpl.cpp - only linked in the executable
void SpriteImpl::one()
{
...
}

void SpriteImpl::two()
{
...
}

// back to the dll loader
// shared header
typedef Sprite* (*newSprite)();
typedef void (*delSprite)(Sprite*);

struct EngineFuncs
{
...
newSprite newSprite;
delSprite delSprite;
};

// executable source module
extern Sprite* allocSprite();
extern void deallocSprite(Sprite*);

void loadDLL
{
...
funcs.newSprite = allocSprite;
funcs.delSprite = deallocSprite;
}

// then somewhere in an executable source module (maybe all alloc/dealloc
// functions could be in the same file, for simplicity)
Sprite *allocSprite()
{
return new SpriteImpl();
}

void deallocSprite(Sprite* sprite)
{
if(sprite)
delete sprite;
}



As for handling constructor args, you might be tempted to include them as args to the alloc functions. I suggest that you don't, as that does not make them part of the interface. Instead, give each interface an init method that takes the args instead. Then you would use it like this:


Sprite s = funcs.newSprite();
s.init(args);


It's a bit of extra work to get everything set up for each class, and adds a little overhead to maintenance (I know these days people just love to automate as much as they can through templates and such), but it's not as bad as it seems. I think this is a much more flexible system than exporting classes, anyway. Besides which, I always prefer to manually load my dlls rather than linking to the import libs on Windows, and this system makes it quite easy to do so.

Share this post


Link to post
Share on other sites

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