(c) И.В. Радинский, 06.10.1999 г.
Как-то раз, передо мной встала проблема - у меня была куча EXE- и DLL-файлов, входящих в одну большую систему. Как водится в больших системах, у нее были одни глобальные настройки и объекты, которые использовали те или иные модули одновременно. Настройки - это не проблема ведь можно просто хранить их в одном файле, который читают все модули. Проблема в объектах - я захотел создать группу разделяемых объектов по работе с БД, и чтобы все модули видели их, ну и заодно можно было бы хранить и общие настройки. Я хотел это реализовать в одной DLL, которую загружали бы модули, и получали бы, таким образом, доступ к этим общим данным.
Проблема нетривиальная - ведь в Win32 каждый процесс имеет свое собственное адресное пространство в 4 Гб, и это все, что он "видит". Если процесс загружает DLL, то ее код проецируется в его адресное пространство и она (DLL) при этом теряет почти всю свою индивидуальность: для потоков код и данные DLL - просто дополнительный код и данные, оказавшиеся в адресном пространстве процесса. Когда поток вызывает из DLL какую-то функцию, та считывает свои параметры из стека потока и размещает в этом стеке собственные локальные переменные. Кроме того, любые созданные кодом DLL объекты принадлежат вызывающему потоку или процессу - DLL в Win32 ничем не владеет.
Но. Есть одно "но". Переменные в EXE- или DLL-файлах можно поместить в особые раделы, которые можно разделить для всех процессов.
Немного о разделах. Начнем с того, что любой образ EXE- или DLL-файла состоит из группы разделов (sections). Вот названия наиболее часто встречающихся разделов:
| Имя раздела | Описание |
| .text | Код приложения или DLL. |
| .bss | Неинициализированные данные. |
| .rdata | Неизменяемые данные периода выполнения. |
| .rsrc | Ресурсы. |
| .edata | Таблица экспортируемых имен. |
| .data | Инициализированные данные. |
| .xdata | Таблица для обработки исключений. |
| .idata | Таблица импортируемых имен. |
| .CRT | Неизменяемые данные стандартной библиотеки C. |
| .reloc | Настроечная информация - таблица переадресации (fixup table). |
| .debug | Отладочная информация. |
| .tls | Локальная память потока. |
Попробуйте посмотреть любой EXE- или DLL-файл - и вы наверняка найдете несколько разделов из этой таблицы.
Так вот, например, этот код:
#pragma data_seg("MySection")
TCHAR szName[100] = {0};
LONG lModuleUsage;
#pragma data_seg()
#pragma comment(linker,"/SECTION:MySection, RWS")
заставит компилятор создать раздел MySection и поместить в него все ИНИЦИАЛИЗИРОВАННЫЕ переменные, встретившиеся между #pragma data_seg("MySection") и #pragama data_seg(). Директива #pragma comment(linker, "/SECTION:MySection, RWS") говорит компановщику включить строку сделать раздел MySection с аттрибутами READ, WRITE и SHARED. Угадайте с трех раз, какие переменные попадут в эту раздел. Вот они - грабли !!! Только szName и попадет ! А lModuleUsage попадает в секцию .bss (неинициализированные данные) - т.е. каждый поток будет иметь свою собственную lModuleUsage ! Отсюда сразу выходит, что код типа
#pragma data_seg("MySection")
CSesssion g_Session;
#pragma data_seg()
#pragma comment(linker, "/SECTION:MySection, RWS")
обречен - переменная g_Session будет в разделе .bss, а никак не в MySection.
Если подумать, то это справедливо - ведь каждый процесс вызывает конструктор объекта. А где исполняется конструктор - правильно, в адресном пространстве процесса. И все побочные данные объекта где будут? В адресном пространстве этого самого процесс а, что для других процессов уже никак не подходит.
А от такой схемы пришлось отказаться. Во-первых таким образом нарушается зашита уровня B (B-level security). Во-вторых ошибки в приложении с общими переменными могу повлиять на другие приложения, т.е. одна программа может "завалить" всю систему.