在进程和DLL之间共享全局/静态变量

时间:2011-02-06 06:32:56

标签: c++ c windows linux dll

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


阐述问题:

假设x中存在静态/全局变量a.cpp。 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上解决此问题最简单的方法是什么?

8 个答案:

答案 0 :(得分:11)

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

您可以使用DEF文件(see microsoft's documentation)执行此操作,或者使用变量__declspec(dllexport)标记其中的用途,并在其使用的任何其他模块中标记__declspec(dllimport)({ {3}})。这与在Windows中的模块之间共享任何函数,对象或变量的方式相同。

如果您希望程序在运行时加载库,但主程序可能必须在加载库之前使用该变量,程序应该导出变量,并且dll应该导入它。这里有一点鸡和蛋的问题,因为dll取决于主程序,主程序取决于dll。见see microsoft's documentation

我已经编写了一个示例,说明如何使用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 explicit 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 explicit 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 ..

构建mingw implicit.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 implicit.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 ..

答案 1 :(得分:8)

首先,我发现this article是关于动态链接库的一个非常有趣和简洁的读物(这篇文章仅针对Linux,但这些概念肯定也适用于Windows,你可能会得到一些见解你所看到的不同行为)。特别是静态和动态载荷之间的根本区别。

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

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

答案 2 :(得分:5)

如果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不同。

答案 3 :(得分:5)

我发现这是一个非常有趣的问题,我花时间编写了一个关于如何使用DLL在多个DLL之间共享数据(隐式或显式链接)的广泛教程,但也确保数据不共享在同一个可执行文件的单独进程中。

您可以在此处找到完整的文章:http://3dgep.com/?p=1759


我发现这个问题很有效的解决方案是创建一个“公共”或“共享”的dll,它定义了你想要在多个DLL之间共享的所有数据和方法(但不能在进程间共享)。

假设您要定义一个可以从主应用程序代码(EXE)访问的单例类,但您还希望访问共享中的单例实例(隐式或显式链接的DLL)。首先,您需要在“common”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)修饰类来导出类declaratoin,当您使用DLL时(例如在应用程序中),需要通过装饰导入类定义__declspec(dllimport)的课程。

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

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

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

编译公共dll时,将生成两个文件:

  1. Common.DLL 文件,它是定义DLL使用的方法和数据导出的共享库。
  2. Common.LIB 文件,用于声明从DLL导出的方法和成员的存根。
  3. 如果您将应用程序与导出的LIB文件链接,则DLL文件将在运行时隐式链接(只要在DLL搜索路径中找到DLL文件),您就可以访问在CommonDLL.DLL文件。

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

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

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

答案 4 :(得分:3)

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

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

答案 5 :(得分:1)

感谢您提供各种解决方案。我查看了这些选项并决定使用共享内存实现交叉模块单例,它也适用于我。 我使用Qt QSharedMemory来完成我的任务,但我使用Win32 CreateFileMapping&amp;等

答案 6 :(得分:0)

如果我正确理解你的问题,你将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. 另一个进程加载a.dll,它得到了 它自己的x。

答案 7 :(得分:0)

我已经看到了很多这个问题的答案,因为它有点棘手且不清楚我想提出以下情况。 我们希望在DLL和主程序之间共享一个全局变量,并允许从DLL和主程序中的不同模块访问此变量。

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

在主程序中我们需要提出:

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

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

extern  "C" BOOL ShouldRun;

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

Stop