Разбирающимся в dll и osg2.2

erotic

Имеем в коде OSG:

#define OSGVIEWER_EXPORT __declspec(dllimport)
class OSGVIEWER_EXPORT ViewerBase : public virtual osg::Object
{
public:
...
typedef std::vector<osgViewer::GraphicsWindow*> Windows;
virtual void getWindows(Windows& windows, bool onlyValid=true) = 0;
};

class OSGVIEWER_EXPORT Viewer : public ViewerBase, public osgViewer::View
{
public:
...
virtual void getWindows(Windows& windows, bool onlyValid=true);
};

И мой код:

osgViewer::Viewer::Windows windows;
osgViewer::Viewer::getWindows(windows);

Так вот - первая строчка моего кода создает пустой вектор, когда же он по ссылке передается в функцию, то, хотя адрес его остается тот же, объект получается испорченным с каким-то нереальным количеством элементов в нем, и первая же операция с нем - windows.clear - заканчивается вываливанием проги.
Такое наблюдается во всех комбиациях Debug и Release между поим проектом и OSG, за исключением Debug/Debug - тогда эта строчка проходит нормально.
Почему такая хрень? И такое же случается с другими функциями, подгружаемым из DLL (DLL слинкована статически которым передаются ссылки на объекты, которые производные от контейнеров STL.
P.S. Помню еще, что давно у меня были косяки с возвратом std::string из dll-функции, была какая-то хрень с порчей памяти, поэтому пришлось в dll завести static std::string str, и возвращать из функции константную ссылку на нее, тогда работало.

Dasar

dll и прога собирались с одной и той же версией stl-я? pack-настроек и т.д.?

SPARTAK3959

Потому что объекты и stl-контейнеры нельзя передавать через границы dll. Если ты это все же делаешь, то ты должен убедиться в совпадении версий компилятора и STL. В Debug режиме версия STL, на сколько мне известно, отличается от Release'a. Да, еще не забывай, что память выделенную в dll'ке должна эта же dll'ка и освобождать.

erotic

Да, все собиралось на моей машине в MSVC8.

erotic

Все совпадает, один компилятор, одна ось, одна машина, одна STL.
Меня в таком случае удивляет, почему обо всем этом не знают разработчики OpenSceneGraph? Вплоть до версии 2.2 все у них замечательно работало, теперь вдруг косяки пошли.
Еще один пример, иллюстрирующий, насколько я понимаю, разницу между реализациями STL в MSVC и Linux. Там есть примерно такой кусок кода:
read(&front size*sizeof(value_type str);
Ну так вот, в винде на этом куске валится, т.к. size == 0. В линуксе не валится, наверное, front-таки что-то возвращает, и потом все равно читается 0 байт, ошибки не происходит.
Кстати, в отличие от предыдущих версий OSG, собиравшихся в msvc7.1, эта собрана в 8.
Блин, с учетом
Потому что объекты и stl-контейнеры нельзя передавать через границы dll
я могу сделать вывод, что версия OSG 2.2 не может использоваться в принципе, а до этого она работала только потому, что MSVC7 отличается от MSVC8.
Это верно и для статически линкуемых dll тоже?

SPARTAK3959

Я не знаю что такое "статически линкуемая dll". Если ты прилинкуешь этот код статически (т.е. никакой dll создаваться не будет то все должно работать (если он с собой свой STL не таскает - тогда не скомпилится).
А в linux компиляторов си используемых ярыми линуксоидами 1 штука. И отладочной версии STL там вроде нет.

erotic

Статическая линкуемая dll - которая загружается сразу при старте программы автоматически, и информация о ней зашивается в .exe-файл. Т.е. для нее не требуется выполнять LoadModule и находить адреса функций.
Я вот чего не понимаю: нельзя передавать контейнеры потому, что в них используются указатели, которые в контексте dll-ки становятся невалидными? Тогда как объяснить то, что в функции log4cpp, которые я использую из dll, свободно передаются как указатели const char*, так и ссылки const std::string& ?

SPARTAK3959

Не, указатели валидны в контексте любой DLL. Поэтому при совпадении версий STL баги в конфигурации release/release не понятны. Юзай ollydbg (если msvc поддерживает генерацию ассемблерного листинга, то будет проще сделав запасы чая или кофе, ибо отсуствие проблем при передаче STL контейнеров тебе никто не гарантирует.
PS Из "шаманских" действий можно попробовать вырубить флаг "Whole program optimization" как у DLL, так и основной программы.

vasena

Странное определение, да и вообще странно звучит, Статическая линкуемая dynamic-link library :)). Тем более если учеть, что для загруски библиотек при старте используется LoadModule, а дальше просто прописываются адреса функции в нужное место. И разницы что ты сам вызовишь LoadModule, что нет, просто не придется получать указатели не функции.

erotic

Имею ввиду, что вручную ничего загружать не придется.

erotic

Юзай ollydbg
Спасибо, попробую. Правда, в ассемблере я ни бум-бум.

Dasar

Я вот чего не понимаю: нельзя передавать контейнеры потому, что в них используются указатели, которые в контексте dll-ки становятся невалидными?
у основной программы и у каждой dll - свой C-ишный менеджер памяти.
соответственно, выделенную память в одной из dll/основной программе - можно только там и удалить.
соответственно получается, что надо жестко контролировать чтобы ответственность за удаление - не передавалась между dll-ями.
при передаче stl-контейнеров такое правило не соблюдается, и происходит как раз передача ответственности за удаление с одного модуля на другой, что и приводит к ошибкам.
ps
именно поэтому C++ и не любят в больших приложениях (когда отдельные модули собираются в разное время в разных местах т.к. получается что либо надо писать на C, либо городить свое подобие COM-а
именно поэтому open source развит именно на C++, т.к. без исходников вообще фиг соберешь большое приложение
в той же Delphi развивался именно рынок готовых бинарных компонентов (в том числе и free-шных а не исходников.

Dasar

> Статическая линкуемая dll
статически линкуемая dll, или статически линкуемая либа?

erotic

Объяснил же уже выше: dll, которая подключается к проекту посредством соответствующей .lib, в которой прописаны адреса функций в .dll, чтобы загрузка происходила автоматически при старте программы. Файл .lib может заменяться файлом .def, насколько я знаю.

erotic

соответственно, выделенную память в одной из dll/основной программе - можно только там и удалить.соответственно получается, что надо жестко контролировать чтобы ответственность за удаление - не передавалась между dll-ями.при передаче stl-контейнеров такое правило не соблюдается, и происходит как раз передача ответственности за удаление с одного модуля на другой, что и приводит к ошибкам.
Т.е. если я передаю ссылку на константный контейнер в dll, то он должен нормально читаться? И если я передаю контейнер по значению, то он просто скопируется в стеке dll и тоже все будет в порядке?
А если я передаю ссылку на контейнер в dll, и там пытаются добавить в него элементы, то возможны вызовы new и delete в dll, что приведет к косякам?

SPARTAK3959

у основной программы и у каждой dll - свой C-ишный менеджер памяти.
Кстати, в паскале можно сделать общий менеджер памяти (модуль sharemem). Может и в си есть нечто подобное?

Dasar

Объяснил же уже выше: dll, которая подключается к проекту посредством соответствующей .lib, в которой прописаны адреса функций в .dll
АФАИК, это не есть статически слинкованная dll

Dasar

> Т.е. если я передаю ссылку на константный контейнер в dll, то он должен нормально читаться?
да, если dll-ки собираются одним и тем же компилятором с одними и теми же настройками
ты кстати так и не ответил, #pragma pack в обоих случаях одиннаковая или разная - скорее всего именно она как раз и переопределяется в Release для ускорения
ps
кстати ты уверен, что у тебя нет банального проезда по памяти? просто в debug-е память выделяется с запасом, а в release - память выделяется в притык, поэтому все проезды по памяти в release намного заметнее.

Dasar

И если я передаю контейнер по значению, то он просто скопируется в стеке dll и тоже все будет в порядке?
нет, конечно, т.к. копирование происходит до передачи.

Dasar

А если я передаю ссылку на контейнер в dll, и там пытаются добавить в него элементы, то возможны вызовы new и delete в dll, что приведет к косякам?
да, это приведет к косякам.

Dasar

> Кстати, в паскале можно сделать общий менеджер памяти (модуль sharemem). Может и в си есть нечто подобное?
в паскале(в дельфи) вроде и так общий менеджер памяти внутри одного процесса.
ты уверен, что не путаешь это с шарингом памяти между процессами?

erotic

АФАИК, это не есть статически слинкованная dll
Ну, как ты это называешь? Давай будем называть, как тебе привычнее, но от тебя какого-либо термина на этот счет я еще не увидел :)

erotic

ты кстати так и не ответил, #pragma pack в обоих случаях одиннаковая или разная - скорее всего именно она как раз и переопределяется в Release для ускорения
Ну, если я сам ее не переопределяю нигде, то должна быть одинаковая, я так думаю? Тем более, что во всех STL файлах сначала идет #pragma pack (push, _CRT_PACKING а в конце - #pragma pack(pop).

erotic

кстати ты уверен, что у тебя нет банального проезда по памяти? просто в debug-е память выделяется с запасом, а в release - память выделяется в притык, поэтому все проезды по памяти в release намного заметнее.
Там вроде нечему проезжаться.
Сверху я приводил кусок кода - создается пустой контейнер map, далее сразу по ссылке передается в функцию из dll - и в ней дебаг показывает размер контейнера много-много элементов. Где тут можно было бы напортачить? :)

Dasar

Ну, если я сам ее не переопределяю нигде, то должна быть одинаковая, я так думаю?
ты может и не переопределяешь, но какой-нибудь h-ник переопределяет

erotic

В смысле, ни в одном из файлов моего проекта и проектов dll не встречается комбинация символов "#pragma pack", т.ч. должна быть одинаковая.

erotic

Как выясняется, дебаггер студии еще и врет иногда:
Собрал Release/Release, только оба с Debug Info. Решил вывести размер вектора до операции и после, написал std::cout << windows.size << std::endl; Прога не собралась, ошибка error LNK2001: unresolved external symbol "__declspec(dllimport) public: class std::basic_ostream<char,struct std::char_traits<char> > & __thiscall std::basic_ostream<char,struct std::char_traits<char> >::operator<<(unsigned int)" (__imp_?6?$basDU?$c@@@Z) (стандартная библиотека линкуется статически (/MT. Почему ошибка - совершенно непонятно, проект со статическими библиотеками не линкуется, во всем проекте стоит /MT, должно собираться.
Сменил с /MT на /MD, и все собралось и даже заработало, как ни странно. Решил посмотреть на этот вектор windows, дебаггер показывает в нем много миллиардов элементов, но прога отрабатывает нормально. А вот cout'ы выводят правильно - сначала 0, а после вызова функции - 1.
Наверное, я сошел с ума.

Landstreicher

> Наверное, я сошел с ума.
Ты не одинок. У меня были похожие случаи c GDB. Прога работает, но дебаггер при печати переменных выдает полную чушь. IMHO это свойство дебаггеров.

vasena

Проблема в том, что ты линкуешь библиотеки динамически, то есть загружаешь соответствующие dll во время выполнения, сам или во время загрузки. Как следствие тебе и надо установить правильное значение в списке Runtime library == Multithreaded DLL. Если бы ты линковал статически, то есть полностью линковал при линковке lib файл Runtime library, то надо было бы выбирать Multithreaded. Просто проблема в том, что ты с самого начала путал понятия статической и динамической линковки и при этом придумал совершенно свое понятие статически линкуемая dll :).

erotic

Проблема в том, что ты линкуешь библиотеки динамически, то есть загружаешь соответствующие dll во время выполнения, сам или во время загрузки. Как следствие тебе и надо установить правильное значение в списке Runtime library == Multithreaded DLL. Если бы ты линковал статически, то есть полностью линковал при линковке lib файл Runtime library, то надо было бы выбирать Multithreaded. Просто проблема в том, что ты с самого начала путал понятия статической и динамической линковки и при этом придумал совершенно свое понятие статически линкуемая dll
Не хотелось бы тебя огорчать, но то, что ты говоришь, не совсем верно.
Значение Runtime Library касается только стандартных библиотек C++. При линковке проекта с какими-либо статическими библиотеками у них у всех должно быть одинаковое значение Runtime Library, это бесспорно.
Но вот какое значение Runtime Library у .DLL, которую я подключаю к своему проекту, чтобы она автоматически подгружалась при старте, при помощи ее .lib файла, не имеет никакого значения, потому что в моей проге разрешения имен при этом не происходит, оно происходит только на этапе выполнения, и там уже не важно, как именно эта DLL использует стандартную библиотеку - связана ли она с ней статически или динамически, это ее дело.
Т.ч. проблема тут может быть в том, что когда я подключаю файлы OSG к проекту, то не все функции там экспортируются в DLL, может быть там есть какие-то inline-функции, использующие стандартную библиотеку, и вот тогда уже мне важно, чтобы Runtime Library совпадали.

vasena

Главная проблема при использовании различных версий рантайма заключается в использовании различных функций new и delete в библиотеках и исполняемом модуле с разными версиями рантайма. То есть выделяешь одним рантаймом, а потом пытаешься освободить другим, который про этот указатель ниче не знает. Если ты подняпрягешься, то заметишь, что это правда и объясняет странности твоего поведения. Еще могут быть различные побочные действия аналогичные описанному.
А насчет инлайн неосилил, так как это вообще просто бред сивой кобы.

erotic

А насчет инлайн неосилил, так как это вообще просто бред сивой кобы.
Много думал, пожалуй, да. А насчет выделения памяти вопрос, скажем так, непростой.
Например: для линковки проекта со статическими библиотеками нужно совпадение их рантаймов. Для подгрузки же DLL неважно, каким рантаймом она собиралась, т.е. у меня нормально собираются и работают проекты+DLL в любой комбинации их рантаймов.
В свете вышесказанного про то, что память, выделенная в программе, должна уничтожаться в программе, а выделенная в DLL, должна уничтожаться в DLL, и того, что собранный с одинаковыми рантаймами основного модуля и DLL проект работает, можно сделать вывод, что это правило в моем проекте не нарушается. Значит, и при любых других комбинациях типов рантаймов DLL не должна уничтожать память, выделенную в программе, и наоборот, значит, должно работать.
Верно?

vasena

Примерно, так. На каком алокаторе выделил, там у удалил.
osgViewer::Viewer::getWindows(windows); - аллокирует память внутри dll.
windows.clear - уничтожает уже внутри exe.
Аналогично и со стрингом было.

erotic

osgViewer::Viewer::getWindows(windows); - аллокирует память внутри dll.windows.clear - уничтожает уже внутри exe.
Блин, точно. Тогда получается, что это проект с OSG в принципе не должен работать. Что же теперь, reserve пользоваться перед вызовами функций dll ? :(

slonishka

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

erotic

топег не читал
Почитай, и пиши по-русски, я ни хрена не понял.

vasena

OSG (я не знаю что это такое наверно, собирается с /MD тебе нужно поставить тоже в своем проекте, тогда у вас будет одна копия рантайма на двоих и будет счастье.

erotic

соответственно, выделенную память в одной из dll/основной программе - можно только там и удалить.
По ходу, это не так.
Пару дней мучался с log4cpp, он тоже, зараза, в дебаге нормально работал, а в релизе вылетал. Обнаружил, что указатель на выделяемую в программе память передается затем в функцию из dll и там сохраняется в мапе, а деструктор статического объекта dll затем уничтожает этот указатель. Долго думал, что это косяк log4cpp. Однако заметил, что перед включением хедеров не определил макрос LOG4CPP_HAS_DLL, которые ответственен за добавление __declspec(dllimport) перед объявлениями функций. После его определения проблемы кончились...
Если функция объявлена как __declspec(dllimport а мы пытаемся слинковать ее со статической библиотекой, будет ошибка линкера. К сожалению, если такого объявления перед функцией нет, и она предполагает статическую линковку, то попытавшись слинковать проект с .lib-файлом DLL'ки, MSVC не выдаст никаких предупреждений, и, возможно, программа даже будет работать, но никто не гарантирует.

erotic

Я не знаю что такое "статически линкуемая dll".
Я нашел. Это называется библиотекой импорта! :)
Оставить комментарий
Имя или ник:
Комментарий: