【问题标题】:Static member variable not global between executable and dll静态成员变量在可执行文件和 dll 之间不是全局的
【发布时间】:2018-05-08 06:54:44
【问题描述】:

在链接 DLL 的工作方式方面,我的知识有点模糊,但我观察到可执行文件中静态成员变量的更改不会更改 DLL 中的相同静态成员变量。这是我的场景:

main.cpp 静态链接到 mylib.lib。在 mylib.lib 中,我有以下类:

// foo.h
class Foo
{
public:
    static int m_global;
    Foo();
    ~Foo();
};

// foo.cpp
#include "foo.h"

int Foo::m_global = 5;

我还有一个链接到 mylib.lib 的 DLL,其中包含以下内容:

//testdll.h
#define MATHLIBRARY_API __declspec(dllimport)

void MATHLIBRARY_API printFoo();

// testdll.cpp
#include "testdll.h"
#include <iostream>

void printFoo() {
    std::cout << Foo::m_global << std::endl;
}

最后,在我的可执行文件的 main.cpp 中

// main.cpp
#include <iostream>
#include "testdll.h"
#include "foo.h"

int main() {
    std::cout << Foo::m_global << std::endl;
    Foo::m_global = 7;

    std::cout << Foo::m_global << std::endl;
    printMutiply();

    return 0;
}

我的预期输出是 5、7、7。但是,我看到 5、7、5,这告诉我 DLL 没有看到静态成员变量更改。为什么会这样?以及如何让 DLL 看到可执行文件中静态成员变量的变化??

【问题讨论】:

  • 如果您编译 DLL,class foo 必须与 __declspec(dllexport) 一起归属。如果您编译可执行文件,则(相同的)class foo 必须归属于__declspec(dllimport)。这通常是使用一些宏技巧来完成的(例如,由我们完成)。我发现了一个相关的 SO Q/A:SO: Macro for dllexport/dllimport switch,这可能会有所帮助。

标签: c++ linker


【解决方案1】:

信不信由你,但您的应用程序违反了一个定义规则,因此会触发未定义行为。您的 program(在 C++ 标准中称为)最终具有 Foo::m_global 的双重定义 - 一个在可加载库中,另一个在 main 中。作为这种未定义行为的明显影响,Microsoft 动态加载器创建了两个符号,一个来自可加载对象,另一个来自主对象。

在 Linux 中,ld(linux 加载程序)实际上会将这些符号合并为一个(这反过来会触发非平凡对象的双重破坏)。

底线 - 不要在可加载库和可执行文件之间共享全局符号的定义。这适用于函数和变量,但共享函数通常不会有明显的副作用,尽管从技术上讲也是未定义的行为。

【讨论】:

  • @Scheff 分享我的意思是分享定义。我的措辞不完美,我会编辑。
  • @rubenvb 在问题描述 OP 中表示 dll 和主可执行文件链接到同一个库。
  • @rubenvb,没有。它给出了程序的两个定义。可加载库和主可执行文件都有Foo::m_global 的定义,当可加载库被加载到可执行文件中时,您最终会得到两个。
  • @SergeyA,啊,涉及到静态链接。这确实非常令人困惑和错误。您应该在答案中添加适当的解决方案,而不仅仅是对问题的描述。
  • 很可能不会静态链接任何东西。 @乔
【解决方案2】:

这就是我们在项目中处理它的方式:

/* First a general definition which covers differences
 * of Windows and Linux for all of your libraries:
 */
#ifdef _WIN32
/* for Windows, Visual C++ */
#define MY_EXPORT __declspec(dllexport)
#define MY_IMPORT __declspec(dllimport)
#else /* _WIN32 */
/* for gcc */
#define MY_EXPORT __attribute__((visibility("default")))
#define MY_IMPORT __attribute__((visibility("default")))
#endif /* _WIN32 */

这必须为您的每个库准备好:

/* The API macro to distiguish two cases:
 * 1. DLL/Shared Object
 * 2. usage of DLL/Shared Object
 */
#ifdef BUILD_MY_LIB
#define  MY_LIB_API MY_EXPORT
#else /* BUILD_MY_LIB */
#define MY_LIB_API MY_IMPORT
#endif /* BUILD_MY_LIB */

MY_LIB_API 然后用于要从 MyLib 库中导出的任何 class

class MY_LIB_API Foo {
};

剩下的就是编译器参数了:

  1. 为了编译 MyLib DLL 或共享对象,-DBUILD_MY_LIB 被添加到编译器的命令行参数中。

  2. 要使用 DLL 或共享对象,无需额外设置。

实际上,我们的解决方案也考虑了静态库。它将MY_LIB_API 定义为空。但是,我们已经很久没有使用它了。因此,我省略了这部分。 (我必须承认我忘记了它是如何工作的……)

【讨论】:

    猜你喜欢
    • 2011-06-22
    • 1970-01-01
    • 2018-12-22
    • 1970-01-01
    • 2015-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-28
    相关资源
    最近更新 更多