【问题标题】:Exporting classes from a DLL从 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:未解析的外部符号

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

【问题讨论】:

    标签: c++ visual-c++ dll


    【解决方案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(非虚拟接口或模板模式)更好。
    • 是的。特殊的成员函数几乎总是以“集合”的形式出现——阅读“三规则”和“五规则”。
    猜你喜欢
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-23
    • 2010-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多