Link to home
Start Free TrialLog in
Avatar of millsoft
millsoft

asked on

DIFFICULT: C++ class exporting & __declspec(dllexport)

Problem: Dynamically link C++ classes into my application at run-time to provide specific ustomized behavior.  E.g. in my case, I have several formats of file to read, say F1 & F2.  Currently, my app has a class F1Reader that reads F1 and creates a bunch of other objects of type Factor1, Factor2, Factor3, etc.  I want to add support for a new file type F2, which will produce the same type of Factor1,2,3 objects, but with different data.  Factor objects are already well-defined C++ classes within my application.  Therefore, F1Reader & F2Reader are class factories.  Further, I would like to be able to add F3 in the future without modifying my primary application.  That is I want to be able to provide F3Reader.DLL & some registry settings, and boom, it works.

One obvious solution would be to encapsulate all the Factor type objects as well as the F1Reader & F2Reader classes as COM objects.  This is certainly straight-forward, but it is a LOT of work considering the amount of current code that uses Factor1,2,3 objects.  Therefore, I'm looking for a solution that will continue to create native C++ objects with dynamically linked code, without creating COM wrappers.

My proposed solution is to refactor F1Reader & F2Reader so they derive from a common base (FactorFactoryBase), and put F1Reader & F2Reader in separate C++ DLLs that might or might not be present on the user's machine.  The interface to FactorFactoryBase will provide all my application needs to create the Factor objects.  

Consider this little attempt to test out the concept:

struct __declspec(dllexport) B
{
private:
      string      m_s;

public:
      int            m_x;
      B()
      {
            m_x = 1;
            const type_info& t = typeid(*this);      
            m_s = t.name();  
      }
};

returns a compiler warning:

c:\dev\test\cppdll\b.h(12) : warning C4251: 'm_s' : class 'std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >' needs to have dll-interface to be used by clients of struct 'B'

While this is only a warning, it is a level 1 warning, and sounds pretty fatal since I want the DLLs to derive from B.

It seems I need to instantiate an specific instance of string and export that.  But I haven't been able to figure out how.

I tried:

class __declspec(dllexport) stringEx: public basic_string<char>
{
};

& got the same warning message.

Any suggestions on how to solve this problem?

TIA,
Brad
Avatar of jkr
jkr
Flag of Germany image

DLLs are the Windows way to reuse code, and templates are the C++ way. They do not coexist well, though. My suggestion would be to provide pure abstract interfaces as base classes and let the implementations handle that seamlessly and, even more importand, leave STL objects in the modules they were created in. Provide access interfaces instead. Check out http://support.microsoft.com/default.aspx?scid=kb;en-us;168958 ("How To Exporting STL Components Inside & Outside of a Class") and http://support.microsoft.com/default.aspx?scid=kb;en-us;168958 ("How To Exporting STL Components Inside & Outside of a Class")
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of millsoft
millsoft

ASKER

Hi jkr,

Thanks for your feedback.  Below follows some more research I've done after reading your articles.  You may (or may not) find it interesting.  

Brad
===
Ok, I've been looking into the article "How To Exporting STL Components Inside & Outside of a Class".  When you mentioned it, it rang a bell.  Seems like I've done that before.  Anyway, looking through the CRT code is loaded with related stuff like this:
// copied from Xstring in VC6.

#ifdef      _DLL
#ifdef __FORCE_INSTANCE
template class _CRTIMP2 basic_string<char, char_traits<char>, allocator<char> >;
template class _CRTIMP2 basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >;
#else            // __FORCE_INSTANCE
#pragma warning(disable:4231) /* the extern before template is a non-standard extension */

extern template class _CRTIMP2 basic_string<char, char_traits<char>, allocator<char> >;
extern template class _CRTIMP2 basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >;

#pragma warning(default:4231) /* restore previous warning */
#endif            // __FORCE_INSTANCE
#endif            // _DLL

_CRTIMP2 is defined to __declspec(dllimport) whenever the DLL version of the C-runtime is selected.  So, based upon the article, it appears that MSFT is exporting std::string from their own DLL for our use.  That makes sense, although then it seems that it's not possible to export a class (B) containing an imported data member from the CRT (string), even though both my EXE and DLL should both be able to see (and import) the same exported symbol.  Go figure.

BTW, the two articles you mentioned are not quite consistent.  E.g. one article (168958) says exporting maps is impossible, and the other article (172396) uses that as an example!

Hi jkr,

It turns out my problem didn't have anything to do with the templates at all.  The simple problem was that my projects didn't have the CRT set to the DLL selections.  I normally use CRT in DLLs, but somewhere I missed it here, and they were all set to static linked.  Changing that fixed all the problems because string is now easily imported into the DLLs.

However, your articles did help because I found I couldn't compile the examples from the article which is how I uncovered my settings problem. :)

Brad
====
Here's the code:

// b.h in the EXE
#pragma once

#include <string>
using namespace std;
// DOING_EXE is defined for compiling the EXE containing the actual code.  
// not defined in all other cases.

#ifdef DOING_EXE
#    define EXE_EXPORT_SPECIFIER __declspec(dllexport)
#pragma message("exporting")
#else
#    define EXE_EXPORT_SPECIFIER __declspec(dllimport)
#pragma message("Importing")
#endif

struct EXE_EXPORT_SPECIFIER B
{
      string      m_s;
      int      m_x;
      B();
      virtual ~B();
};

struct EXE_EXPORT_SPECIFIER D:public B
{
      D();
      virtual ~D() {};
};

// derived class in my DLL

#ifdef CPPDLL1_EXPORTS
#define CPPDLL1_API __declspec(dllexport)
#else
#define CPPDLL1_API __declspec(dllimport)
#endif
#include "..\B.h"
// This class is exported from the CPPDLL1.dll

struct CPPDLL1_API D1:public B
{
      D1();
      virtual ~D1();
};



Glad to have been of some help, thanx :o)