Visual Studio 中(基于字符的)STL(流)容器的编译错误

Cri*_*ati 5 c++ windows dllimport visual-studio visual-c++

这基本上与[SO]: C2491: 'std::numpunct<_Elem>::id' : Define of dllimport static data member not allowed [close] 相同,但考虑以下事实:

  • (在我看来)这是一个完全有效的问题(根据[SO]:如何创建一个最小的、完整的和可验证的示例),我真的不知道为什么有些用户有关闭它的冲动
  • 标记为解决方案的答案提供了修复错误的指南(一般而言),但不适用于当前情况,并且当然不会修复它

请不要将其关闭或将其标记为重复项(至少,在没有仔细阅读和理解的情况下)。

主要.cpp

#include <sstream>


//#define THROW_C2491
#if defined(THROW_C2491)
typedef int CharType;
#else
typedef char CharType;
#endif


int main() {
    std::basic_stringstream<CharType> stream;
    CharType c = 0x41;
    stream << c;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

代码稍作修改(简化),如果 THROW_C2491定义则无法编译:

xlocnum(294): error C2491: 'std::numpunct<_Elem>::id': definition of dllimport static data member not allowed
Run Code Online (Sandbox Code Playgroud)

输出

xlocnum(294): error C2491: 'std::numpunct<_Elem>::id': definition of dllimport static data member not allowed
Run Code Online (Sandbox Code Playgroud)

注意事项

  • 一切都是VStudio 2015特定的,但问题可以使用VStudio 2017VStudio 2013VStudio 2010重现(当然,行号不同)。但是,它可以使用VStudio 2005运行
  • 我选择从cmdline粘贴编译器命令和输出,而不是放置带有编译器标志和输出的图片(来自VStudio IDE),因此更容易重现(如果有人愿意尝试)
  • 编译器标志是应用程序 (.exe) VStudio项目 ( Win32 ) 的默认值,除了一些不相关的路径相关的标志(例如.pch文件和其他此类垃圾),我将其删除
  • CharTypechar(或事实上任何窄字符类型)或wchar_t时才编译。禁用[MS.Docs]: /Zc:wchar_t (wchar_t Is Native Type)将unsigned short添加到列表中(这看起来很自然)
  • 在Lnx ( Ubtu 16 x64 ) / g++ ( gcc 5.4.0 )下编译没有问题

[MS.Docs]:编译器错误 C2491非常简单,我很熟悉它,有一些答案(例如[SO]:在不同 VS2010 项目中从 C++ 代码调用 C 函数时出现链接器错误(@CristiFati 的答案)[ SO]:Excel VBA,无法从 DLL 文件中找到 DLL 入口点(@CristiFati 的回答))来支持我。

考虑到上面的注释,我知道这与Win处理char的方式(限制?)有关,但我没有看到代码和 error 之间有任何(直接)联系。[MS.Docs]上没有任何关于此行为的信息: basic_stringstream 类。我(浅薄地)浏览了所涉及的标准包含头文件,但到目前为止我还没有深入了解它。我是否错过了非常明显的事情?

其他参考文献(相同或类似的错误,但没有包含有效的修复):


值得一提的是,最终目标是构建一些第三方软件来实例化一些基于32 位 字符的流。

Cri*_*ati 4

开始笔记

方法

  1. 快速(浅层)调查

    VStudio IDE上,双击“输出”窗口中的第二注释(尝试编译文件后),然后在相关宏上重复单击RClick,然后从上下文菜单中选择“转到定义” ( F12 ):

    • xlocnum (#120) : (注释是原始文件/行的一部分)

      __PURE_APPDOMAIN_GLOBAL _CRTIMP2_PURE static locale::id id; // unique facet id
      
      Run Code Online (Sandbox Code Playgroud)
    • yvals.h:(#494)

           #define _CRTIMP2_PURE _CRTIMP2
      
      Run Code Online (Sandbox Code Playgroud)
    • crtdefs.h(#29+)

      #ifndef _CRTIMP2
          #if defined CRTDLL2 && defined _CRTBLD
              #define _CRTIMP2 __declspec(dllexport)
          #else
              #if defined _DLL && !defined _STATIC_CPPLIB
                  #define _CRTIMP2 __declspec(dllimport)  // @TODO - cfati: line #34: Here is the definition
              #else
                  #define _CRTIMP2
              #endif
          #endif
      #endif
      
      Run Code Online (Sandbox Code Playgroud)

    正如所见,__declspec(dllimport)是在第 #34 行定义的。在宏上重复该过程_DLL,没有产生任何结果。在 [MSDN] 上找到:预定义宏

    _DLL当设置/MD/MDd (多线程 DLL)编译器选项时定义为 1 。否则,未定义。

    我想到了两种可能的方法(都导致了成功的构建):

    • 使用静态版本的CRT 运行时[MSDN]:/MD、/MT、/LD(使用运行时库))。我不认为这是一个可行的选择,特别是当项目由.dll组成时(确实如此):可能会发生不好的事情(例如[SO]: Errors when linking to protobuf 3 on MSVC 2013,甚至更糟糕的事情也可能发生发生在运行时)
    • 手动#undef _DLL(在main.cpp中,任何之前#include)。这是一个蹩脚的解决方法(gainarie)。它构建得很好,但是篡改这些东西可能(并且很可能会)在运行时触发未定义的行为

    这两个选项都不是完全令人满意,因此:

  2. 更深入一点

    试图进一步简化事情(main.cpp):

    #include <sstream>
    
    
    //typedef unsigned short CharType;  // wchar_t  unsigned short
    #define CharType unsigned short
    
    
    int main() {
        std::basic_stringstream<CharType> stream;
        CharType c = 0x41;
        stream << c;
        return 0;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    注意事项

    • 替换typedef#define(消除新类型定义的复杂性)
    • 切换到unsigned shortwhich iswchar_t的定义 ( /Zc:wchar_t-) 以避免任何可能的类型大小/对齐差异


    使用[MSDN]: /E(预处理到标准输出)[MSDN]: /EP(不使用 #line 指令预处理到标准输出)“编译”上述代码(以便警告/错误仅引用当前文件中的行号):

    • 生成的预处理文件(使用上面的每个标志):~1MB+~56.5k行)
    • 文件中唯一的区别是最后某个地方的#define( wchar_t vs. ) unsigned short
    • 编译这些文件(令人震惊:))产生了相同的结果:wchar_t编译时失败unsigned short并出现相同的错误
    • 在失败的文件中添加了一些#pragma message语句(是的,它们由预处理器处理,但仍然如此)(在每个警告/注释之前),注意到 2 之间存在一些差异#define,但到目前为止无法弄清楚为什么1
    • 在浏览生成的文件时,注意到一个template<> struct char_traits<char32_t>定义,所以我尝试了一下,它起作用了(至少编译了当前的程序)1(并且,正如预期的那样,sizeof(char32_t)是 4)。然后,找到了[MSDN]:char、wchar_t、char16_t、char32_t


    注意事项

    • 虽然这解决了我当前的问题(仍然不知道为什么),但必须尝试一下最终目标
    • 1虽然我查看了该文件,但我没有看到任何仅针对“特权”类型的模板定义(例如,我没有看到任何可以区分wchar_tsigned charchar32_t内容unsigned short),所以我(还)不知道为什么它适用某些类型但不适用于其他类型这是一个开放的话题,每当我收到新的更新时,我都会分享它们

底线

根据经验发现,在使用基于字符的STL 容器时,允许使用以下类型:

  • char
  • unsigned char
  • signed char
  • wchar_t
  • char16_t
  • char32_t
  • unsigned short/Zc:wchar_t- 仅有的

最后注意事项

  • 我将在答案中加入任何有用的内容(例如评论)

@编辑0