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

2024-10-21 09:14:00
admin
原创
74
摘要:问题描述:我想仅在进程和该进程调用的 dll 之间共享静态/全局变量。exe 和 dll 位于同一内存地址空间中。我不希望该变量在其他进程之间共享。问题阐述:假设x中有一个静态/全局变量a.cpp。exefoo.exe和 dllbar.dll都有a.cpp,因此该变量x在两个映像中都有。现在,foo.exe动...

问题描述:

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


问题阐述:

假设x中有一个静态/全局变量a.cpp。exefoo.exe和 dllbar.dll都有a.cpp,因此该变量x在两个映像中都有。

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

在 Windows 中,这两个家伙从不共享x:exe 和 dll 将拥有 的单独副本x。然而,在 Linux 中,exe 和 dll 确实共享变量x

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

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


两个问题:

  1. 为什么 Linux 和 Windows 的行为不同?有人能详细解释一下吗?

  2. 在 Windows 上解决这个问题最简单的方法是什么?


解决方案 1:

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

您可以使用 DEF 文件(请参阅微软的文档__declspec(dllexport))来实现这一点,或者通过在变量定义的位置以及__declspec(dllimport)在任何其他模块中使用该变量来标记其用途(请参阅微软的文档)。这与 Windows 中任何函数、对象或变量在模块之间共享的方式相同。

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

我已经写了一个例子,说明如何使用微软的编译器和 mingw(windows 中的 gcc)来做到这一点,包括程序和库可以相互链接的所有不同方式(静态、在程序启动时加载的 dll、在运行时加载的 dll)

主程序

#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

主程序

#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
", &x);
      printf("x is set to %i in main()
", 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
", 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
}

动态链接库

#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

动态链接库

#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
", x_ptr);
      printf("Value of x as seen in call_dll: %i()
", *x_ptr);
      *x_ptr = 31415;
      printf("x is set to %i in call_dll()
", *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
", library);
      ptr = GetProcAddress(hdll, object);
      if (ptr)
      {
         printf("Found %s in %s
", object, library);
      } else {
         printf("Could not find %s in %s
", object, library);
      }
   } else {
      printf("Could not load library %s
", library);
   }
   return ptr;
}

链接_导入.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

链接_导出.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 ..

构建 mingw implied.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_bothobj
cd win_explicit_bothobj

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 ..\nmove main.exe ..\ncd ..

构建 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_dllobj
cd win_explicit_dllobj

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 ..\nmove main.exe ..\ncd ..

构建 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_mainobj
cd win_explicit_mainobj

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 ..\nmove main.exe ..\ncd ..

构建 msvc 隐式.bat

@echo off
echo Building configuration where main and
echo dll implicitly link to each other
rd /s /q win_implicit
md win_implicitobj
cd win_implicitobj

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 ..\nmove main.exe ..\ncd ..

构建 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_staticobj
cd win_staticobj

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 ..\ncd ..

解决方案 2:

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

我认为您想要或尝试实现的是“跨模块单例”模式。如果您阅读了此主题的答案,我不知道如何回答您的问题才能比 Ben Voigt 回答该帖子更好。我之前已经使用他描述的方法实现了跨模块单例(实际上有几次),效果非常好。

当然,您无法保持全局变量在 cpp 文件中的整洁。您必须使用静态指针和一些访问函数以及引用计数。但它可以工作。我不太确定如何避免 foo.exe 和 foo.exe 共享同一个 bar.dll 全局数据实例,我从来没有这样做过,也想不出办法,抱歉。

解决方案 3:

我发现这是一个非常有趣的问题,因此我花时间编写了一篇详尽的教程,介绍如何使用 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

解决方案 4:

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

解决方案 5:

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

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

解决方案 6:

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

解决方案 7:

如果我正确理解了你的问题,你正在将 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。

解决方案 8:

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

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

在主程序中我们需要输入:

__declspec(dllexport)  bool ShouldRun;

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

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

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

extern  "C" BOOL ShouldRun;
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用