По следам опыта создания DLL’ек.

Март 18th, 2011

Ну, что может быть сложного в написании DLL? Ну пишем код, помечаем функции которые экспортируются, компилируем… вуаля… готово.

Ну, это естественно на бумаге, а в жизни, как оказалось множество оврагов. Очень быстро и поверхностно (все пишется с точки зрения C/C++, но на самом деле актуально фактически для всего из чего можно собрать DLL)

— Ну начнем с простого факта. В DLLMain нельзя делать много из того, что можно делать в многих других местах.
а) Вызывать LoadLibrary, FreeLibrary
б) Работать с Registry
в) Работать с потоками и процессами. (Создавать потоки можно, но ждать их нельзя)
г) Использовать API, которые предоставляются не Kernel32.dll
д) И само собой нельзя делать все то, что использует вышеперечисленные вещи. Например нельзя использовать COM

— Увы, факт ограничений в DLL написал маленькими буквами где-то на 5 странице документации. Еще хуже то, что зачастую все будет работать нормально, до тех пор пока вам какой-нибудь заказчик не пришлет баг, которые повторяется в 30% случаев, когда луна находится в перегелии и не зная этих ограничений можно долго и счастливо все это дебажить.

— Ладно. С ограничениями разобрались. Если нам что-то надо будет сделать, мы создадим потом, который сделает эти операции или сделаем глобальную переменную объекта, в конструкторе которого сделаем все что нам надо. Правильно? Бззззз… Ответ не правильный.
а) Если мы создаем поток и не ждем его (так как ждать нельзя), мы не можем быть уверенными что DllMain уже закончится, так что мы просто создали race condition и проблемы стали еще более тяжело воспроизводимы
б) Глобальные переменные и статические члены классов инициализируются и деинициализируются внутри DllMain. Хотя это с ходу не видно, но если полезть в CRT, то это станет понятно. Таким образом, описанные ограничения касаются также конструкторов и деструкторов.

— Итого, имеем, чтобы по честному сделать какую-ту серьезную инициализацию, то нужно иметь функцию в нашей DLL которую клиент вызовет и там мы сделаем все сложные действия. Аналогично, кстати для деинициализации.

— Особенно все прикольно становится, когда мы пишем DLL которую будет впихивать в чужие процессы с помощью SetWindowHookEx или просто plugin’ы к какой-то системе. В обоих случаях, интерфейс может быть заранее определен и там нету функций инициализации и деинициализации. Соотвественно, не ясно что делать.

— Ok. Поздняя инициализация нам поможет насчет инициализации. То есть когда-то кто-то вызовет первый из интерфейсов, мы сделаем все сложные действия. А что делать с деинициализацией? В отличии поздней инициализации, ранней деинициализации не существует то? На это увы, общего ответа я не нашел и в каждом случае, нужно искать свое решение.

— Следующая особенность состоит в том, что все потоки созданные в DLL на самом деле принадлежат процессу. То есть если вдруг DLL будет выгружена из памяти (потому что вызывающий процесс сделал FreeLibrary и DLL counter стал равным 0), то внезапно адресное пространство где была DLL будет высвобождено. А поток останется __ЖИВ__. То есть он попытается выполнить свою следующую операцию, для этого попытается считать следующую команду из адресного пространства где была DLL и мгновенно закрешится. Скажите прекрасно?

— Ok. То есть нам надо остановиться все потоки, которые мы создали в DLL перед тем, как она будет выгружена и это при том, что мы еще даже точно не знаем, как нам запустить какой-то код перед выгрузкой, причем в момент, где нет ограничений. А да, и я еще молчу о том, что написать нормальный thread manager который обрабатывает достаточно большое количество разных ситуаций не вызывая dead lock’ов и race condition, задача в целом не тривиальная.

— Так… Что там у нас дальше. Ага. Вам нужно использовать COM? Само собой вы привыкли к CoInitialize(Ex) и побежали. Только вот, есть одна проблема. Если вы это делаете в DLL то вы исполняетесь в чужом потоке и у потока уже может быть инициализирован COM, причем не в том режиме (STA vs MTA) в котором вам нужно. Что делать? Создавать отдельный поток в котором выполнять все COM действия.

— Единственное пожалуй из положительного. Многое из того, что я писал относится к выгрузке DLL системой. Есть простой трюк, как удостовериться, что система никогда не выгрузит ваш DLL (исключая закрытие программы). Все что вам нужно сделать в DLL это вызывать GetModuleHandle(Ex). Это добавит 1 к DLL counter’у и можно не беспокоиться, что counter когда-либо вернется к нулевому значению.

— А еще во время выхода из процесса, система убивает все потоки, причем поток мог быть посередине модифицирования каких-нибудь данных (внутри критической секции). Еще приятней то, что он мог быть внутри функции работы с heap и таким образом heap будет в inconsistent state. И поэтому лучше не делать ничего хитрого (а по возможность вообще ничего) в DLLMain DLL_PROCESS_DETACH и в деструкторах глобальных объектов (взято отсюда: http://blogs.msdn.com/b/oldnewthing/archive/2007/05/03/2383346.aspx

Вроде все, хотя может что-то еще вспомню.

Первый пошел…

Февраль 17th, 2011

Я в декабре говорил про пристреливание мобильных операционных систем.

Так вот, Symbian R.I.P. (http://www.ibtimes.com/articles/113429/20110217/nokia-ditches-symbian-in-favour-of-microsoft-s-windows-mobile-os.htm)

Как по мне, Nokia совершила дорогое самоубийство. Они конечно молодцы, что смогли подоить Microsoft, но думаю, что ничем хорошим это для них не кончиться.

О том, что мне нравится.

Февраль 5th, 2011

Я вот подумал, что я частенько брюзжу, понимаете ли в Ruby нету скобок вокруг аргументов, а в iPhone функции не возвращают коды ошибок… Со стороны вероятнее всего выглядит, как будто старпер вылез из замшелой конуры и рассказывает о том, что мол солнце раньше было ярче и трава зеленее.

Так что, решил, коротенько написать что же мне нравится из программистского (или около программитского)

— Хотя я всего немножко поигрался с Ruby, в конечном счете мне язык понравился. Гибкий и очень быстрый для прототипирования (особенно Ruby on Rails).

— Очень нравится документация в MSDN (описание Windows API). Там все расписано до последней запятой, зачастую с примерами на нескольких языках. Плюс, когда ищешь в Google она чаще всего идет первой или второй строкой

— Нравится RFC. Хотя читать их на ночь вряд ли будет большинство психически здоровых людей. Но, Волосы дыбом становятся, если представить, что не было бы стандартизации ключевых протоколов и т.п.

— Нравится C#. В основном из-за того, что после C/C++ простые вещи на нем пишутся со скоростью пули (когда не нужно заморачиваться с memory management, поиском библиотек и другими прелестями). Хотя я понимаю, что аргумент ну абсолютно невесомый.

— Нравится виртуальные машины, потому что можно спокойно иметь с десяток разных машин с разными серверами, IDE, SDK и т.п. И машина не превращается в гигантскую свалку софта.

— Нравится куча хорошо отлаженого и достаточно вылизанного open source кода, который можно использовать (и тем самым сохранить месяцы, если не годы работы)

— Нравится, что все больше становится бесплатного (и качественного софта для разработки).

Вот такие пироги… Но трава таки раньше была зеленее 🙂

Проблема грязной микроволновки.

Январь 16th, 2011

Когда-то писал уже грязной микроволновке.

Вроде все просто, есть грязная микроволновка. Многим она неприятна, так как все хотят разогревать еду в чистой микроволновке, но каждый отказывается ее мыть, так как остальные не особо хотят присоединяться.

Самая большая проблема тут даже не в самой микроволновке, а в атмосфере, которая складывается вокруг нее. Проблема заключается в том, что такая атмосферу откровенно тяжело изменить.

Приходит новый человек, смотрит и говорит… «Блин, ну нельзя же так люди, ну давайте вместе будем мыть микроволновку. Если каждый это будет делать раз в 3 месяца, то она постоянно будет общая чистыми усилиями». В ответ все молчат. Человек вместо широковещательного сообщения начинает подходить к людям по отдельности и люди ему начинают рассказывать, почему мыть микровоклновку они не хотят — один ей редко пользуется, другой не считает это своей обязанностью, третий обижен на весь мир, что ему раньше никто не хотел помогать мыть и поэтому он теперь не собирается это делать и так далее.

В определенный момент, новый человек махает рукой и решает сам ее мыть. Моет раз… моет два… моет три. И вот тут происходит переломный момент.

Есть два менталитета
а) Как бы мне побольше выиграть (нормальный эгоизм).
б) Как бы мне поменьше проиграть (нежелание делать что-либо от чего выигрывают другие).

Если люди не окончательно застряли в менталитете б), то понемногу они начнут подтягиваться и помогать, дабы увеличить общий (и таким образом свой персональный выигрыш) в виде чистой микроволновки. Но, если таки каждому интересно заработать максимально количество очков (относительно других людей), то помогать так и не станут, так как выигрыш они получают не тратя сила.

Как мне кажется ситуацию а) можно расшевелить активностью. А вот ситуацию б) невозможно изменить изнутри, только обладаю властью в такой ситуации, можно вывести ее из клинча.

Кстати, чем очень опасна ситуация б), тем что новый человек после определенного время битвы с вязким болотом, решает, ну их всех, будут держать в чистоте свой маленький мирок. И собственно говоря, пожив в таком режиме достаточно долго, он сам переходит в разряд «поменьше проиграть».

Кстати, еще раз упомяну о блоге «Психология IT«, тема точно по тематике их сайта..