My question is related to exporting a C++ class with STL inside. For example:
class __declspec(dllexport) Hello
{
std::string name;
public:
std::string& getName();
void setName(const std::string& name);
}
Various articles seems to indicate that this is very bad, which is quite understandable. Everything must be compiled with the same compiler settings and CRT version. Otherwise everything will crash and burn.
What I don't understand is why only data members seem to have an issue. With the below code, I get: "C4251: needs to have dll-interface to be used by clients of class"; which is apparently fixed by exporting the instantiated std::string:
struct __declspec(dllexport) SomeClass
{
// Removes the warning?
// http://www.unknownroad.com/rtfm/VisualStudio/warningC4251.html
// template class __declspec(dllexport) std::string;
std::string name; // Compiler balks at this
}
And the fixed version is:
// Export the instantiations of allocator and basic_string
template class __declspec(dllexport) std::allocator<char>;
template class __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char> >;
struct __declspec(dllexport) SomeClass
{
std::string name; // No more balking!
}
(This will give LNK2005 "basic_string already defined" when you try to use the DLL, meaning you have to not link in the CRT on the client - so it ends up using the instantiation in the DLL).
Return types and arguments seem to have no problem with the STL, and do not receive the same treatment data members get from the compiler.
// No exporting required?
struct __declspec(dllexport) SomeOtherClass
{
std::string doSomething1(); // No problemo
void doSomething2(const std::string& s); // No problemo
}
In both:
class A {
std::string foo() { return std::string(); }
// std::string& foo(); gives the same result!
// std::string* foo(); also gives the same result!
}
class B {
std::string a;
}
Neither seem to export std::basic_string or std::allocator. Rather, they only export the members/functions of the class.
However the fixed version mentioned in the question exports both basic_string and allocator.
You can declare C++ classes with the dllimport or dllexport attribute. These forms imply that the entire class is imported or exported. Classes exported this way are called exportable classes.
You can export an entire C++ class by placing the __declspec(dllexport) before the class name, or you can export a single method by placing __declspec(dllexport) before the method name. Make class methods static and public. The C/C++ DLL Adapter does not allow you to call public non-static methods.
The exports table contains the name of every function that the DLL exports to other executables. These functions are the entry points into the DLL; only the functions in the exports table can be accessed by other executables. Any other functions in the DLL are private to the DLL.
Various articles seems to indicate that this is very bad
Yes, it can be. And your project settings are going to get you into the kind of trouble they are warning about. Exposing C++ objects by value requires the client of your DLL to use the same CRT so that objects that are created in the DLL can be safely destroyed by the client app. And the other way around. Which requires that these modules use the same heap.
And your project settings prevent that from being possible, the gist of the compiler warning. You must specify the shared version of the CRT so that all modules load the one-and-only implementation of the CRT.
Fix that with Project + Properties, C/C++, Code Generation, Runtime library setting. You have it now at /MT, it must be /MD. Change this for all modules and all configurations.
This boils down to how certain things get built.
When the compiler sees
__declspec(dllimport) std::string f();
// ...
{
std::string tmp = f();
}
It must figure out what to call, and where to get it from. So in this case :
std::string tmp; => sizeof( std::string ), new (__stack_addr) std::string;
tmp = f(); => call f(), operator=( std::string )
But because it sees the complete implementation of std::string it can just use a new instance of the the according template. So it can just instantiate the template functions of std::string and call it a day, and leave the function coalescing to the linker stage, where the linker tries to figure out which functions it can fold into just one. The only unknown function is f() which the compiler must import from the dll itself. (It's marked external for him).
Members are a bigger problem for the compiler. It has to know the according functions to export (constructor,copy-constructor,assignment-operator,destructor call) and when you mark a class as 'dllexport', it must export/import every single one of them. You can export explicitly only certain parts of your class, by declaring only the necessary functions as dllexport (ctor/dtor) and disallow e.g. copying. This way, you don't have to export everything.
One note about std::string is, that its size/contents changed between compiler versions, so that you never can safely copy a std::string between compiler versions. (E.g. in VC6 a string was 3 pointers large, currently it's 16 bytes + size + sizeof allocator, which I think got optimized away in VS2012). You never should use std::string objects in your interface. You may create a dll-exported string implementation that converts on the caller site into a std::string by using non-exported inline functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With