1

I have a Logging class, which can be simplified to something like:

template <int TARGET>
struct Logging
{
  static ostream * sOstream;
  static void log(const char * _txt);
  static void set_log_file(const char * _file_name)
  {
     sOstream = new ofstream(_file_name);
  }
}

The whole thing works well in a single binary. However, I'd /like/ to be able to use Logging<TARGET_DEBUG>::log("some message"); inside a DLL, after having called Logging<TARGET_DEBUG>::set_log_file(someFilePath.c_str()); in an executable that uses that DLL. Originally, it didn't work because I wasn't exporting the class with __declspec(dllexport). However, when I add the appropriate macro to the logging class definition (header only), I get a series of error messages:

warning C4273: 'sOstream' : inconsistent dll linkage // why does this occur for a pointer?

error C2491: 'Logging::sOstream' : definition of dllimport static data member not allowed

Various other classes in the application are exported/imported without issue, but this is the only templated class I'm trying to share between binaries. It's not an area I'm overly familiar with so: how can I make this work as desired? Note the EXE includes the Logging struct with __declspec(dllimport), the DLLs with __declspec(dllexport)

Rollie
  • 4,391
  • 3
  • 33
  • 55

1 Answers1

4

You need to use explicit template instantiation in conjunction with the __declspec modifier. This will cause the template members to be generated and exported from the DLL. It will also tell the application accessing the template members that they should be imported rather than generated.

The code below goes into a header file in your DLL project

// Normal __declspec import/export stuff
// Define EXPORT_LOGGING in the preprocessor settings of your DLL project 

#ifdef EXPORT_LOGGING
#define LOG_EXPORT __declspec(dllexport)
#else
#define LOG_EXPORT __declspec(dllimport)
#endif

template <int TARGET>
struct LOG_EXPORT Logging
{
    static std::ostream * sOstream;
    static void log(const char * _txt)
    {
        // ... logging code ...
    }
    static void set_log_file(const char * _file_name)
    {
        sOstream = new std::ofstream(_file_name);
    }
};

The code below goes into a .cpp file in your DLL project

// static member for TARGET_DEBUG
std::ostream* Logging<TARGET_DEBUG>::sOstream = nullptr;

//  explicit template instantiation
template struct Logging<TARGET_DEBUG>;
Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • Worked great, thanks! Only change I made was to define the static members and the explicit instantiation in the template header file, guarded by `#ifdef EXPORT_LOGGING`, so any project that wants this feature need only include the header to make it work. My only remaining concerns are: will this solution work with multiple DLLs that use the same header? And is there an elegant way of making this solution available for a stand-alone executable that doesn't link against a DLL exporting these definitions? – Rollie Jul 08 '13 at 23:25
  • Yes it will work with multiple modules linking against the main DLL whether they are an EXE or DLL file. You _can_ get it to work stand along with some `#ifdef`'s but whether it's elegant is in the eye of the beholder ;) You could wrap the existing `#ifdef` statements in `#ifdef USE_IN_DLL` and an `#else` that defines `LOG_EXPORT` as empty. – Captain Obvlious Jul 08 '13 at 23:29