C++ : Extend class in dll

Started by
8 comments, last by yellowsputnik 7 years, 6 months ago

Hello,

I have a program that defines a class

class GuiItem {

...

};

and then I extend this class for specific items

class TextBox : public GuiItem {

...

};

All works well, but I would like to implement a sort of extension system so that one can write it's own dll with the new specific GuiItem.

Then, just placing the dll in the program folder I would like to use this new item.

How can I do this?

Thank you.

Advertisement

Typically this is achieved through some form of dynamic type information, be it using templates + typeid() or some other form of unique type identification

The basic idea consists of two classes. A "Manager" and a "Factory". The manager stores a collection of factories and associated type identification, and the factories know how to instantiate the type you need.

The following is a complete example using std::type_info. Note that you will need C++11 because of hash_code(). If you must use c++03 then you can replace hash_code() with name() and run that through your own hash function (or change the type in the container from size_t to std::string).


#include <typeinfo>
#include <map>
#include <string>
#include <iostream>


class Factory {
public:
    virtual void* Create() = 0;
};


template <class T>
class FactoryImpl : public Factory
{
    virtual void* Create() override
    {
        return new T();
    }
};


class Manager
{
public:
    template <class T>
    void RegisterObjectFactory()
    {
        factoryMap_[typeid(T).hash_code()] = new FactoryImpl<T>();
    }


    template <class T>
    T* CreateObject() const
    {
        auto factory = factoryMap_.find(typeid(T).hash_code());
        if(factory == factoryMap_.end())
            return NULL;
        return static_cast<T*>(factory->second->Create());
    }


    template <class T>
    void DestroyObject(T* object)
    {
        delete object;
    }


private:
    std::map<size_t, Factory*> factoryMap_;
};


// ----------------------------------------------------------------------------
// Example usage
// ----------------------------------------------------------------------------
class Test
{
public:
    void Greet() { std::cout << "it works!" << std::endl; }
};


int main()
{
    Manager manager;
    manager.RegisterObjectFactory<Test>();
    Test* test = manager.CreateObject<Test>();


    test->Greet();


    manager.DestroyObject(test);
}

One pitfall to look out for with factories is to make sure you delete the object in the same DLL you created it. This is why Manager::DestroyObject() is necessary. If you were to convert it from raw pointers to std::shared_ptr then this would no longer be necessary to do.

With this method you would need to pass the manager instance to your plugin DLLs when they start (typically achieved by defining your own start_plugin() function). Then your plugins can call Manager::RegisterObjectFactory<Whatever>() to register the types they require. This will enable the core application to start instantiating types that weren't originally there when compiling.

You might consider extending the example to make it possible to pass the actual class name to the manager, and not just the has code. For example:


manager.CreateObject("Test");

instead of


manager.CreateObject<Test>();

For this you would need to store typeid(T).name() in the corresponding factory class.

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

A slightly more concrete example, which also walks through the differences between the naive and mature approaches...

http://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL

RIP GameDev.net: launched 2 unusably-broken forum engines in as many years, and now has ceased operating as a forum at all, happy to remain naught but an advertising platform with an attached social media presense, headed by a staff who by their own admission have no idea what their userbase wants or expects.Here's to the good times; shame they exist in the past.

Ah, I should also mention that the code I posted is mostly taken from the Urho3D project, which is heavily based on this templated factory paradigm. You should check out their code under Source/Urho3D/Core/Context.cpp

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty

Look into virtual abstract base classes. The dll can return a pointer to a descendant in a factory function and the host can use it because it knows the base class. (EDIT: I see Wyrframe's link talks about them, so read that).

The main reason for this is to support dlls made in different languages and with different compilers than yours.

Do make sure you define calling conventions and data (struct) alignment. For calling conventions, cdecl and stdcall are good choices.

 

Thank you all, I will read and try to implement.

in addition to already commented, you can use shared_ptr to wrap your types to avoid pitfalls between different runtimes in windows

Or implement your own wrapper and provide the type erasure yourself, I see this approach cleaner and less error-prone that relying on an API deletor

Does this have to be C++?

I only ask because C++ does not have an ABI and inheriting across dlls can be a pain in the arse.

For something like this, C# would be much less painful.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

Look into virtual abstract base classes. The dll can return a pointer to a descendant in a factory function and the host can use it because it knows the base class. (EDIT: I see Wyrframe's link talks about them, so read that).

The main reason for this is to support dlls made in different languages and with different compilers than yours.

Do make sure you define calling conventions and data (struct) alignment. For calling conventions, cdecl and stdcall are good choices.


Is this, in fact, not what COM technology is all about? Although it includes additional features such as reference counting as well.

Look into virtual abstract base classes. The dll can return a pointer to a descendant in a factory function and the host can use it because it knows the base class. (EDIT: I see Wyrframe's link talks about them, so read that).

The main reason for this is to support dlls made in different languages and with different compilers than yours.

Do make sure you define calling conventions and data (struct) alignment. For calling conventions, cdecl and stdcall are good choices.


Is this, in fact, not what COM technology is all about? Although it includes additional features such as reference counting as well.

The reason this works is in fact COM: the ABI for virtual abstract classes was standardized in Windows compilers to support COM interfaces. At least that's what I heard a number of years ago.

 

This topic is closed to new replies.

Advertisement