21

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

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

public:
    std::string&  getName();
    void          setName(const std::string& name);
}

各种文章似乎都表明这是非常糟糕的,这是可以理解的。一切都必须使用相同的编译器设置和 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
}

固定版本是:

// 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!
}

(当您尝试使用 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
}

附加信息(上面有问题)

同时:

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;
}

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

但是,问题中提到的固定版本同时导出了 basic_string 和分配器。

4

2 回答 2

10

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

是的,可以。你的项目设置会让你陷入他们警告的那种麻烦。按值公开 C++ 对象要求 DLL 的客户端使用相同的 CRT,以便客户端应用程序可以安全地销毁在 DLL 中创建的对象。反之亦然。这要求这些模块使用相同的堆。

并且您的项目设置阻止了这种情况的发生,这是编译器警告的要点。您必须指定 CRT 的共享版本,以便所有模块加载 CRT 的唯一实现。

使用项目 + 属性、C/C++、代码生成、运行时库设置修复该问题。你现在在 /MT 有它,它必须是 /MD。为所有模块和所有配置更改此设置。

于 2012-12-13T19:15:44.417 回答
4

这归结为某些事物的构建方式。

当编译器看到

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

{
  std::string tmp = f();
}

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

std::string tmp; => sizeof( std::string ), new (__stack_addr) std::string;
tmp = f();       => call f(), operator=( std::string )

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

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

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

于 2012-12-13T18:20:56.777 回答