从 DLL 导出类
Posted
技术标签:
【中文标题】从 DLL 导出类【英文标题】:Exporting classes from a DLL 【发布时间】:2016-05-31 06:32:47 【问题描述】:我对将类导出为 DLL 有一些疑问。假设我有如下类,我希望从 DLL 中导出 BookStore
。这样客户端就可以从BookCollection
中获取Book
的值,例如:getTitle()
。
#ifdef _EXPORTING
#define BOOKSTORE_API __declspec(dllexport)
#else
#define BOOKSTORE_API __declspec(dllimport)
#endif
class Book
std::string title;
std::string publisher;
char * getTitle();
char * getPublisher();
class BookCollection
std::vector<Book> books;
int getBooksCount();
Book getBook(int location);
BOOKSTORE_API class BookStore
BookCollection bookCollection;
BookCollection getBookCollection();
因此,我怎样才能成功导出该类以用于其他项目,我可以这样做:
BookStore * bookStore = RandomBookStoreGenerator::createBookStore();
std::cout << bookStore->getBookCollection().getBook(0).getTitle() << '\n';
导出BookStore
是否也会间接导出BookCollection
和Book
,或者它们还需要宏来导出?
编辑...
我已导出 DLL 并在测试程序中进行了尝试。以下;
BookCollection bookCollection = bookStore->getBookCollection();
导致错误
LNK2001:未解析的外部符号
任何想法,这可能是因为我没有正确导出类吗?
【问题讨论】:
【参考方案1】:导出
BookStore
是否也会间接导出BookCollection
和Book
,或者它们还需要宏来导出?
他们也需要宏。
编译器只导出标记为导出的内容。它不会自动导出参数,也不会为正在导出的方法和函数返回类型。
如下;
class BOOKSTORE_API Book
std::string title;
std::string publisher;
char * getTitle();
char * getPublisher();
class BOOKSTORE_API BookCollection
std::vector<Book> books;
int getBooksCount();
Book getBook(int location);
class BOOKSTORE_API BookStore
// ...
;
您将收到有关未导出成员的其他警告。 如果您对 dll 和 exe 使用相同的编译器和设置,这些是 largely noise and can be silenced(或禁用)。
更全面的替代方案是 pimpl pattern 并删除 std::vector
等。人。从类定义和标准库成员不需要从 dll 中导出。 MSDN has a nice article on this.
class BOOKSTORE_API BookCollection
protected:
struct Pimpl;
Pimpl* pimpl_;
// in the cpp compiled into the dll
struct BookCollection::Pimpl
// ...
std::vector<Book> books;
// ...
关于“三规则”和“五规则”以及未解决的符号......
从 dll 中导出类时,最好同时导出所有特殊成员,以避免未解决的符号错误。如果使用 pimpl 成语,这尤其适用。
[S]假设所有这些类都在不同的文件中,宏应该保持不变还是每个文件都需要更改?
保持宏和包含它的 #define
每个 dll 相同。因此,如果对于单个 dll,它们位于三个文件中,那么它们都使用相同的 #define
块。本质上,您是在每个 dll 的基础上控制导入/导出。我还将定义块放入它自己的标题中,并将其包含在每个类的标题中(但这不是必需的)。
[F] 从这个简单的示例中,[a] 不同的 msvc 编译器版本或客户端代码的 CRT 会引发未定义的行为,因为我知道返回 [an] STL 对象会导致这种情况。由于在这段代码中,getter 只返回原始 C 数据类型,这也会有问题吗?
是的,不同的编译器版本和标准库可能/将会导致问题。例如。即使所有的构造函数、赋值运算符和析构函数都从 dll 中导出,客户端仍然需要为此类的对象分配正确的空间量(通过。new
或在堆栈上)。不同的标准库对于std::string
等可能有不同的大小,混合它们会造成内存损坏等。在这方面,pimpl 或 NVI(非虚拟接口或模板模式)更好。
【讨论】:
嗨 Niall,假设所有这些类都在不同的文件中,宏应该保持不变还是每个文件都需要更改? 保持宏和包含它的#define
每个 dll 相同。因此,如果对于单个 dll,它们位于三个文件中,那么它们都使用相同的 #define
块。本质上,您是在每个 dll 的基础上控制导入/导出。我还将定义块放入它自己的标题中,并将其包含在每个类的标题中(但这不是必需的)。
嗨 Niall,从这个简单的例子来看,不同的 msvc 编译器版本或客户端代码的 CRT 会引发未定义的行为,因为我知道返回 STL 对象会导致这种情况。由于在这段代码中,getter 只返回原始 C 数据类型,这也会有问题吗??
是的,不同的编译器版本和标准库可能/将会导致问题。例如。即使所有的构造函数、赋值运算符和析构函数都从 dll 中导出,客户端仍然需要为此类的对象分配正确的空间量(通过。new
或在堆栈上)。不同的标准库对于std::string
等可能有不同的大小,混合它们会造成内存损坏等。在这方面,pimpl 或 NVI(非虚拟接口或模板模式)更好。
是的。特殊的成员函数几乎总是以“集合”的形式出现——阅读“三规则”和“五规则”。以上是关于从 DLL 导出类的主要内容,如果未能解决你的问题,请参考以下文章
从 DLL 导出包含 `std::` 对象(矢量、地图等)的类