我的问题与导出带有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。
答案 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。