Аннотация :
Если вы пытались работать с экземплярами классов STL, передавая их в DLL, или получая оттуда, а потом бросили это занятие из-за непонятных ошибок, возникающих в вашей программе, то эта заметка для вас. Даже если видимых проблем в вашей программе нет, то все равно прочитайте эту заметку, чтобы знать что делать, когда они появятся :)
По ссылке на drinkumware последнее обновление 10 Ноября 2002.
Добавлено исправление в <vector>.
Кроме того в представленном архиве <istream> исправлен "по майкрософтовски", <string> не исправлен, <memory> не исправлен (хотя надобность в исправлении <memory> сомнительна).
Также можно добавить нижеследуюжее исправление:
In <fstream>, <f>basic_filebuf::overflow<d>, change the return statement
from:
return (fwrite(_Str->begin(), _N, 1, _File) == _N ? _C : _Tr::eof());
Цитата
"Следовательно, из параметров функции ничего получить нельзя. Что же происходит?
Существует два способа хранения информации о размере выделенной памяти: физический,
перед выделенным фрагментом памяти (в нашем примере по адресу p-sizeof — структуры с
информацией о блоке памяти), или же во внутренних структурах библиотеки.
Для CRT, в Visual C++, фирма Microsoft воспользовалась вторым способом."
Почему сделан вывод, что MS использует второй способ? Я бы наверное назвал его смешанным. Так если смотреть реализацию delete для DEBUG версии, то информация о выделенной памяти все-таки храниться в заголовке по адресу "p-sizeof(BlockHeader)", если быть еще точнее, то по адресу "p-0x20". Кроме размера в этом же заголовке хранится укзатели на предыдущий и следующий блоки памяти, тип блока и т.д. Т.е. на лицо связный список. А вот уже сама CRT содержит другие данные необходимые для управления этим связным списком. Так имеются глобальные переменные _pFirstBlock, _pLastBlock (указатели на заголовки блоков памяти) и другие данные. Естественно если выделить память в DLL, а попытаться удалить в приложении, при том что используются разные копии CRT, то скорее всего программа вылетит. Т.к. перед тем как удалить
блок, CRT делает кучу проверок валидности блока (заголовка блока) — есть ли данный заголовок в списке, правильный его тип и т.д. Затем пытается склеить части списка, а уже потом удаляет блок.
Выход, как уже было сказано, использовать ключ компилятора /MD. Однако тут могут возникнуть проблемы, но не то что нет нужного dll, а то что могут использоваться все равно разные dll. Если dll написана на VC4, то используется "msvcrt40.dll", а если приложение на VC5, то используется "msvcrt.dll". В некоторых локализациях Win поставляется файл "msvcrt40.dll", который переправляет все вызовы к "msvcrt.dll".
В своем проекте я использовал STL и в главном приложении и в динамически подключаемых DLL (LoadLibrary/GetProcAddress)
и мне приходилось передавать указатели на объекты map & list из DLL в EXE. При попытке использовать объекты созданные в DLL в основном приложенни иногда получал memory fault. После отладки оказалось что каждый экземпляр внутренних структур STL инициализировался по разному, в частности для проверки окончания списка (дерева) используется не проверка на NULL, а сравнение со static членом внутренней структуры дерева. Соответственно этот static-member по разному инициализировался в разных экземплярах STL (в EXE и в DLL). По этому использовать напрямую объект выделенный в DLL нельзя, но можно только после того как он будет скопирован в экземпляр созданный в EXE файле.
Re: Использование STL в динамически загружаемых DL
По поводу Проблеммы2 см. статьи в MSDN->(KB:VisualC++)->("HOWTO: Exporting STL Components Inside Outside of a Class ID: Q168958") и MSDN->(KB:VisualC++)->("PRB: Access Violation When Accessing STL Object in DLL ID: Q172396")
Re: Заметка о некоторых особенностях использования STL в DLL
Здравствуйте, Роман Хациев, Вы писали:
РХ>Здесь можно привести множество аргументов против инициализации и освобождения указателя в разных модулях, что это дурной стиль программирования — тот кто выделил память, тот ее и должен освобождать. Спорить не буду, но хочу напомнить, что даже инкапсуляция указателя в класс, что вряд ли является дурным стилем программирования, так же подвержена вышеописанной проблеме в силу особенностей дизайна CRT.
Кстати, boost::shared_ptr решает эту проблему. Он рядышком со счётчиком ссылок хранит и указатель на функцию, удаляющую объект. Обычно это operator delete. Хитрость в том, что запоминается указатель на operator delete именно того модуля, в котором объект создаётся!
--
wbr, Peter Taran
Re[2]: Заметка о некоторых особенностях использования STL в
Здравствуйте, tarkil, Вы писали:
T>Здравствуйте, Роман Хациев, Вы писали:
РХ>>Здесь можно привести множество аргументов против инициализации и освобождения указателя в разных модулях, что это дурной стиль программирования — тот кто выделил память, тот ее и должен освобождать. Спорить не буду, но хочу напомнить, что даже инкапсуляция указателя в класс, что вряд ли является дурным стилем программирования, так же подвержена вышеописанной проблеме в силу особенностей дизайна CRT.
T>Кстати, boost::shared_ptr решает эту проблему. Он рядышком со счётчиком ссылок хранит и указатель на функцию, удаляющую объект. Обычно это operator delete. Хитрость в том, что запоминается указатель на operator delete именно того модуля, в котором объект создаётся!
Это только вершина айсберга. Попробуй передать boost::shared_ptr<std::string> в dll и поработать там с этой строкой.
Т.ч. он практически ничего не решает. Единственное, что можно будет сделать с объектом — удалить его и не больше.
Здравствуйте, remark, Вы писали:
R>Это только вершина айсберга. Попробуй передать boost::shared_ptr<std::string> в dll и поработать там с этой строкой. R>Т.ч. он практически ничего не решает. Единственное, что можно будет сделать с объектом — удалить его и не больше.
Он нормально всё решает, если им управлять всей динамической памятью. Внутри std::string же не boost::shared_ptr, верно?
А STL-ным объектам можно ещё подсовывать самописные аллокаторы, которые корректно работают при пересечении границы модуля.
Здравствуйте, tarkil, Вы писали:
T>Здравствуйте, remark, Вы писали:
R>>Это только вершина айсберга. Попробуй передать boost::shared_ptr<std::string> в dll и поработать там с этой строкой. R>>Т.ч. он практически ничего не решает. Единственное, что можно будет сделать с объектом — удалить его и не больше.
T>Он нормально всё решает, если им управлять всей динамической памятью. Внутри std::string же не boost::shared_ptr, верно?
Ну так как же он решает задачу использования контейнеров стандартной библиотеки в dll?
T>А STL-ным объектам можно ещё подсовывать самописные аллокаторы, которые корректно работают при пересечении границы модуля.
T>typedef std::basic_string<char, char_traits<char>, safeAllocator<char> > safe_string;
T>И юзаешь safe_string на здоровье. Вопрос реализации safeAllocator — отдельный.
Здравствуйте, remark, Вы писали:
T>>Он нормально всё решает, если им управлять всей динамической памятью. Внутри std::string же не boost::shared_ptr, верно? R>Ну так как же он решает задачу использования контейнеров стандартной библиотеки в dll?
Никак Он решает вопросы передачи указателей на объекты между модулями. Просто это тоже в тему межмодульных грабель, наряду с контейнерами.
T>>А STL-ным объектам можно ещё подсовывать самописные аллокаторы, которые корректно работают при пересечении границы модуля. R>здесь
Идея виртуальной функции, как гаранта одиночности реализации мне понравилась. Но, как я понял из дискуссии, в случае агрессивной оптимизации даже вынос оных в отдельный класс-интерфейс ничего не гарантирует. Что грустно. Хоть реестр заводи.
--
wbr, Peter Taran
Re[6]: Заметка о некоторых особенностях использования STL в
T>Идея виртуальной функции, как гаранта одиночности реализации мне понравилась.
Реализации будет две. Просто будет запомнен указатель на одну из них.
T>Но, как я понял из дискуссии, в случае агрессивной оптимизации даже вынос оных в отдельный класс-интерфейс ничего не гарантирует. Что грустно. Хоть реестр заводи.
В том топике мы вроде сошлись на версии, когда в объекте аллокатора вручную запоминается указатель на функцию выделения/освобождения, а не через виртуальную функцию. По крайней мере такую версию я взял на вооружение. Как раз по причине возможной оптимизации.
Здравствуйте, remark, Вы писали:
R>В том топике мы вроде сошлись на версии, когда в объекте аллокатора вручную запоминается указатель на функцию выделения/освобождения, а не через виртуальную функцию. По крайней мере такую версию я взял на вооружение. Как раз по причине возможной оптимизации.
Это которое Кодт предложил? Ага, дочитал (тормозит у меня сайт жутко). Хорошее решение, тоже запомнил.