C++: Dynamically loading classes from dlls

2019-01-31 09:22发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

[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.)


回答4:

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