Home > Software engineering >  Dynamic load a class from a dll in Windows
Dynamic load a class from a dll in Windows

Time:05-17

I want to implement a multiplatform plugin system on an application that I am working, but I am unable to make it work on Windows.

The proposal of this plugin system is to add the posibility of to compile the library and load it in the main program without to have to recompile it (dynamic load).

I have modified an example I have found in internet, and it compiles and works without problem in Linux, but on Windows it crash when the load function is executed.

My code looks like this:

export.h

#ifndef _SHARED_EXPORTS_H__
#define _SHARED_EXPORTS_H__

#ifdef _WIN32
#include <windows.h>
#include <string>

#define LIBLOAD(x) LoadLibraryA(x)
#define dlclose(x) FreeLibrary((HMODULE)x)
#define dlsym(x, y) GetProcAddress((HINSTANCE)x, y)

char *dlerror()
{
    DWORD errorMessageID = GetLastError();
    if (errorMessageID == 0)
    {
        return NULL; // No error message has been recorded
    }

    LPSTR messageBuffer = nullptr;
    // Ask Win32 to give us the string version of that message ID.
    // The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
    size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

    // Copy the error message into a std::string.
    std::string message(messageBuffer, size);

    // Free the Win32's string's buffer.
    LocalFree(messageBuffer);

    char *cstr = new char[message.length()   1];
    strcpy(cstr, message.c_str());

    // char *cstr = (char *)message.c_str();
    fprintf(stderr, "Error: %s\n\n", cstr);

    return cstr;
}

#ifdef BUILD_LIB
#define SHARED_EXPORT __declspec(dllexport)
#else
#define SHARED_EXPORT __declspec(dllimport)
#endif
#else
#include <dlfcn.h>
#define SHARED_EXPORT
#define LIBLOAD(x) dlopen(x, RTLD_LAZY)
#endif

#endif /* _SHARED_EXPORTS_H__ */

main.cpp

/*
 *
 * Main application which will load the plugins dinamically
 *
 */

#include <vector>
#include "plugin_handler.hpp"

#ifdef _WIN32
#define EXT ".dll"
#else
#define EXT ".so"
#endif

int main()
{
    auto plugins = load_plugins("plugins/", EXT);
    for (auto ph : plugins)
    {
        fprintf(stderr, "Loading plugin...\n");
        auto plugin = ph.load();
        if (plugin == NULL)
        {
            fprintf(stderr, "The plugin is not loaded correctly\n");
            continue;
        }
        fprintf(stderr, "Plugin loaded\n");
        fprintf(stderr, "Auto loaded plugin: %s, version: %s\n", ph.get_name().c_str(), ph.get_version().c_str());
        fprintf(stderr, "Running plugins command method:\n");
        fprintf(stderr, "%s\n", plugin->command("Command here", "options here").c_str());
    }
    return 0;
}

plugin_handler.hpp

#include <string>
#include "plugin.hpp"
#include <filesystem>

class PluginHandler
{
    std::shared_ptr<Plugin> (*_load)() = NULL;
    void *handle = NULL;
    char *(*_get_name)() = NULL;
    char *(*_get_version)() = NULL;
    char *last_error = NULL;

    std::shared_ptr<Plugin> instance;

public:
    PluginHandler(std::string name)
    {
        handle = LIBLOAD(name.c_str());
        if (!handle || ((last_error = dlerror()) != NULL))
        {
            // Maybe the last_error variable is NULL because the handler is empty directly.
            // In that case, try to return the error again
            if (last_error == NULL)
            {
                last_error = dlerror();
            }

            // If the error still null here, then just add a general error text
            if (last_error == NULL)
            {
                last_error = (char *)"Handler is empty. Maybe the library file is damaged.";
            }
            fprintf(stderr, "There was an error loading the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        dlerror(); /* Clear any existing error */

        _load = (std::shared_ptr<Plugin>(*)())dlsym(handle, "load");
        if (!_load)
        {
            printf("La cagaste\n");
        }
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the load symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _get_name = (char *(*)())dlsym(handle, "name");
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the name symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _get_version = (char *(*)())dlsym(handle, "version");
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the version symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }
    }

    ~PluginHandler()
    {
        instance.reset();
        if (handle != NULL)
        {
            dlclose(handle);
        }
    }

    std::string get_name()
    {
        return std::string(_get_name());
    }

    std::string get_version()
    {
        return std::string(_get_version());
    }

    std::shared_ptr<Plugin> load()
    {
        if (!instance && _load != NULL)
        {
            fprintf(stderr, "Iniatilizing the class %d\n", _load);
            instance = _load();
            fprintf(stderr, "Initialized...\n");
        }

        return instance;
    }

    bool has_error()
    {
        if (last_error != NULL)
        {
            return true;
        }
        return false;
    }

    char *get_error()
    {
        if (last_error == NULL)
        {
            return (char *)'\0';
        }
        else
        {
            return last_error;
        }
    }

    // Use it under your risk... If an error was set maybe something happens.
    void clear_error()
    {
        last_error = NULL;
    }
};

std::vector<PluginHandler> load_plugins(std::string path, std::string extension)
{
    std::vector<PluginHandler> plugins;

    for (auto &p : std::filesystem::recursive_directory_iterator(path))
    {
        if (p.path().extension() == extension)
        {
            PluginHandler plugin = PluginHandler(p.path().string());
            if (!plugin.has_error())
            {
                plugins.push_back(plugin);
            }
            else
            {
                fprintf(stderr, "There was an error loading the plugin %s\n", p.path().string().c_str());
            }
        }
    }

    return plugins;
}

plugin.hpp

/*

  This header file is the virtual plugin definition which will be used in derivated plugins and main program

*/

#include "export.h"
#include <string>
#include <memory>

class Plugin
{
public:
    Plugin(){};
    virtual ~Plugin(){};
    virtual std::string command(std::string command, std::string options) { return ""; }
};

#define DEFINE_PLUGIN(classType, pluginName, pluginVersion)                 \
    extern "C"                                                              \
    {                                                                       \
        std::shared_ptr<Plugin> SHARED_EXPORT load()                        \
        {                                                                   \
            fprintf(stderr, "Creating the pointer\n");                      \
            std::shared_ptr<Plugin> output = std::make_shared<classType>(); \
            fprintf(stderr, "Pointer was created. Returning it...\n");      \
            return output;                                                  \
        }                                                                   \
                                                                            \
        const char SHARED_EXPORT *name()                                    \
        {                                                                   \
            return pluginName;                                              \
        }                                                                   \
                                                                            \
        const char SHARED_EXPORT *version()                                 \
        {                                                                   \
            return pluginVersion;                                           \
        }                                                                   \
    }

plugin1.cpp

#include "plugin.hpp"
#include <iostream>

class SHARED_EXPORT Plugin1 : public Plugin
{
public:
    virtual std::string command(std::string command, std::string options)
    {
        return command   " "   options;
    }
};

DEFINE_PLUGIN(Plugin1, "Plugin1", "0.0.1")

I am compiling both versions on Linux and the commands are:

Linux .so

g   -fPIC -c plugin1.cpp -o plugin1.o
g   -shared -o plugins/plugin1.so plugin1.o

g   main.cpp -ldl -std=c  17 -o main

Windows .dll

x86_64-w64-mingw32-g   -fPIC -DBUILD_LIB -g -static-libgcc -static-libstdc   -c plugin1.cpp -o plugin1.o -Wl,--out-implib,plugin1.a
x86_64-w64-mingw32-g   -DBUILD_LIB -g -shared -static-libgcc -static-libstdc   -o plugins/plugin1.dll plugin1.o

x86_64-w64-mingw32-g   main.cpp -g -static-libgcc -static-libstdc   -std=c  17 -o main.exe

On linux the execution works fine:

$ ./main
Loading plugin...
Iniatilizing the class -2057853339
Creating the pointer
Pointer was created. Returning it...
Initialized...
Plugin loaded
Auto loaded plugin: Plugin1, version: 0.0.1
Running plugins command method:
Command here options here

But on Windows looks like it loads the dll library, but on "load" function execution it just crash:

>main.exe
Loading plugin...
Iniatilizing the class 1801459034

(Here returns to command line again)

Looks like the problem is when it tries to execute the "load" function, because It doesn't executes the first print I have added into that function. The problem is that I don't know where is the problem and how to debug it. I have noticed that in every execution the handler is the same, so maybe the library is on memory and is not unloaded or maybe is not even loading it and is failing. It is supposed to be NULL if fails, but now I am not sure if it is working.

What I am doing wrong?

Best regards!

CodePudding user response:

When you call dlclose from ~PluginHandler the code of the shared object can be deleted from the address space, so calling any functions from the shared object can lead to segmentation fault. Possible fix:

@@ -67,7  67,7 @@
         instance.reset();
         if (handle != NULL)
         {
-            dlclose(handle);
 //          dlclose(handle);
         }
     }

CodePudding user response:

Finally I have found the way to do it and I have fixed some mistakes I made.

export.h

#ifndef _SHARED_EXPORTS_H__
#define _SHARED_EXPORTS_H__

#ifdef _WIN32
#include <windows.h>
#include <string>

#define LIBLOAD(x) LoadLibrary(x)
#define dlclose(x) FreeLibrary((HMODULE)x)
#define dlsym(x, y) GetProcAddress((HINSTANCE)x, y)
char *dlerror()
{
    DWORD errorMessageID = GetLastError();
    if (errorMessageID == 0)
    {
        return NULL; // No error message has been recorded
    }

    LPSTR messageBuffer = nullptr;
    // Ask Win32 to give us the string version of that message ID.
    // The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
    size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

    // Copy the error message into a std::string.
    std::string message(messageBuffer, size);

    // Free the Win32's string's buffer.
    LocalFree(messageBuffer);

    char *cstr = new char[message.length()   1];
    strcpy(cstr, message.c_str());

    // char *cstr = (char *)message.c_str();
    fprintf(stderr, "Error: %s\n\n", cstr);

    return cstr;
}

#ifdef BUILD_LIB
#define SHARED_EXPORT __declspec(dllexport)
#else
#define SHARED_EXPORT __declspec(dllimport)
#endif
#else
#include <dlfcn.h>
#define SHARED_EXPORT
#define LIBLOAD(x) dlopen(x, RTLD_LAZY)
#endif

#endif /* _SHARED_EXPORTS_H__ */

main.cpp

/*
 *
 * Main application which will load the plugins dinamically
 *
 */

#include <vector>
#include "plugin_handler.hpp"

#ifdef _WIN32
#define EXT ".dll"
#else
#define EXT ".so"
#endif

int main()
{

    auto plugins = load_plugins("plugins/", EXT);
    for (auto ph : plugins)
    {
        fprintf(stderr, "Loading plugin...\n");
        auto plugin = ph->load();
        if (plugin == NULL)
        {
            fprintf(stderr, "The plugin is not loaded correctly\n");
            continue;
        }
        fprintf(stderr, "Plugin loaded\n");
        fprintf(stderr, "Auto loaded plugin: %s, version: %s\n", ph->get_name().c_str(), ph->get_version().c_str());
        fprintf(stderr, "Running plugins command method:\n");
        fprintf(stderr, "%s\n", plugin->command("Command here", "options here").c_str());

        delete ph;
    }

    return 0;
}

plugin_handler.h

#include <string>
#include "plugin.hpp"
#include <filesystem>
#include <memory>

class PluginHandler
{
    using allocClass = Plugin *(*)();
    using deleteClass = void (*)(Plugin *);
    using charPReturn = char *(*)();

    allocClass _load = NULL;
    deleteClass _unload = NULL;
    void *handle;
    charPReturn _get_name = NULL;
    charPReturn _get_version = NULL;
    char *last_error = NULL;

    std::shared_ptr<Plugin> instance;

public:
    PluginHandler(std::string name)
    {
        if (!(handle = LIBLOAD(name.c_str())))
        {
            last_error = dlerror();

            // If the error still null here, then just add a general error text
            if (last_error == NULL)
            {
                last_error = (char *)"Handler is empty. Maybe the library file is damaged.";
            }
            fprintf(stderr, "There was an error loading the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _load = reinterpret_cast<allocClass>(dlsym(handle, "load"));
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the load symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _unload = reinterpret_cast<deleteClass>(dlsym(handle, "unload"));
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the load symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _get_name = reinterpret_cast<charPReturn>(dlsym(handle, "name"));
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the name symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }

        _get_version = reinterpret_cast<charPReturn>(dlsym(handle, "version"));
        if ((last_error = dlerror()) != NULL)
        {
            fprintf(stderr, "Error getting the version symbol in the %s lib:\n%s\n", name.c_str(), last_error);
            return;
        }
    }

    ~PluginHandler()
    {
        if (last_error != NULL)
        {
            delete[] last_error;
        }

        /*
        if (handle != NULL)
        {
            dlclose(handle);
            handle = NULL;
        }
        */
    }

    std::string get_name()
    {
        return std::string(_get_name());
    }

    std::string get_version()
    {
        return std::string(_get_version());
    }

    std::shared_ptr<Plugin> load()
    {
        if (_load != NULL)
        {
            instance = std::shared_ptr<Plugin>(_load(), _unload);
            return instance;
        }

        return NULL;
    }

    void unload()
    {
        if (_unload != NULL)
        {
            _unload(instance.get());
        }
    }

    bool has_error()
    {
        if (last_error != NULL)
        {
            return true;
        }
        return false;
    }

    char *get_error()
    {
        if (last_error == NULL)
        {
            return (char *)'\0';
        }
        else
        {
            return last_error;
        }
    }

    // Use it under your risk... If an error was set maybe something happens.
    void clear_error()
    {
        delete[] last_error;
        last_error = NULL;
    }
};

std::vector<PluginHandler *> load_plugins(std::string path, std::string extension)
{
    std::vector<PluginHandler *> plugins;

    for (auto &p : std::filesystem::recursive_directory_iterator(path))
    {
        if (p.path().extension() == extension)
        {
            PluginHandler *plugin = new PluginHandler(p.path().string());
            if (!plugin->has_error())
            {
                plugins.push_back(plugin);
            }
            else
            {
                fprintf(stderr, "There was an error loading the plugin %s\n", p.path().string().c_str());
            }
        }
    }

    return plugins;
}

plugin.hpp

#pragma once

/*

  This header file is the virtual plugin definition which will be used in derivated plugins and main program

*/

#include "export.h"
#include <string>
#include <memory>

class Plugin
{
public:
    virtual ~Plugin() = default;
    virtual std::string command(std::string command, std::string options) = 0;
};

#define DEFINE_PLUGIN(classType, pluginName, pluginVersion) \
    extern "C"                                              \
    {                                                       \
        SHARED_EXPORT classType *load()                     \
        {                                                   \
            printf("Creating new class pointer\n");         \
            return new classType();                         \
        }                                                   \
                                                            \
        SHARED_EXPORT void unload(classType *ptr)           \
        {                                                   \
            delete ptr;                                     \
        }                                                   \
                                                            \
        const char SHARED_EXPORT *name()                    \
        {                                                   \
            return pluginName;                              \
        }                                                   \
                                                            \
        const char SHARED_EXPORT *version()                 \
        {                                                   \
            return pluginVersion;                           \
        }                                                   \
    }

plugin1.cpp

#include "plugin.hpp"
#include <iostream>

class SHARED_EXPORT Plugin1 : public Plugin
{
public:
    virtual std::string command(std::string command, std::string options)
    {
        return command   " "   options;
    }
};

DEFINE_PLUGIN(Plugin1, "Plugin1", "0.0.1")

Works better now and all the functions works as expected on Linux and Windows. After fixing the real problem, I am not sure how was working on Linux because the object creation was on a function which was going out of scope as soon the object is created. After converting this object to a pointer, the program started to work. Maybe the other fixes also helped, but this was the main.

One think I don't understand, is why to free the library with dlclose at the object destruction created a segmentation fault when the program exits. Removing that line exits the program as expected, but I am afraid to create a memory leak there.

Best regards.

  • Related