微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

从 Windows 下的共享库中使用时会忽略 imbue/facet

如何解决从 Windows 下的共享库中使用时会忽略 imbue/facet

我有一个奇怪的行为,通过 facet 传递给 std::ostreamimbue 在 Android (imbue/facet ignored when used from shared libraries depending on dynamic load order under Android) 下的一个非常具体的架构中被忽略。

在尝试解决解决的问题时,我可以使用更简单的架构在 Windows 下重现该问题。在如此简单的 MCVE 中解决这个问题更令人惊讶:

我声明了一个静态链接以提升 date_time 的共享库:

bug_datetime_libwin.h:

#pragma once

#ifdef BUG_DATETIME_LIBWIN_EXPORTS
    #define BUG_DATETIME_LIBWIN_API __declspec(dllexport)
#else
    #define BUG_DATETIME_LIBWIN_API __declspec(dllimport)
#endif

#include <ostream>

class BUG_DATETIME_LIBWIN_API BoostFacets
{
public:
    static void SetupStream( std::ostream& str );
    static void PrintTime( std::ostream& str );
};

bug_datetime_libwin.cpp:

#include "bug_datetime_libwin.h"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning( disable: 4005 ) // if one already included math.h,we get duplicated macro defeinition warnings
#endif
#include <boost/date_time/posix_time/posix_time.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif

#define READABLE_DATE_FORMAT "%Y-%b-%d"
#define READABLE_TIME_FORMAT_ACC "%H:%M:%s"

void BoostFacets::SetupStream( std::ostream& str )
{
    static std::string accurateFormat = std::string(READABLE_DATE_FORMAT) + " " + READABLE_TIME_FORMAT_ACC;
    
    boost::posix_time::time_facet* facet = new boost::posix_time::time_facet(accurateFormat.c_str());
    str.imbue(std::locale(str.getloc(),facet));
}

void BoostFacets::PrintTime( std::ostream& str )
{
    SetupStream( str );
    boost::posix_time::ptime t1(boost::gregorian::date(2002,boost::gregorian::Jan,10),boost::posix_time::time_duration(1,2,4));
    str << t1;
}

然后我有一个应用程序动态链接到共享库并静态链接到提高 date_time:

main.cpp:

#include <sstream>
#include <ostream>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "bug_datetime_libwin/bug_datetime_libwin.h"

int main( int argc,char* argv[] )
{
    {
        std::stringstream temp;
        BoostFacets::PrintTime( temp );

        std::cout << "PrintTime: " << temp.str().c_str() << std::endl;
    }

    {
        std::stringstream temp;
        BoostFacets::SetupStream( temp );

        boost::posix_time::ptime t1(boost::gregorian::date(2002,4));
        temp << t1;

        std::cout << "SetupStream: " << temp.str().c_str() << std::endl;
    }

    return 0;
}

函数输出

PrintTime: 2002-Jan-10 01:02:04.000000
SetupStream: 2002-Jan-10 01:02:04

如您所见,如果库在流上执行 imbue,创建一个 boost::posix_time::ptime 对象并将其打印到流中,则它工作正常。

但如果 lib 在流上执行 imbue,并且应用程序创建一个 boost::posix_time::ptime 对象并将其打印到字符串,则它无法正常工作:imbue 无效!

我做错了什么?


更新 1:

尝试将 boost 编译为共享库,以防止 Alan Birtles 提出的 ODR 违规。它没有解决问题。实际上,因为使用的 mincludes 是“仅标头”,因此甚至不需要链接代码即可运行此代码。所以问题不是由于静态提升链接


更新 2:

我使用调试器查看发生了什么:

问题来自:

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,p);
    }
    return os;
  }

在 posix_time_io.hpp 中。

  • 当main调用PrintTime时,它调用SetupStream,这里创建的facet id(boost::posix_time::time_facet::id)是5
  • 那么当 SetupStream 调用 str << t1 时,custom_ptime_facet 在 posix_time_io.hpp 的 operator<< 中的 id 值为 5,所以 { {1}} 返回 has_facet 并格式化字符串。
  • 当 main 调用 true 时,这里创建的 SetupStream id (facet) 仍然是 boost::posix_time::time_facet::id
  • 但是当 5 调用 main 时,str << t1 在 posix_time_io.hpp 的 custom_ptime_facet 中的 operator<< 值为 id,所以 { {1}} 返回 44 并且字符串未格式化。

我怀疑这是因为 has_facet 被包含两次:

  • 一旦从库中,将静态 false 分配给 posix_time_io.hpp
  • 一旦从程序中,将静态 custom_ptime_facet::id 分配给 5

所以我进行了测试,并在 MCVE 的主要功能结束时执行此操作:

custom_ptime_facet::id

输出44。之所以有效,是因为此处创建的 { static std::string accurateFormat = std::string(READABLE_DATE_FORMAT) + " " + READABLE_TIME_FORMAT_ACC; std::stringstream temp; boost::posix_time::time_facet* facet = new boost::posix_time::time_facet(accurateFormat.c_str()); temp.imbue(std::locale(temp.getloc(),facet)); boost::posix_time::ptime t1(boost::gregorian::date(2002,4)); temp << t1; std::cout << "main: " << temp.str().c_str() << std::endl; } PrintTime: 2002-Jan-10 01:02:04.000000facet,然后 id 返回 true。

这是否意味着,通过构造,因为它使用仅标头方法,提升日期时间将不允许一个模块设置 44 而另一个模块使用 has_facet

解决方法

您违反了 ODR。由于 boost::posix_time::time_facet 是仅标头模板,因此每个翻译单元都有自己的模板实例副本。创建共享库或可执行文件时,链接器会将所有重复项合并到单个对象中。但是,在创建可执行文件时,链接器看不到共享库中对象的存在,因此可执行文件会获得自己的对象副本。这意味着静态 std::locale::id id 成员有两个单独的实例,标准库(正确)分配了不同的值。

要解决此问题,您必须从共享库中导出 time_facet,并确保您的可执行文件使用共享库版本而不是创建自己的副本。

在一个翻译单元的共享库中添加:

template class __declspec(dllexport)
boost::date_time::time_facet<boost::posix_time::ptime,char>;

在包含 boost 之后但在任何方面添加实例之前的应用程序中:

template class __declspec(dllimport)
boost::date_time::time_facet<boost::posix_time::ptime,char>;

在共享库边界上与 C++ 对象进行互操作时存在许多陷阱。将所有内容静态链接到单个可执行文件中要容易得多。

,

Alan 答案的替代方法是确保此代码仅编译一次。

所以我创建了一个新库 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,p);
    }
    return os;
}

在任何地方使用它,而不是直接使用 boost date_time。这彻底解决了这个问题。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?