从DLL导出STL类 - 为什么返回类型没有警告?

yiy*_*c91 20 c++ dll stl instantiation visual-studio-2010

我的问题与导出带有STL的C++类有关.例如:

class __declspec(dllexport) Hello
{
    std::string   name;

public:
    std::string&  getName();
    void          setName(const std::string& name);
}
Run Code Online (Sandbox Code Playgroud)

各种文章似乎表明这是非常糟糕的,这是很容易理解的.必须使用相同的编译器设置和CRT版本编译所有内容.否则一切都会崩溃和燃烧.

题:

我不明白为什么只有数据成员似乎有问题.使用下面的代码,我得到:" C4251:需要让dll接口被类的客户端使用 "; 这显然是通过导出实例化的std :: string来修复的:

struct __declspec(dllexport) SomeClass
{
    // Removes the warning?
    // http://www.unknownroad.com/rtfm/VisualStudio/warningC4251.html
    //   template class __declspec(dllexport) std::string;

    std::string name;  // Compiler balks at this
}
Run Code Online (Sandbox Code Playgroud)

固定版本是:

// Export the instantiations of allocator and basic_string
template class __declspec(dllexport) std::allocator<char>;
template class __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char> >;

struct __declspec(dllexport) SomeClass
{
    std::string name;  // No more balking!
}
Run Code Online (Sandbox Code Playgroud)

(当您尝试使用DLL时,这将为LNK2005"basic_string已定义",这意味着您不必在客户端上的CRT中链接 - 因此它最终使用DLL中的实例化).

返回类型和参数似乎与STL没有问题,并且不会从编译器接收相同的处理数据成员.

// No exporting required?
struct __declspec(dllexport) SomeOtherClass
{
    std::string  doSomething1();                       // No problemo
    void         doSomething2(const std::string& s);   // No problemo
}
Run Code Online (Sandbox Code Playgroud)

附加信息(问题在上面)

同时:

class A {
    std::string foo() { return std::string(); }
    // std::string& foo(); gives the same result!
    // std::string* foo(); also gives the same result!
}

class B {
    std::string a;
}
Run Code Online (Sandbox Code Playgroud)

似乎都没有导出std :: basic_string或std :: allocator.相反,它们只导出类的成员/函数.

但是,问题中提到的固定版本会导出basic_string和allocator.

Han*_*ant 10

各种文章似乎表明这是非常糟糕的

是的,它可以.而你的项目设置会让你遇到他们警告的那种麻烦.按值公开C++对象需要DLL的客户端使用相同的CRT,以便客户端应用程序可以安全地销毁DLL中创建的对象.反过来说.这要求这些模块使用相同的堆.

并且您的项目设置会阻止编译警告的要点.您必须指定CRT的共享版本,以便所有模块加载CRT的一次性实现.

使用Project + Properties,C/C++,代码生成,运行时库设置进行修复.你现在在/ MT,它必须是/ MD.更改所有模块和所有配置.

  • @Bas - 他不明白的是他使用了错误的编译选项.使用正确的,一切都没有任何黑客.这个问题没有说明,他没有意识到这一点很重要.原始洞察力,不值得投票. (3认同)

Chr*_*her 4

这归结为某些东西是如何构建的。

当编译器看到

__declspec(dllimport)    std::string f();
// ...

{
  std::string tmp = f();
}
Run Code Online (Sandbox Code Playgroud)

它必须弄清楚要调用什么以及从哪里获取它。所以在这种情况下:

std::string tmp; => sizeof( std::string ), new (__stack_addr) std::string;
tmp = f();       => call f(), operator=( std::string )
Run Code Online (Sandbox Code Playgroud)

但因为它看到了 std::string 的完整实现,所以它只能使用相应模板的新实例。因此,它只需实例化 std::string 的模板函数即可,然后将函数合并留给链接器阶段,链接器在该阶段尝试找出可以将哪些函数折叠为一个函数。唯一未知的函数是 f(),编译器必须从 dll 本身导入它。(它被标记为他的外部)。

对于编译器来说,成员是一个更大的问题。它必须知道要导出的相应函数(构造函数、复制构造函数、赋值运算符、析构函数调用),并且当您将一个类标记为“dllexport”时,它必须导出/导入其中的每一个。您可以通过仅将必要的函数声明为 dllexport (ctor/dtor) 并禁止复制等方式,仅显式导出类的某些部分。这样,您就不必导出所有内容。

关于 std::string 的一个注意事项是,它的大小/内容在编译器版本之间发生变化,因此您永远无法在编译器版本之间安全地复制 std::string 。(例如,在 VC6 中,字符串有 3 个指针大,目前它是 16 个字节 + size + sizeof 分配器,我认为在 VS2012 中已对其进行了优化)。您永远不应该在界面中使用 std::string 对象。您可以创建一个 dll 导出的字符串实现,通过使用非导出的内联函数在调用方站点上将其转换为 std::string。