imbue/facet 从 Windows 下的共享库中使用时被忽略
imbue/facet ignored when used from shared libraries under Windows
我有一个奇怪的行为,facet
通过 imbue
传递给 std::ostream
在 Android () 下的一个非常特定的架构中被忽略了。
在尝试解决未解决的问题时,我可以使用更简单的体系结构在 Windows 下重现该问题。在如此简单的 MCVE 中解决这个问题更令人惊讶:
我声明了一个静态链接到 boost 的共享库 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,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,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
object 并将其打印到流中,它工作正常。
但是如果 lib 在流上执行 imbue
,并且应用程序创建 boost::posix_time::ptime
object 并将其打印到字符串,则它无法正常工作:imbue
没有效果!
我做错了什么?
更新 1:
尝试将 boost 编译为共享库以防止 Alan Birtles 提议的 ODR 违规。它没有解决问题。实际上,甚至不需要链接 boost 就可以使此代码正常工作,因为使用的 minclude 是“header-only”。所以问题不是由于静态升压链接。
更新 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, os, os.fill(), 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
,所以 has_facet
返回 true
并且字符串被格式化。
- 当main调用
SetupStream
时,这里创建的facet
id(boost::posix_time::time_facet::id
)仍然是5
- 但是当
main
调用 str << t1
时,custom_ptime_facet
在 posix_time_io.hpp 的 operator<<
中的 id
值为 44
,因此 has_facet
返回 false
并且字符串未格式化。
我怀疑这是因为 posix_time_io.hpp
被包含了两次:
- 一次从图书馆,分配静态
custom_ptime_facet::id
到5
- 一旦从程序中,将静态
custom_ptime_facet::id
分配给44
所以我测试了,并在 MCVE 的主要功能结束时这样做:
{
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,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,4));
temp << t1;
std::cout << "main: " << temp.str().c_str() << std::endl;
}
输出PrintTime: 2002-Jan-10 01:02:04.000000
。之所以有效,是因为此处创建的 facet
的 id
为 44
,然后 has_facet
returns 为真。
这是否意味着,通过构造,因为它使用 header-only 方法,提升日期时间将不允许一个模块设置 imbue
而另一个模块使用 operator<<
?
您违反了 ODR。由于 boost::posix_time::time_facet
是一个仅 header 的模板,每个翻译单元都有自己的模板实例化副本。 linker 在创建共享库或可执行文件时将任何重复项合并为一个 object。然而,在创建可执行文件时,共享库中 object 的存在对 link 用户是不可见的,因此可执行文件获得了自己的 object 副本。这意味着有两个单独的静态 std::locale::id id
成员实例,标准库(正确地)分配了不同的值。
要解决此问题,您必须从共享库中导出 time_facet
并确保您的可执行文件使用共享库版本而不是创建自己的副本。
在一个翻译单元的共享库中添加:
template class __declspec(dllexport)
boost::date_time::time_facet<boost::posix_time::ptime, char>;
在您的应用程序中,在包含 boost 之后但在 facet 的任何实例化之前添加:
template class __declspec(dllimport)
boost::date_time::time_facet<boost::posix_time::ptime, char>;
在共享库边界上与 c++ object 进行互操作时存在许多陷阱。将所有内容静态 link 放入单个可执行文件中要容易得多。
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, os, os.fill(), p);
}
return os;
}
在每个地方都使用它,而不是直接使用 boost date_time。这很好地解决了这个问题。
我有一个奇怪的行为,facet
通过 imbue
传递给 std::ostream
在 Android (
在尝试解决未解决的问题时,我可以使用更简单的体系结构在 Windows 下重现该问题。在如此简单的 MCVE 中解决这个问题更令人惊讶:
我声明了一个静态链接到 boost 的共享库 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,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,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
object 并将其打印到流中,它工作正常。
但是如果 lib 在流上执行 imbue
,并且应用程序创建 boost::posix_time::ptime
object 并将其打印到字符串,则它无法正常工作:imbue
没有效果!
我做错了什么?
更新 1:
尝试将 boost 编译为共享库以防止 Alan Birtles 提议的 ODR 违规。它没有解决问题。实际上,甚至不需要链接 boost 就可以使此代码正常工作,因为使用的 minclude 是“header-only”。所以问题不是由于静态升压链接。
更新 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, os, os.fill(), 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
,所以has_facet
返回true
并且字符串被格式化。 - 当main调用
SetupStream
时,这里创建的facet
id(boost::posix_time::time_facet::id
)仍然是5
- 但是当
main
调用str << t1
时,custom_ptime_facet
在 posix_time_io.hpp 的operator<<
中的id
值为44
,因此has_facet
返回false
并且字符串未格式化。
我怀疑这是因为 posix_time_io.hpp
被包含了两次:
- 一次从图书馆,分配静态
custom_ptime_facet::id
到5
- 一旦从程序中,将静态
custom_ptime_facet::id
分配给44
所以我测试了,并在 MCVE 的主要功能结束时这样做:
{
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,boost::gregorian::Jan,10),
boost::posix_time::time_duration(1,2,4));
temp << t1;
std::cout << "main: " << temp.str().c_str() << std::endl;
}
输出PrintTime: 2002-Jan-10 01:02:04.000000
。之所以有效,是因为此处创建的 facet
的 id
为 44
,然后 has_facet
returns 为真。
这是否意味着,通过构造,因为它使用 header-only 方法,提升日期时间将不允许一个模块设置 imbue
而另一个模块使用 operator<<
?
您违反了 ODR。由于 boost::posix_time::time_facet
是一个仅 header 的模板,每个翻译单元都有自己的模板实例化副本。 linker 在创建共享库或可执行文件时将任何重复项合并为一个 object。然而,在创建可执行文件时,共享库中 object 的存在对 link 用户是不可见的,因此可执行文件获得了自己的 object 副本。这意味着有两个单独的静态 std::locale::id id
成员实例,标准库(正确地)分配了不同的值。
要解决此问题,您必须从共享库中导出 time_facet
并确保您的可执行文件使用共享库版本而不是创建自己的副本。
在一个翻译单元的共享库中添加:
template class __declspec(dllexport)
boost::date_time::time_facet<boost::posix_time::ptime, char>;
在您的应用程序中,在包含 boost 之后但在 facet 的任何实例化之前添加:
template class __declspec(dllimport)
boost::date_time::time_facet<boost::posix_time::ptime, char>;
在共享库边界上与 c++ object 进行互操作时存在许多陷阱。将所有内容静态 link 放入单个可执行文件中要容易得多。
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, os, os.fill(), p);
}
return os;
}
在每个地方都使用它,而不是直接使用 boost date_time。这很好地解决了这个问题。