Partager via


exporting C++ classes with STL members from DLLs

I've found this strange hack that lets you DLLexport the C++ classes with the public members of the STL types. It's a really basic and very useful thing but one that you can't normally do with DLLs. Except with this hack. Let me explain it starting from the start.

Unlike Unix/Linux shared libraries, the Windows DLLs don't just export all the external symbols in them. You have to mark all such symbols explicitly. It actually gets worse since you have to mark up two separate versions of your headers: one to export the symbols when you build the DLL, another to import the symbols when you use the DLL.OK, you can also use the .DEF files but that doesn't work any good with the C++ symbols. So, the mark-up usually goes like this:

#ifdef MYDLL_EXPORTING
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif

class DLLEXPORT myclass
{
public:
    std::wstring field_;
};

Then when you build your DLL, you use the option -DMYDLL_EXPORTING in the compiler and get the symbols declared for export. As you can see, you can export the whole classes, not having to do method-by-method. (By the way, exporting a class doesn't mean that its nested classes get exported, you need to mark up all of them explicitly).

However the code as it shown won't link because an exported class in it has a public field of an STL type std::wstring which actually expands to the instantiation of a template std::basic_string<wchar_t>. And the compiler has the smart checking that the exported types depend only on the exported types. And the idea of exporting std::wstring doesn't occur to it, so here the linking crashes and burns. So okay, the first workaround I've thought about was to wrap std::wstring into my own class that I'll export. The only pain in wrapping is with the constructors, but C++11 provides a way to inherit them. So it would look like this:

class DLLEXPORT mywstring: public std::wstring
{
    using std::wstring::wstring;
};

class DLLEXPORT myclass
{
public:
    mywstring field_;
};

But no, it's not how things work. It fails saying that the class std::basic_string<wchat_t, ...> doesn't have the method wstring. And no amount of messing with the typedefs helped. However this led me to an accidental discovery of a workaround that works: as it turns out, once you export a base class, the compiler automatically exports its templatized base class, and the whole DLL benefits from it. So you don't even need to change the type of field_. You just add anywhere in your headers

class DLLEXPORT mywstring: public std::wstring
{};

and it magically makes the original code work. The same applies to any other DLL templates. If you need to use a vector of reference-counted pointers to your class in your other class, you just write

class DLLEXPORT _force_export_vector_shared_ptr_myclass: public std::vector<std::shared_ptr<myclass> >
{ };

and then you can use it, it works.

The caveat is obviously what is going to happen if you try to use two substantially different versions of STL in the same program, one used when the DLL was built, another used when the program gets built. With the proper wrapper classes it would just work. With the hack, it probably won't. But well, just use the same version of STL everywhere or the backwards-compatible version in the program. It's the common caveat for the templates and macros.

Comments

  • Anonymous
    October 06, 2015
    Nice but too convoluted!!. I still believe a simpler approach would be to use a PIMPL design pattern, when such things are needed.