Boost logo

Boost :

Subject: Re: [boost] [Serialization] (Commented) assertion when using types in multiple DLLs on Windows (Ticket #3934 and #4394)
From: Martin B. (0xCDCDCDCD_at_[hidden])
Date: 2011-12-14 03:03:01


On 13.12.2011 18:22, Robert Ramey wrote:
> Martin B. wrote:
>> [Serialization] (Commented) assertion when using types in multiple
>
>> Robert writes in his comment:
>>
>>> The problem is that the way the code is structured,
>>> you'll have multiple instances of some functions.
>>> The best way would be eliminate the source of the
>>> problem by structuring code like this:
>>> ...
>>>
>>
>> and he proposes to move the `template<class Archive> void serialize`
>> function of the classes from the header file into the cpp file.
>>
>> However, *our* code already does this, and we get this assertion
>> regardless. The reason I suspect is that both the main module code as
>> well as the DLL module code are calling into the recursive_register
>> function to register the type, and since the code for the class is
>> (identically) duplicated in the DLL as well as in the executable,
>> obviously it is registered twice.
>
> You might want to investigate this a little more. The "registration"
> occurs before main(..) is called. The static object which does this
> is created as a side-effect of invoking serialization. (...)

The code that does this is invoked as a side effect of
BOOST_CLASS_EXPORT_IMPLEMENT. Obviously, I must call this, as otherwise
my code wouldn't work anymore when the serialization is actually done :-)

>
> As I said that is my belief - I feel that I could be proved wrong
> and perhaps you've done just that. or perhaps not. So I would
> be interested in seeing what you find when you get to the bottom
> of it. Here a couple of ideas you might try.
>
> Using the debugger, trap the code at the registration point and
> verify where each module is invoking the registration from. (...)

Done this. Here are the call stacks from my test project: (sorry for the
long lines - hop not too messed up)

(1) This is from the DLL init
-----------------------------
>
boost_serialization-vc80-mt-gd-1_44.dll!boost::serialization::void_cast_detail::void_caster::recursive_register(bool
includes_virtual_base=true) Line 229
 
BoostSerializeMainDll.dll!boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>::void_caster_virtual_base<Derived,Base>()
  Line 238 + 0xd bytes
 
BoostSerializeMainDll.dll!boost::serialization::detail::singleton_wrapper<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::singleton_wrapper<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base> >() + 0x4a bytes
 
BoostSerializeMainDll.dll!boost::serialization::singleton<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::get_instance() Line 128 + 0x28 bytes
   BoostSerializeMainDll.dll!`dynamic initializer for
'boost::serialization::singleton<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::instance''() Line 149 + 0x23 bytes
   msvcr80d.dll!_initterm
   BoostSerializeMainDll.dll!_CRT_INIT
   BoostSerializeMainDll.dll!__DllMainCRTStartup
   BoostSerializeMainDll.dll!_DllMainCRTStartup

(2) this is the second place (with the assert) from the exe init
-------------------------------------
> msvcr80d.dll!_wassert(const wchar_t * expr=0x003c3854, const wchar_t
* filename=0x003c3808, unsigned int lineno=230) Line 370C
 
boost_serialization-vc80-mt-gd-1_44.dll!boost::serialization::void_cast_detail::void_caster::recursive_register(bool
includes_virtual_base=true) Line 230 + 0x1d bytesC++
 
BoostSerializeMainExe.exe!boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>::void_caster_virtual_base<Derived,Base>()
  Line 238 + 0xd bytesC++
 
BoostSerializeMainExe.exe!boost::serialization::detail::singleton_wrapper<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::singleton_wrapper<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base> >() + 0x4a bytesC++
 
BoostSerializeMainExe.exe!boost::serialization::singleton<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::get_instance() Line 128 + 0x28 bytesC++
   BoostSerializeMainExe.exe!`dynamic initializer for
'boost::serialization::singleton<boost::serialization::void_cast_detail::void_caster_virtual_base<Derived,Base>
>::instance''() Line 149 + 0x23 bytesC++
   msvcr80d.dll!_initterm
   BoostSerializeMainExe.exe!__tmainCRTStartup()
   BoostSerializeMainExe.exe!mainCRTStartup()

>
> In a large program, (...)
> As I said, I believe this assertion can be trapped only if
> the serialization code is included in multiple modules.
>

The types declared to be serialized *are* included in several modules.
But they are used independently.

>> ## Question: Am I correct?
>
> lol - as far as anyone else is. We're sort of in uncharted
> territory.
>
>> So, the first question obviously is whether I'm correct in my
>> analysis.
>> I.e., the `assert(result.second);`that is currently commented out can
>> *never* work properly for types that live in two modules (DLLs) in the
>> same process and Boost:serialization is used as DLL.
>
> I"m not sure what "never work" means. I would phrase this as:
> the assert will trap whenever serialization for the same type is include
> in more than one module.
>

Yes, this captures it. So what would the solution be? Make sure
BOOST_CLASS_EXPORT_IMPLEMENT is only used in one module?

>
>> ## Question: Any harm done?
>> (...)
>
> Care has been taken to be sure that this will work as one would expect.
> That is, when a module is unloaded, the registry entry dropped is the
> same one that was created when the module was loaded. (...)

OK, that would mean that when the DLL is unloaded, it *also* drops the
registration for the type in the executable module, meaning no
serialization for the type will work after the DLL has been unloaded,
although the type itself is still there.

> (...)
> A more interesting question is: What if serialization code for the same
> is instantiated in multiple modules? I have taken care that that
> should create no problem. Again, I haven't explicitly tested this.
> On the other hand, it doesn't seem that anyone has reportd a problem
> since I commented out the assertion. for whatever that's worth.
> My motivation for including the assertion was the possibility
> that one might have different versions of the serialization
> code in different DLLS. That seemed to me to be a situation
> which would/could errors which would be almost impossible
> to debug. Hence the assertion and my advice to structure
> the code to avoid this kind of problem. (...)

As far as I see this, the only way to avoid this problem would be for
the types to be serialized (or at least their serialization code) to be
put in a separate, "third" DLL, that could be used by DLLs and the exe
module. This is overhead, but might be well worth a try. (Though I'm not
sure how this would work well together with the helper macros and all
the templates in Boost Serialization -- but I haven't tried.)

>
>> ## Question: Solutions?
>> (...)
> I would like to see you invest some more effort to see
> exactly where the extra registration comes from.

Well, see my call-stack above and I'll dump a sourcecode sketch below.
This does reproduce the thing for me, but other that ignoring the assert
(upgrading to boost 1.48) I haven't tried any approaches yet.

cheers,
Martin

ps: Sourcecode (files separated by ++++++++)

a) 2 Projects, one executable and one DLL that is "statically" used by
the exe.

EXE:
Types.cpp
main.cpp

DLL:
Types.cpp
dll_main.cpp

b) An abstract base class and a derived class that are used in the EXE
as well as in the DLL, but are compiled and linked for both.

c) The exe uses these types in serialization and the DLL as well.

++++++++
// Types.h
#pragma warning( disable : 4250 )
#define BOOST_SERIALIZATION_DYN_LINK
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/scoped_ptr.hpp>
#include <boost/serialization/export.hpp>

class Base
{
public:
        virtual ~Base() {}

        virtual void DoStuff() = 0;

private:
        friend class boost::serialization::access;

        template<class Archive>
   void serialize(Archive & ar, const unsigned int file_version);
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base);

struct SData {
        boost::scoped_ptr<Base> obj;
};
BOOST_CLASS_TRACKING(SData, boost::serialization::track_never);

namespace boost {
namespace serialization {

template<class Archive>
void serialize(Archive & ar, SData & data, const unsigned int version)
{
        assert(version == 0);
        ar & data.obj;
}

} // namespace serialization
} // namespace boost

class Derived : virtual public Base {
public:
        Derived();

        virtual void DoStuff();

private:
        int m_number;

private:
        friend class boost::serialization::access;

        template<class Archive>
   void serialize(Archive & ar, const unsigned int file_version);
};

BOOST_CLASS_EXPORT_KEY(Derived);

++++++++
// Types.cpp
#include "Types.h"

template<class Archive>
void Base::serialize(Archive & ar, const unsigned int file_version)
{ /*empty*/ }

BOOST_CLASS_EXPORT_IMPLEMENT(Derived);

Derived::Derived()
: m_number(42)
{ }

void Derived::DoStuff() {
        std::cout << static_cast<void*>(this) << " - Derived::DoStuff:\n" << "
  current value = " << m_number << "\n";
        ++m_number;
        std::cout << " new value = " << m_number << "\n";
}

template<class Archive>
void Derived::serialize(Archive & ar, const unsigned int version)
{
        ar & boost::serialization::base_object<Base>(*this);

        assert(version == 0);
        ar & m_number;
}

+++++++++
main.cpp (executable)
#include ...
#include "Types.h"

int main() {
        SData d1;
        d1.obj.reset(new Derived());
        d1.obj->DoStuff();

        std::ostringstream out_buf;
        boost::archive::text_oarchive out_archive(out_buf);
        out_archive << d1;
        const std::string data = out_buf.str();

        std::istringstream in_buf(data);
        boost::archive::text_iarchive in_archive(in_buf);
        SData d2;
        in_archive >> d2;

        d2.obj->DoStuff();
        d1.obj->DoStuff();
}

++++++++
// dll_main.cpp
#include ...
#include "Types.h"

void dll_fn_that_uses_serialization() {
        SData d1;
        d1.obj.reset(new Derived());
        d1.obj->DoStuff();

        std::ostringstream out_buf;
        boost::archive::text_oarchive out_archive(out_buf);
        out_archive << d1;
        const std::string data = out_buf.str();

        std::istringstream in_buf(data);
        boost::archive::text_iarchive in_archive(in_buf);
        SData d2;
        in_archive >> d2;

        d2.obj->DoStuff();
        d1.obj->DoStuff();
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk