Подтвердить что ты не робот

Совместное использование глобальной/статической переменной между процессом и 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. Сначала я рассмотрел использование pragma data_seg в Windows. Однако, даже если я правильно настроил общий сегмент данных, foo.exe и bar.dll никогда не разделяют x. Напомним, что bar.dll загружается в адресное пространство foo.exe. Однако, если я запускаю другой экземпляр foo.exe, тогда x является общим. Но я не хочу, чтобы x делился различными процессами. Таким образом, с помощью data_seg не удалось.

Я могу использовать файл с отображением памяти, создав уникальное имя между exe и dll, которое я сейчас пытаюсь сделать.


Два вопроса:

  • Почему поведение Linux и Windows отличается? Может ли кто-нибудь объяснить об этом больше?
  • Что было бы самым простым способом решить эту проблему в Windows?
4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

В случае, если вы хотите, чтобы программа загружала библиотеку во время выполнения, но основной программе, возможно, придется использовать переменную перед загрузкой библиотеки, программа должна экспортировать эту переменную, и 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 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 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 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 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 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 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 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 exports)
gcc -shared -odll.dll dll.o main.a -Wl,--out-implib,libdll.a

# create the executable (exe needs to know about dll 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 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 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 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 exports)
link /nologo /dll dll.obj main.lib

rem create the executable (exe needs to know about dll 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 ..

Ответ 3

Если 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.

Ответ 4

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

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


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

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

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

Спасибо за предоставление различных решений. я рассмотрел этот вариант и решил реализовать сингл-модуль cross module с использованием общей памяти, и он отлично сработал у меня. я использовал 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, он получает его собственный х.

Ответ 8

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

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

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

__declspec(dllexport)  bool ShouldRun;

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

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

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

extern  "C" BOOL ShouldRun;