在进程和 DLL 之间共享全局/静态变量
- 2024-10-21 09:14:00
- admin 原创
- 74
问题描述:
我想仅在进程和该进程调用的 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 之间创建一个唯一的名称来使用内存映射文件,我现在正在尝试。
两个问题:
为什么 Linux 和 Windows 的行为不同?有人能详细解释一下吗?
在 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时,将产生两个文件:
Common.DLL文件是定义 DLL 使用的导出方法和数据的共享库。
Common.LIB文件声明从 DLL 导出的方法和成员的存根。
如果将应用程序链接到导出的 LIB 文件,则 DLL 文件将在运行时隐式链接(只要在 DLL 搜索路径中找到该 DLL 文件),并且您将可以访问 CommonDLL.DLL 文件中定义的单例。
此外,任何与 CommonDLL.LIB 文件链接的共享库(例如插件)在由应用程序动态加载时都将能够访问相同的单例实例。
有关此解决方案的完整说明(包括源代码示例),请查看我发布的以下文章,标题为“使用动态链接库(DLL)创建插件”:
解决方案 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,那么您将获得所需的行为:
foo.exe 加载 a.dll 并创建 x。
bar.dll 被加载并且看到 a.dll 被加载则不再加载,它们共享 x。
另一个进程加载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;
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件