首页 > 解决方案 > 根据 Android 下的动态加载顺序,从共享库中使用 imbue/facet 时会被忽略

问题描述

我正在 Android 上部署一个 C++ 应用程序,它使用boost::date_time. 它有很多库,一些在编译时链接(共享库),另一些是插件,在运行时通过dlopen. 在某些库中,将 a 设置boost::posix_time::time_facet为 a std::ostream(使用imbue)以自定义boost::posix_time::ptime显示没有效果(被忽略)。我可以在以下 MCVE 中隔离问题:

在此处输入图像描述

bug_datetime_base是一个共享库,它使用boost::date_time但仅编译将 a 重定向boost::posix_time::ptime到 a 的代码std::ostream(未boost::posix_time::time_facet使用):

MyClass::MyClass( const boost::posix_time::ptime& timeInfo )
{
    std::cout << timeInfo;
}

bug_datetime_lib是一个共享库,它使用boost::date_time并导出一个函数,该函数将使用 a 将 aboost::posix_time::time_facet重定向boost::posix_time::ptimestd::ostream具有特定格式的 a:

#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_baseand bug_datetime_lib,仅此而已:

MyWrapperClass::MyWrapperClass()
{
}

bug_datetimeboost::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();
}

在主程序中:

所以程序输出是:

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-builds 重现该问题。

库架构毫无意义,这是因为我删除了很多代码来隔离 MCVE。如果我从项目中删除bug_datetime_wrapper或删除bug_datetime_base,则问题不再可重现。

请注意,我发现了许多可以解决此问题的解决方法,它们都非常令人惊讶:

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


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

标签: c++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。然后,由于代码是在许多不同的翻译单元中编译的,你最终会得到许多boost::date_time::time_facet用不同的id. 如果一个翻译单元创建了一个boost::posix_time::time_facetfacet 并且另一个翻译单元使用了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。这可以永久解决问题。


推荐阅读