I'm deploying a C++ app on Android, it uses boost::date_time. It has lots of libraries, some being linked at compile time (shared libraries), others, sort of plugins, being loaded dynamically at runtime through dlopen. In some libraries, setting a boost::posix_time::time_facet to a std::ostream (using imbue) in order to customize a boost::posix_time::ptime display has no effect (is ignored). I could isolate the issue in the following MCVE:
bug_datetime_base is a shared library that uses boost::date_time but only compiles code redirecting a boost::posix_time::ptime to a std::ostream (no boost::posix_time::time_facet used):
MyClass::MyClass( const boost::posix_time::ptime& timeInfo )
{
std::cout << timeInfo;
}
bug_datetime_lib is a shared library that uses boost::date_time and exports a function that will use a boost::posix_time::time_facet to redirect a boost::posix_time::ptime to a std::ostream with a specific formatting:
#include <sstream>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <QDebug>
void TestBoost()
{
boost::posix_time::ptime t1(boost::gregorian::date(2002,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,4));
std::stringstream temp;
temp << "FROM TestBoost:" << std::endl << "Unformatted:" << t1 << std::endl;
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y$%b$%d %H:%M:%S.TestBoost.%f");
const std::locale loc = std::locale(std::locale::classic(), facet);
temp.imbue(loc);
temp << "Formatted:" << t1;
qDebug() << temp.str().c_str();
}
bug_datetime_wrapper is a shared library that just links to bug_datetime_base and bug_datetime_lib, does nothing more:
MyWrapperClass::MyWrapperClass()
{
}
bug_datetime is the main program that uses boost::date_time, links to bug_datetime_base and dynamically loads bug_datetime_wrapper through dlopen:
#include <sstream>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <QDebug>
#include <QApplication>
#include <dlfcn.h>
typedef void* dllHandle;
int main( int argc, char* argv[] )
{
QApplication app( argc, argv );
void* wrapperPtr = NULL;
// Workaround2:
// if commenting line below, bug_datetime_wrapper is not loaded, using imbue from any places works perfectly
wrapperPtr = dlopen( "libbug_datetime_wrapper_armeabi-v7a.so", 0);
if ( wrapperPtr )
qDebug() << "Loaded bug_datetime_wrapper, then formatting will fail";
else
qDebug() << "Failed to load bug_datetime_wrapper, then formatting will work";
{
boost::posix_time::ptime t1(boost::gregorian::date(2002,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,4));
std::stringstream temp;
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y$%b$%d %H:%M:%S.main.%f");
const std::locale loc = std::locale(std::locale::classic(), facet);
temp.imbue(loc);
temp << t1;
qDebug() << "FROM MAIN: " << temp.str().c_str();
}
auto libPtr = dlopen( "libbug_datetime_lib_armeabi-v7a.so", 0);
if ( libPtr )
{
typedef void (*TestBoostFunc)();
auto func = (TestBoostFunc) dlsym( libPtr, "TestBoost" );
if ( func )
(*func)();
else
qDebug() << "Failed to load TestBoost function";
}
else
{
qDebug() << "Failed to load library function";
}
return app.exec();
}
Within the main program:
- Using a
boost::posix_time::time_facetto customize redirection of aboost::posix_time::ptimeto astd::ostreamworks fine - However, invoking code from
bug_datetime_libdoing the same thing does not work (facet is ignored)
So program output is:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan$10 01:02:04.main.000000
D libbug_datetime_armeabi-v7a.so: FROM TestBoost:
D libbug_datetime_armeabi-v7a.so: Unformatted:2002-Jan-10 01:02:04
D libbug_datetime_armeabi-v7a.so: Formatted:2002-Jan-10 01:02:04
While expecting:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan$10 01:02:04.main.000000
D libbug_datetime_armeabi-v7a.so: FROM TestBoost:
D libbug_datetime_armeabi-v7a.so: Unformatted:2002-Jan-10 01:02:04
D libbug_datetime_armeabi-v7a.so: Formatted:2002$Jan$10 01:02:04.TestBoost.000000
The whole code is available here: https://github.com/jporcher/bug_datetime
Note that I use QtCreator to easily compile and deploy the app, but I'm pretty sure the problem can be reproduced with regular ndk-builds.
The libraries architecture makes no sense, it's because I removed lots of code to isolate a MCVE. If I remove bug_datetime_wrapper or bug_datetime_base from the project, the problem is not reproductible anymore.
Note that I found many workarounds that would fix the issue, they are all very surprising:
- Workaround1: in
bug_datetime_base, commentingstd::cout << timeInfo;fixes the issue - Workaround2: commenting
wrapperPtr = dlopen( "libbug_datetime_wrapper_armeabi-v7a.so", 0);(so not loadingbug_datetime_wrapper) fixes the issue - Workaround3: removing a link below fixes the issue
- Workaround3.1 not linking
bug_datetimeprogram tobug_datetime_base(removinglink1) - Workaround3.2 not linking
bug_datetime_wrappertobug_datetime_base(removinglink2) - Workaround3.3 not linking
bug_datetime_wrappertobug_datetime_lib(removinglink3)
- Workaround3.1 not linking
- Workaround4: changing link order in
bug_datetime_wrapper, linkingbug_datetime_libbeforebug_datetime_basefixes the issue - Workaround5: linking
bug_datetimeprogram tobug_datetime_wrapperat compile time fixes the issue
The current code has no undefined behaviour and is perfectly valid, so I'm looking for a rational explanation of what's going wrong and how this should be fixed cleanly (preserving existing links as they are needed in the original project I created this MCVE from).
Edit 07 June: Tried to compile boost as shared libraries rather than static. I still observe the same issue.
