Windows中的DLL主函数与Linux上的__attribute__((constructor))入口点

9

考虑以下代码

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

输出结果有所不同:
在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

在Windows上,Moo构造函数在DLLMain之前调用,而在Linux上,则在使用attribute((constructor))定义的Initializer之后调用。

为什么会这样呢?

2
为什么它们应该是相同的呢?它们是不同的实现,我们正在讨论非常特定于平台的方面,这些方面不受标准覆盖。如果它们是相同的,我会感到惊讶。 - Matteo Italia
2个回答

8
Moo构造函数不是在DllMain之前被调用,而是在DllMain中被调用。准确来说,它是从真实的DllMain函数中调用的,这是Windows首先调用的函数。这个真正的DllMain调用了C++构造函数,然后调用了你的C++DllMain。这个真实的DllMain的原因正是为了初始化构造函数,在C语言中不需要这样做。
Linux(GCC/ELF)根本没有这个概念;它只有构造函数。你手动编写的构造函数和Moo的C ++构造函数被视为相同。

1
文档说明:http://msdn.microsoft.com/en-us/library/988ye33t.aspx 中包含了C/C++运行库代码中的DLL入口函数_DllMainCRTStartup。_DllMainCRTStartup函数执行多个操作,其中包括调用_CRT_INIT,该函数初始化C/C++运行库并在静态非局部变量上调用C++构造函数。除了初始化C运行库外,_DllMainCRTStartup还调用一个名为DllMain的函数。 - Ben Voigt
你所谓的“已记录的DllMain入口点”是CRT调用的用户函数的文档,正如“DllMain是库定义的函数名称的占位符。在构建DLL时,必须指定实际使用的名称。有关更多信息,请参阅随开发工具提供的文档。”所明确的那样。该文档似乎并不是真正入口点的准确描述。 - Ben Voigt
@MSalters:GetExitCodeProcess文档提到了有关库调用的用户main的事情(为什么他们坚持使用CRT彩色眼镜?)。它没有说明实际入口点的返回值,事实上,我认为正确编写的(EXE)入口点不会返回,而是调用ExitProcess - Ben Voigt
事实上,多年前我在查看CRT实现后,在“GetExitCodeProcess”页面上留下了社区贡献。 “退出代码始终来自ExitProcess或TerminateProcess。其他情况只是调用ExitProcess的特殊情况(当然知道传递的值很方便): 当您的main / WinMain函数返回时,C运行时库调用ExitProcess。 默认的未处理异常过滤器在异常未处理时调用ExitProcess。” - Ben Voigt
@Ben:你读过http://blogs.msdn.com/b/oldnewthing/archive/2010/08/27/10054832.aspx吗? - MSalters
显示剩余10条评论

0

有一种方法:

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 !
}

你的 DllMain 必须只是一个空壳,而在那些构造函数和析构函数中进行通常的安全初始化和终止,就像在 Linux 中一样。

注意:小心!如果您计划同步它们,则不能在静态或全局构造函数/析构函数中创建/删除线程。 就这样吧!

编辑:您还需要在您知道已链接的函数中调用 needStartupAndCleanup(),否则对象文件 StartupCleanup.obj 将被丢弃,以及那些全局构造函数/析构函数。


在GCC / Clang中替代#pragma init_seg(lib)的选项是什么? - Abhishek Jain
如果我理解正确,StartupAndCleanup cror 在所有其他静态对象初始化之前运行,而StartupAndCleanup dtor在所有其他静态对象被销毁后运行。这与DllMain的工作方式相反,也与通常所需的方式不同。 - Alex Che
很抱歉,我是4年前发布的,所以可能记不清了。那个类有两个作用: 1)允许按照您想要的方式对单例进行构建和销毁。 2)主类肯定会首先构建并最后销毁,因此如果在该类中未定义的另一个单例引用了在该类中定义的单例,则没有问题。销毁也是一样的。 现在,这对于 DllMain 如何处理所有静态对象并没有任何改变:我们只是设置了静态对象的优先级顺序。因此,我不明白您最后一句话的意思。 - hlide

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接