Macmade
Macmade

Reputation: 53960

C++ header-only with global state in a shared library

I'm working on a C++ library that I would ideally keep header-only.

A specific part of this library requires a global state.
Let's say it needs a global vector of strings for this example.

I can easily achieve this with a static variable inside a function:

std::vector< std::string > & GetGlobalStrings( void )
{
    static auto g = new std::vector< std::string >();

    return *( g );
}

This works great for executables using the library.

Now for some reason, I also need to package that library inside a macOS framework.

Inside that framework, there's compiled code that will access this global state.
So does the executable linked with that framework.

Obviously this doesn't work, as the executable and the framework will have separate definitions for the static variable, thus making this global state not so global.

Is there any way to accomplish this in a convenient manner?

Upvotes: 2

Views: 1086

Answers (2)

What about:

// GlobalString.h

#include <string>
#include <vector>

#ifdef _MSC_VER
  #ifdef GLOBAL_STRING_SRC
    #define GLOBAL_STRING_DECLSPEC __declspec(dllexport)
  #else
    #define GLOBAL_STRING_DECLSPEC __declspec(dllimport)
  #endif //GLOBAL_STRING_DECLSPEC
#endif // GLOBAL_STRING_SRC

inline EXPORT_SYMBOL std::vector<std::string>& GetGlobalStrings() noexcept
{
  static std::vector<std::string> retval;
  return retval;
}

Then write a .cpp that ODR uses your definition of GetGlobalStrings. On Windows, declaring an function dllexport is an implicit ODR use. Compiling a cpp that includes GlobalString.h and linking it into the dll should work.

// GlobalSring.cpp

#define GLOBAL_STRING_SRC
#include <GlobalString.h>

The inline keyword guarantees that multiple definitions of GetGlobalStrings from different compilation units seen by the linker will be merged into only one if GetGlobalStrings gets ODR used. Be reassured that C++ guarantees that static variables from within inline function are also merged. Note that dllimport on function definition is illegal unless the definition is declared inline. I am not much familar with MacOS dynamic libraries, but it should work similarly with clang with -fvisibility=default flag.

With C++17 one could also probably use an inline variable instead of a fuction:

inline EXPORT_SYMBOL std::vector<std::string> GlobalString;

Upvotes: 2

Matthieu Brucher
Matthieu Brucher

Reputation: 22023

You could force the symbols to be in only one file, for instance:

#if defined(I_NEED_A_BAD_HACK) || defined(GLOBAL_STATE_STORE)
# define USE_FULL_FUNCTION
#endif

#ifdef USE_FULL_FUNCTION
std::vector< std::string >& GetGlobalStrings()
{
    static std::vector< std::string > g;

    return g;
}
#else
std::vector< std::string >& GetGlobalStrings();
#endif

Then in one of your framework cpp, define the macro before including the header.

Now, if you need to export the symbol (Windows mainly but also Linux/macOS with visibility hidden), then it gets a little bit trickier, as you need another global flag saying if you are in the framework or not and activating the export/import attribute.

It's definitely not great, but at least you ensure that you have only one instance of the static variable in one file. Also works properly with a static library, of course.

Upvotes: 1

Related Questions