Search code examples
c++serializationboostdynamic-linkingboost-serialization

Boost.serialization unregistered class exception with serialized class defined in a runtime-linked shared library


I am trying to create a modular game system, and I would like user - defined classes to be able to be serialized. To to this, I am placing classes derrived from a polymorphic base class. I am running into troubles while trying to implement serialization on this class. I keep getting the unregistered class exception (a runtime error).

Here is a minimal test case:

Environment: windows 8.1 MSVC++ 12 (visual studio 2013)

parent_class.h -- defines the parent_class class that is polymorphic

#pragma once

#include <boost/serialization/access.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/export.hpp>

class parent_class
{

protected:

    friend boost::serialization::access;

    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version)
    {
        ar & BOOST_SERIALIZATION_NVP(x) & BOOST_SERIALIZATION_NVP(y);
    }

    float x;
    float y;

public:

    explicit parent_class(float x, float y) : x(x), y(y) {}

    // virtual deconstructor to make it polymorphic
    virtual ~parent_class()
    {
    }
};

BOOST_CLASS_EXPORT(parent_class);

Main.cpp - the only .cpp in the .exe

#include "parent_class.h"

#include <boost/archive/xml_oarchive.hpp>

#include <fstream>
#include <iostream>

#include <Windows.h>

typedef parent_class* addChildFun(float, float, float);

int main()
{
    // acquire module
    HMODULE module = LoadLibraryA("SerializationDLL.dll");
    assert(module);

    // acquire function ptr
    FARPROC addChildRaw = GetProcAddress(module, "makeChild");
    assert(addChildRaw);
    addChildFun* addChildPtr = reinterpret_cast<addChildFun*>(addChildRaw);

    // make polymorphic pointer
    parent_class* child = addChildPtr(325.f, 214.f, 2.5f);

    // INIT BOOST SERIALIZIZATION ARCHIVE
    std::ofstream stream{ "file.txt" };
    boost::archive::xml_oarchive arch{ stream };

    try
    {

        arch << BOOST_SERIALIZATION_NVP(child);

    }
    catch (std::exception& e)
    {
        std::cout << e.what(); // prints "unregistered class - derived class not registered or exported
    }


    std::cin.get();
    delete child;
}

And finally here is my child_class.cpp -- the only .cpp in the .dll

#include <parent_class.h>
#include <boost/archive/xml_oarchive.hpp>


class child_class : public parent_class
{
    friend boost::serialization::access;

public:

    float z;

    explicit child_class(float x, float y, float z)
        : parent_class(x, y),
        z(z)
    {
    }

    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version)
    {
        ar & boost::serialization::make_nvp("owner", boost::serialization::base_object<parent_class>(*this));

        ar & BOOST_SERIALIZATION_NVP(z);
    }

    virtual ~child_class() override
    {

    }

};

// export the class
BOOST_CLASS_EXPORT(child_class)

// yes I am using MSVC -- hence dllexport
extern "C" __declspec(dllexport) parent_class* makeChild(float x, float y, float z)
{
    return new child_class(x, y, z);
}

All of the code should be pretty self-explanatory -- If you have any questions, feel free to comment.

Sorry that it's a lot of code -- I couldn't really cut down.


Solution

  • Alright, after a painstaking amount of effort, I found the solution, and it resided in two lines of code.

    The problem seemed to be that the map that stored the associations between the GUID for a class and the implementation was defined twice, because it resided in a .lib file that both the .dll and the .exe were linked to. The obvious solution to this is using the dynamic linking support for the Boost.serialization library.

    How to use this is to put a #define BOOST_SERIALIZATION_DYN_LINK 1 at the beginning of every .cpp file that used the serialization library. This way, the code is only instantiated once, and boost can find the class in the map.