2

I have an application (in MS Visual Studio) that contains 3 projects:

  • main (the one that contains the main function)
  • device (models some hardware device)
  • config (contains some configuration for both other projects)

So the dependency graph is:

  • main depends on device, which depends on config
  • main depends on config

The config project contains a Singleton, which holds some configuration parameters.

I decided to turn the device project into a DLL. When i did this, it seems that i got two instances of the Singleton in the config project! I guess this is a classic problem, which might have a good solution. So how can i fix this?

I reproduced the problem with the following (relatively small) code. Of course, in my case there are some 30 projects, not just 3. And i would like to make just 1 DLL (if possible).


// config.h
#pragma once
#include <string>
#include <map>
class Config
{
public:
    static void Initialize();
    static int GetConfig(const std::string& name);

private:
    std::map<std::string, int> data;
};

// config.cpp
#include "config.h"

static Config g_instance;

void Config::Initialize()
{
    g_instance.data["one"] = 1;
    g_instance.data["two"] = 2;
}

int Config::GetConfig(const std::string& name)
{
    return g_instance.data[name];
}

// device.h
#pragma once

#ifdef _DLL
#define dll_cruft __declspec( dllexport )
#else
#define dll_cruft __declspec( dllimport )
#endif

class dll_cruft Device
{
public:
    void Work();
};

// device.cpp
#include "device.h"
#include <iostream>
#include "config.h"

void Device::Work()
{
    std::cout << "Device is working: two = " << Config::GetConfig("two") << '\n';
}

// main.cpp
#include <iostream>
#include "config.h"
#include "device.h"

int main()
{
    std::cout << "Before initialization in application: one = " << Config::GetConfig("one") << '\n';
    Config::Initialize();
    std::cout << "After initialization in application: one = " << Config::GetConfig("one") << '\n';
    Device().Work();
    std::cout << "After working in application: two = " << Config::GetConfig("two") << '\n';
}

Output:

Before initialization in application: one = 0

After initialization in application: one = 1

Device is working: two = 0

After working in application: two = 2

Some explanations on what the code does and why:

  1. Main application starts
  2. The first print is just to show that the singleton is not initialized yet
  3. Main application initializes the singleton
  4. The first print shows that the initialization worked
  5. Main application starts the "hardware device"
  6. Inside the DLL, the singleton is not initialized! I expect it to output two = 2
  7. The last print shows that the singleton is still initialized in main application
anatolyg
  • 26,506
  • 9
  • 60
  • 134

3 Answers3

2

When I ran into this same problem I solved it by creating another DLL whose sole purpose is to manage the singleton instance. All attempts to get a pointer to the singleton call the function inside this new DLL.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • In my own (limited) experience this is the only way. I think if I really hated the idea of making another DLL, I might try to export the **config** symbols out of the **device** DLL and see if the Windows loader will match them up with the same objects in **main**. But so far as I know yours is the only reasonable solution. – Jeff Trull Mar 22 '13 at 21:50
  • @JeffTrull, if the symbols are available in a static library then it won't matter if they're exported from the DLL or not; the static library takes precedence. And you'll still get multiple copies. – Mark Ransom Mar 23 '13 at 02:27
2

You can decide where singleton should reside and then expose it to other consumers.


Edited by OP:

For example, i want that the config instance appear only in the EXE (not DLL).

  1. Turn the instance into a pointer

    static Config* g_instance;
    
  2. Add a separate initializing function to device's exported functions:

    void InitializeWithExisting(Config* instance) {g_instance=instance;}
    
  3. After initializing the singleton normally, use the second initialization:

    Config::Initialize();
    Config::InitializeWithExisting();
    
anatolyg
  • 26,506
  • 9
  • 60
  • 134
Dialecticus
  • 16,400
  • 7
  • 43
  • 103
  • How can i do it? I guess that by "where" you mean "exe or dll"; suppose i decided "exe", so what should i do next? – anatolyg May 09 '12 at 21:40
  • Have a special function in DLL with which you would register your instance, `SetConfig(Config* pConfig)`. After that DLL can use the `Config` – Dialecticus May 09 '12 at 21:46
  • Config::InitializeWithExisting(); the argument is missing. This code cannot compile. – jimifiki Dec 01 '17 at 14:45
-2

I believe that defining and accessing singleton instance this way might solve your problem:

Config& getInstance()
{
  static Config config;
  return config;
}

This way you also don't need to have (and call) the Initialize method, you can use constructor for initializing, that will be called automatically when you call getInstance for the first time.

kovarex
  • 1,766
  • 18
  • 34
  • The problem is that the code gets two different instantiations of the static `config` variable. – Mark Ransom May 09 '12 at 17:19
  • Ok, I'm probably wrong, I just want to understand it. What is the difference between this and your answer, this is practically DLL that sole purpose is to manage the single ton instance now – kovarex May 09 '12 at 17:32
  • In the original question both the `main` application and `device` DLL link the `config` library, resulting in two copies of the function and two static variables. My solution is to move this function to its own DLL so it is no longer part of the linkable library. – Mark Ransom May 09 '12 at 18:50
  • Call me stupid, but I still don't get it. Now you have your special dll for singleton, it is still used by both main and config, thus you end up with two static variables. – kovarex May 11 '12 at 22:25
  • the code that contains the static variable must go in only one place. Who uses it is irrelevant. – Mark Ransom May 12 '12 at 03:24