1

I have some doubts in exporting classes as DLL. Supposed that I have classes as below and I wish to export BookStore from a DLL. So that client is able to get values out of Book from BookCollection by eg: 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();
}

Thus, how can I export the class successfully to be used in other project where i can do something like this:

BookStore * bookStore = RandomBookStoreGenerator::createBookStore();
std::cout << bookStore->getBookCollection().getBook(0).getTitle() << '\n';

Would exporting BookStore will also export BookCollection and Book indirectly or they also need the macro for export?

Edit...

I have exported the DLL and tried it in a test program. The following;

BookCollection bookCollection = bookStore->getBookCollection();

Results in an error

LNK2001: unresolved external symbol

Any ideas, is this possibly because I'm not exporting the classes correctly?

Niall
  • 30,036
  • 10
  • 99
  • 142
vincent911001
  • 523
  • 1
  • 6
  • 20

1 Answers1

3

Would exporting BookStore will also export BookCollection and Book indirectly or they also need the macro for export?

They also need the macro.

The compiler only exports what is marked for export. It doesn't automatically export arguments nor return types for methods and functions that are being exported.

As follows;

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 {
  // ...
};

You will get additional warnings about the members not being exported. Provided you use the same compiler and settings for the dll and the exe, these are largely noise and can be silenced (or disabled).

A more comprehensive alternative is the pimpl pattern and remove the std::vector et. al. from the class definition(s) and the standard library members would not need to be exported from the 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;
    // ...
}

On the "rule of three" and the "rule of five" and the unresolved symbol(s)...

When exporting classes from a dll, it is best to export all the special members as well to avoid unresolved symbol errors. This is especially applicable if using the pimpl idiom.


[S]uppose all these classes are in different files, should the macro remain the same or [does it] need to be changed per file?

Keep the macro and the #define that contains it the same per dll. So, if for the single dll, they are in three files, then they all use the same #define block. Essentially you are controlling the import/export on a per dll basis. I would also put the define block into a header of its own and include it in the header for each class (but that is not needed).

[F]rom this simple example, would [a] different msvc compiler version or CRT of the client code raise undefined behaviour as I am aware that returning [an] STL object would cause this. Since in this code, the getter only return primitive C datatype, would it be a problem as well?

Yes, different compiler versions and standard libraries could/would cause problems. E.g. even if all the constructors, assignment operators and destructors were exported from the dll, the client still needs to allocate the correct amount of space (via. new or on the stack) for objects of this class. Different standard libraries could have different sizes for std::string etc. and mixing them will create memory corruption etc. In this respect, the pimpl or NVI (non-virtual interface or template patten) is better.

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • hi Niall, supposed all these classes are in different files, should the macro remains the same or need to be changed per files? – vincent911001 May 31 '16 at 07:09
  • 1
    Keep the macro and the `#define` that contains it the same per dll. So, if for the single dll, they are in three files, then they all use the same `#define` block. Essentially you are controlling the import/export on a per dll basis. I would also put the define block into a header of its own and include it in the header for each class (but that is not needed). – Niall May 31 '16 at 07:13
  • Hi Niall, from this simple example, would different msvc compiler version or CRT of the client code raises undefined behaviour as i am aware that returning STL object would cause this. Since in this code, the getter only return primitive C datatype, would it be a problem as well?? – vincent911001 May 31 '16 at 07:23
  • 1
    Yes, different compiler versions and standard libraries could/would cause problems. E.g. even if all the constructors, assignment operators and destructors were exported from the dll, the client still needs to allocate the correct amount of space (via. `new` or on the stack) for objects of this class. Different standard libraries could have different sizes for `std::string` etc. and mixing them will create memory corruption etc. In this respect, the pimpl or NVI (non-virtual interface or template patten) is better. – Niall May 31 '16 at 07:28
  • Hi Niall, could you offer me any advice on the situation that i faced above as in edits? – vincent911001 May 31 '16 at 08:10
  • Have you implemented the copy constructors/assignments etc.? The unresolved errors are covered here; http://stackoverflow.com/q/12573816/3747990. And the crash sounds like some undefined behaviour lurking in the implementation. It could be grounds for a followup question (you can link back to this one). – Niall May 31 '16 at 08:17
  • Hi Niall, the crash solved after I cleaned and rebuilt the DLL, ya, I didnt implement the copy constructor. I have one doubt, if the `BookStore` object is in the heap, does it mean that `BookCollection`, member variable of `BookStore` also in the heap. So, if i am to implement the copy constructor, should i get a pointer from client code and then write a new object on it? – vincent911001 May 31 '16 at 08:37
  • @vincent911001. Yes it would be. All "automatic" members of an object on the heap would be on the heap (but are they don't need a `delete`). All dynamic members are always on the heap. – Niall May 31 '16 at 08:40
  • Thanks Niall, one final question, so is the copy constructor needed to be implement in order for this to works `BookCollection bookCollection = bookStore->getBookCollection()`. So how should i implement the copy constructor since the DLL and client address space is different. – vincent911001 May 31 '16 at 08:45
  • *so is the copy constructor needed to be implement in order for this to work*? Yes. *So how should i implement the copy constructor since the DLL and client address space is different*? The same way as normal. The process maintain one data address space for all the data used in-proc (by the exe and loaded dlls). – Niall May 31 '16 at 08:49
  • Hi Niall, i have learnt so much today, thanks a lot for your help. Lastly, would it be a best practice to override copy constructor as well as copy assignment operator? – vincent911001 May 31 '16 at 08:52
  • 1
    Yes it is. The special member functions almost always come in "sets" - read up on the "rule of three" and the "rule of five". – Niall May 31 '16 at 08:59