从 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 是否也会间接导出BookCollectionBook,或者它们还需要宏来导出?

编辑...

我已导出 DLL 并在测试程序中进行了尝试。以下;

BookCollection bookCollection = bookStore->getBookCollection();

导致错误

LNK2001:未解析的外部符号

任何想法,这可能是因为我没有正确导出类吗?

【问题讨论】:

【参考方案1】:

导出BookStore 是否也会间接导出BookCollectionBook,或者它们还需要宏来导出?

他们也需要宏。

编译器只导出标记为导出的内容。它不会自动导出参数,也不会为正在导出的方法和函数返回类型。

如下;

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::` 对象(矢量、地图等)的类

从 DLL 导出包含 `std::` 对象(矢量、地图等)的类

dll从静态链接库导出函数符号

C ++中的DLL导出和继承

MFC生成的DLL导出类的函数,在Qt中如何调用

在 VC++ 中,有没有办法知道没有任何头文件的 dll 的导出类?