Boost.serialization 未注册 class 异常,在运行时链接的共享库中定义了序列化 class

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

我正在尝试创建一个模块化游戏系统,我希望用户定义的 classes 能够被序列化。为此,我放置了从多态基础 class 派生的 classes。在尝试对此 class 进行序列化时,我 运行 遇到了麻烦。我不断收到未注册的 class 异常(运行时错误)。

这是一个最小的测试用例:

环境:windows8.1 MSVC++ 12(visual studio2013)

parent_class.h -- 定义parent_class class 即多态

#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 - .exe

中唯一的.cpp
#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;
}

最后是我的 child_class.cpp -- .dll

中唯一的 .cpp
#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);
}

所有代码都应该是不言自明的——如果您有任何问题,请随时发表评论。

抱歉,代码太多了——我真的没法删减。

好吧,千辛万苦找到解决办法了,就两行代码。

问题似乎是存储 class 的 GUID 与实现之间关联的映射被定义了两次,因为它驻留在 .lib 文件中,.dll 和 . exe被链接到。显而易见的解决方案是使用 Boost.serialization 库的动态链接支持。

如何使用它是在每个使用序列化库的.cpp 文件的开头放置一个#define BOOST_SERIALIZATION_DYN_LINK 1。这样代码只被实例化一次,boost就可以找到map中的class.

"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."

这个问题实际上是静态链接工作方式的问题。您强制使用 DLL 版本的解决方案会起作用,但它不是最好的。 不使用内联序列化函数可以更好地解决该问题。所以替换

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);
}

template <typename Archive>
void serialize(Archive& ar, const unsigned int version);

并包含在您的 child *.cpp 文件中

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);
}
template child_class::serialize<boost::archive::text_oarchive>;
template child_class::serialize<boost::archive::text_iarchive>;

换句话说 - 避免使用内联函数。这将保证您将获得一个且只有一个序列化函数的实例化。这将适用于静态和共享库构建。它还将消除有时因违反 "one definition rule"

而引起的混淆

顺便说一句,不要在源文件中添加诸如 e#define BOOST_SERIALIZATION_DYN_LINK 1 之类的内容,而是通过 make 或项目文件在编译器命令行中添加。这样您就可以保留将代码用作静态库、共享库或直接导入到项目中的能力。