28

It is not a good practice using stl-classes in the dll-interface as Common practice in dealing with warning c4251: class … needs to have dll-interface explains. An example is given:

#include <iostream>
#include <string>
#include <vector>


class __declspec(dllexport) HelloWorld
{
public:
    HelloWorld()
    {
        abc.resize(5);
        for(int i=0; i<5; i++)
            abc[i] = i*10;

        str="hello the world";
    }
    ~HelloWorld()
    {

    }

    std::vector<int> abc;
    std::string str;

};

When compiling this file, the following warnings can be observed:

 warning C4251: 'HelloWorld::str' : class 'std::basic_string<_Elem,_Traits,_Ax>' needs to have dll-interface to be used by clients of class 'HelloWorld'    
 warning C4251: 'HelloWorld::abc' : class 'std::vector<_Ty>' needs to have dll-interface to be used by clients of class 'HelloWorld'

Then the question is how we can implement the same functionality without using STL class vector and string. One implementation I could think of is as follows:

class __declspec(dllexport) HelloWorld2
{
public:
    HelloWorld2()
    {
         abc_len = 5;
         p_abc = new int [abc_len];
         for(int i=0; i<abc_len; i++)
             p_abc[i] = i*10;

         std::string temp_str("hello_the_world");
         str_len = temp_str.size();
         p_str = new char[str_len+1];
         strcpy(p_str,temp_str.c_str());
    }
    ~HelloWorld2()
    {
        delete []p_abc;
        delete []p_str;
    }

    int *p_abc;
    int abc_len;
    char *p_str;
    int str_len;



};

As you can see, in the new implementation we use int *p_abc to substitute vector abc and char *p_str to replace string str. The question I have is whether there are other elegant implementation approaches that can do the same. Thanks!

Community
  • 1
  • 1
feelfree
  • 11,175
  • 20
  • 96
  • 167

4 Answers4

35

I think you've got at least 5 possible options to solve this problem:

  1. You could still keep STL/template classes for your fields and also use them as parameter types, as long as you keep all the fields private (which is a good practice anyway). Here is a discussion about this. To remove the warnings you could simply use according #pragma statements.

  2. You could create small wrapper classes with private STL fields and expose fields of your wrapper class types to the public instead, but this would most probably only move the warnings to another places.

  3. You could use the PIMPL idiom to hide your STL fields in a private implementation class, which will not even be exported from your library nor be visible outside of it.

  4. Or you could actually export all the required template class specializations, as suggested in the C4251 warning, in a way that is described here. In short you would have to insert following lines of code before your HelloWorld class:

    ...
    #include <vector>
    
    template class __declspec(dllexport) std::allocator<int>;
    template class __declspec(dllexport) std::vector<int>;
    template class __declspec(dllexport) std::string;
    
    class __declspec(dllexport) HelloWorld
    ...
    

    By the way, the order of those exports seems to be important: the vector class template uses the allocator class template internally, so the allocator instantiation has to be exported before the vector instantiation.

  5. The direct use of intrinsics is probably your worst option, but if you encapsulate your fields

    int *p_abc;
    int abc_len;
    

    in something like class MyFancyDataArray and the fields

    char *p_str;
    int str_len;
    

    in something like class MyFancyString, then this would be a more decent solution (similar to the one described at the second point). But it is probably not the best habit to reinvent the wheel too often.

user276648
  • 6,018
  • 6
  • 60
  • 86
swalex
  • 3,885
  • 3
  • 28
  • 33
  • I tried your 4th suggestion, but I get the error C2242 when compiling with VS2010. – Simon Jan 12 '16 at 08:09
  • It is hard to say, why the compiler complains with C2242 without to see the code or at least the full error message, but I would guess that there is probably a semicolon missing somewhere before a typedef. Maybe the line `class __declspec(dllexport) HelloWorld` was copied by accident? – swalex Jan 12 '16 at 09:34
  • 2
    @Simon : use this `template class __declspec(dllexport) std::basic_string;` – I-A-N May 05 '17 at 10:09
  • In option #1 (keeping the field private) a problem can happen: e.g. `sizeof(std::string)` may be e.g. 48 in a DLL (e.g. if it's in Debug configuration), but another size (e.g. 32) in the EXE. Then the DLL would think the class has one size, while the EXE would treat the class as another size. Then all fields after such STL-class field change their positions and you get a spectacular crash. – Serge Rogatch Jul 18 '17 at 18:39
  • 1
    The suggested PIMPL implementation uses another STL class (`unique_ptr`), which also doesn't have a dll-interface, and it will be tricky (if possible at all) to export template for a private class... – AntonK Apr 25 '18 at 18:53
  • 1
    Exporting `std::vector` (with VS2017) is not an easy option - you'll have to dig through its implementation to export needed "parts" as well (down to `_Compressed_pair` or even further). – AntonK Apr 25 '18 at 19:14
  • @SergeRogatch Does this problem manifest in mixing compile settings? – Candy Chiu Jul 17 '18 at 22:00
  • @CandyChiu , or when DLL and EXE have different STL versions, compiler versions, etc. – Serge Rogatch Jul 18 '18 at 17:52
  • 1
    The link in (4) is dead :-( . – Trantor Nov 07 '19 at 14:01
  • (4) is a bad idea, because then you lose inlining (execution speed) in the client executable. – Keith Russell Aug 05 '21 at 14:23
9

OR do the easiest thing to do, move the __declspec to the ONLY members you care to export:

class HelloWorld
{
public:
    __declspec(dllexport) HelloWorld()
    {
        abc.resize(5);
        for(int i=0; i<5; i++)
            abc[i] = i*10;

        str="hello the world";
    }
    __declspec(dllexport) ~HelloWorld()
    {
    }
    std::vector<int> abc;
    std::string str;
};
Niall
  • 30,036
  • 10
  • 99
  • 142
  • 2
    Does exproting only some methods but not the whole class really works? – jpo38 Feb 08 '16 at 17:49
  • It does, when you export the class what the compiler does is export all members of the class, there is really no 'class' symbol in the binary, but there is a (albeit mangled) HelloWorld::HelloWorld() and HelloWorld::~HelloWorld() in this particular case. Since you're not directly accessing the data members, there is no need to export them, however the compiler still needs to see their declaration in order to figure out the size of an instance of the class. Making then private is always a good practice. – Rodrigo Hernandez Feb 08 '16 at 20:11
  • 2
    When exporting individual methods from a dll, you need to be careful to control access to the non-exported members since the compiler (for the exe) will happily generate the appropriate code for them on the "other side" of the dll and invoke the exported functions from the dll. The access control needs to be extended to member variables and special members (such as copy and assignment); the best may be to make the non-exported members all private (or `=delete`). – Niall Feb 09 '16 at 07:03
  • 1
    Exporting just functions works - kind-of. Because there is more then just the ctors + dtor + member functions. There are also some "nameless" helper functions like the scalar deleting destructor and the vector deleting destructor. Of course they do have a decorated name, and can be exported via a .def file. But it's very annoying to manually keep track of those functions and update the .def file every time a new class is exported or an old class removed. – Paul Groke May 17 '16 at 23:17
  • @PaulGroke you can use `= default` to `dllexport` those implicitly-defined functions in the C++ code instead of a .def file. – Keith Russell Aug 05 '21 at 14:21
  • 2
    @KeithRussell How does that work for stuff like the `scalar deleting destructor` and `vector deleting destructor` functions? Are they exported automatically when you `dllexport` the dtor? – Paul Groke Aug 09 '21 at 16:39
  • @PaulGroke Huh -- I'd never actually heard of those until now. Apparently, they're an MSVC implementation detail. Thanks for tipping me off about them. No idea, though. – Keith Russell Aug 09 '21 at 18:22
9

I'm not sure which problem you want to solve here. There are two different issues: Cross compiler binary compatibility and avoiding "undefined symbol" linker errors. The C4251 warning you quoted talks about the second issue. Which is really mostly a non-issue, especially with SCL classes like std::string and std::vector. Since they're implemented in the CRT, as long as both sides of your application use the same CRT, everything will "just work". The solution in that case is to just disable the warning.

Cross compiler binary compatibility OTOH, which is what is discussed in the other stackoverflow question you linked, is a whole different beast. For that to work out, you basically have to keep all your public header files free of any mention of any SCL class. Which means you either have to PIMPL everything or use abstract classes everywhere (or a mix thereof).

Paul Groke
  • 6,259
  • 2
  • 31
  • 32
3

Just use Pointer To Implementation (pImpl) idiom.

qehgt
  • 2,972
  • 1
  • 22
  • 36