c++ - 从共享库中使用时忽略 imbue/facet,具体取决于 Android 下的动态加载顺序

标签 c++ boost

我正在 Android 上部署一个 C++ 应用程序,它使用 boost::date_time .它有很多库,有些是在编译时链接的(共享库),有些是插件,在运行时通过 dlopen 动态加载。 .在某些库中,设置 boost::posix_time::time_facetstd::ostream (使用 imbue )为了定制一个 boost::posix_time::ptime display 没有效果(被忽略)。我可以在以下 MCVE 中隔离问题:
enter image description here
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::ptimestd::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 是一个共享库,只链接到 bug_datetime_basebug_datetime_lib , 什么都不做:
MyWrapperClass::MyWrapperClass()
{
}
bug_datetime 是使用 boost::date_time 的主程序, 链接到 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 调用代码做同样的事情不起作用(忽略方面)

  • 所以程序输出是:
    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
    
    在期待时:
    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
    
    完整代码可在此处获得:https://github.com/jporcher/bug_datetime
    请注意,我使用 QtCreator 轻松编译和部署应用程序,但我很确定可以使用常规 ndk-build 重现该问题。
    库架构没有意义,这是因为我删除了大量代码来隔离 MCVE。如果我删除 bug_datetime_wrapperbug_datetime_base从项目中,问题不再重现。
    请注意,我发现了许多可以解决该问题的解决方法,它们都非常令人惊讶:
  • 解决方法 1 :在 bug_datetime_base , 评论 std::cout << timeInfo;修复问题
  • 解决方法 2 : 评论 wrapperPtr = dlopen( "libbug_datetime_wrapper_armeabi-v7a.so", 0); (所以不加载 bug_datetime_wrapper )修复了问题
  • 解决方法 3 :删除下面的链接可以解决问题
  • Workaround3.1 未链接 bug_datetime计划到 bug_datetime_base (删除 link1)
  • Workaround3.2 未链接 bug_datetime_wrapperbug_datetime_base (删除 link2)
  • Workaround3.3 未链接 bug_datetime_wrapperbug_datetime_lib (删除 link3)

  • 解决方法 4 :更改 bug_datetime_wrapper 中的链接顺序, 链接 bug_datetime_lib之前 bug_datetime_base修复问题
  • 解决方法 5 : 链接 bug_datetime计划到 bug_datetime_wrapper在编译时修复了问题

  • 当前代码没有未定义的行为并且完全有效,所以我正在寻找对出了什么问题的合理解释以及应该如何干净地修复(保留现有链接,因为它们在我创建这个 MCVE 的原始项目中是需要的) .

    6 月 7 日编辑:尝试将 boost 编译为共享库而不是静态库。我仍然观察到同样的问题。

    最佳答案

    此问题是由于 ODR 违规造成的。 Boost 日期时间库是一个只有头文件的库,这意味着代码在包含它的每个翻译单元中被编译。
    然后,见 operator<<定义于 posix_time_io.hpp :

    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检查静态 id facet成员(member)目的。然后,由于代码是在许多不同的翻译单元中编译的,你最终会得到许多 boost::date_time::time_facet用不同的 id 定义的类.如果翻译单元创建了 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。这很好地解决了这个问题。

    关于c++ - 从共享库中使用时忽略 imbue/facet,具体取决于 Android 下的动态加载顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67784836/

    相关文章:

    c++ - boost/Netbeans : Recursive Includes Related to BSD on Linux Mint 17. 2

    c++ - 简单 boost 序列化中的未定义引用错误

    c++ - 为什么 `boost::lower_bound` 按值接受它的参数?

    c++ - 关于Boost共享内存的问题

    c++ - 如何编译 C++ 项目(使用 g++)以在其他计算机上使用?

    c++ - 如何使用 boost 库反序列化和获取成员值

    c++ - 如果有 constexpr if 语句,为什么没有其他 constexpr 语句呢?

    c++ - std::tuple_size 和引用

    c++ - 指针问题(可能很简单)

    c++ - 模板的使用