Sign in to follow this  
issch

Question about DLL's

Recommended Posts

issch    479
Right, Im loading a DLL and I want this host program to provide an API to the DLL. I tried doing the following, but it resulted in undefined refferences when compiling the DLL:
// In dllheader.h:

#include "dllapi.h"

class foobar
{
protected:
  MyAPI* API;
};

// In dllapi.h:

class MyAPI
{
public:
  void foo ();
};

// In dllmain.cpp:

#include "dllheader.h"

class MyDLLClass : public foobar
{
 void bar ()
 {
    API->foo();
 }
}

This results in the linker error "Undefined reference to foo()". The idea is that the host defines the actual function and passes a pointer to the DLL. This way doesn't seem to work, any idea of how I can do this?

Share this post


Link to post
Share on other sites
Evil Steve    2017
The usual way to do this is using interfaces. You can export a class from your DLL like this:

// DLL public header:
class IFoo
{
public:
IFoo() {}
virtual ~IFoo() {}

virtual void Foo() = 0;
virtual void Bar() = 0;
};

IFoo* CreateInstance();


// DLL private header (only included when building the DLL)
// Implementation of all functions in some .cpp file in the DLL
class CFoo
{
public:
CFoo();
virtual ~CFoo();

virtual void Foo();
virtual void Bar();
};

// DLL source file:
IFoo* CreateInstance()
{
return new CFoo();
}


And then to use CFoo from in the application:

IFoo* pInterface = CreateInstance(); // Or LoadLibrary() & GetProcAddress()
pInterface->Foo();
pInterface->Bar();



You can do something similar to declare the code in the application, just create another interface (call it IBar), and then create your CBar class in the application, and pass it to whatever function you want in the DLL that takes an IBar*.

HTH,
Steve

Share this post


Link to post
Share on other sites
issch    479
Thanks for the reply, though I can't seem to get that working right either...
I can call functions in the DLL from the host, but not the other way around.

I even tried having function pointers in the DLL and then have the host set them, but I keep getting segfaults when the DLL tries to call them..

Garr! This is driving me crazy!

Share this post


Link to post
Share on other sites
ArmitageIII87    228
you need to export the class/function from the dll via the __declspec( dllexport ) / _declspec( dllimport ) attribute. Look it up in your visual studio help. Plus there are tons of tutorials on the web...google for it.

Share this post


Link to post
Share on other sites
issch    479
GRRRRR!!!!

I tried that.. it still wont work! If I just declare function prototypes and try to call them directly, regardless of wether they're defined in a class or not or if they're set as extern, extern "C", or __declspec(dllimport)/__declspec(dllexport), the linker complains about undefined references.
If I have a pointer to a class with virtual functions (and then provide a way to set the pointer to a derived fully implemented version) or if I use function pointers and set them from the host program it compiles but generates a segfault when running..!!

How do you guys manage your DLL/host program interaction?

Share this post


Link to post
Share on other sites
stormrunner    720
Quote:
How do you guys manage your DLL/host program interaction?

Alright, well, this is going to be rather lengthly, but I'll try.

First, you need to realize that the whole "__declspec(dllexport)" along with function pointers doesn't work terribly well with Microsoft compilers. GCC doesn't appear to complain, but VC6/VS.Net will sometimes freeze up (depending on how you implement it). So, if you have a base class in your host, along with an interface (to obtain an instance) like this :

class Interface
{
public:
CClass* getInst();
private:
CClass** m_inst; // you could expand
// and get away without this, but
// for this discussion we'll have it
};

class CClass
{
public:
// a random virtual function
virtual void Out() = 0;
};

// here we define function pointers to
// load and release functions
extern "C" {
// we can't have the actual definitions
// VC7 tends to get confused
// but then maybe that was the .def
// that screwed it over - meh
// declare the fp
typedef int (*loadClass)(CClass** _inst);
typedef int (*freeClass)(CClass** _inst);
// the "int" is for error checking
};


Now we switch to the DLL itself - here is the sample implementation of the Derived class :

class CDerived : public CClass
{
public:
void Out()
{
// cool implementation here
}
};


In a separate header, because we're trying to be organized, we declare the definitions of the functions we want to load/free our class

#include "Derived.h"
extern "C" {
int __declspec(dllexport) LoadClass(CClass** _inst);
int __declspec(dllexport) FreeClass(CClass** _inst);
}
// in the .cpp file
// because they're being exported
// the host will find them via GetProcAddress
// ... we hope ;)
extern "C" {
int LoadClass(CClass** _inst)
{
// make sure it's not a null pointer
if(!_inst)
return -1;
*_inst = new CDerived(); // or something similar
return 1;
}
int FreeClass(CClass** _inst)
{
// free the class
return 1;
}
}


Hope that was somewhat clear. In your interface, you'd use the function pointers in conjunction with GetProcAddress to retreive the class. Actually, you don't need the interface, it's just nice to have (and considered good OO practice, I suppose).

Cheers.

Share this post


Link to post
Share on other sites
issch    479
Thanks for the reply.
One question (before I try work my code around to work how you did it): does this work in the reverse - can the DLL (loaded using LoadLibrary and accessed with GetProcAddress) use this method to access functions in the host program (the one that loaded the DLL).

The reason for this is that im using DLL's as modules (or plugins) in my program and they need to be able to interact with the host, change settings or whatever, and to do this it needs to be able to call functions.
My problem is not with the program calling functions in the DLL (like most programs which use DLL's do), that works perfectly! It is when I attempt to do the reverse that I run into problems.

Thanks for the help! Ive spent all day trying to figure this out, but no matter what I try, I keep running into problems...

Share this post


Link to post
Share on other sites
d_emmanuel    271
You might want to re-think your system's architecture.

Have the engine be a dll (or possibly a collection of dlls depending on its complexity) and have both the plugin and editor implicitly link (no LoadLibrary call is needed) with the engine dll.

Your engine DLL can declare an interface (e.g. CApplication) that
you will then implement within the plugin dll. For example:

class CMyApplicationPlugin: public CApplication {
public:
// Implement CApplication interface here
};

__declspec(dllexport) CApplication& getApplicationInstance() {
// Return an instance CMyApplicationPlugin
}

And now, within your editor you explicitly link (call LoadLibrary) with the plugin dll and obtain a pointer to the getApplicationInstance function.
For example:

// Load dll plugin and get pointer to getApplicationInstance function here.
// Now, get the CApplication instance
CApplication& app = getApplicationInstance();

Hope you get the basic idea.

Share this post


Link to post
Share on other sites
issch    479
Sigh, that wouldn't really make my life much easier...

What if I kept some sort of state information that gets altered by statically linked libraries (which the DLL obviously has access to) and then at certain points during execution (like once per frame in a game) the host program calls a GetStateInformation function which is defined in the DLL (since the host calling the DLL works perfectly) and then updates itself according to this information..
Would that work well? Would it be efficient?
Its the only thing I can think of now without completely trashing a lot of working code and spending another ages on a redesign...

Share this post


Link to post
Share on other sites
Aldacron    4544
What you have to remember when passing data from the host to the DLL (and vice-versa if you are loading the DLL via LoadLibrary and not linking to the import lib) is that both the executable and the DLL are compiled in the same manner, i.e. at the end of compilation a link step occurs where the compiled object files are processed. This differs from building an object archive (aka library - .lib or .a for example) which is only compiled and isn't linked until it is added in to an executable or DLL project. What this means is that when building executables and DLLs the linker needs the object files of class and function implementations.

So, if you were to declare a class with at least one function that is not pure virtual in your host project, and then try to use that class in your DLL project, the linker will fail the 'undefined reference' errors because the DLL project knows nothing about the compiled object file that contains the implementation of that class.

One way to solve this issue is to add your implementaion files to the DLL project as will as the host app's project. That way the DLL will compile its own version of the object file and link it in. If you look at the Quake2 source, you will see a file called qshared.c which is included in both the server and client side projects. This has the same effect as building and linking to an object archive (library), except that the object file is built twice instead of once. This approach may not always be desirable, however, particularly if the class is a Singleton or contains static data, or needs to interact with a memory manager, or many other possibilites.

Another way to solve the problem is through pure virtual classes, or interfaces. When every method of a class is pure virtual and contains no static members, the linker doesn't look for object files. If you need to pass an object from the host to a DLL (or vice-versa), define an interface for the object, and in the methods/functions that need to reference it you can pass a pointer to an implementation which is created on the host side. Or, you can define a set of instantiation functions and pass those to the DLL at load time. This is a good argument for loading a dll Dynamically rather than linking to an import library:


// commonHeader.h
class InterfaceA
{
virtual void init(int arg) = 0;
}

class InterfaceB
{
virtual void init(float arg1, float arg2) = 0;
}

typedef InterfaceA* (*pfCreateA)();
typedef InterfaceB* (*pfCreateB)()
struct Instantiator
{
pfCreateA createA;
pfCreateB createB;
}

// in one of the host source files (assuming Windows)
// someHostFile.cpp

#include "commonHeader.h"
typedef void (*pfMyDLLInit)(Instantiator*);

Instantiator instantiator;

InterfaceA* createA()
{
return new InterfaceA();
}

InterfaceB* createB()
{
return new InterfaceB();
}

void loadMyDLL()
{
instantiator.createA = createA;
instantiator.createB = createB;

HMODULE = LoadLibrary("MyDLL");
pfMyDLLInit init = (pfMyDLLInit)GetProcAddress("MyDLLInit");
init(&instantiator);
}

// myDLLInternal.h

#include "commonHeader.h"
extern Instantiator* gInst;

// myDLL.cpp
#include "myDLLInternal.h"

Instantiator *gInst;

extern "C" void MyDLLInit(Instantiator *inst)
{
gInst = inst;
}

void someFunctionOrMethodInMyDLL()
{
// now when you need a new InterfaceA or InterfaceB, use the instantiator instead of operator new
InterfaceA *a = gInst->createA();
a->init(someInt);
InterfaceB *b = gInst->createB();
b->init(someFloat1, someFloat2);
}


// you can also pass interface pointers to the DLL from the host
extern "C" void doSomethingToAnA(InterfaceA *a)
{
}

// and if you have interfaces defined for DLL objects, then the implementations can also accept interface pointers
void SomeDLLInterfaceImplementation::SetA(InterfaceA *a)
{
}





If you have interfaces for the DLL side that the host needs to use, you can use the same technique of passing a struct around (which can also then be used by other DLLs) to instantiate the implementation instances, or just expose the individual methods via extern "C" and load them in in MyDLLInit. You should also provide destroy functions to match the create functions and use those in place of operator delete so that memory is freed in the same space it was allocated:

gInst->destroyA(a);

It's really not complicated once you wrap your head around it. Since I use DLLs primarily for switching between implementations of audio systems, renderers and such, I always use this approach - pure virtual interfaces and manual loading. Staying away from linking to import libraries gives you more flexibility (particularly in upgrading the DLL) and fewer headaches.

Share this post


Link to post
Share on other sites
issch    479
Oh man, you saved my sanity!!! It works now, THANKS VERY MUCH!!!!!! I love you man!! Big fat rating++ on its way!

Thanks also to everyone else who took the time to help me!

Share this post


Link to post
Share on other sites
bkt    178
I wrote a DLL factory awhile back; you're welcome to take a look at it:
HPP

struct sFactory {
sFactory(HINSTANCE hLib,iFactoryObject *pFactory);

HINSTANCE m_hLib;
iFactoryObject *m_pFactory;
};

class cFactoryManager : public cSingleton<cFactoryManager> {
public:
cFactoryManager(void) { }
virtual ~cFactoryManager(void);

bool Load(std::string,iFactoryObject** ppFactory);
void UnLoad(sFactory *pFactory);

private:
typedef std::map<std::string,sFactory *> map_t;
map_t m_pFactoryObjects;
};
}



CPP

sFactory::sFactory(HINSTANCE hLib,iFactoryObject* pFactory)
: m_hLib(hLib),m_pFactory(pFactory) { }

cFactoryManager::~cFactoryManager(void) {
map_t::iterator it;
for(it=m_pFactoryObjects.begin();it!=m_pFactoryObjects.end();it++) {
if(it->second)
UnLoad(it->second);
}
m_pFactoryObjects.clear();
}

bool cFactoryManager::Load(std::string strDllName,iFactoryObject** ppFactory) {
assert(ppFactory && strDllName.c_str());

map_t::iterator it=m_pFactoryObjects.find(strDllName);
if(it!=m_pFactoryObjects.end()) {
*ppFactory=it->second->m_pFactory;
return true;
}

typedef iFactoryObject* (*pfn)();

HINSTANCE hLib=::LoadLibrary(strDllName.c_str());
pfn Factory;

if(! hLib) {
BUG("cFactoryManager::Load","Failed loading Factory dll");
return false;
}

Factory=reinterpret_cast<pfn>(::GetProcAddress(hLib,"ExportFactory"));

if(! Factory) {
::FreeLibrary(hLib);
BUG("cFactoryManager::Load","Failed connecting factory");
return false;
}

(*ppFactory)=Factory();
m_pFactoryObjects[strDllName] = new sFactory(hLib,*ppFactory);
DEBUG("Factory module loaded");
return true;
}

void cFactoryManager::UnLoad(sFactory *pFactory) {
assert(pFactory);

pFactory->m_pFactory->Release( );
::FreeLibrary(pFactory->m_hLib);
delete pFactory;
}



It's probably a year and a half old; but it worked at the time for what I needed it for (the same thing it seems you want). I would rewrite it now; but I'm still in the phase of getting some other things working.

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