Совместное использование глобальной / статической переменной между процессом и DLL - PullRequest
21 голосов
/ 06 февраля 2011

Я хотел бы разделить статическую / глобальную переменную только между процессом и 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.Сначала я подумал об использовании 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?

Ответы [ 8 ]

11 голосов
/ 08 ноября 2011

Чтобы получить поведение linux, когда основная программа и dll совместно используют один и тот же x, вы можете экспортировать эту переменную либо из dll, либо из основной программы. Другой модуль должен импортировать эту переменную.

Это делается с помощью файлов DEF ( см. Документацию Microsoft ) или путем пометки использования переменной с __declspec(dllexport), где она определена, и __declspec(dllimport) в любом другом используемом модуле ( см. Документацию Microsoft ). Это аналогично тому, как любая функция, объект или переменная используется совместно модулями в Windows.

В случае, если вы хотите, чтобы программа загружала библиотеку во время выполнения, но основной программе, возможно, придется использовать переменную до загрузки библиотеки, программа должна экспортировать переменную, а DLL должна импортировать ее. Здесь есть небольшая проблема с курицей и яйцом, потому что dll зависит от основной программы, а основная программа зависит от dll. Смотри http://www.lurklurk.org/linkers/linkers.html#wincircular

Я написал пример того, как вы можете сделать это, используя как компилятор Microsoft, так и mingw (gcc в windows), включая все различные способы, которыми программа и библиотека могут ссылаться друг на друга (статически, 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 ..

build 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 ..

build 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 ..

build 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 ..

build 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 ..

build 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 ..

build 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 ..

build 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 ..

build 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 ..
8 голосов
/ 06 февраля 2011

Во-первых, я обнаружил, что эта статья была очень интересной и краткой информацией о библиотеках динамической компоновки (статья относится только к Linux, но концепции, безусловно, применимы и к Windows, и вы можетенекоторое представление о различном поведении, которое вы видите).Особенно принципиальное различие между статической и динамической загрузкой.

Я думаю, что вы хотите или пытаетесь реализовать это шаблон "кросс-модульный одиночный".Если вы прочитаете ответы на в этой теме , я не знаю, как бы я мог ответить на ваш вопрос лучше, чем Бен Фойгт ответил на этот пост.Я реализовал кросс-модульный синглтон раньше (на самом деле несколько раз), используя метод, который он описывает, и он работает как шарм.

Конечно, вы не сможете сохранить чистоту, просто имеяглобальная переменная находится там в файле cpp.Вам нужно будет использовать статический указатель и некоторые функции доступа и подсчет ссылок.Но это может сработать.Я не совсем уверен, как можно было бы избежать того, чтобы foo.exe и foo.exe совместно использовали один и тот же экземпляр глобальных данных one bar.dll, мне никогда не приходилось это делать, и я не могу придумать, как это сделать.это, прости.

5 голосов
/ 19 октября 2011

Мне показалось, что это такой интересный вопрос, что я нашел время, чтобы написать обширное руководство о том, как использовать библиотеки DLL для обмена данными между несколькими библиотеками DLL (неявно или явно связанными), но также чтобы убедиться, что данные не передаются.среди отдельных процессов одного и того же исполняемого файла.

Вы можете найти полную статью здесь: http://3dgep.com/?p=1759


Решение этой проблемы, которое я нашел, чтобы работать довольно хорошо, состоит в создании«общая» или «общая» dll, которая определяет все данные и методы, которые вы хотите совместно использовать в нескольких DLL (но не разделяете между процессами).

Предположим, вы хотите определить одноэлементный класс, который может бытьдоступ из основного кода приложения (EXE), но вы также хотите получить доступ к экземпляру singleton в совместно используемой (неявно или явно связанной 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 или EXE, которая неявно ссылается на общую DLL.

Определение класса MySingleton может выглядеть примерно так:

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

При компиляции общей библиотеки DLL будут созданы два файла:

  1. Файл Common.DLL , который является общей библиотекой, которая определяет методы иэкспортированные данные используются DLL.
  2. Файл Common.LIB , в котором объявляются заглушки для методов и членов, экспортируемых из t.DLL.

Если вы связываете свое приложение с экспортированным файлом LIB, то файл DLL будет неявно связан во время выполнения (до тех пор, пока файл DLL будет найден в путях поиска DLL) и выбудет иметь доступ к одноэлементному файлу, определенному в файле CommonDLL.DLL.

Кроме того, любая общая библиотека (например, плагины), которая также ссылается на файл CommonDLL.LIB, будет иметь доступ к тем же экземплярам одноэлементного файла, когдадинамически загружается приложением.

Для полного объяснения этого решения, включая пример исходного кода, ознакомьтесь со следующей опубликованной мною статьей «Использование динамических библиотек (DLL) для создания плагинов»:

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

5 голосов
/ 06 февраля 2011

Если foo.exe всегда загружает bar.dll, вы можете реализовать переменную в bar.dll и экспортировать ее.Например, какой-то файл b.cpp скомпилирован только в bar.dll, а не в foo.exe:

__declspec(dllexport) int x;

Затем импортируйте его в исходный файл c.cpp, скомпилированный в foo.exe:

__declspec(dllimport) int x;

Однако, если иногда foo.exe не загружает bar.dll, это не будет работать.Кроме того, я пишу это по памяти, и поэтому могут быть некоторые синтаксические ошибки, но, надеюсь, этого достаточно, чтобы указать вам верное направление.

Я не могу ответить, почему это другой Linux.

3 голосов
/ 10 февраля 2012

Разница между GCC и Visual Studio заключается в том, что в Linux он неявно позволяет коду видеть символы из других, динамически связанных (общих) библиотек без необходимости делать что-то особенное для программиста.Все символы доступны в общей (динамически связанной) библиотеке для динамического компоновщика, чтобы разрешить при запуске программы.В Windows вы должны специально экспортировать символ из DLL, а также явно импортировать его в программу или библиотеку, которая его использует.(Обычно это делается с помощью макроса (#define), который расширяется, чтобы иметь объявление dllexport в заголовочном файле при сборке самой dll, но когда заголовочный файл включен другой программой, использующей dll, он расширяется, чтобы иметь dllimportвместо этого объявление. По моему мнению, это боль в шее, и поведение GCC проще, так как вам не нужно делать ничего особенного, чтобы получить поведение, которое вы обычно хотите.

В более новой версии GCC,по умолчанию вы можете установить скрытие символов при создании динамической (общей) библиотеки, если хотите.

1 голос
/ 20 июня 2012

Спасибо за предоставление различных решений по этому вопросу. Я посмотрел на эту опцию и решил реализовать кросс-модуль синглтона с использованием общей памяти, и он также работал хорошо для меня. я использовал Qt QSharedMemory для выполнения своей задачи, но прототип, который я написал с использованием Win32 CreateFileMapping и т. д.

0 голосов
/ 21 октября 2017

Я видел много ответов на этот вопрос, и, поскольку он немного сложен и неясен, я хотел бы привести следующий сценарий. Мы хотим разделить глобальную переменную между DLL и основной программой, а также разрешить доступ к этой переменной из разных модулей в DLL и в основной программе.

Переменная - это BOOL, указывающая, должна ли программа продолжать работать или останавливаться. Имя переменной ShouldRun ;

В основной программе нам нужно поставить:

__declspec(dllexport)  bool ShouldRun;

В основной модуль DLL нужно поместить:

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

В любом другом модуле внутри проекта DLL мы будем использовать:

extern  "C" BOOL ShouldRun;
0 голосов
/ 06 февраля 2011

Если я правильно понимаю ваш вопрос, вы статически связываете 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.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...