0

I have a code base which run well on iOS and Android using Clang. Recently I migrate the code to Windows using Visual C++ 2019. There are several strange behaviors of cl compiler.

class Engine {
public:
    static Engine* sharedInstance() {
        Engine* aaa = Factory(); // Test code
        static Engine* instance = Factory();
        return instance;
    }

    static Engine* Factory();
};

where Factory is a static function implemented in each platform specific file return platform unique Engine subclass instance.

class EngineWindows: public Engine {
};

Engine* Engine::Factory() {
    return new EngineWindows();
}

But when I debug. instance is always nullptr and cannot enter Factory() function. I create another non-static instance using 'Engine* aaa = Factory();'. I can enter Factory() function and aaa has valid pointer.

So strange.

enter image description here

Assembly code shows that for instance, there is no Factory call and the code jump away at "jle".

enter image description here

By the way, I turned on /Gw /GA /GL for my DLL. After I removed these flags. Everything goes fine, the instance successfully initialized. And the assembly codes look like this.

enter image description here

wqyfavor
  • 519
  • 1
  • 4
  • 14
  • call heron::Engine::Factory is there. that jle jumps around it to return if variable already initialized. Do you call this function from several threads? it is not thread-safe in this implementation, it possible to have data race. In that case, use initialization on demand holder pattern, lazy initialization in this case isn't safe – Swift - Friday Pie Aug 28 '19 at 08:00
  • I did not call this function in several threads. If the variable is already initialized, why I always got nullptr.. By the way I use this code in a dll. But I don't think this affects the behavior. – wqyfavor Aug 28 '19 at 08:05
  • My code compiles with c++14 and runs on Win10. According to document thread safety is guaranteed. https://learn.microsoft.com/en-us/cpp/build/reference/zc-threadsafeinit-thread-safe-local-static-initialization?view=vs-2019 – wqyfavor Aug 28 '19 at 08:08
  • It called Engine::Factory somewhere then. your architecture is weird. Singletons in DLL aren't quite canonic case... dlls are loaded before any entry into main program. For it's Engine::Factory which probably returns 0. Note, you have only one instance of Engine's descendant for all children. There can be only one `Engine::Factory()`, only one definition of that function in program. Maybe this is more of CRTP use case. your sharedInstance is inline, it may end with wrong instance within your program (as DLL is technically _separate_ program with own entry point) – Swift - Friday Pie Aug 28 '19 at 08:21

2 Answers2

2

Personally, I think you are trying wrong approach. Instead of trying to hack it using replacing .cpp unit, do it by conditional compilation. Like here, for example - C++ compiling on Windows and Linux: ifdef switch

  • use of DLLs might be legit if it's meant to create a program that can be modified after compilation. But in that case APIs between modules shouldn't rely on statically-initialized objects. Best case if they are pure-functional, although use of classes are possible, just how MS docs describe. – Swift - Friday Pie Aug 28 '19 at 08:31
0

you should check return value, NRVO is likely upsetting your debugging. if result is still 0, it's likely something prevents vtable construction. Working example:

#include <iostream>


class EngineWindows;

class Engine {
public:
    static Engine* sharedInstance() {
        static Engine* instance = Factory();
        return instance;
    }

    static Engine* Factory();
};

class EngineWindows: public Engine {
};

Engine* Engine::Factory() {
    return new EngineWindows();
}

int main()
{
    std::cout << EngineWindows::sharedInstance() << std::endl;
}
Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42