【问题标题】:Sharing a global/static variable between a process and DLL在进程和 DLL 之间共享全局/静态变量
【发布时间】:2011-06-22 04:06:17
【问题描述】:

我想仅在进程和进程调用的 dll 之间共享静态/全局变量。 exe和dll在同一个内存地址空间。我不希望变量在其他进程之间共享。


问题的详细说明:

假设a.cpp 中有一个静态/全局变量x。 exe foo.exe 和 dll bar.dll 都有a.cpp,所以变量x 在两个图像中。

现在,foo.exe 动态加载(或静态)bar.dll。那么问题来了,变量x是不是被exe和dll共享了。

在 Windows 中,这两个人从不共享 x:exe 和 dll 将拥有一个单独的 x 副本。但是,在 Linux 中,exe 和 dll 共享变量 x

不幸的是,我想要 Linux 的行为。我首先考虑在 Windows 上使用pragma data_seg。但是,即使我正确设置了共享数据段,foo.exebar.dll 也永远不会共享x。回想一下bar.dll 被加载到foo.exe 的地址空间中。但是,如果我运行foo.exe 的另一个实例,那么x 是共享的。但是,我不希望 x 被不同的进程共享。所以,使用data_seg 失败了。

我可以通过在 exe 和 dll 之间创建一个唯一的名称来使用内存映射文件,我现在正在尝试。


两个问题:

  1. 为什么 Linux 和 Windows 的行为不同?谁能详细解释一下?
  2. 在 Windows 上解决此问题最简单的方法是什么?

【问题讨论】:

    标签: c++ c windows linux dll


    【解决方案1】:

    如果 foo.exe 总是加载 bar.dll,那么您可以在 bar.dll 中实现变量并将其导出。例如,某些文件 b.cpp 只编译成 bar.dll,而不编译成 foo.exe:

    __declspec(dllexport) int x;
    

    然后在编译成foo.exe的源文件c.cpp中导入:

    __declspec(dllimport) int x;
    

    但是,如果有时 foo.exe 不加载 bar.dll,那么这将不起作用。另外,我是凭记忆写的,因此可能存在一些语法错误,但希望它足以为您指明正确的方向。

    我无法回答为什么它与 Linux 不同。

    【讨论】:

    • 感谢您的回答。不幸的是,foo.exe 并不总是调用 bar.dll。但是,`x 总是被 foo.exe 使用。
    • 也许这只是一个术语的东西,但是......没有必要调用DLL中的任何函数,只是为了加载它。
    • 您的解决方案仅适用于静态加载(即 dll 与 exe 同时加载,因此,全局变量在执行开始之前被链接)。但是 OP 需要一个与动态加载一起使用的解决方案(使用 LoadLibrary() 或 dlopen() 随时加载库),在这种情况下,由于进程正在运行(基本上是,Linux在某种程度上可以做到,windows根本做不到)。
    • @Mikael:是的,确实它只适用于静态加载,我没有说清楚。但请注意,原始问题说静态加载是一种选择。
    【解决方案2】:

    首先,我发现this article 是一本关于动态链接库的非常有趣且简明的读物(这篇文章仅针对 Linux,但这些概念肯定也适用于 windows,您可能会对您看到的不同行为)。尤其是静态加载和动态加载之间的根本区别。

    我认为您想要或正在尝试实现的是“跨模块单例”模式。如果您阅读了this thread 的答案,我不知道我怎么能比 Ben Voigt 回答那个帖子更好地回答您的问题。我之前(实际上是几次)使用他描述的方法实现了一个跨模块单例,它就像一个魅力。

    当然,您将无法保持将全局变量放在 cpp 文件中的简洁性。您将不得不使用静态指针和一些访问器函数和引用计数。但它可以工作。我不太确定如何避免 foo.exe 和 foo.exe 共享一个 bar.dll 的全局数据的同一个实例,我从来不需要这样做,也想不出办法它,对不起。

    【讨论】:

    • 感谢您提供的优质信息。通过使用内存映射对象(=共享内存),我能够编写类似的跨模块单例。谢谢!
    【解决方案3】:

    如果我正确理解您的问题,您将 a.cpp 静态链接到 foo.exe 和 bar.dll,因此您会获得 2 个 x 实例。

    如果您创建了第三个 dll(例如 a.dll),并将 foo.exe 和 bar.dll 动态链接到 a.dll,您将获得所需的行为:

    1. foo.exe 加载 a.dll 并创建 x。
    2. bar.dll 被加载并看到 a.dll 已加载但不加载 再次,他们分享 x。
    3. 另一个进程加载一个.dll,它得到 它自己的 x。

    【讨论】:

      【解决方案4】:

      我发现这是一个非常有趣的问题,因此我花时间编写了一篇详尽的教程,介绍如何使用 DLL 在多个 DLL(隐式或显式链接)之间共享数据,同时确保不共享数据在同一可执行文件的不同进程之间。

      你可以在这里找到完整的文章:http://3dgep.com/?p=1759


      我发现这个问题的解决方案非常有效,它是创建一个“通用”或“共享”dll,它定义了您希望在多个 DLL 之间共享(但不在进程之间共享)的所有数据和方法。

      假设您想定义一个可以从主应用程序代码(EXE)访问的单例类,但您还想访问共享中的单例实例(隐式或显式链接的 DLL)。首先,您需要在“通用”DLL 中声明单例类:

      // Export the class when compiling the DLL, 
      // otherwise import the class when using the DLL.
      class __declspec(dllexport) MySingleton 
      {
      public:
          static MySingleton& Instance();
      };
      

      在编译CommonDLL项目时,需要通过__declspec(dllexport)装饰类来导出类声明,而在使用DLL时(例如在应用程序中),需要通过装饰类来导入类定义__declspec(dllimport)

      当通过使用__declspec(dllexport) 说明符修饰类来导出类时,类的所有方法和数据(甚至私有数据)都从DLL 导出,并且可供隐式链接到公共DLL 的任何DLL 或EXE 使用。

      MySingleton 类的定义可能如下所示:

      MySingleton& MySingleton::Instance()
      {
          static MySingleton instance;
          return instance;
      }
      

      编译通用dll时,会产生两个文件:

      1. Common.DLL 文件是共享库,用于定义 DLL 使用的导出方法和数据。
      2. Common.LIB 文件声明从 DLL 导出的方法和成员的存根。

      如果您将应用程序链接到导出的 LIB 文件,则 DLL 文件将在运行时被隐式链接(只要在 DLL 搜索路径中找到 DLL 文件)并且您将可以访问在CommonDLL.DLL 文件。

      此外,任何也链接到 CommonDLL.LIB 文件的共享库(例如插件)在由应用程序动态加载时都可以访问相同的单例实例。

      有关此解决方案的完整说明(包括源代码示例),请查看我发布的以下标题为“使用动态链接库 (DLL) 创建插件”的文章:

      http://3dgep.com/?p=1759

      【讨论】:

        【解决方案5】:

        要获得主程序和 dll 共享相同 x 的 linux 行为,您可以从 dll 或主程序中导出该变量。另一个模块必须导入该变量。

        您可以通过使用 DEF 文件 (see microsoft's documentation) 来执行此操作,或者通过在定义变量的位置使用 __declspec(dllexport) 标记使用,并在使用它的任何其他模块中使用 __declspec(dllimport) (see microsoft's documentation) 标记使用。这与 windows 中的模块之间共享任何函数、对象或变量的方式相同。

        如果您希望程序在运行时加载库,但主程序可能必须在加载库之前使用该变量,则程序应导出变量,而 dll 应导入它。这里有点鸡和蛋的问题,因为dll依赖于主程序,而主程序又依赖于dll。见http://www.lurklurk.org/linkers/linkers.html#wincircular

        我已经编写了一个示例,说明如何使用 Microsoft 的编译器和 mingw(Windows 中的 gcc)来执行此操作,包括程序和库可以相互链接的所有不同方式(静态,在程序启动时加载的 dll , dll 在运行时加载)

        main.h

        #ifndef MAIN_H
        #define MAIN_H
        
        // something that includes this
        // would #include "linkage_importing.h"
        // or #include "linkage_exporting.h"
        // as appropriate
        
        #ifndef EXPLICIT_MAIN
        LINKAGE int x;
        #endif // EXPLICIT_MAIN
        
        #endif // MAIN_H
        

        main.c

        #ifdef EXPLICIT_DLL
        #include "dyn_link.h"
        #endif // EXPLICIT_DLL
        #include <stdio.h>
        #include "linkage_exporting.h"
        #include "main.h"
        #include "linkage_importing.h"
        #include "dll.h"
        
        FNCALL_DLL get_call_dll(void);
        
        int main(int argc, char* argv[])
        {
           FNCALL_DLL fncall_dll;
           fncall_dll = get_call_dll();
           if (fncall_dll)
           {
              x = 42;
              printf("Address of x as seen from main() in main.c: %p\n", &x);
              printf("x is set to %i in main()\n", x);
              fncall_dll();
              // could also be called as (*fncall_dll)();
              // if you want to be explicit that fncall_dll is a function pointer
              printf("Value of x as seen from main() after call to call_dll(): %i\n", x);
           }
           return 0;
        }
        
        FNCALL_DLL get_call_dll(void)
        {
        #ifdef EXPLICIT_DLL
           return get_ptr("dll.dll", "call_dll");
        #else
           return call_dll;
        #endif // EXPLICIT_DLL
        }
        

        dll.h

        #ifndef DLL_H
        #define DLL_H
        
        // something that includes this
        // would #include "linkage_importing.h"
        // or #include "linkage_exporting.h"
        // as appropriate
        
        // declaration of type to hold a
        // pointer to the function
        typedef void(*FNCALL_DLL)(void);
        
        #ifndef EXPLICIT_DLL
        LINKAGE void call_dll(void);
        #endif // EXPLICIT_DLL
        
        #endif // DLL_H
        

        dll.c

        #ifdef EXPLICIT_MAIN
        #include "dyn_link.h"
        #endif // EXPLICIT_MAIN
        #include <stdio.h>
        #include "linkage_importing.h"
        #include "main.h"
        #include "linkage_exporting.h"
        #include "dll.h"
        
        int* get_x_ptr(void);
        
        LINKAGE void call_dll(void)
        {
           int* x_ptr;
           x_ptr = get_x_ptr();
           if (x_ptr)
           {
              printf("Address of x as seen from call_dll() in dll.c: %p\n", x_ptr);
              printf("Value of x as seen in call_dll: %i()\n", *x_ptr);
              *x_ptr = 31415;
              printf("x is set to %i in call_dll()\n", *x_ptr);
           }
        }
        
        int* get_x_ptr(void)
        {
        #ifdef EXPLICIT_MAIN
           return get_ptr("main.exe", "x");   // see note in dyn_link.c about using the main program as a library
        #else
           return &x;
        #endif //EXPLICIT_MAIN
        }
        

        dyn_link.h

        #ifndef DYN_LINK_H
        #define DYN_LINK_H
        
        // even though this function is used by both, we link it
        // into both main.exe and dll.dll as necessary.
        // It's not shared in a dll, because it helps us load dlls :)
        void* get_ptr(const char* library, const char* object);
        
        #endif // DYN_LINK_H
        

        dyn_link.c

        #include "dyn_link.h"
        #include <windows.h>
        #include <stdio.h>
        
        void* get_ptr(const char* library, const char* object)
        {
           HINSTANCE hdll;
           FARPROC ptr;
           hdll = 0;
           ptr = 0;
        
           hdll = LoadLibrary(library);
           // in a better dynamic linking library, there would be a
           // function that would call FreeLibrary(hdll) to cleanup
           //
           // in the case where you want to load an object in the main
           // program, you can use
           // hdll = GetModuleHandle(NULL);
           // because there's no need to call LoadLibrary on the
           // executable if you can get its handle by some other means.
        
           if (hdll)
           {
              printf("Loaded library %s\n", library);
              ptr = GetProcAddress(hdll, object);
              if (ptr)
              {
                 printf("Found %s in %s\n", object, library);
              } else {
                 printf("Could not find %s in %s\n", object, library);
              }
           } else {
              printf("Could not load library %s\n", library);
           }
           return ptr;
        }
        

        linkage_importing.h

        // sets up some macros to handle when to use "__declspec(dllexport)",
        // "__declspec(dllimport)", "extern", or nothing.
        
        // when using the LINKAGE macro (or including a header that does):
        //    use "#include <linkage_importing.h>" to make the LINKAGE macro
        //    do the right thing for importing (when using functions,
        //    variables, etc...)
        //
        //    use "#include <linkage_exporting.h>" to make the LINKAGE macro
        //    do the right thing for exporting (when declaring functions,
        //    variables, etc).
        //
        //    You can include either file at any time to change the meaning of
        //    LINKAGE.
        
        // if you declare NO_DLL these macros do not use __declspec(...), only
        // "extern" as appropriate
        
        #ifdef LINKAGE
        #undef LINKAGE
        #endif
        #ifdef NO_DLL
           #define LINKAGE extern
        #else
           #define LINKAGE extern __declspec(dllimport)
        #endif
        

        linkage_exporting.h

        // See linkage_importing.h to learn how this is used
        #ifdef LINKAGE
        #undef LINKAGE
        #endif
        #ifdef NO_DLL
           #define LINKAGE
        #else
           #define LINKAGE __declspec(dllexport)
        #endif
        

        构建mingw显式both.sh

        #! /bin/bash
        echo Building configuration where both main
        echo and dll link explicitly to each other
        rm -rf mingw_explicit_both
        mkdir -p mingw_explicit_both/obj
        cd mingw_explicit_both/obj
        
        # compile the source code (dll created with position independent code)
        gcc -c -fPIC -DEXPLICIT_MAIN ../../dll.c
        gcc -c -DEXPLICIT_DLL ../../main.c
        gcc -c ../../dyn_link.c
        
        #create the dll from its object code the normal way
        gcc -shared -odll.dll dll.o dyn_link.o -Wl,--out-implib,libdll.a
        
        # create the executable
        gcc -o main.exe main.o dyn_link.o
        
        mv dll.dll ..
        mv main.exe ..
        cd ..
        

        构建 mingw 显式 dll.sh

        #! /bin/bash
        echo Building configuration where main explicitly
        echo links to dll, but dll implicitly links to main
        rm -rf mingw_explicit_dll
        mkdir -p mingw_explicit_dll/obj
        cd mingw_explicit_dll/obj
        
        # compile the source code (dll created with position independent code)
        gcc -c -fPIC ../../dll.c
        gcc -c -DEXPLICIT_DLL ../../main.c
        gcc -c ../../dyn_link.c
        
        # normally when linking a dll, you just use gcc
        # to create the dll and its linking library (--out-implib...)
        # But, this dll needs to import from main, and main's linking library doesn't exist yet
        # so we create the linking library for main.o
        # make sure that linking library knows to look for symbols in main.exe (the default would be a.out)
        gcc -omain.exe -shared main.o -Wl,--out-implib,main.a  #note this reports failure, but it's only a failure to create main.exe, not a failure to create main.a
        
        #create the dll from its object code the normal way (dll needs to know about main's exports)
        gcc -shared -odll.dll dll.o dyn_link.o main.a -Wl,--out-implib,libdll.a
        
        # create the executable
        gcc -o main.exe main.o dyn_link.o
        
        mv dll.dll ..
        mv main.exe ..
        cd ..
        

        构建mingw显式main.sh

        #! /bin/bash
        echo Building configuration where dll explicitly
        echo links to main, but main implicitly links to dll
        rm -rf mingw_explicit_main
        mkdir -p mingw_explicit_main/obj
        cd mingw_explicit_main/obj
        
        # compile the source code (dll created with position independent code)
        gcc -c -fPIC -DEXPLICIT_MAIN ../../dll.c
        gcc -c ../../main.c
        gcc -c ../../dyn_link.c
        
        # since the dll will link dynamically and explicitly with main, there is no need
        # to create a linking library for main, and the dll can be built the regular way
        gcc -shared -odll.dll dll.o dyn_link.o -Wl,--out-implib,libdll.a
        
        # create the executable (main still links with dll implicitly)
        gcc -o main.exe main.o -L. -ldll
        
        mv dll.dll ..
        mv main.exe ..
        cd ..
        

        构建mingwimplicit.sh

        #! /bin/bash
        echo Building configuration where main and
        echo dll implicitly link to each other
        rm -rf mingw_implicit
        mkdir -p mingw_implicit/obj
        cd mingw_implicit/obj
        
        # compile the source code (dll created with position independent code)
        gcc -c -fPIC ../../dll.c
        gcc -c ../../main.c
        
        # normally when linking a dll, you just use gcc
        # to create the dll and its linking library (--out-implib...)
        # But, this dll needs to import from main, and main's linking library doesn't exist yet
        # so we create the linking library for main.o
        # make sure that linking library knows to look for symbols in main.exe (the default would be a.out)
        gcc -omain.exe -shared main.o -Wl,--out-implib,main.a  #note this reports failure, but it's only a failure to create main.exe, not a failure to create main.a
        
        # create the dll from its object code the normal way (dll needs to know about main's exports)
        gcc -shared -odll.dll dll.o main.a -Wl,--out-implib,libdll.a
        
        # create the executable (exe needs to know about dll's exports)
        gcc -o main.exe main.o -L. -ldll
        
        mv dll.dll ..
        mv main.exe ..
        cd ..
        

        构建mingw static.sh

        #! /bin/bash
        echo Building configuration where main and dll
        echo statically link to each other
        rm -rf mingw_static
        mkdir -p mingw_static/obj
        cd mingw_static/obj
        
        # compile the source code
        gcc -c -DNO_DLL ../../dll.c
        gcc -c -DNO_DLL ../../main.c
        
        # create the static library
        ar -rcs dll.a dll.o
        
        # link the executable
        gcc -o main.exe main.o dll.a
        
        mv main.exe ../
        cd ..
        

        构建 msvc 显式 both.bat

        @echo off
        echo Building configuration where both main
        echo and dll link explicitly to each other
        rd /s /q win_explicit_both
        md win_explicit_both\obj
        cd win_explicit_both\obj
        
        rem compile the source code
        cl /nologo /c /DEXPLICIT_MAIN ..\..\dll.c
        cl /nologo /c /DEXPLICIT_DLL ..\..\main.c
        cl /nologo /c ..\..\dyn_link.c
        
        rem create the dll from its object code the normal way
        link /nologo /dll dll.obj dyn_link.obj
        
        rem create the executable
        link /nologo main.obj dyn_link.obj
        
        move dll.dll ..\
        move main.exe ..\
        cd ..
        

        构建 msvc 显式 dll.bat

        @echo off
        echo Building configuration where main explicitly
        echo links to dll, but dll implicitly links to main
        rd /s /q win_explicit_dll
        md win_explicit_dll\obj
        cd win_explicit_dll\obj
        
        rem compile the source code
        cl /nologo /c ..\..\dll.c
        cl /nologo /c /DEXPLICIT_DLL ..\..\main.c
        cl /nologo /c ..\..\dyn_link.c
        
        rem normally when linking a dll, you just use the link command
        rem that creates the dll and its linking library.
        rem But, this dll needs to import from main, and main's linking library doesn't exist yet
        rem so we create the linking library for main.obj
        rem make sure that linking library knows to look for symbols in main.exe (the default would be main.dll)
        lib /nologo /def /name:main.exe main.obj
        
        rem create the dll from its object code the normal way (dll needs to know about main's exports)
        link /nologo /dll dll.obj main.lib
        
        rem create the executable
        link /nologo main.obj dyn_link.obj
        
        move dll.dll ..\
        move main.exe ..\
        cd ..
        

        构建 msvc 显式 main.bat

        @echo off
        echo Building configuration where dll explicitly
        echo links to main, but main implicitly links to dll
        rd /s /q win_explicit_main
        md win_explicit_main\obj
        cd win_explicit_main\obj
        
        rem compile the source code
        cl /nologo /c /DEXPLICIT_MAIN ..\..\dll.c
        cl /nologo /c ..\..\main.c
        cl /nologo /c ..\..\dyn_link.c
        
        rem since the dll will link dynamically and explicitly with main, there is no need
        rem to create a linking library for main, and the dll can be built the regular way
        link /nologo /dll dll.obj dyn_link.obj
        
        rem create the executable (main still links with dll implicitly)
        link /nologo main.obj dll.lib
        
        move dll.dll ..\
        move main.exe ..\
        cd ..
        

        构建 msvc 隐式.bat

        @echo off
        echo Building configuration where main and
        echo dll implicitly link to each other
        rd /s /q win_implicit
        md win_implicit\obj
        cd win_implicit\obj
        
        rem compile the source code
        cl /nologo /c ..\..\dll.c
        cl /nologo /c ..\..\main.c
        
        rem normally when linking a dll, you just use the link command
        rem that creates the dll and its linking library.
        rem But, this dll needs to import from main, and main's linking library doesn't exist yet
        rem so we create the linking library for main.obj
        rem make sure that linking library knows to look for symbols in main.exe (the default would be main.dll)
        lib /nologo /def /name:main.exe main.obj
        
        rem create the dll from its object code the normal way (dll needs to know about main's exports)
        link /nologo /dll dll.obj main.lib
        
        rem create the executable (exe needs to know about dll's exports)
        link /nologo main.obj dll.lib
        
        move dll.dll ..\
        move main.exe ..\
        cd ..
        

        构建 msvc static.bat

        @echo off
        echo Building configuration where main and dll
        echo statically link to each other
        rd /s /q win_static
        md win_static\obj
        cd win_static\obj
        
        rem compile the source code
        cl /nologo /DNO_DLL /c ..\..\dll.c
        cl /nologo /DNO_DLL /c ..\..\main.c
        
        rem create the static library
        lib /nologo dll.obj
        
        rem link the executable
        link /nologo main.obj dll.lib
        
        move main.exe ..\
        cd ..
        

        【讨论】:

        • 如果有人能告诉我一个更好的方法来做一个全面的例子的代码转储,我很想知道。
        【解决方案6】:

        GCC 和 Visual Studio 之间的区别在于,在 Linux 上,它隐含地允许代码查看来自其他动态链接(共享)库的符号,而程序员无需做任何特殊的事情。所有符号都在共享(动态链接)库中可用,供动态链接器在程序运行时解析。在 Windows 上,您必须专门从 DLL 导出符号,并明确将其导入到使用它的程序或库中。 (通常这是通过宏 (#define) 完成的,该宏在构建 dll 本身时扩展为在头文件中包含 dllexport 声明,但是当其他使用 dll 的程序包含头文件时,它会扩展为具有 dllimport而是声明。在我看来,这是一个令人头疼的问题,而且 GCC 的行为更容易,因为您不必做任何特别的事情来获得您通常想要的行为。

        在较新版本的 GCC 上,如果您愿意,可以在构建动态(共享)库时设置默认隐藏符号。

        【讨论】:

          【解决方案7】:

          感谢您为此提供各种解决方案。我查看了这些选项并决定使用共享内存实现跨模块单例,它对我也很有效。 我使用 Qt QSharedMemory 来完成我的任务,但我使用 Win32 CreateFileMapping 等编写的原型。

          【讨论】:

            【解决方案8】:

            我已经看到了很多关于这个问题的答案,由于它有点棘手和不清楚,我想带来以下场景。 我们希望在 DLL 和主程序之间共享一个全局变量,并允许从 DLL 和主程序中的不同模块访问这个变量。

            该变量是一个 BOOL,指示程序应该继续运行还是停止。变量名是ShouldRun;

            在主程序中我们需要放:

            __declspec(dllexport)  bool ShouldRun;
            

            在DLL的主模块中我们需要放:

            extern "C" BOOL __declspec(dllexport) ShouldRun = TRUE;
            

            在我们将使用的 DLL 项目内的任何其他模块中:

            extern  "C" BOOL ShouldRun;
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2010-11-23
              • 2020-05-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-04-08
              相关资源
              最近更新 更多