Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 08:18
Оценка:
Здравствуйте. Открыл для себя неожиданный неприятный момент и обращаюсь сюда в надежде разобраться.

Есть класс реализованный и экспортированный из mylib.dll. В классе есть виртуальный деструктор. Задумывается что объекты этого класса могут создаваться где угодно, а после использования будут возвращаться в mylib.dll для утилизации. В силу некоторых причин утилизация не быстрое дело, и ей занимается отдельный поток в mylib.dll, который ожидает завершения некоторых операций после чего уничтожает его (delete).

Есть модуль dynaload.dll. Он создает объект (new) этого класса, а в конце жизни поступает с ним по описанному выше сценарию. Здесь я столкнулся с проблемой. Модуль dynaload.dll динамически выгружаемый модуль и выгружается он быстрее чем mylib.dll вызывает delete и тогда я получаю EXCEPTION_ACCESS_VIOLATION.

Оказалось, что при создании объекта на стороне dynaload.dll меняется vtable на другую, фактически на свой деструктор, который прежде чем вызвать оригинальный занимается чем-то (не силен в ассемблере) и потом вызывает оригинальный, реализованный в mylib.dll. Поэтому когда dynaload.dll выгружается установленный деструктор сваливает вместе с модулем. Отсюда и EXCEPTION_ACCESS_VIOLATION.

Конечно проблема решаемая: делаю экспортированную функцию в mylib.dll, в которой создается объект и дело в шляпе, ведь так сохраняется оригинальная vtable. Но хочу понять зачем компилятор VS2012 меняет vtable и как это отключить и какую работу выполняет эта обертка вокруг деструктора.
Re: Виртуальный деструктор
От: rg45 СССР  
Дата: 05.03.13 08:28
Оценка:
Здравствуйте, 3m-soft, Вы писали:

3S>Оказалось, что при создании объекта на стороне dynaload.dll меняется vtable на другую, фактически на свой деструктор, который прежде чем вызвать оригинальный занимается чем-то (не силен в ассемблере) и потом вызывает оригинальный, реализованный в mylib.dll. Поэтому когда dynaload.dll выгружается установленный деструктор сваливает вместе с модулем. Отсюда и EXCEPTION_ACCESS_VIOLATION.



А правильно ли класс экспортируется из mylib.dll? Изнутри mylib.dll он должен быть виден как __declspec(dllexport), а в других модулях как __declspec(dllimport). Обычно это достигается использованием нехитрых макросов.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 08:35
Оценка:
Здравствуйте, rg45, Вы писали:

R>А правильно ли класс экспортируется из mylib.dll? Изнутри mylib.dll он должен быть виден как __declspec(dllexport), а в других модулях как __declspec(dllimport). Обычно это достигается использованием нехитрых макросов.


Разумеется с этим все правильно. Методы класса не могли бы даже вызываться в dynaload.dll.
Re[3]: Виртуальный деструктор
От: rg45 СССР  
Дата: 05.03.13 08:41
Оценка:
Здравствуйте, 3m-soft, Вы писали:

3S>Здравствуйте, rg45, Вы писали:


R>>А правильно ли класс экспортируется из mylib.dll? Изнутри mylib.dll он должен быть виден как __declspec(dllexport), а в других модулях как __declspec(dllimport). Обычно это достигается использованием нехитрых макросов.


3S>Разумеется с этим все правильно. Методы класса не могли бы даже вызываться в dynaload.dll.


По описанному тобой поведению, как раз и получается, что модуль dynaload.dll не использует функции-члены, экспортируемые из mylib.dll, вместо этого он нагенерил свои собственные и использует их. Это указывает на то, что dynaload.dll не воспринимает этот класс как импортируемый. Я бы в первую очередь проверил активную конфигурацию проекта mylib — он часом не как статическая библиотека собирается?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 09:07
Оценка:
Здравствуйте, rg45, Вы писали:

R>По описанному тобой поведению, как раз и получается, что модуль dynaload.dll не использует функции-члены, экспортируемые из mylib.dll, вместо этого он нагенерил свои собственные и использует их. Это указывает на то, что dynaload.dll не воспринимает этот класс как импортируемый. Я бы в первую очередь проверил активную конфигурацию проекта mylib — он часом не как статическая библиотека собирается?


Во-первых, mylib это точно динамическая библиотека. Во-вторых, в классе в хидере не реализован ни один метод, только декларация. Физически тела методов находятся в mylib.dll (если бы мне изменяла память и я не помнил как устроен мой проект, то хотя бы дебагер и стек вызовов не дадут мне в этом ошибиться). И напомню, класс выполняет свои функции, программа работает как надо до момента деструкции объекта.
Re[5]: Виртуальный деструктор
От: rg45 СССР  
Дата: 05.03.13 09:47
Оценка:
Здравствуйте, 3m-soft, Вы писали:

R>>По описанному тобой поведению, как раз и получается, что модуль dynaload.dll не использует функции-члены, экспортируемые из mylib.dll, вместо этого он нагенерил свои собственные и использует их. Это указывает на то, что dynaload.dll не воспринимает этот класс как импортируемый. Я бы в первую очередь проверил активную конфигурацию проекта mylib — он часом не как статическая библиотека собирается?


3S>Во-первых, mylib это точно динамическая библиотека. Во-вторых, в классе в хидере не реализован ни один метод, только декларация. Физически тела методов находятся в mylib.dll (если бы мне изменяла память и я не помнил как устроен мой проект, то хотя бы дебагер и стек вызовов не дадут мне в этом ошибиться). И напомню, класс выполняет свои функции, программа работает как надо до момента деструкции объекта.


Это все замечательно, но факт остается фактом (из твоего же описания) — динамическая библиотека dynaload.dll содержит свой набор функций-членов класса, вместо того, чтобы использовать экспортируемые. Этого достаточно, чтобы утверждать, что экспорт/импорт не выполнен правильно. Ни отсутствие определений функций членов в заголовочном файле, ни корректная работа программы в определенный период не исключают этого факта.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Виртуальный деструктор
От: Кодт Россия  
Дата: 05.03.13 10:08
Оценка:
Здравствуйте, 3m-soft, Вы писали:

<>
Нет ли там виртуального наследования?
Другие виртуальные функции этого класса — тоже опосредованы?
И ещё: dynaload.dll использует общий с mylib.dll CRT, или свой собственный?
Перекуём баги на фичи!
Re: Виртуальный деструктор
От: dmitry_npi Россия  
Дата: 05.03.13 10:47
Оценка:
Здравствуйте, 3m-soft, Вы писали:


3S>Оказалось, что при создании объекта на стороне dynaload.dll меняется vtable на другую, фактически на свой деструктор, который прежде чем вызвать оригинальный занимается чем-то (не силен в ассемблере) и потом вызывает оригинальный, реализованный в mylib.dll. Поэтому когда dynaload.dll выгружается установленный деструктор сваливает вместе с модулем. Отсюда и EXCEPTION_ACCESS_VIOLATION.


3S>Конечно проблема решаемая: делаю экспортированную функцию в mylib.dll, в которой создается объект и дело в шляпе, ведь так сохраняется оригинальная vtable. Но хочу понять зачем компилятор VS2012 меняет vtable и как это отключить и какую работу выполняет эта обертка вокруг деструктора.


Попробуйте посмотреть здесь. Из этого следует, что не нужно писать __dllimport, тогда компилятор не будет генерировать обертки в ИМПОРТИРУЮЩЕМ модуле, в вашем случае dynaload.dll.
Атмосферная музыка — www.aventuel.net
Re[2]: Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 11:25
Оценка:
Без __dllimport линкер конечно вываливает пару сотен ошибок линковки ибо mylib.dll это базовая библиотека для всех других dll и exe. CRT общий для всех и вся: msvcp110.dll+msvcr110.dll. Класс, о котором идет речь, не использует виртуального наследования. У него одна виртуальная функция — деструктор. У него один предок. И, конечно, у предка одна виртуальная функция — деструктор. Предок является базовым классом для многих других и только обеспечивает функционал для работы intrusive_ptr. Всё! Тут всего два гвоздя и такая загвоздка.
Re[6]: Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 11:36
Оценка: 8 (1)
Здравствуйте, rg45, Вы писали:

R>Это все замечательно, но факт остается фактом (из твоего же описания) — динамическая библиотека dynaload.dll содержит свой набор функций-членов класса, вместо того, чтобы использовать экспортируемые.


Она не содержит свой набор функций, все методы она вызывает из mylib.dll (ну я же вижу своими глазами на дебагере, по стеку вызовов, по адресному пространству наконец). Она содержит только обертку вокруг родного деструктора, и чтобы вызывалась именно обертка, а не родной деструктор, подменяется vtable. В дебаг версии я, по крайней мере, понимаю смысл этой обертки. Судя по вызовам, она занимается runtime check. Но в релизной версии runtime check не совместима с оптимизациями, но компилятор все равно делает обертку.
Re[7]: Виртуальный деструктор
От: rg45 СССР  
Дата: 05.03.13 11:57
Оценка:
Здравствуйте, 3m-soft, Вы писали:

3S>Здравствуйте, rg45, Вы писали:


R>>Это все замечательно, но факт остается фактом (из твоего же описания) — динамическая библиотека dynaload.dll содержит свой набор функций-членов класса, вместо того, чтобы использовать экспортируемые.


3S>Она не содержит свой набор функций, все методы она вызывает из mylib.dll (ну я же вижу своими глазами на дебагере, по стеку вызовов, по адресному пространству наконец). Она содержит только обертку вокруг родного деструктора, и чтобы вызывалась именно обертка, а не родной деструктор, подменяется vtable. В дебаг версии я, по крайней мере, понимаю смысл этой обертки. Судя по вызовам, она занимается runtime check. Но в релизной версии runtime check не совместима с оптимизациями, но компилятор все равно делает обертку.


Действительно, создание подобных оберток — это большая свинья со стороны компилятора, и это похоже на правду. Только ориентироваться на адресное пространство в этом случае не приходится, т.к. оно общее для всех динамических библиотек, подгружаемых в один процесс. Тут нужно сравнивать отдельные адреса — создать два объекта — один создать в mylib.dll, другой — в dynaload.dll, и сравнить их vptr, а также их содержимое (можно в отладчике, а можно вывести в файл или консоль). Ради эксперимента можно также временно добавить в класс другие виртуальные функции. Если это действительно обертки, то делегирование вызовов должно быть прекрасно видно в отладчике.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[8]: Виртуальный деструктор
От: 3m-soft  
Дата: 05.03.13 12:35
Оценка: 8 (3)
Вот!!! Наконец нашел эту проблему. Ясно же, что я не мог быть один.

http://stackoverflow.com/questions/1865631/c-loading-an-exe-as-a-dll-local-vftable-problem
Re[3]: Виртуальный деструктор
От: sgenie  
Дата: 05.03.13 20:31
Оценка:
Проблема в аллокации памяти — как были слинкованы ДЛЛки — статик или динамик? Если они слинкованы статик, то у них различные кучи и менеджеры памяти и поэтому аллокация и деаллокация должны происходить в контексте однои ДЛЛ.
Re[4]: Виртуальный деструктор
От: 3m-soft  
Дата: 06.03.13 04:19
Оценка:
Здравствуйте, sgenie, Вы писали:

S>Проблема в аллокации памяти — как были слинкованы ДЛЛки — статик или динамик? Если они слинкованы статик, то у них различные кучи и менеджеры памяти и поэтому аллокация и деаллокация должны происходить в контексте однои ДЛЛ.


Конечно динамик. dmitry_npi правильно же посоветовал, я это тогда ещё не понимал. Когда по своей ссылке все прочитал, а в особенности на groups.google.com, тогда всё встало на свои места. Phil Lucido там всё подробно объяснил.
Re: Виртуальный деструктор
От: MasterZiv СССР  
Дата: 11.03.13 12:36
Оценка:
Здравствуйте, 3m-soft, Вы писали:

3S>Оказалось, что при создании объекта на стороне dynaload.dll меняется vtable на другую, фактически на свой деструктор, который прежде чем вызвать оригинальный занимается чем-то (не силен в ассемблере) и потом вызывает оригинальный, реализованный в mylib.dll. Поэтому когда dynaload.dll выгружается установленный деструктор сваливает вместе с модулем. Отсюда и EXCEPTION_ACCESS_VIOLATION.


Я думаю, что ты не прав в своих предположениях, и на самом деле у тебя в программе всё работает немного не так, как ты думаешь.
Также могу предположить, что у тебя в программе присутствует нарушение one definition rule, раз у тебя есть две разных виртуальных таблицы для класса.
Re: Виртуальный деструктор
От: saf_e  
Дата: 12.03.13 16:26
Оценка:
Здравствуйте, 3m-soft, Вы писали:

3S>Здравствуйте. Открыл для себя неожиданный неприятный момент и обращаюсь сюда в надежде разобраться.


3S>Есть класс реализованный и экспортированный из mylib.dll. В классе есть виртуальный деструктор. Задумывается что объекты этого класса могут создаваться где угодно, а после использования будут возвращаться в mylib.dll для утилизации. В силу некоторых причин утилизация не быстрое дело, и ей занимается отдельный поток в mylib.dll, который ожидает завершения некоторых операций после чего уничтожает его (delete).


3S>Есть модуль dynaload.dll. Он создает объект (new) этого класса, а в конце жизни поступает с ним по описанному выше сценарию. Здесь я столкнулся с проблемой. Модуль dynaload.dll динамически выгружаемый модуль и выгружается он быстрее чем mylib.dll вызывает delete и тогда я получаю EXCEPTION_ACCESS_VIOLATION.


3S>Оказалось, что при создании объекта на стороне dynaload.dll меняется vtable на другую, фактически на свой деструктор, который прежде чем вызвать оригинальный занимается чем-то (не силен в ассемблере) и потом вызывает оригинальный, реализованный в mylib.dll. Поэтому когда dynaload.dll выгружается установленный деструктор сваливает вместе с модулем. Отсюда и EXCEPTION_ACCESS_VIOLATION.


3S>Конечно проблема решаемая: делаю экспортированную функцию в mylib.dll, в которой создается объект и дело в шляпе, ведь так сохраняется оригинальная vtable. Но хочу понять зачем компилятор VS2012 меняет vtable и как это отключить и какую работу выполняет эта обертка вокруг деструктора.


Все что могу сказать, удалось повторить это таинственное поведение 2012 студии, и никакими настройками отключить не удалось.

Все что вам остается, это или не выгружать длл-ку пока объекты не выгрузились, или ждать патча который это полечит.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.