DLL Main on Windows Vs. __attribute__((constructor

2020-06-04 09:28发布

问题:

Consider code

EXE:

int main ()
{

    printf("Executable Main, loading library\n");
#ifdef HAVE_WINDOWS
    HMODULE lib = LoadLibraryA ("testdll.dll"); 
#elif defined(HAVE_LINUX)
    void * lib  = dlopen("testdll.so", RTLD_LAZY);  
#endif 

    if (lib) {
        printf("Executable Main, Freeing library\n");
    #ifdef HAVE_WINDOWS
        FreeLibrary (lib); 
    #elif defined(HAVE_LINUX)
        dlclose(lib);   
    #endif 
    }
    printf("Executable Main, exiting\n");
    return 0;
}

DLL

struct Moo
{
    Moo() { printf("DLL Moo, constructor\n"); }
    ~Moo() { printf("DLL Moo, destructor\n"); }
};

Moo m;

#ifdef HAVE_WINDOWS
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        printf("DllMain, DLL_PROCESS_ATTACH\n");
        break;
    case DLL_THREAD_ATTACH:
        printf("DllMain, DLL_THREAD_ATTACH\n");
        break;
    case DLL_THREAD_DETACH:
        printf("DllMain, DLL_THREAD_DETACH\n");
        break;
    case DLL_PROCESS_DETACH:
        printf("DllMain, DLL_PROCESS_DETACH\n");
        break;
    default:
        printf("DllMain, ????\n");
        break;
    }
    return TRUE;
}
#else
CP_BEGIN_EXTERN_C
__attribute__((constructor))
/**
 * initializer of the dylib.
 */
static void Initializer(int argc, char** argv, char** envp)
{
    printf("DllInitializer\n");
}

__attribute__((destructor))
/** 
 * It is called when dylib is being unloaded.
 * 
 */
static void Finalizer()
{
    printf("DllFinalizer\n");
}

CP_END_EXTERN_C
#endif

The output differs:

On windows

Executable Main, loading library
DLL Moo, constructor
DllMain, DLL_PROCESS_ATTACH
Executable Main, Freeing library
DllMain, DLL_PROCESS_DETACH
DLL Moo, destructor
Executable Main, exiting

Linux

Executable Main, loading library
DllInitializer
DLL Moo, constructor
Executable Main, Freeing library
DllFinalizer
DLL Moo, destructor
Executable Main, exiting

On windows, Moo constructor is called before DLLMain and whereas on linux it is called after Initializer defined using attribute((constructor)).

WHY?

回答1:

Moo constructor isn't called before DllMain, it is called from DllMain. To be precise, its called from the real DllMain, the function Windows calls first. This real DllMain calls C++ constructors and then calls your C++ DllMain. The reason for this real DllMain is exactly to initialize constructors, something which wasn't needed in C before

Linux (GCC/ELF) doesn't have this concept at all; it only has constructors. Your manual ctor and the C++ ctor for Moo are treated the same.



回答2:

There is a way:

StartupCleanup.cpp:

// Redefine the same StartupCleanup class as it is in DllMain.cpp
// Definition of constructor and destructor must stay in DllMain.cpp
// And including here any headers which may define normal static or global constructors/destructors is strictly forbidden!
struct StartupAndCleanup
{
    /**/  StartupAndCleanup();
    /**/ ~StartupAndCleanup();
};

// It ensures this instance is the first to be constructed *BEFORE* any normal static or global constructors calls
// and the last to be destructed *AFTER* all normal destructors calls.
// The key to do so is using #pragma init_seg(lib), but that key applies for all the static and global constructors/destructors in the same .obj file! 
#pragma warning(push)
#pragma warning(disable:4073)
#pragma init_seg(lib)
#pragma warning(pop)

// this function is just to keep linker from discarding startupAndCleanup.obj when linking to an executable or dll
void needStartupAndCleanup()
{
}

static StartupAndCleanup startupAndCleanup;

DllMain.cpp:

...
// Definition of this class should be the same as in StartupAndCleanup.cpp!
struct StartupAndCleanup
{
    /**/  StartupAndCleanup();
    /**/ ~StartupAndCleanup();
};

StartupAndCleanup::StartupAndCleanup()
{
    // Do your initialization here !
}

StartupAndCleanup::~StartupAndCleanup()
{
    // Do your termination here !
}

You DllMain must be just an empty shell and do your usual safe initialization and termination in those constructor and destructor like for Linux.

NOTE: Beware! you cannot create/delete threads inside static or global constructors/destructors if you plan to synchronize them. That's all folks !

EDIT: you also need to call needStartupAndCleanup() in a function you know it is linked, otherwise the object file StartupCleanup.obj will be discarded and those global constructor/destructor as well.