views:

2053

answers:

4

For my current project I want to be able to load some classes from a dll (which is not always the same, and may not even exist when my app is compiled). There may also be several alternative dll's for a given class (eg an implementation for Direct3D9 and one for OpenGL), but only one of the dlls will be loaded/used at any one time.

I have a set of base classes that define the interface plus some basic methods/members (ie the ones for refrence counting) of the classes I want to load, which the dll projects then derive from when creating there classes.

//in namespace base
class Sprite : public RefCounted//void AddRef(), void Release() and unsigned refCnt
{
public:
 virtual base::Texture *GetTexture()=0;
 virtual unsigned GetWidth()=0;
 virtual unsigned GetHeight()=0;
 virtual float GetCentreX()=0;
 virtual float GetCentreY()=0;
 virtual void SetCentre(float x, float y)=0;

 virtual void Draw(float x, float y)=0;
 virtual void Draw(float x, float y, float angle)=0;
 virtual void Draw(float x, float y, float scaleX, flota scaleY)=0;
 virtual void Draw(float x, float y, float scaleX, flota scaleY, float angle)=0;
};

The thing is I'm not sure how to do it all so that the executable and other dlls can load and use these classes since ive only ever used dlls where there was only one dll and I could have the Visual Studio linker sort it all out using the .lib file I get when compileing dll's.

I dont mind using factory methods for instancing the classes, many of them do already by design (Ie a sprite class is created by the main Graphics class, eg Graphics->CreateSpriteFromTexture(base::Texture*)

EDIT: When I needed to write some c++ dlls for use in python I used a library called pyCxx. The resulting dll basicly only exported one method, which created an instance of the "Module" class, which could then contain factory methods to create other classes etc.

The resulting dll could be imported in python just with "import [dllname]".

//dll compiled as cpputill.pyd
extern "C" void initcpputill()//only exported method
{
    static CppUtill* cpputill = new CppUtill;
}

class CppUtill : public Py::ExtensionModule<CppUtill>
{
public:
    CppUtill()
    : Py::ExtensionModule<CppUtill>("cpputill")
    {
        ExampleClass::init_type();

        add_varargs_method("ExampleClass",&CppUtill::ExampleClassFactory, "ExampleClass(), create instance of ExampleClass");
        add_varargs_method("HelloWorld",  &CppUtill::HelloWorld,  "HelloWorld(), print Hello World to console");

        initialize("C Plus Plus module");
    }
...
class ExampleClass
...
    static void init_type()
    {
        behaviors().name("ExampleClass");
        behaviors().doc ("example class");
        behaviors().supportGetattr();
        add_varargs_method("Random", &ExampleClass::Random, "Random(), get float in range 0<=x<1");
    }

How exactly does that work, and could I use it in a purely c++ enviroment to solve my problem here?

A: 

Perhaps you want to look into DLL Delay-Loading (http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx) - this will give you what you want without having to work too hard for it

Paul Betts
Doesnt work when I dont have the dll when the main app is built (it requires the .lib created with the dll), and I see no way to load one of several possible dlls even if they all existed when the main app was built.
Fire Lancer
+5  A: 

Easiest way to do this, IMHO, is to have a simple C function that returns a pointer to an interface described elsewhere. Then your app, can call all of the functions of that interface, without actually knowing what class it is using.

Edit: Here's a simple example.

In your main app code, you create a header for the interface:

class IModule
{
public:
    virtual ~IModule(); // <= important!
    virtual void doStuff() = 0;
};

Main app is coded to use the interface above, without any details on the actual implementation of the interface.

class ActualModule: public IModule
{
/* implementation */
};

Now, the modules - the DLL's have the actual implementations of that interface, and those classes don't even have to be exported - __declspec (dllexport) isn't needed. The only requirement for the modules is to export a single function, that would create and return an implementation of the interface:

__declspec (dllexport) IModule* CreateModule()
{
    // call the constructor of the actual implementation
    IModule * module = new ActualModule();
    // return the created function
    return module;
}

note: error checking left out - you'd usually want to check, if new returned the correct pointer and you should protect yourself from the exceptions that might be thrown in the constructor of the ActualModule class.

Then, in your main app, all you need is to simply load the module (LoadLibrary function) and find the function CreateModule (GetProcAddr function). Then, you use the class through the interface.

Edit 2: your RefCount (base class of the interface), can be implemented in (and exported from) the main app. Then all your module would need to link to the lib file of the main app (yes! EXE files can have LIB files just like DLL files!) And that should be enough.

Paulius Maruška
Can you explain/link example of what you mean? I expect what your saying is basicly what pyCxx is doing with python somehow, but I dont know how that works either :(
Fire Lancer
Ok one more thing (since I dont have compilers to test with here), if I compiled my app with say VC9, and someone else created there own version of one of the dlls with say gcc and told my app to use it instead of my dll, would it work?
Fire Lancer
Knowing, that COM objects can be built with any compiler (or language), I'd say it should be possible to built the modules with any compiler. But, you might need to explicitly state the calling convention, like COM does.
Paulius Maruška
Fire Lancer: Although binary compatibility is a big weakness of C++, C functions have a standard naming system and calling convention so you should be alright as the only function you call by name is in C. Check that calling conventions agree for the C++ functions (use compilation switches).
j_random_hacker
... but see also brone's point about using "delete" on objects returned by the DLL -- if you want to do that, both DLL and main program must be using the same runtime library. Also applies for other shared resources, e.g. stdio/iostreams buffered output I imagine.
j_random_hacker
j_random_hacker: I don't think he'd be using delete - there's RefCounted class - I'd assume, that his RefCounted class would delete the objects once the refcount is 0. Personally, I would also have a static alloc function in the RefCounted class and I would use placement new to construct the object.
Paulius Maruška
Both good points Paulius.
j_random_hacker
Ok, I got it working under VC9, I then compiled a third dll (c.dll) using g++, however when attempting to use this dll from my VC9 exe it crashed. Creating the class with the C method worked, but calling any members crashed with access violations eg: "Access violation writing location 0x00000004"
Fire Lancer
You might want to make that another question.
MSalters
This is a good solution that I've used myself in the past. HOWEVER, because all the vtable stuff is not standard across compilers, you must use the same compiler for the dlls as the main app. If you are not able to guarantee this, you shouldn't use this solution.
markh44
@markh44: as far as I know, the standard doesn't say anything about vtable, most compilers (even those of other languages) use the same approach. Microsoft's COM heavily relies on this fact (you can write COM dll's in Delphi, C, C++ - anything that can create a DLL).
Paulius Maruška
+9  A: 

You are re-inventing COM. Your RefCounted class is IUnknown. Your abstract class is an interface. A COM server in a DLL has an entrypoint named DllGetClassObject(), it is a class factory. There is lots of documentation available from Microsoft on COM, poke around a bit to see how they did it.

Hans Passant
+1 My thoughts exactly. Here's a link to an introductory article: http://www.codeproject.com/KB/COM/comintro2.aspx
ChrisN
+3  A: 

[Edit: whilst I was composing all this, Paulius Maruška edited his comment to say basically the same. So apologies for any duplication. Though I suppose you've now got one for spare :)]

Off the top of my head, and assuming Visual C++...

You need to use LoadLibrary to load a DLL in dynamically, then use GetProcAddress to retrieve from it the address of a function that will create the actual derived class that the DLL code implements. How you decide to do this precisely is up to you (the DLLs need finding, the way the expose their functionality needs specifying, etc.) so for now let's assume that plugins only provides new Sprite implementations.

To do this, decide on the signature of the function in the DLL that the main program will call to create one of these new sprites. This one looks suitable:

typedef Sprite *(*CreateSpriteFn)();

Then, from the main program, you'll have to load a DLL (again, how you find this DLL is up to you) and get the sprite creation function from it. I've decided that the sprite creation function will be called "CreateSprite":

HMODULE hDLL=LoadLibrary(pDLLName);
CreateSpriteFn pfnCreateSprite=CreateSpriteFn(GetProcAddress(hDLL,"CreateSprite"));

Then to actually create one of these, just call the function:

Sprite *pSprite=(*pfnCreateSprite)();

Once you are done with the DLL and there are no objects left that were created by it, you then use FreeLibrary to get rid of it:

FreeLibrary(hDLL);

To create a DLL that sports this interface, write the code for the derived class and so on then somewhere in the DLL code provide the CreateSprite function that the calling program needs, using the appropriate signature:

__declspec(dllexport)
extern "C"
Sprite *CreateSprite()
{
    return new MyNewSprite;
}

The dllexport thing means that you can use GetProcAddress to pick this function up by name, and the extern "C" ensures that the name is unmangled and doesn't end up as "CreateSprite@4" or something like.

Two other notes:

  1. GetProcAddress returns 0 if it couldn't find the function, so you can use this to scan through a list of DLLs (e.g., returned from FindFirstFile and friends) looking for DLLs that support the interface, and/or try to find multiple entry points and support multiple types per plugin.
  2. if the main program is going to delete the sprite using the delete operator, this requires that both DLL and main program are built using the same runtime library, so that the main program's delete and the DLL's new are both using the same heap. You can work around this to some extent by having the DLL provide a DeleteSprite function, that deletes the sprite using the DLL's runtime library rather than the main program's, but you still have to take care about the rest of your program, so you may just want to force DLL writers to use the same runtime library as your program. (I wouldn't say you'd be in good company by doing this, but it's not uncommon. 3D Studio MAX requires this, for example.)
brone
Good point about delete.
j_random_hacker