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

时间:2012-12-13 16:46:56

标签: c++ visual-studio-2010 dll stl instantiation

我的问题与导出带有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。相反,它们只导出类的成员/函数。

然而,问题中提到的 fixed 版本会导出basic_string和allocator。

2 个答案:

答案 0 :(得分:10)

  

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

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

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

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

答案 1 :(得分: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的模板函数并将其调用一天,并将函数合并到链接器阶段,其中链接器尝试确定哪些函数可以折叠成一个。唯一未知的函数是f(),编译器必须从dll本身导入。 (这标志着他的外在。)

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

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