Корректная работа с хендлами
Естественно, класс переопределяет стандартный конструктор копирования по умолчанию, поскольку простое копирование числовых значений хендлов некорректно, т.к. при удалении экземпляра класса будет вызван соответствующий деструктор, закрывающий хендлы, в результате чего хендл перестанет быть валидным. Вывод - нужно дублировать хендлы.
{
bool rc = DuplicateHandle(GetCurrentProcess(),src.m_hModule,GetCurrentProcess(),&m_hModule,0,false,DUPLICATE_SAME_ACCESS);
bool rct= DuplicateHandle(GetCurrentProcess(),src.m_hThread,GetCurrentProcess(),&m_hThread,0,false,DUPLICATE_SAME_ACCESS);
if ((rc && rct) == false)
{
MessageBox(NULL,TEXT("Error in CPlugin(const CPlugin & src)"),TEXT("Core error"),MB_ICONERROR);
}
}
При этом rc всегда равен false, соответственно после попытки дублирования хендла модуля библиотеки последняя ошибка устанавливается в ERROR_INVALID_HANDLE.
Хендл потока копируется корректно.
Вопрос - можно ли, и если да, каким образом копировать хендлы библиотек, а именно - что "необходимо и достаточно" для того, чтобы счетчик пользователей хендла этого типа увеличился/уменьшился на 1.
Не вижу оснований дублировать хендлы.
Создай класс-обертку над хендлом с подсчетом ссылок.
Не вижу оснований дублировать хендлы.
Создай класс-обертку над хендлом с подсчетом ссылок.
... и вследствие того, что обращаться к этому хендлу нужно из нескольких потоков, используй глобалыные переменные (прим:там, где можно этого избежать), со всеми прелестями синхронизации потоков, его использующих...
Другими словами: "Продублируй то, что уже реализовано системой для всех хендлов, своими руками"
Ребят, вчитывайтесь пожалуйста, вопрос стоял отнюдь не такой: "Как избежать дублирования хендла модуля", и не такой: "А как выдумаете, зачем я..."
Вопрос стоял так: "Как корректно скопировать хендл модуля, чтобы при его закрытии система не сочла модуль не нужным и не выгрузила его, а также для того, чтобы не произошло ситуации с хранением и использованием уже закрытых хенлов". Разжевал.
Все же объясняю, зачем.
Еще раз:
{[INDENT]std::list<CPlugin> lst;
if ( . . . )
{[INDENT]CPlugin newp( . . . ); // тут создался экземпляр класса, он содержит хендлы молуля своей ДЛЛ плагина, и хендл созданного ему отдельного замороженного потока
lst.push( newp ); // тут создается дополнительное звено списка (включающее экземпляр CPlugin), а затем вызывается конструктор копирования или операция присваивания (суть одна). Которая по умолчанию копирует числовые значения хендлов (предположим, хендл модуля == 5)[/INDENT]}[INDENT]//конец блока. Удаление всех его локальных переменных. В том числе вызов деструктора для newp. Чтобы не возникало утечки памяти во время работы программы, он должен освободить хендл. Что он и делает. Теперь хендл с числовым значением 5 закрыт (не валидный)[/INDENT]
. . .
CPlugin next = lst.pop();[INDENT]// Тут снова (упрощено) идет вызов конструктора копирования, который снова копирует числовое значение хендла модуля (5), уже невалидного, в next, далее удаляется последнее звено списка, в том числе, вызывается деструктор для экземпляра СPlugin, который в этом звене хранится, и который опять же, содержит неверное значение хендла модуля (5). Деструктор закрывает этот хенл. Это недопустимо - хендл неверный.[/INDENT]
/* а далее, каким-либо образом происходят попытки использовать next (который содержит "дважды закрытый" хендл модуля, с числовым значением 5) */
}
[/INDENT]
Искренне надеюсь, что на все вопросы вида "зачем так" и "почему так" я ответил. Пишется устойчивый, расширяемый и удобный для сопровождения движок плагинов для моего проекта.
Очень надеюсь все же постичь, каким образом корректно скопировать хендл модуля, чтобы при его закрытии система не сочла модуль не нужным и не выгрузила его, а также для того, чтобы не произошло ситуации с хранением и использованием уже закрытых хендлов, два простейших примера которой приведено выше.
?
Зачем?!
Да, но эту часть можно упростить. А возможно, можно и не реализовывать. Все зависит от архитектуры.
Кстати, ты уверен, что при твоем решении с DuplicateHandle, тебе не нужна будет синхронизация? Точно уверен?
Другими словами: "Продублируй то, что уже реализовано системой для всех хендлов, своими руками"
Вся проблема в том, что ты используешь "тяжелую артиллерию" для относительно простой операции.
Подробнее, смотри здесь:
http://blog.not-a-kernel-guy.com/2006/10/31/93
Ребят, вчитывайтесь пожалуйста, вопрос стоял отнюдь не такой: "Как избежать дублирования хендла модуля", и не такой: "А как выдумаете, зачем я..."
Задумайся, пожалуйста над тем, что иногда на вопрос "как мне сделать глупость", отвечают "как её не делать". И над этим стоит подумать, а не идти напролом.
P.S. Кстати, а ты уверен, что тебе вообще нужно копировать хендлы? Думаю, что можно обойтись и без копирования и без подсчета ссылок и без прочих усложнений.
Вы меня правильно поняли. Конечно же, хендл модуля имеет этот тип и получен подобным образом.
Виноват, не уточнил.
{
private:
...
HMODULE hModule;
HANDLE hThread;
Подробнее, смотри здесь:
http://blog.not-a-kernel-guy.com/2006/10/31/93
Спасибо, просмотрел, добавил в закладки. интересно, доработаю и буду использовать, однако в моем случае это неприменимо по той причине, что ни использованный в классе CloseHandle ни DuplicateHandle неприменимы (проверено экспериментально) к хендлам модулей (HMODULE).
Реализацию же универсальной классовой обертки считаю более трудоемкой задачей... Кстати, при таком подходе в определенных ситуаиях все же придется копировать хендл модуля... Внутри обертки... проблема не будет решена.
Согласен. Предложите, пожалуйста, вариант корректного копирования.
Классовую обертку вижу так:
Создаем приватную структуру, которая хранит хендл и число ссылок на него.
Создаем класс управления, содержащий указатель на эту структору. В классе:
"обычный" конструктор - выделяет память под приватную структуру, инициаизирует хендл заданным значением, и его счетчик использования единицей.
конструктор копирования - присваивает экземпляру-копии указатель на ту же (уже выделенную) структуру. инкрементирует счетчик ссылок.
деструктор - выполняет декремент счетчика ссылок. Если счетчик ссылок стал равен 0, то закрывает хендл модуля (FreeLibrary(..)) и удаляет выделенную структуру.
Перечитав то, что надумал, задаюсь вопросом -- а не реализовываю ли я заново механизм работы с хендлами, уже реализованный в Винде? Думаю, что да. Возможно, я, конечно неправильно понял ваш совет, в таком случае скажите, как понять правильно.
Виноват, не уточнил.
{
private:
...
HMODULE hModule;
HANDLE hThread;
Ok. Все понятно. Тогда, собственно, проблема заключается в том, что HMODULE и HANDLE это абсолютно разные типы. HMODULE - это просто базовый адрес, по-которому загружена DLL в адресное пространство процесса. HANDLE - это индекс (или байтовое смещение) строки в таблице описателей объектов ядра процесса. Короче говоря код
не выполнится успешно, так как src.m_hModule - это не HANDLE.
Решение - не париться с копированием описателей. Модуль выгрузится либо только при явном вызове FreeLibrary либо при завершении процесса.
Вот в том-то и пробема, что FreeLibrary вызывается в деструкторе... отсюда если не увеличить счетчик ссылок на описатель то будет беда...
Кстати:
Буду копать в сторону LoadLibrary. Правда, там могут быть подводные камни насчет неожиданных вызовов DllEntryРoint и получения по хендлу имени модуля, но думаю, это решабельно. Еще думаю над архитектурой всей этой системы - плагины будут писаться кроме меня еще одним-двумя прогерами, возможно на дельфях, надо интерфейс сделать максимально прозрачно.
З.Ы. А я, дурак, еще на асме это собирался кодить... )))
Не видел, пока писал ответ. Спасибо, именно туда и копаю. Или LoadLibrary, или LoadLibraryEx
If the module is a DLL not already mapped for the calling process, the system calls the DLL's DllMain function with the DLL_PROCESS_ATTACH value.
[/QUOTE]
Так что нормально все
Про HMODULE уже проговорили в теме. Это, действительно , не хендл объекта и т.о. не копируется. Кстати, что копирует DuplicateHandle перечислено в таблице на странице с его описанием в MSDN.
Реализацию же универсальной классовой обертки считаю более трудоемкой задачей... Кстати, при таком подходе в определенных ситуаиях все же придется копировать хендл модуля... Внутри обертки... проблема не будет решена.
Обертка призвана сделать так, чтоб не копировать хендл. Если "в определенных ситуаиях все же придется копировать", значит обертка не справляется со своими обязанностями, а сл-но написана неправильно.
Классовую обертку вижу так:
Создаем приватную структуру, которая хранит хендл и число ссылок на него.
Создаем класс управления, содержащий указатель на эту структору. В классе:
"обычный" конструктор - выделяет память под приватную структуру, инициаизирует хендл заданным значением, и его счетчик использования единицей.
конструктор копирования - присваивает экземпляру-копии указатель на ту же (уже выделенную) структуру. инкрементирует счетчик ссылок.
деструктор - выполняет декремент счетчика ссылок. Если счетчик ссылок стал равен 0, то закрывает хендл модуля (FreeLibrary(..)) и удаляет выделенную структуру.
Ну в общем схема правильная. Есть обертка агрегирующая хендл и производящая подсчет ссылок, есть вспомогательный объект для автоматического приращения счетчика ссылок. Совсем несложная схема.
Перечитав то, что надумал, задаюсь вопросом -- а не реализовываю ли я заново механизм работы с хендлами, уже реализованный в Винде? Думаю, что да. Возможно, я, конечно неправильно понял ваш совет, в таком случае скажите, как понять правильно.
Механизм хендлов в системе занимается (в т.ч.) подсчетом ссылок. Вышеописанный механизм тоже (и только) занимается подстчетом ссылок. Но вопрос в уровне на котором они это делают. Товой механизм локальный и более высокоуровневый.
Но во многих случаях даже такой механизм не нужен.
По большому счету, тебе надо обеспечить чтоб время жизни одного объекта было продолжительнее времени жизни другого. Это можно сделать по-разному.
Ну как пример, почему бы не создавать объект в начале работы и не уничтожать его в самом конце.
Т.е. применительно к DLL, загрузить DLL в начале, а выгрузить в конце. Не обязательно при завершении программы, но в конце того участка, где этот модуль используется.
Ты говорил про многопоточность. Значит есть место, где эти потоки рождаются. Вот и загрузить эту DLL до этого места. По-хорошему, должно быть место в основном потоке, где дожидается завершение всех дочерних потоков. Вот и выгрузить эту DLL сразу за этим местом.