【问题标题】:DLL Main on Windows Vs. __attribute__((constructor)) entry points on LinuxWindows Vs 上的 DLL Main __attribute__((constructor)) Linux 上的入口点
【发布时间】:2014-05-10 22:22:07
【问题描述】:

考虑代码

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

输出不同:

在窗户上

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 上,它在使用属性((构造函数))定义的 Initializer 之后调用。

为什么?

【问题讨论】:

  • 为什么它们应该是一样的?它们是不同的实现,我们谈论的是标准未涵盖的平台特定方面。如果它们是相同的,我会感到惊讶。

标签: c++ linux windows shared-libraries dllmain


【解决方案1】:

有办法:

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)。
  • 如果我理解正确 StartupAndCleanup cror 在所有其他静态对象初始化之前运行,并且 StartupAndCleanup dtor 在所有其他静态对象被破坏后运行。这与 DllMain 的工作方式以及通常需要的方式相反。
  • 抱歉,我是 4 年前发布的,所以我可能记不太清了。该课程有两件事:1)允许根据需要订购单例的构造和销毁。 2) 主类肯定先构造,最后销毁,所以如果该类中未定义的另一个单例引用该类中定义的单例,那就没问题了。毁灭也一样。现在它不会改变 DllMain 关于它如何处理所有静态对象的任何内容:我们只是设置静态对象的优先级顺序。所以我不明白你最后的陈述是什么意思。
【解决方案2】:

Moo 构造函数在之前 DllMain 没有被调用,它被 DllMain 调用。准确地说,它是从真正的DllMain 调用的,Windows 最先调用的函数。这个真正的DllMain 调用C++ 构造函数,然后调用你的C++ DllMain。这个真正的DllMain 的原因正是为了初始化构造函数,这在 C 之前是不需要的

Linux (GCC/ELF) 根本没有这个概念;它只有构造函数。您的手动 ctor 和 Moo 的 C++ ctor 的处理方式相同。

【讨论】:

  • 此文档:msdn.microsoft.com/en-us/library/988ye33t.aspx "包含在 C/C++ 运行时库代码中的是名为 _DllMainCRTStartup 的 DLL 入口点函数。_DllMainCRTStartup 函数做了几件事,包括调用 _CRT_INIT,它初始化C/C++ 运行时库并在静态、非局部变量上调用 C++ 构造函数。”和“除了初始化 C 运行时库之外,_DllMainCRTStartup 还调用了一个名为 DllMain 的函数。”
  • @BenVoigt:那是 Microsoft Visual Studio 文档,它确实记录了“C++ DllMain”。从操作系统方面(使用内核有色眼镜,引用 Raymond Chen 的话)DllMain 是 DLL 的入口点。 “真正的 DllMain”必须遵守所有操作系统规则,例如不调用可能导致加载另一个 DLL 的函数。这意味着 DLL 中的全局 ctor 不能做很多事情。
  • @MSalters:从操作系统的角度来看,没有“真正的DllMain”。 DllMain 不是导出符号。 PE 标头中有一个入口点地址,如果您使用 Microsoft 工具链(不使用 LINK.EXE/ENTRY 开关),该入口点指向 _DllMainCRTStartup。不幸的是,操作系统文档有 C 色眼镜,除了我链接的页面外,几乎所有地方都使用名称 DllMain
  • 我以前查看过 Windows 文档,但实际上找不到任何有关 EXE 或 DLL 的直接入口点所需签名的文档。它所谈论的只是用户入口点WinMainwWinMainmainwmainDllMain。我得出的结论是,对于EXE,不带任何参数(使用GetCommandLineW())并且永不返回(使用ExitProcess()),从而避免了签名匹配问题。对于 DLL,请使用用户 DllMain__stdcall 约定的记录签名。
猜你喜欢
  • 1970-01-01
  • 2012-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-28
  • 2020-05-29
  • 1970-01-01
相关资源
最近更新 更多