imbue/facet 根据 Android 下的动态加载顺序从共享库使用时被忽略
imbue/facet ignored when used from shared libraries depending on dynamic load order under Android
我正在 Android 上部署 C++ 应用程序,它使用 boost::date_time
。它有很多库,一些在编译时被 link 编辑(共享库),其他的,某种插件,在运行时通过 dlopen
动态加载。在某些库中,将 boost::posix_time::time_facet
设置为 std::ostream
(使用 imbue
)以自定义 boost::posix_time::ptime
显示无效(被忽略)。我可以在以下 MCVE 中隔离问题:
bug_datetime_base 是一个使用 boost::date_time
的共享库,但只编译将 boost::posix_time::ptime
重定向到 std::ostream
的代码(未使用 boost::posix_time::time_facet
):
MyClass::MyClass( const boost::posix_time::ptime& timeInfo )
{
std::cout << timeInfo;
}
bug_datetime_lib 是一个共享库,它使用 boost::date_time
并导出一个函数,该函数将使用 boost::posix_time::time_facet
重定向 boost::posix_time::ptime
到具有特定格式的 std::ostream
:
#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 是一个共享库,仅 link 到 bug_datetime_base
和 bug_datetime_lib
,仅此而已:
MyWrapperClass::MyWrapperClass()
{
}
bug_datetime是主程序,使用boost::date_time
,links到bug_datetime_base
,动态加载bug_datetime_wrapper
通过 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();
}
在主程序中:
- 使用
boost::posix_time::time_facet
自定义从 boost::posix_time::ptime
到 std::ostream
的重定向效果很好
- 但是,从
bug_datetime_lib
调用代码做同样的事情不起作用(facet 被忽略)
所以程序输出为:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan 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
期待中:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan 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 01:02:04.TestBoost.000000
完整代码可在此处获得:https://github.com/jporcher/bug_datetime
请注意,我使用 QtCreator 轻松编译和部署应用程序,但我很确定可以使用常规 ndk-builds.
重现该问题
库架构没有意义,这是因为我删除了很多代码来隔离 MCVE。如果我从项目中删除 bug_datetime_wrapper
或 bug_datetime_base
,问题将不再重现。
请注意,我发现了很多可以解决该问题的解决方法,它们都非常令人惊讶:
- 解决方法 1:在
bug_datetime_base
中,评论 std::cout << timeInfo;
解决了问题
- 解决方法 2:评论
wrapperPtr = dlopen( "libbug_datetime_wrapper_armeabi-v7a.so", 0);
(因此不加载 bug_datetime_wrapper
)修复了问题
- 解决方法 3:删除下面的 link 可解决问题
- Workaround3.1 不 linking
bug_datetime
编程到 bug_datetime_base
(删除 link1
)
- Workaround3.2 不 linking
bug_datetime_wrapper
到 bug_datetime_base
(删除 link2
)
- Workaround3.3 不是 linking
bug_datetime_wrapper
到 bug_datetime_lib
(删除 link3
)
- 解决方法 4:更改
bug_datetime_wrapper
中的 link 顺序,linking bug_datetime_lib
之前 bug_datetime_base
修复问题
- 解决方法 5:在编译时 linking
bug_datetime
程序到 bug_datetime_wrapper
修复了问题
当前代码没有未定义的行为并且完全有效,所以我正在寻找一个合理的解释,说明出了什么问题以及如何彻底解决这个问题(根据需要保留现有的 links在我创建这个 MCVE 的原始项目中)。
6 月 7 日编辑:尝试将 boost 编译为共享库而不是静态库。我仍然观察到同样的问题。
此问题是由于违反 ODR。 Boost 日期时间库是一个 header 唯一的库,这意味着代码会在包括它的每个翻译单元中编译。
然后,参见posix_time_io.hpp
中定义的operator<<
:
template <class CharT, class TraitsT>
inline
std::basic_ostream<CharT, TraitsT>&
operator<<(std::basic_ostream<CharT, TraitsT>& os,
const ptime& p) {
boost::io::ios_flags_saver iflags(os);
typedef boost::date_time::time_facet<ptime, CharT> custom_ptime_facet;
std::ostreambuf_iterator<CharT> oitr(os);
if (std::has_facet<custom_ptime_facet>(os.getloc()))
std::use_facet<custom_ptime_facet>(os.getloc()).put(oitr, os, os.fill(), p);
else {
//instantiate a custom facet for dealing with times since the user
//has not put one in the stream so far. This is for efficiency
//since we would always need to reconstruct for every time period
//if the locale did not already exist. Of course this will be overridden
//if the user imbues as some later point.
custom_ptime_facet* f = new custom_ptime_facet();
std::locale l = std::locale(os.getloc(), f);
os.imbue(l);
f->put(oitr, os, os.fill(), p);
}
return os;
}
has_facet
检查 facet
object 的静态 id
成员。然后,由于代码是在许多不同的翻译单元中编译的,你最终会得到许多用不同的 id
定义的 boost::date_time::time_facet
类。如果一个翻译单元创建一个 boost::posix_time::time_facet
facet 而另一个翻译单元使用 operator<<
,那么这个运算符将不会使用 facet
.
解决方案是确保此代码仅编译一次。
所以我创建了一个新库 boost_datetime
,其中:
boost_datetime.h:
#pragma once
#include <ostream>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace boost
{
namespace posix_time
{
class ptime;
}
}
class BoostDateTime
{
public:
static void setFacet( std::ostream& os );
};
std::ostream& operator<<( std::ostream& os, const boost::posix_time::ptime& p );
boost_datetime.cpp:
#include "boost_datetime.h"
void BoostDateTime::setFacet( std::ostream& os )
{
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y$%b$%d %H:%M:%S.%f");
os.imbue(std::locale(os.getloc(), facet));
}
std::ostream& operator<<( std::ostream& os, const boost::posix_time::ptime& p )
{
// copied from posix_time_io.hpp
boost::io::ios_flags_saver iflags(os);
typedef boost::posix_time::time_facet base_ptime_facet;
std::ostreambuf_iterator<char> oitr(os);
if (std::has_facet<base_ptime_facet>(os.getloc()))
std::use_facet<base_ptime_facet>(os.getloc()).put(oitr, os, os.fill(), p);
else {
//instantiate a custom facet for dealing with times since the user
//has not put one in the stream so far. This is for efficiency
//since we would always need to reconstruct for every time period
//if the locale did not already exist. Of course this will be overridden
//if the user imbues as some later point.
base_ptime_facet* f = new base_ptime_facet();
std::locale l = std::locale(os.getloc(), f);
os.imbue(l);
f->put(oitr, os, os.fill(), p);
}
return os;
}
在每个地方都使用它,而不是直接使用 boost date_time。这很好地解决了这个问题。
我正在 Android 上部署 C++ 应用程序,它使用 boost::date_time
。它有很多库,一些在编译时被 link 编辑(共享库),其他的,某种插件,在运行时通过 dlopen
动态加载。在某些库中,将 boost::posix_time::time_facet
设置为 std::ostream
(使用 imbue
)以自定义 boost::posix_time::ptime
显示无效(被忽略)。我可以在以下 MCVE 中隔离问题:
bug_datetime_base 是一个使用 boost::date_time
的共享库,但只编译将 boost::posix_time::ptime
重定向到 std::ostream
的代码(未使用 boost::posix_time::time_facet
):
MyClass::MyClass( const boost::posix_time::ptime& timeInfo )
{
std::cout << timeInfo;
}
bug_datetime_lib 是一个共享库,它使用 boost::date_time
并导出一个函数,该函数将使用 boost::posix_time::time_facet
重定向 boost::posix_time::ptime
到具有特定格式的 std::ostream
:
#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 是一个共享库,仅 link 到 bug_datetime_base
和 bug_datetime_lib
,仅此而已:
MyWrapperClass::MyWrapperClass()
{
}
bug_datetime是主程序,使用boost::date_time
,links到bug_datetime_base
,动态加载bug_datetime_wrapper
通过 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();
}
在主程序中:
- 使用
boost::posix_time::time_facet
自定义从boost::posix_time::ptime
到std::ostream
的重定向效果很好 - 但是,从
bug_datetime_lib
调用代码做同样的事情不起作用(facet 被忽略)
所以程序输出为:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan 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
期待中:
D libbug_datetime_armeabi-v7a.so: Loaded bug_datetime_wrapper, then formatting will fail
D libbug_datetime_armeabi-v7a.so: FROM MAIN: 2002$Jan 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 01:02:04.TestBoost.000000
完整代码可在此处获得:https://github.com/jporcher/bug_datetime
请注意,我使用 QtCreator 轻松编译和部署应用程序,但我很确定可以使用常规 ndk-builds.
重现该问题库架构没有意义,这是因为我删除了很多代码来隔离 MCVE。如果我从项目中删除 bug_datetime_wrapper
或 bug_datetime_base
,问题将不再重现。
请注意,我发现了很多可以解决该问题的解决方法,它们都非常令人惊讶:
- 解决方法 1:在
bug_datetime_base
中,评论std::cout << timeInfo;
解决了问题 - 解决方法 2:评论
wrapperPtr = dlopen( "libbug_datetime_wrapper_armeabi-v7a.so", 0);
(因此不加载bug_datetime_wrapper
)修复了问题 - 解决方法 3:删除下面的 link 可解决问题
- Workaround3.1 不 linking
bug_datetime
编程到bug_datetime_base
(删除link1
) - Workaround3.2 不 linking
bug_datetime_wrapper
到bug_datetime_base
(删除link2
) - Workaround3.3 不是 linking
bug_datetime_wrapper
到bug_datetime_lib
(删除link3
)
- Workaround3.1 不 linking
- 解决方法 4:更改
bug_datetime_wrapper
中的 link 顺序,linkingbug_datetime_lib
之前bug_datetime_base
修复问题 - 解决方法 5:在编译时 linking
bug_datetime
程序到bug_datetime_wrapper
修复了问题
当前代码没有未定义的行为并且完全有效,所以我正在寻找一个合理的解释,说明出了什么问题以及如何彻底解决这个问题(根据需要保留现有的 links在我创建这个 MCVE 的原始项目中)。
6 月 7 日编辑:尝试将 boost 编译为共享库而不是静态库。我仍然观察到同样的问题。
此问题是由于违反 ODR。 Boost 日期时间库是一个 header 唯一的库,这意味着代码会在包括它的每个翻译单元中编译。
然后,参见posix_time_io.hpp
中定义的operator<<
:
template <class CharT, class TraitsT>
inline
std::basic_ostream<CharT, TraitsT>&
operator<<(std::basic_ostream<CharT, TraitsT>& os,
const ptime& p) {
boost::io::ios_flags_saver iflags(os);
typedef boost::date_time::time_facet<ptime, CharT> custom_ptime_facet;
std::ostreambuf_iterator<CharT> oitr(os);
if (std::has_facet<custom_ptime_facet>(os.getloc()))
std::use_facet<custom_ptime_facet>(os.getloc()).put(oitr, os, os.fill(), p);
else {
//instantiate a custom facet for dealing with times since the user
//has not put one in the stream so far. This is for efficiency
//since we would always need to reconstruct for every time period
//if the locale did not already exist. Of course this will be overridden
//if the user imbues as some later point.
custom_ptime_facet* f = new custom_ptime_facet();
std::locale l = std::locale(os.getloc(), f);
os.imbue(l);
f->put(oitr, os, os.fill(), p);
}
return os;
}
has_facet
检查 facet
object 的静态 id
成员。然后,由于代码是在许多不同的翻译单元中编译的,你最终会得到许多用不同的 id
定义的 boost::date_time::time_facet
类。如果一个翻译单元创建一个 boost::posix_time::time_facet
facet 而另一个翻译单元使用 operator<<
,那么这个运算符将不会使用 facet
.
解决方案是确保此代码仅编译一次。
所以我创建了一个新库 boost_datetime
,其中:
boost_datetime.h:
#pragma once
#include <ostream>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace boost
{
namespace posix_time
{
class ptime;
}
}
class BoostDateTime
{
public:
static void setFacet( std::ostream& os );
};
std::ostream& operator<<( std::ostream& os, const boost::posix_time::ptime& p );
boost_datetime.cpp:
#include "boost_datetime.h"
void BoostDateTime::setFacet( std::ostream& os )
{
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y$%b$%d %H:%M:%S.%f");
os.imbue(std::locale(os.getloc(), facet));
}
std::ostream& operator<<( std::ostream& os, const boost::posix_time::ptime& p )
{
// copied from posix_time_io.hpp
boost::io::ios_flags_saver iflags(os);
typedef boost::posix_time::time_facet base_ptime_facet;
std::ostreambuf_iterator<char> oitr(os);
if (std::has_facet<base_ptime_facet>(os.getloc()))
std::use_facet<base_ptime_facet>(os.getloc()).put(oitr, os, os.fill(), p);
else {
//instantiate a custom facet for dealing with times since the user
//has not put one in the stream so far. This is for efficiency
//since we would always need to reconstruct for every time period
//if the locale did not already exist. Of course this will be overridden
//if the user imbues as some later point.
base_ptime_facet* f = new base_ptime_facet();
std::locale l = std::locale(os.getloc(), f);
os.imbue(l);
f->put(oitr, os, os.fill(), p);
}
return os;
}
在每个地方都使用它,而不是直接使用 boost date_time。这很好地解决了这个问题。