Текст
                    Illlllllllllllllllllll
профессиональный журнал
ПРОГРАММИСТ
---------№ 32001------
САМОМОДИФИ11ИРУЮШИЙСЯ код в
WINDOWS-стр.14
стр.35 - СОЗДАЕМ
ИНСТАЛЛЯТОРЫ
GNU Make:
ЭФФЕКТИВНОЕ ИСПОЛЬЗОВАНИЕ - стр.7Б
_	„ИНТЕРВЬЮ- _
стр.80 - Сертификация ПО
www.programme.ru

Профессиональный журнал ПРОГРАММИСТ __ ОТ РЕДАКЦИИ Здравствуйте, уважаемые. Как обычно, у нас с вами - сплошные праздники. Мужской день - с грехом пополам отметили, на очереди - Женский (за прекрасных дам! и все такое...); а тут еще и наш третий номер выходит! Цифра, конечно, не круглая (разве что в троичной системе; и еще - Бог ее, говорят, любит :), но по-своему вполне почетная. Были тут у отдельных - политически несознательных - граждан опасе- ния, что выйдет у нас пара номеров, и на этом все заглохнет... так вот: НЕ ДОЖДЕТЕСЬ! Мы продолжаем нести в массы разумное, доброе, вечное, объектно-ори- ентированное и более-менее отлаженное. Venseremos! Не Москва ль за нами. Однако - в головы все равно упрямо лезут мысли о будущем: «как нам обустроить Россию?», «что делать?», «камо грядеши?» - и прочая. Думали найти ответ в ваших письмах - не получилось. Нет, не подумай- те - вы у нас самые замечательные; просто все почему-то хотите разно- го. Одни желают читать только про UNIX/Linux, и иначе журнал покупать не согласны. Другие нажимают: «посложнее давайте! нечего для чайни- ков стараться!»; третьи, наоборот, объявляют наши материалы слишком сложными, и требуют исходных кодов целиком - «а то у нас не работа- ет». Кто-то говорит, что нельзя, мол, писать обо всем сразу; и давайте- ка придерживаться одной темы: Delphi, например... Посмотрите сами в разделе «Письма» - чего там только не требуют... Ребята! Давайте жить дружно! Ваши замечания и пожелания, конечно же, учтем; будет вам и белка, бу- дет и свисток. Просто - мы ведь не можем угодить всем и во всем; мы считаем, что программистский журнал должен писать обо всем понемно- гу. И о UNIX, и о Windows, и о web-программировании... и статьи у нас будут разного уровня - от чайников до профи. Не обессудьте. Еще, как вы помните, у нас открылся сайт - www.programme.ru. Хотя сайт новый (и пока еще очень сырой - кстати, спасибо всем за констру- ктивную критику :) - посетители туда ходят, и даже ежедневно оставля- ют сообщения в форуме; где уже накопилось много вопросов по про- граммированию - а ответов маловато. Не будьте жадинами - сходите на сайт! помогите коллегам. Мы бы и сами рады ответить, но времени нет - да и вопросы встречаются непростые. Заходите! Ну а сейчас - милости просим - читать подано! Журнал «Программист», №3, с пылу - с жару. Очень рекомендуем к пиву на лавочке (если, конечно, погода не подве- дет :). Нескучной вам весны!... о
ПРОГРАММИСТ Профессиональный журнал От редакции ....................................................................1 Новости ........................................................................4 Система ...................................................................... 8 Корректное завершение работы приложения в WIN32 (Андрей Нефёдов) .... 9 Казалось бы - закрыть приложение - это не проблема. Языки высокого уровня позволяют программисту не задумываться об этом Однако - корректное завершение работы - не такая тривиальная задача. Запрет запуска копии припожения под Windows (Андрей Нефёдов)...............................................12 Вы. конечно, видели ряды одинаковых пиктограмм на панели задач - чаще всего это многочисленные копии браузера. Но некоторые приложения позволяют запустить только один экземпляр себя. Здесь рассказано, как писать такие приложения Самоыодифицирующийся код в современных ОС (Крис Касперски).........................14 В эпоху MS-DOS программистами широко использовался самомодифицирующийся код. без которого не обходилась практически ни одна мало-мальски серьезная защита. Но с приходом Windows 95/МТ разработчиком пришлось отмазаться от бесконтрольного доступа н «железу» Появилось даже убеждение, что. дескать, под Windows создание самомодифицирующегося кода вообще невозможно. . Как написать «хранитель экрана» для Windows {Александр Шевелев)............................................20 Почти в любом учреждении сотрудники с детской радостью ставят на свои компьютеры «хранители экрана» - так ловно перевели переводчики Microsoft слово screensaver. У нас в конторе популярна, например, рекламная заставка пива Heineken Читайте о программировании таких штучек. Офис........................................................................................27 Бухгалтер, милый мой бухгалтер или как написать бухгалтерскую систему? (Леонид Бойцов).......28 По статистике бухгалтерскими программами занимается немалая часть программистской братии В основном это модификация популярных программ вроде 1С или Бест. Но встречаются и «самостийные» разработки. По качеству такие программы не уступают «фирменным». В статье приведены некоторые соображения на эту тему. Окна - такие разные: круглые, треугольные, звездообразные... [Николай Больсунов)...............................31 Когда я впервые увидел непрямоугольное окно - это был шок. Нан сейчас помню - это был трЗ-кодер. И вот - статья о том, как это делается. Итан - аплодисменты! На арене - функции региона окна! Создаем инсталляторы (Петр Семилетов).....................................35 Установка ПО под современными ОС нередко представляет собой сложный процесс. Но современные программисты избавлены от необходимости делать программы-установщики вручную. Для этого существуют многочисленные конструкторы инсталляторов. Сети ...................................................................................................... 41 Приемы защиты веб-лрнложешй на РНР (Илья Басалаев) ............................................42 Пользователи интернета - это по определению такие злобные хакеры, которые только и ищут момента, как бы напихать в формы ввода всякую дрянь типа PHP. JavaScript. SSI. вызовов своих жутко хакерских скриптов и тому подобных ужасных вещей. Вашему вниманию: приемы защиты. Фреймы - согласование состояния (М.Ю.Третьяков, И.Ю. Тимофеев)............................46 Фреймы - практичное средство организации web-сайтов, обладающее, однако, массой врожденных недостатков. Один из них - синхронизация состояний различных фреймов в зависимости друг от друга. ш X Z ЭЕ о. ш 1=1 о _______________________________I Адрес редакции: г. Москва, 125047 ул.Бутырский вал Д.2О тел.: 250-3801 (редакция) 250-3810 (отдел распространения) факс: 250-2121 e-mail: info@programme.ru www.programme.ru I_______________________ Главный редактор Максим Туйкин Ответственный редактор Данила Воробьёв Дизайн и верстка Андрей Соболев Служба распространения Николай Куженькин, Михаил Зайцев U I
Профессиональный журнал ПРОГРАММИСТ! Понятный JavaScript (часть 2) (Михаил Мельников) ...............................................................51 JavaScript - достаточно универсальное и богатое возможностями средство web-лрограммирования и web-дизайна. В этой статье мы продолжаем знаномить начинающих web-программистов с его возможностями. Примеры из статьи можно увидеть на номмерчесних сайтах Мульти ............................................................................... 56 Вывод звука в Windows (И.В Великанов!....................... 57 Наличие звуковой нарты и колонок делает общение с компьютером намного приятней. Я. например, постоянно слушаю за работой audioCO и mp3. А когда приходит почта - раздается «ding». В статье - обзор средств, с помощью которых реализуются такие штуки. Программная генерация звуковых сэмплов (Дмитрий Сазонов) .................................. 60 Интересующимся программированием звуковых приложений предлагается эта статья. Цифровая обработка и генерация звука - основа основ современной звуковой студии. Начнем с азов... Программирование ЗВ-графиии: Текстурирование (Андрей Аксенов] ...... 65 В этом номере мы продолжаем серию статей «Программирование 30-графики». Напомним, что в первой статье были даны самые начальные сведения на этот счет • объекты, грани, камеры: проецирование. Вторая статья была посвящены удалению невидимых частей сцены. Теперь перейдем к текстурированию - «раскрашиванию» 30-объектов 20-тенстурами. Матчасть.............................................................................................. .71 Эффективная разработка ПО с использованием технологий RATIONAL (окончание) (А.Н. Новичков)........................................................72 Те. кто принимал участие в крупных софтверных проектах - знают, что согласовать результаты труда многих разработчиков, учесть все требования заназчина. полностью протестировать программу очень непросто. Технологии компании Rational предназначены для решения именно таких проблем. В этой статье речь пойдет преимущественно о средствах тестирования. Эффективное использование GNU Make (Владимир Игнатов) . ...........76 Все UNIX-программисты хорошо знают утилиту GNU Make. Однако, как водится, не все находят время внимательно ее изучить. А зря... Утилита обладает массой уникальных возможностей, которые могут здорово облегчить разработку вашего проекта. Жизнь.......................................................................................... 79 Сертификация программного обеспечения (интервью).......................................80 Недавно первая программа, сделанная в России получила сертифинат о совместимости с Windows 2000. Мы взяли интервью у сотрудников компании, выпустившей программу... Заработок для программиста в сети (sidecut) ..................... 82 Итан - самая лучшая программа на свете написана. Что же с ней теперь делать? И. главное, как заработать на ней денег9 Возможностей масса - и у всех есть свои плюсы и минусы. В статье представлен их обзор Письма .......................................................................................... 88 ШЕВ-обзор .........................................................................................90 Объявления.........................................................................................94 Подписка ..........................................................................................96 ___________________I Учредитель: ЗАО «Издательство «Тополь» Издатель: ООО «Викфилд» Издание зарегистрировано в Министерстве Российской Федерации по делам печати, радиовещания и средств массовых коммуникаций 08.12.2000 г., регистрационное свидетельство ПИ №77-5890. i__________________________ Отпечатано в типографии АО «Молодая гвардия» Формат А4. Гарнитура Прагматика. Печать офсетная Подписано в печать 04.02.2001 Заказ 17255 Тираж 10000 зкз. ежемесячно © Перепечатка материалов без письменного разрешения редакции запрещена. Редакция не несет ответственности за содержание рекламных материалов. О
- ПРОГРАММИСТ Профессиональный журнал Microsoft против Open-Source По мнению одного из представите- лей корпорации Microsoft, про- граммное обеспечение, созданное в среде open-source, опасно для дальнейшего развития отрасли и для рынка интеллектуальной собст- венности вообще. Технология open- source названа «разрушителем ин- теллектуальной собственности». Также прозвучало заявление, что самая большая компания в сфере производства программного обес- печения должна начать серьёзно работать в этом направлениии с по- литиками. Последовали и отзывы по поводу обвинений Microsoft: Брайан Белендорф (Brian Behlendorf), основатель компании CollabNet Inc., которая помогает другим компаниям реализовывать свои open-source проекты, сооб- щил, что некоторые фирмы, кото- рые работают в этом направлении, всё же иногда сохраняют права на свои разработки. Также он заме- тил, что Microsoft, призывая к пол- ному запрещению ПО с открытым кодом, подходит к проблеме слиш- ком радикально. Вместе со всем этим, как обычно, от Microsoft звучат заявления, что корпорация совершенно не опаса- ется конкуренции Linux. ф Нано-провода Специалисты одной из лаборато- рий департамента энергетики США и Стэнфордского университета дос- тигли заметного успеха в исследо- ваниях новых возможностей пере- дачи информации. Они создали опытный образец провода, облада- ющего очень малым диаметром и сопротивлением, вследствие чего удалось достичь очень большой скорости передачи данных. Сам провод составлен из молекул ново- го вещества, синтезированного стэнфордскими учёными. По сути это вещество представляет собой цепи повторяющихся связей, соз- данных между атомами углерода и водорода, что даёт возможность для более быстрого и дальнего пе- рехода электронов. При дальней- ших испытаниях также выяснилось, что при значительном увеличении длины такого провода скорость электронов практически не меняет- ся, что снимает проблему больших расстояний. • Borland анонсирует Enterprise Studio Java Edition... Borland Enterprise Studio Java Edition - пакет средств разработки для электронной коммерции, кото- рый призван ускорить процесс соз- дания многоуровневых web-прило- жений. Эта разработка корпорации Borland представляет собой закон- ченное решение, включающее в се- бя передовые продукты для разра- ботки, моделирования и управле- ния процессом создания Java-при- ложений. Пакет Enterprise Studio со- стоит из средств разработки, при- надлежащих Borland (Borland JBuilder и AppServer Java), а также разработок компании Rational - Rational Rose (визуальное модели- рование и дизайн) и Rational Unified Process для управления процесса- ми. Плюс, в Enterprise Studio при- сутствуют средства разработки Web-приложений Dreamweaver UltraDev от Macromedia. • Спасатель в кармане Благодаря разработкам группы учёных из Bell Labs в Нью-Джерси, теперь можно определять частоту дыхания человека с помощью обычного сотового телефона, даже когда телефон просто лежит в кар- мане. Они заметили, что некото- рые волны, которые излучает сото- вый аппарат, возвращаются к не- му, отражаясь от тела человека и его движущихся органов (сердце, лёгкие). Учёным удалось обнару- жить предсказуемые изменения в частотах волн, отражённых от орга- нов. Таким образом - стало воз- можным определять, когда, напри- мер, лёгкое находится на выдохе, когда на вдохе; измерять пульс. Для этого необходимо только, что- бы аппарат некоторое время нахо- дился в неподвижном состоя- нии. Эта технология, о внедрении которой уже заявила Lucent Technologies (которой, кстати, при- надлежит Bell Labs), сделает воз- можным дистанционный монито- ринг состояния здоровья пациента. Поможет она и в экстремальных ситуациях, когда добраться до по- страдавшего затруднительно (зем- летрясения, автокатастрофы и т.д.).
Профессиональный ПРОГРАММИСТ * А победителей куда? Известный портал devicetop.com, посвящённый программированию различных «умных» устройств - та- ких как подключённые к интернету холодильники, автомобильные на- вигационные системы, телефоны и т.п., анонсировал конкурс среди программистов, работающих в этой области. Конкурс пройдёт в мае, победители будут объявлены в ию- не. Те, кого интересует сам кон- курс, могут посетить портал, и уз- нать более подробную информа- цию. Стоит рассказать о призах. Итак. Первый приз - полностью оп- лаченная поездка на двоих в Моск- ву на четыре дня, и полёт на борту МИГ-25, с подъёмом в верхние слои атмосферы, плюс 100 тысяч рублей на карманные расходы. Второй приз - поездка в Россию, и полёт на самолёте, включая специальное па- дение для имитации невесомости. Третий приз - экскурсионный тур по некоторым открытым россий- ским космическим объектам. Впро- чем, можно получить и денежные эквиваленты. Но хотел бы я посмо- треть на того, кто от ЭТОГО отка- жется! • Форматы всех стран, объединяйтесь! Generic Media, интернет-компания, специализирующаяся на разработке решений для улучшения способов доставки мультимедиа-данных, со- общила о своей новой инициативе. Компания открывает новую службу, которая называется Generic Media Publishing Service. Традиционная модель публикации мультимедиа-данных заставляет со- здавать несколько копий ресурса - для того, чтобы поддерживать все комбинации проигрывателей, стан- дартов, и скоростных возможностей работы с данными. Динамические возможности преобразования носи- теля новой службы Generic Media по- зволяют хранить ресурс в одном файле (streaming master source file), качественно предоставляя его поль- зователям, у которых разные воз- можности и настройки. Обработка (декодирование, фильтрация и т.п.) происходит в реальном времени, сам ресурс может храниться в любом формате (wav, mp3, avi и т.д.). • 5 Вышел LockBox 2 Компания-производитель программ- ного обеспечения TurboPower сооб- щила о выходе LockBox 2, кросс- платформенного пакета разработ- чика для шифрования данных. LockBox 2 поддерживает все но- вейшие технологии - такие как шифрование с открытым ключом и поддержка цифровых подписей. Также в пакет включена поддержка новейших стандартов, например, AES (Advanced Encryption Standard - стандарт, созданный для того, чтобы заменить устаревший DES). Пакет поставляется с полным ис- ходным кодом (написанным на Delphi) и расширенной документа- цией. Дополнительную информа- цию можно найти на сайте www. turbopower.com. • Новая система распознавания образов от Toshiba Новые возможности системы рас- познавания образов строятся вок- руг перенастраиваемых процессо- ров, которые делают возможной оп- тимизацию и ускорение работы по- средством конфигурирования про- цессора под конкретную задачу. Эта значит, что новые возможности будут эффективными для встроен- ных систем. Технические стороны решения про- блемы не разглашаются, однако из- вестен ряд характеристик новой си- стемы. Она предназначена для гра- жданского автомобилестроения. За счёт использования перенастраи- ваемых процессоров резко возрас- тёт скорость работы, достигая 100 кадров в секунду (пригодится для операций типа адаптивного круиз- контроля и смены ряда). Планиру- ется построить наиболее полную систему контроля за управлением автомобиля и дорожной ситуацией вокруг; например, планируется обеспечить улучшенный контроль за т.н. мёртвыми зонами и потенци- альной опасностью от машин, нахо- дящихся вокруг. •
ПРОГРАММИСТ Профессиональный журнал Adaptec будет дружить с Linux-разработчиками... Adaptec Inc. представила новую программу взаимодействия с Linux- разработчиками - Open Source Driver Development Program. Adaptec обеспечивала поддержку SCSI RAID для Linux в течение уже нескольких лет, но впервые RAID- драйверы были внедрены в ядро Linux. Одним из ключевых момен- тов программы является создание web-сайта http://linux.adaptec.com, на котором разработчики могут найти самые последние версии исходников Linux-драйверов и BSD/OS-драйверов. На сайте есть форум, где разработчики смогут обменяться информацией по разра- ботке драйверов. • 6 ...a Microsoft со студентами Корпорация Microsoft объявила о новой инициативе - MSDN Acade- mic Alliance. Основные задачи - предоставление самой последней технической информации, ресур- сов, техники и т.п., студентам и ком- пьютерным лабораториям различ- ных учебных заведений. Таким об- разом, любой университет или кол- ледж, который предоставляет воз- можность обучения компьютерным технологиям, может обеспечить практически неограниченный дос- туп к современным ресурсам. На- пример, обещано, что любой сту- дент аккредитованного учебного заведения сможет беспрепятствен- но устанавливать любые продукты корпорации на свой компьютер со- вершенно бесплатно, если это не- обходимо ему для научно-исследо- вательских целей, (российские сту- денты снисходительно улыбну- лись...). Бета-тестирование систе- мы начнётся поздней весной. • Обнаружена дыра в защите Java Новая ошибка проявляется в вер- сиях JRE, которые Sun выпустила для серверов работающих под уп- равлением Linux, Windows и Solaris. Это явление не распро- страняется на Java-компоненты, включённые в Netscape Navigator и Internet Explorer. Незащищёнными от вторжения версиями являются версии 1.1 и 1.2. Sun сообщила, что для устранения этой ошибки необходимо произвести обновле- ние до версии 1.2.2_006. Сама Sun Microsystems не считает, что ошиб- ка опасна, так как она может про- явиться только в очень редких слу- чаях, что связано с особым поряд- ком получения прав на выполне- ние команд. Java 2 не содержит этой ошибки. • Работу вирусописателям! Стало известно, что мэр голланд- ского города Снеек (Sneek), назва- ние которого стало часто мелькать в новостях, сообщавших о вирусе «Anna Kournikova», решил награ- дить автора вируса, известного под ником OnTheFly, за вклад в дело развития города, и взять его на ра- боту в отдел информационных тех- нологий мэрии. Сам мэр абсолютно не воспринимает вирус как угрозу, и очень рад тому, что вирус при- влёк такое внимание. В то же вре- мя, представители одной из антиви- русных компаний заявили, что, во- первых, вирус скорее всего был со- здан с помощью одного из много- численных наборов для их разра- ботки (вот ламер...), и на его созда- ние потребовалось всего пара ми- нут, во-вторых, такое поощрение может сыграть плохую роль. •
Профессиональный » ’ " * ” ПРОГРАММИСТ * * Новая Opera Пользователей Macintosh в Росии совсем немного; однако эта но- вость, думаю, будет интересной для российских интернетчиков, пользующихся браузером Opera. 21 февраля Opera Software выпус- тила preview-версию новой Орег'ы - Opera 5 for Мас. Всё равно прият- но, правда? Для тех, кто имеет счастье/вынуж- ден работать на Маках, сообщу не- которые характеристики браузера: Opera for Macintosh будет работать под MacOS версии не ранее 7.5, и поддерживать стандарты: 128-bit encryption, TLS 1.0, SSL 2/3, CSS1 & CSS2, XML, HTML 4.0, HTTP 1.1, WAP-WML, ECMAScript , JavaScript 1.3. • URL по-русски Компания Network Solutions (VeriSign) начала проведение ре- формы, после которой английский язык утратит статус монополиста, и станет возможной регистрация до- менных имён на любом языке. В данный момент уже можно зареги- стрировать доменное имя на рус- ском. Эту услугу предоставляет компания РосБизнесКонсалтинг, официальный дилер Network Solutions. Но, как сообщает РБК, полноценное использование «рус- ских» адресов (для размещения web-серверов и электронной почты) станет возможным только по завер- шению работ над новой инфрастру- ктурой, которые ориентировочно будут закончены через 3-4 месяца. Стоимость имен в зонах .com .org .net составит 30 долларов США (Без НДС и НсП). • Новая система называется Windows ХР (бывш. Whistler), и будет постав- ляться в двух вариантах - Windows ХР Professional и Windows ХР Ноте Edition. Обещается, что это будет абсолютно новая система, которая будет нести наиболее значимые от- личия (по части интерфейса) от пре- дыдущих версий Windows; так же, как в своё время Windows 95 (чуть ли не новая концепция...). Плюс но- вые возможности работы с интер- нетом, мультимедиа, и т.д. Ознако- миться с некоторыми нововведени- ями можно уже сейчас по адресу http://www.microsoft.com/windowsxp. Новая ОС базируется на расширен- ном ядре Windows 2000, выпуск си- стемы планируется во второй поло- вине текущего года. • Печальная новость В субботу, 24 февраля, на 84-ом го- ду жизни скончался Клод Элвуд Шеннон, один из родоначальников современной теории информации. Работы Шеннона актуальны до сих пор; он ввел многие понятия, счита- ющиеся фундаментальными в дис- кретной математике и теории ин- формации (даже такие как «бит» и «двоичный код»). В 1948 году Шеннон опубликовал свой главный труд: «Математиче- ская теория коммуникаций». Эта по- истине первопроходческая работа начинается с определения: «Основ- ная задача связи - воспроизвести (точно или приблизительно) в од- ной точке сообщение, сформиро- ванное в другой точке». Шеннон на- столько логично и основательно ус- тановил фундамент теории инфор- мации, что его терминология и классификация и по сей день оста- ются общепринятым стандартом. Именно Шеннон первым применил Булеву алгебру для функциональ- ного описания цифровых схем; ему же принадлежит «Теория тайных коммуникаций», положившая нача- ло современной криптографии. Шеннон отличался разнообразием интересов и экстравагантностью поведения; известная байка гласит, что он разъезжал по коридорам родных Лабораторий Белла на од- ноколесном велосипеде, жонглируя апельсинами :) За свою жизнь Шеннон придумал и построил массу занимательных и полезных машин; как говорил он сам, «мне просто всегда было инте- ресно, как устроены разные шту- ки...» • х
Нан обычно, эта рубрика - о железяках, драйверах, ассемблерах, опе- рационках, и тому подобных ужасах. О том, что из себя представляет всяческое «аппаратное обеспечение» - и нак с ним бороться. Низкоуровневое программирование, вопросы совместимости и перено- симости софта и харда, аппаратные интерфейсы, отладчики, прерыва- ния, системы команд - это все здесь. В этом номере - мы решили опубликовать пару статей типа «полезные мелочи» - подборки советов «как узнать, что одна копия приложения уже запущена» и «как потом это приложение культурно закрыть». Статья про написание «хранителей экрана» (як моснали нличут screen saver?!:) - ориентирована, скорее, на начинающих (там все объясняет- ся очень подробно, и с полным иллюстрирующим кодом); надеемся, те, нто просил «полных листингов» будут довольны. Для мастеров защиты и взлома - наверное, будет интересна статья о самомодифицирующемся коде под Windows; оказывается, такое воз- можно - правда, если ваш код размещается (и исполняется) в стеке. А вот всех, кто хочет побольше материалов «про UNIX» - просим изви- нить... в этот номер не получилось подобрать интересную статью вам по вкусу... Обязательно исправимся в следующий раз!
Профессиональный журнал ПРОГРАММИСТ КОРРЕКТНОЕ ЗАВЕРШЕНИЕ РАБОТЫ ПРИЛОЖЕНИЯ В WIN32 Почти каждый, кто занимался программированием под Windows, рано или поздно сталкивался с этой - как ни странно - проблемой (странно, конечно, только для тех, чей опыт работы с WinAPI невелик). Написать эту статью меня побудили мои собственные мытарства, отсутствие полностью корректного решения вообще (см. далее), а также большое количество так называемых «окончательных решений», разбросанных там и сям по различным FAQ, конференциям и т.д. Читатели посерьезнее, наверно, уже напряглись, и думают - «ну-ну; почитаем, че- го он тут насочинял... Microsoft-то уже давно написала, как закрывать приложения под Windows, и документик соответствующий в MSDN есть...» Документик, конечно, есть. Называется «HOWTO: Terminate an Application ‘Cleanly’ in Win32» (Article ID: Q178893). Однако, во-первых - этот способ трудно назвать полноценным (он не решает проблему до конца). А во-вторых - не у всех, к сожа- лению, есть доступ к MSDN. К тому же, никогда не мешает разобраться в пробле- ме поглубже. Надеюсь, для вас, дорогие читатели, не будут новостью строки из того самого до- кумента, наиболее полно отражающие суть всех дальнейших выкрутасов: «Хотя нет гарантированного «чистого» пути, чтобы закрыть приложение в Win32, есть шаги, которые Вы можете предпринять, чтобы быть уверенным, что приложение использует наилучший метод для освобождения ресурсов». Этими шагами мы и займёмся. Примечание: код тестировался на Windows 95/98, для 32 и 16-ти разряд- ных процессов. Не консольных! Это вообще отдельная песня. Итак: на какие шаги целесообразно разбить работу по закрытию приложения? В MSDN описано три этапа: 1. Отослать WM_CLOSE всем top-level окнам, принадлежащим процессу; 2. Подождать; при этом дать приложению достаточное время - чтобы, например, пользователь успел закрыть все модальные диалоги, которые программа выве- ла при закрытии; 3. Если приложение не закрылось, уничтожить его с помощью TerminateProcess. Ну и какие проблемы, спросит читатель? Дело в том, что вызова TerminateProcess надо избегать всеми способами. После такого «аварийного» завершения процесса в системе остается масса «мусора» - неосвобожденные ресурсы, индикаторы на панели задач, неочищенные области экрана и т.д. Что же можно сделать еще? Ну, например - почему бы не пробежаться по потокам процесса, и не отослать каждому WM_QUIT? Ведь как только все потоки процесса закрыты (закрыт последний), завершается и процесс. В штатных ситуациях сооб- щение WM_QUIT генерируется, когда поток вызывает PostQuitMessage; эта про- цедура помещает WM_QUIT в очередь сообщений потока, и не ждёт его обработ- ки - просто указывает системе, что поток надо будет закрыть. Т.е. принудительная рассылка WMQUIT фактически убивает поток «изнутри»; мы выполняем те же действия, что и PostQuitMessage. Вот соответствующая процедура: procedure KillPidTtireads(PID:dujord); par {закрываем потоки, относящиеся к данному процессу} ProcHandle : THandle; aThreadEntry : ThreadENtry32; l Андрей Нефёдов andreynefedov@mtu-net.ru Хотя нет гарантированного «чистого» пути, чтобы закрыть приложение в Win32, есть шаги, которые Вы можете предпринять, чтобы быть уверенным, что приложение использует наилучший метод для освобождения ресурсов. / Microsoft /
ПРОГРАММИСТ Профессиональный журнал Не надо думать, что «мусор» в системе остается только в « чрезвычайных обстоител ьствах », т.е. после вызова TerminateProcess. Такое может произойти, даже если приложение послушно закрылось по WM.CLOSE. begin {Делаем "снимок" состояния системы) ProcHandle := CreateToolHelp32Snapshot(TH32CS_SNflPTHRE0D, 0); if ProcHandle = -1 then Exit; afhreadEntry.dwSize := Size0f(ThreadENtry32); if Thread32First(ProcHandle,aThreadEntry) then begin {если хозяин потока - наш процесс) if aThreadEntry.th320iunerProcesslD=pid then {то отсылаем UJM_QUIT) PostThreadMessage(aThreadEntry.th32ThreadlD,lDM_QUIT,0,0); tuhile Thread32next(ProcHandle,aThreadEntrg) do begin {аналогично) if athreadentrg.th320u>nerProcesslD=pid then PostThreadMessage(aThreadEntry.th32ThreadlD,UJM_QUIT,0,B); end; end; CloseHandle(ProcHandle); end; Таким вот образом (в простых случаях) можно эмулировать нормальное закрытие приложения; т.е. прикрыть примерно 99% приложений, окна которых не отвечают на WM_CLOSE. А что же оставшийся 1%? Это «упрямые» приложения - например, IE. Ни закрытие окон, ни завершение потоков его не берет... Далее - не надо думать, что «мусор» в системе остается только в «чрезвычайных обстоятельствах», т.е. после вызова TerminateProcess. Такое может произойти, даже если приложение послушно закрылось по WM_CLOSE. Например, программа Netscape mail notification utility «после смерти» оставляет иконку на панели задач. В случае с панелью приложения (application bar) - после закрытия самого приложения может остаться занятой некоторая область экрана... Почему эти моменты не учтены в MSDN-овском примере? Очень просто - пример, очевидно, дан для программ, «полностью отвечающих тре- бованиям Microsoft» - т.е. обязанных обрабатывать всевозможные события, учиты- вать нюансы работы, правильно вести себя в предусмотренных аварийных ситуа- циях, и т.д. Где вы видели такую программу, кроме как у Microsoft (и то, кстати, с натяжкой)?... Итак - вот код, отвечающий за «принудительное» снятие панели приложения с учё- та (что, естественно, освобождает занятую область экрана). Это действие совме- щается с закрытием top-level окон, т.к. практически не влияет на выполнение ос- новной процедуры их закрытия. procedure KillByTopLUindoLus(pid:diuord); begin {закрываем все top-leuel окна, параллельно пытаясь снять панель для каждого окна) EnumWindouis(@enumiuindouJscallback,pid); end; function EnumllJindoiDsCallback(iDindouj:hiund;pid:diuord):bool;stdcafl; маг {вспомогательная callback-функция для EnumlUindoms) u>inpid:dtuord; appbar:appbardata;{CTpyKTypa, отвечающая за панель) begin appbar.cbSize:=sizeof(appbar); GetUJindoii)ThreadProcessld(uJindoLU,@uiinpid); {получаем процесс (PID) для окна) if u>inpid=pid then {если окно принадлежит нашему процессу)
Профессиональный журнал ПРОГРАММИСТ ‘ ‘ begin appbar.hLUnd:=tuindoiu; {указываем, что панель принадлежит закрываемому окну) shappbarmessage(abm_remoue,appbar); {пытаемся снять панель с учёта} PostMessage(windom,WM_CLOSE,0,0); {и отсылаем IDM_CLOSE) end; result:=true;{nepe6npaeM окна дальше...} end; Отдельно стоит вынести «снятие с учёта» индикатора на панели задач, т.к. во-пер- вых - этот момент можно проигнорировать, а во-вторых - он существенно замед- ляет работу. Замедляет, потому что - по всей видимости - нет никакого способа узнать идентификатор значка (кроме как перебором). Аналогично предыдущему: procedure KillNotify(pid:dLuord); begin {убираем знчок} EnumtUindoius(@enumiuindoiuscallbacknotify,pid); end; function EnumUJindoujsCallbackl4otify(iuindoiu:hLund;pid:dujord):bool;stdcall; uar {вспомогательная callback-функция для Enumldindoms} цлпр<Р:РшогР;{Вспомогательная переменная} NID:Notifylcondata;{CTpyKTypa, отвечающая за иконку} cntr:integer;{C4eT4HK} begin result:=true; NID.cbSize:=sizeof(nid); GetWindoujThreadProcessld(windouj,@ujinpid); {получаем процесс (PID) для окна} if iuinpid=pid then {если окно принадлежит нашему процессу} begin result:=false; nid.LUnd:=iuindouj; {Заполняем поле, отвечающее за окно-владельца} {перебор (до описателя окна, и котя можно покрыть весь диапазон, но...)} for cntr:=1 to luindoui do begin NID.ulD:=cntr;{ID! Мы его не знаем, и поэтому перебор) if ShelI_Notifylcon(NIM_Delete,@NID) then break; {удаляем иконку} end; end; end; Таким образом, наш план таков: 1. Еще до закрытия окон - уничтожить иконку на панели задач (если она есть, ко- нечно). 2. Отослать WM_CLOSE всем top-level окнам, принадлежащим процессу; парал- лельно - на всякий случай - снимая (возможно, несуществующую) панель при- ложения с учёта. 3. Подождав нужное время - отослать (если необходимо) WM_QUIT всем потокам процесса. 4. Еще подождать; и, если приложение все-таки не закрылось - уничтожить его с помощью TerminateProcess. Конечно, приведенный код не претендует на совершенство; я не сомневаюсь, что многие найдут недочёты; кое-где не хватает проверок, и т.п. Если у вас есть действительно дельные замечания, предложения по усовершенст- вованию - пишите мне (andreynefedov@mtu-net.ru)! л 11 CJ
ПРОГРАММИСТ Профессиональный журнал 12 Андрей Нефёдов andreynetedov@mtU4iet/u ЗАПРЕТ ЗАПУСКА КОПИИ ПРИЛОЖЕНИЯ ПОД WINDOWS Эта проблема уже успела навязнуть в зубах. Однако, не каждый программист назовёт с ходу больше одного-двух вариантов ее решения. Думаю, читателю будет интересно узнать, какими ещё способами можно определить на- личие запущенной копии программы - и какие из них подходят именно ему. Способ 1 Ну конечно, старый добрый FindWindow! Это, без сомнения, первый способ, который приходит в голову. Действительно - почему бы просто не найти окно программы по его типу (заголовку); и, в случае обнаружения, прекратить ра- боту. If FmdWindoiBCTMyClassName’.’My ujindoiu’)<>B then ... Минусов у этого решения предостаточно. Во-первых, есть вероятность того, что другое приложение зарегистрирует окно с тем же именем класса. Конеч- но, можно (как и показано в примере) вызывать FindWindow с указанием за- головка; но для окон, имеющих динамически меняющиеся заголовки, такой способ не годится; да и элегантности решению не прибавляет. Во-вторых - если две копии приложения запускаются с достаточно малым ин- тервалом между запусками, эта проверка может вообще не сработать! Способ 2 В этом способе используется объект синхронизации Mutex. При запуске про- граммы мы пытаемся создать его, используя уникальное имя - и, в случае, если объект уже существует, получаем ERROR_ALREADY_EXISTS. иаг MH:thandle; key:array [0..10] of char; MH:=CreateMuteK(NIL,TRUE,Key); {пытаемся создать) if MH<>0 then if GetLastError=ERROR_RLRERDV_EKISTS then ... (есть копия) Наличие запущенной копии программы можно Следующие три способа не блещут оригинальностью, и работают по такой же схеме, однако их стоит привести. Ведь в каждой программе свои нюансы; и, возможно, некоторые из этих способов не подойдут, а другие как раз приго- дятся. Способ 3 Аналогично используется объект синхронизации Semaphore. определить следующими средствами: 1. FindWindow 2. Mutex 3. Semaphore иаг SHandle:thandle; SHandle:=CreateSemaphore(0,0,1,’mysem’); {пытаемся создать) if SHandle<>0 then if GetLastError= then ERROR_RLRERDV_ERISTS then ... {есть копия) 4. Atom 5. CreateFileMapping 6. Registerwindow Message Способ 4 Здесь, в том же ключе, используется Atom.
Профессиональный журнал ПРОГРАММИСТ L2 Uar Fatom:Tatom; if GlobalFindfltom(‘PROGRRM_RUNNING’) = 0 {если нет атома} then ffltom := Globalfiddfltom(‘PROGRRM_RUNNING’) {то добавляем} else begin ... {есть копия} Способ 5 Ну и - чтобы домучить тему до конца - файл, проецируемый в память. жIB uar FM:Thandle; {пытаемся создать} FM:=CreateFileMapping($ffffffff,nil,PRGE_RERDONLY,0,52,’UniqueName’); if GetLastError = ERROR_RLRERDY_EHISTS then ... {есть копия} ь Способ 6 Если ни один из приведённых выше способов вас не устраивает — можно по- пробовать вот такой - отослать всем top-level окнам самостийное сообщение. Как это сделать? При запуске программы регистрируем сообщение с помощью функции RegisterWindowMessage. Эта функция гарантирует, что сообщение будет уникальным. Если потом вызвать RegisterWindowMessage (из этого же или другого приложения) с тем же параметром, она возвратит то же значение. нживви иаг FM_FINDMYRPP: Integer; FM_FindMgfipp:=RegisterlDindoiuMessage(‘FindMyRppMessage’); Остается лишь грамотно обработать сообщение. Тут можно просто воспользоваться возвращаемым значением, или отослать ответное сообщение. Можно ограничить количество копий программы в системе, можно тихо следить за пользователем (не знаю, правда, зачем это нужно...). * procedure Tforml.UJndProc(uar М: TMessage); begin if m.Msg=FM_FINDMYRPP then begin {обработка} end; INHERITED IDndProc(M); end; r В принципе, это далеко не все. Если есть желание - можно реализовать еще несколько алгоритмов: на основе обработки данных о состоянии системы, процессов, перебора окна, работы с shared memory... Но думаю - до таких извращений дело не дойдет. • Это далеко не все. Если есть желание - можно реализовать еще несколько алгоритмов: на основе обработки данных о состоянии системы, процессов, перебора окна, работы с shared memory... 13 s
ПРОГРАММИСТ Профессиональный журнал Крис Касперски kpnc@aport.ru САМОМОДИФИЦИРУЮЩИИСЯ код В СОВРЕМЕННЫХ ОС Лет десять-двадцать тому назад, в эпоху рассвета MS-DOS, программистами широко использовался самомоди- фицирующийся код, без которого не обходилась практически ни одна мало-мальски серьезная защита. Да и не только защита - он встречался в компиляторах, компилирующих код в память, распаковщиках исполняемых фай- лов, полиморфных генераторах и т.д. Но в середине 90-х началась массовая миграция пользователей с MS-DOS на Windows 95/NT, и разработчикам пришлось отказаться от бесконтрольного доступа к «железу», памяти, компонентам операционной системы и свя- занных с этим хитроумных трюков. В частности, стала невозможна непосредственная модификация исполняемо- го кода приложений, поскольку Windows защищает его от изменений. Появилось даже убеждение, что, дескать, под Windows создание самомодифицирующегося кода вообще невозможно (по крайней мере, без использования VxD и недокументированных возможностей ОС). 14 Есть по крайней мере два легальных способа изменения кода приложений, вполне доступных гостевому пользователю и нормально работающих под Windows 95/98/Me/NT/2000. На самом деле - есть по крайней мере два легальных способа изменения кода приложений, вполне доступных гостево- му пользователю и нормально работаю- щих под Windows 95/98/Me/NT/2000. Во-первых, kernel32.dll экспортирует функцию WriteProcessMemory, прямо предназначенную для модификации па- мяти процесса. Во-вторых, практически все ОС (включая Windows и Linux) разрешают выполнение и модификацию кода, размещенного в стеке. В принципе, можно создать самомодифи- цирующийся код исключительно средст- вами языков высокого уровня - таких как С, C++, Паскаль - без применения ас- семблера. Дальше будем говорить о компиляторе Microsoft Visual C++ и 32-разрядном ис- полняемом коде (под Win 3.x приведен- ные примеры работать не будут). Архитектура памяти Windows Создание самомодифицирующегося ко- да требует знания некоторых тонкостей архитектуры Windows, не освещенных в документации, но одинаково реализо- ванных на всех Windows-платформах, и активно используемых компилятором Visual C++ от Microsoft. Для адресации 4 ГБ виртуальной памяти, выделенной в распоряжение процесса, Windows использует два селектора; один загружается в сегментный регистр CS, а другой - в регистры DS, ES и SS. Оба се- лектора ссылаются на один и тот же ба- зовый адрес памяти (равный нулю), и имеют лимит в 4 ГБ. (Замечание: кроме перечисленных, Windows использует еще и регистр FS, в который загружает селе- ктор сегмента, содержащего информаци- онный блок потока). Фактически существует всего один сег- мент, вмещающий в себя и код, и дан- ные, и стек процесса. Благодаря этому передача управления коду, расположен- ному в стеке, осуществляется близким (near) вызовом или переходом, и для до- ступа к содержимому стека использова- ние префикса SS совершенно необяза- тельно. Несмотря на то, что значение ре- гистра CS не равно значению регистров DS, ES и SS, команды MOV dest,CS:[src]; MOV dest,DS:[src] и MOV dest,SS:[src] в действительности обращаются к одной и той же ячейке памяти. Отличия между регионами кода, стека и данных заключаются в атрибутах при- надлежащих им страниц: страницы кода допускают чтение и исполнение, страни- цы данных - чтение и запись, а стека - чтение, запись и исполнение одновре- менно. Помимо этого, каждая страница имеет специальный флаг, определяющий уро- вень привилегий, необходимых для дос- тупа к этой странице. Некоторые страни- цы - например, те, что принадлежат ОС, требуют наличия прав супервизора, кото- рыми обладает только код нулевого коль- ца. Прикладные программы, исполняю- щиеся в кольце 3, таких прав не имеют, и при попытке обращения к защищенной странице вызывают исключение. Манипулировать атрибутами страниц, равно как и ассоциировать страницы с линейными адресами, может только опе- рационная система (или код, исполняю- щийся в нулевом кольце защиты). Прав- да, в защите Win’95/98 есть дыры, позво- ляющие коду повысить свои привилегии до супервизора; но в Windows NT/2000 это уже не проходит. и
Профессиональный журнал Замечание: среди начинающих про- граммистов ходит совершенно нелепая байка о том, что, дескать, если обратить- ся к коду программы командой, предва- ренной префиксом DS, Windows якобы беспрепятственно позволит его изме- нить. На самом деле - это ерунда; обра- титься-™ она позволит, а вот изменить - нет, каким бы способом ни происходило обращение, т.к., защита работает на уровне физических страниц, а не логиче- ских адресов. Использование WriteProcessMemory Если требуется изменить некоторое ко- личество байт своего (или чужого) про- цесса, самый простой способ сделать это - вызвать функцию WriteProcess Memory. Она позволяет модифициро- вать страницы памяти с неустановлен- ным флагом супервизора (т.е. страницы, доступные из кольца 3, где выполняются прикладные приложения). Бесполезно пытаться изменить с ее помощью крити- ческие структуры данных ОС (например, page directory или page table) - они дос- тупны лишь из нулевого кольца. Поэтому эта функция не представляет никакой уг- розы для безопасности системы, и ус- пешно вызывается независимо от уров- ня привилегий пользователя (Write ProcessMemory НЕ требует прав отладки приложений). Процесс, в память которого происходит запись, должен быть предварительно от- крыт функцией OpenProcess с атрибута- ми доступа PROCESS_VM_OPERATION и PROCESS_VM_WRITE. Вот простейший пример использования функции WriteProcessMemory для созда- ния самомодифицирующегося кода; инст- рукция бесконечного цикла JMP short $-2 заменяется на условный переход JZ $-2, который продолжает нормальное выпол- нение программы. int LUrit eMefuoid *addr, int шЬ) { HANDLE h=OpenProcess( PROCESS_UM_OPERRTION|PROCESS_UM_1DRITE, true,GetCurrentProcessld()); return IDriteProcessMemory(h,addr,&wb,1, NULL); } int main(int argc, char* argull) { _asm { push 0k74; JMP -> > JZ push offset Here call UJriteMe add esp,8 Here: JMP short here ) printf("#JMP SHORT $-2 mas changed to JZ $-2\n"); return 0; ) Еще лучше, если вызов WriteMe располо- жен подальше от изменяемого кода (напр. в отдельном потоке), а модифици- руемый код вполне естественен сам по себе, и внешне не вызывает никаких по- дозрений - в этом случае хакер, дизас- семблировав программу, будет долго блу- ждать в той ветке кода, которая при вы- полнении программы вообще не получит управления. Поскольку Windows для экономии памяти разделяет код между процессами, возни- кает вопрос: а что произойдет, если запу- стить вторую копию самомодифицирую- щейся программы? Создаст ли ОС новые страницы - или отошлет приложение к уже модифицируемому коду? В документации по Win’NT и 2000 сказа- но, что они поддерживают копирование при записи (copy on write), т.е. автомати- чески дублируют страницы кода при по- пытке их модификации. Напротив, Win95/98 не поддерживают такую воз- можность; однако, сама функция Write ProcessMemory создает копии всех моди- фицируемых страниц, распределенных между процессами. Поэтому самомоди- фицирующийся код одинаково хорошо работает во всех вариантах Windows. Но нужно учесть, что копии приложения, мо- дифицируемые любым иным путем (на- пример, командой mov кольца 0), под Win 95/98 будут разделять одни и те же стра- ницы кода, со всеми вытекающими отсю- да последствиями. Теперь об ограничениях. Во-первых, ис- пользовать WriteProcessMemory разумно только в компиляторах, компилирующих в память, или распаковщиках исполняемых файлов; а в защитах - несколько наивно. Мало-мальски опытный взломщик сразу обнаружит подвох, заметив эту функцию в таблице импорта. Затем он отловит вы- зов WriteProcessMemory, и будет контро- лировать все операции записи в память... Другое ограничение WriteProcessMemory - невозможность создания новых страниц; ей доступны лишь уже существующие страницы. А если требуется выделить не- которое количество памяти - например, ПРОГРАММИСТ lj_ Самый простой способ динамически изменить код - вызвать функцию WriteProcess Memory. Она позволяет модифицировать страницы памяти с неустановленным флагом супервизора. 15
ПРОГРАММИСТ Профессиональный журнал 16 Выполнение кода в стеке разрешено, поскольку исполняемый стек необходим многим программам (в том числе и самой ОС) для выполнения некоторых системных функций. для кода, динамически генерируемого «на лету»? Ведь в динамической памяти выполнение кода запрещено... Вот тут и пригодится Выполнение кода в стеке Выполнение кода в стеке разрешено, по- скольку исполняемый стек необходим многим программам (в том числе и са- мой ОС) для выполнения некоторых сис- темных функций; к тому же, это упроща- ет генерацию кода компиляторами и компилирующими интерпретаторами. Однако вместе с тем увеличивается и потенциальная угроза атаки - если вы- полнение кода в стеке разрешено, и ошибки реализации при определенных обстоятельствах приводят к передаче управления на данные, введенные поль- зователем, злоумышленник получает возможность передать и выполнить на удаленной машине свой собственный зловредный код. Для операционных сис- тем Solaris и Linux существуют «заплат- ки», установка которых приводит к за- прету исполнения кода в стеке; но они делают невозможной работу множества программ, и большинство пользовате- лей предпочитают ими не пользоваться. Поэтому использование стека для вы- полнения самомодифицирующегося ко- да вполне законно, и более-менее сис- темно независимо. Такое решение уст- раняет оба недостатка функции Write ProcessMemory: Во-первых, выявить и отследить коман- ды, модифицирующие заранее неиз- вестную ячейку памяти, чрезвычайно трудно; взломщику придется провести кропотливый анализ кода защиты без надежды на скорый успех. Во-вторых, приложение в любой момент может выделить столько стековой памя- ти, сколько ему заблагорассудится, а за- тем, при исчезновении потребности - ее освободить. По умолчанию система ре- зервирует 1 МБ стекового пространства; если этого недостаточно, нужное количе- ство можно указать при компоновке про- граммы. Однако, программирование кода, выпол- няющегося в стеке, имеет ряд специфи- ческих особенностей, о которых и будет рассказано ниже. «Подводные камни» перемещаемого кода При разработке кода, выполняющегося в стеке, надо учесть, что в Windows 9х, NT и 2000 стек размещается по-разному; поэтому код должен быть безразличен к адресу, по которому он будет загружен. Такой код называют перемещаемым, и в его создании нет ничего сложного; до- статочно следовать нескольким простым соглашениям - и все. Замечательно, что у микропроцессоров серии Intel х86 все короткие переходы (short jump) и близкие вызовы (near call) относительны, т.е. содержат не линей- ный целевой адрес, а разницу целевого адреса и адреса следующей выполняе- мой инструкции. Это значительно упро- щает создание перемещаемого кода, но вместе с тем - накладывает некоторые ограничения. Что произойдет, если следующую функ- цию void Demo() { printf("Demo\n");} ско- пировать в стек и передать ей управле- ние? Поскольку, инструкция call, вызы- вающая функцию pritnf, «переехала» на новое место, разница адресов вызывае- мой функции и следующей за call инст- рукции изменится, и управление получит отнюдь не printf (а, скорее всего, случай- ный «мусор», который вызовет аварий- ное закрытие приложения). Такое ограничение можно обойти, ис- пользуя регистровую адресацию. Пере- мещаемый вызов функции printf может выглядеть, например, так: lea eax, printf call eax Однако, такой подход требует знания ас- семблера, поддержки компилятором ас- семблерных вставок, и не очень-то нра- вится прикладным «высокоуровневым» программистам. Последним придется передавать стеко- вому коду указатели на вызываемые функции как аргументы. Это несколько неудобно; но более короткого пути, види- мо, нет. Вот простой пример копирования и вы- полнения функций в стеке: void Demotint (*_printf) (const char ) { _printf("Hello, IDord!\n"); return; } int maintint argc, char* argot]) { char bufftlDDB]; int (*_printf) (const char *,...); int (*_main) (int, char **); uoid (*_Demo) (int (*) (const char *,...)); _printf=printf;
Профессиональный журнал int func_len = (unsigned int) _main - (unsigned int) _Demo; for (int a=0;a<func_ien;a++) butf[a]= ((char *) _Demo)[a); JDemo = (uoid (*) (int {*) (const char * ,...))) &buff[0]; _Demo(_printf); return 0; } Особенности оптимизирующих компиляторов Если беретесь разрабатывать код, вы- полняемый в стеке - основательно изу- чите документацию по вашему компиля- тору. В большинстве случаев - код функ- ции, скопированный в стек, с первой по- пытки запустить не получится (особенно если включена оптимизация кода). Вообще, на чистом языке высокого уровня (С, Паскаль...), скопировать код функции в стек (или куда-то еще) прин- ципиально невозможно - поскольку стандарты языка не оговаривают, как именно должна осуществляться компи- ляция. Программист может получить указатель на функцию, но стандарт не оговарива- ет, как ее интерпретировать - с точки зрения программиста это «магическое число», понятное только компилятору. К счастью, логика кодогенерации боль- шинства компиляторов более или менее одинакова, и это позволяет делать неко- торые предположения об организации откомпилированного кода. В частности, приведенная выше про- грамма молчаливо полагает, что указа- тель на функцию совпадает с точкой вхо- да в эту функцию, а все тело функции расположено непосредственно за точкой входа. Именно такой «логичный» код и генерирует подавляющее большинство компиляторов. Большинство, но не все! Тот же Microsoft Visual C++ в режиме от- ладки вместо функций вставляет «пере- ходники», а сами функции размещает совсем в другом месте. В результате, в стек копируется содержимое «переход- ника», но не само тело функции! Заста- вить Microsoft Visual C++ генерировать «правильный» код можно сбросом флажка «Link incrementally». У других компиляторов название этой опции мо- жет отличаться или вообще отсутство- вать... Еще одна проблема - как достоверно оп- ределить длину тела функции? Язык С не дает никакой возможности узнать значение этой величины; оператор sizeof вернет только размер указателя на функцию. Правда, компиляторы как пра- вило располагают функции в памяти по порядку их объявления в исходной про- грамме, и длина тела функции вычисля- ется как разность указателей на данную и следующую за ней функции. Поскольку Windows-компиляторы пред- ставляют указатели 32-разрядными це- лыми числами, их можно безболезненно преобразовывать в тип unsigned int, и вы- полнять над ними различные математи- ческие операции. К сожалению, оптими- зирующий компилятор может изменить порядок функций или «развернуть» не- которые из них, подставив содержимое функции на место вызова. Так что соот- ветствующие опции оптимизации (если они есть) придется отключить. Другое коварство оптимизирующих ком- пиляторов заключается в выкидывании ими всех не используемых (с их точки зрения) переменных. Например, в приве- денном примере в буфер buff что-то пи- шется, но оттуда ничего не читается! А передачу управления на буфер большин- ство компиляторов (в том числе и Microsoft Visual C++) распознать не в си- лах... Если с этим возникнут проблемы, попробуйте сбросить флажок «Global optimization», а лучше - отключить опти- мизацию вообще. Откомпилированная программа по- прежнему не работает? Возможно, ком- пилятор вставляет в конец каждой функ- ции проверку состояния стека. Именно так ведет себя Microsoft Visual C++, по- мещая в отладочные проекты вызов функции __chkesp (не ищите ее описа- ния в документации - его там нет). К со- жалению, никакого документированного способа это запретить нет; но в финаль- ных (release) проектах Visual C++ не кон- тролирует состояние стека при выходе из функции, и все работает нормально. Самомодифицирующийся код как средство защиты приложений И вот - после стольких мытарств и ухищ- рений - злополучный пример запущен, и победно выводит на экран «Hello, World!». Резонный вопрос - и зачем, соб- ственно, все это нужно? Какая выгода оттого, что функция таки исполняется в стеке? Основное преимущество - в том, что код функции, исполняющийся в стеке, можно прямо «на лету» изменять (например, расшифровывать). ь ПРОГРАММИСТ Резонный вопрос - и зачем, собственно, все это нужно? Какая выгода оттого, что функция таки исполняется в стеке? 17
Профессиональный журнал ПРОГРАММИСТ Шифрованный код чрезвычайно затрудняет дизассемблирование и усиливает стойкость защиты; хотя одна лишь шифровка кода - не очень-то серьезное препятствие для взломщика, вооруженного отладчиком или продвинутым дизассемблером, наподобие IDA Pro. 18 Шифрованный код чрезвычайно затруд- няет дизассемблирование и усиливает стойкость защиты; хотя одна лишь шиф- ровка кода - не очень-то серьезное пре- пятствие для взломщика, вооруженного отладчиком или продвинутым дизас- семблером, наподобие IDA Pro. Но анти- отладочные приемы - тема отдельного разговора, выходящего за рамки настоя- щей статьи. Простейший алгоритм шифрования - по- следовательная обработка каждого бай- та операцией «исключающее ИЛИ» (XOR). Повторное применение XOR к шифрованному тексту позволяет вновь получить исходный текст. Следующий пример читает содержимое функции Demo, зашифровывает его и за- писывает полученный результат в файл. uoid _build() { FILE *f; char buff[1000l; uoid (*_Demo) (int (*) (const char *,...)); uoid (*_Build) (); _0emo=Demo; _Build=_build; int func_len = (unsigned int) _Build - (unsigned int) _Demo; f=fopen( "Demo32.bin “,"wb”); for (int a=0;a<func_len;a++) fputc(((int) buff(a)) ' 0K77,f); fclose(f); ) После этого - из исходного текста про- граммы функцию Demo можно удалить, разместив ее зашифрованный текст (из файла), например, в строковой перемен- ной. В нужный момент оно будет рас- шифровано, скопировано в локальный буфер и вызвано для исполнения. Теперь - вспомним «приветствие» функ- ции printf(); задумайтесь, где размещена строка «Hello, World!». Разумеется, в сег- менте кода ей не место (хотя некоторые компиляторы фирмы Borland помещают ее именно туда). Выходит - в сегменте данных? Но если так, то одного лишь ко- пирования тела функции явно недоста- точно - придется копировать и саму строковую константу... Хотя существует и другой способ - создать локальный бу- фер и инициализировать его по ходу вы- полнения программы; например, так: ...buf[32]; buff[O]='H'; buff[1]=’e’; buff[2]=’I’; buff[3]=’l’;buff[4]=’o',... - не самый корот- кий, но, ввиду своей простоты, широко распространенный путь. В следующем примере - даже при нали- чии исходных текстов алгоритм работы функции Demo остается загадкой! Этим можно воспользоваться для сокрытия некоторой критической информации - например, процедуры генерации ключа или проверки серийного номера. int main(int argc, char* argu[]) { char buffi 1000); int (*_printf) (const char *,...); uoid (*_Demo) (int (*) (const char *,...)); char со0е[]="\к22\кРС\к9В... здесь - зашифрованный код функции...\кВ4"; printf=printf; int code_size=strlen(&code(0]); strcpy(&buff(B],&code[B]); for (int a=0;a<code_size;a++) buff[a] = buff(a) л Вк77; _Demo = (uoid (*) (int (*) (const char *,...))) &buff[0]; _Demo(_printf); return 0; } Проверку серийного номера желательно организовать так, чтобы даже после рас- шифровки кода ее алгоритм оставался головоломкой для хакера. Например, можно динамически изменять инструкцию, отвечающую за преобразо- вание бит; вместе с ней, соответственно, изменится и результат вычислений. Поскольку при создании самомодифици- рующегося кода требуется точно знать, в какой ячейке памяти какой байт распо- ложен, придется прибегнуть к ассемблеру. С этим связана одна проблема - чтобы модифицировать какой-то байт, инструк- ции MOV требуется передать его абсо- лютный линейный адрес; а он заранее неизвестен. Однако его можно узнать в ходе выполнения программы. Наиболее популярна конструкция CALL $+5 POP reg mou [reg+relatiue_addres], кк т.е. вызов са//-командой следующей ин- струкции, которая извлечет из стека ад- рес возврата - являющийся абсолютным
Профессиональный журнал ПРОГРАММИСТ !_?_ адресом этой самой команды. В даль- нейшем этот адрес используется в каче- стве базы для адресации кода стековой функции. Вот пример процедуры генерации серий- ного номера, предназначенной для вы- полнения в стеке: MyFunc: ; сояранение регистра esi в стеке push esi ; ESI = &username[0] mou esi, [esp+8] ; сояранение проник регистров в стеке push еЬк push ecu push edK ; обнуление рабочий регистров ног еак, еак йог edK, edK ; цикл обработки строки байт-за-байтом Repeatstring: ; читаем очередной байт в RL lodsb ; достигнут конец строки? test al, al jz short Enit ; Значение счетчика для обработки ; одного байта строки. ; Значение счетчика следует выбирать ; так, чтобы, с одной стороны, все биты ; полностью перемешались, а с другой - ; была обеспечена четность (нечетность) ; преобразований операции ког mou еск, 21 h RepeatChar: ; циклически меняется с ког на adc ког edK, еак гог еак, 3 rol edK, 5 call $+5 ; еЬк = eip pop еЬк ; Эта команда обеспечивает цикл, ког byte ptr [еЬк-RDh], 26h ; изменение инструкции ког на adc loop RepeatChar jmp short Repeatstring EKit: ; результат работы (ser.num) в еак Kchg еак, edK ; восстановление регистров pop edK pop еск pop еЬк pop esi ; возврат из функции retn Приведенный алгоритм интересен тем, что повторный вызов функции с передачей тех же самых аргументов может возвращать либо тот же самый, либо совершенно дру- гой результат - если длина имени пользо- вателя нечетна, то при выходе из функции XOR меняется на ADC с очевидными пос- ледствиями. Если же длина имени четна - ничего подобного не происходит. Разумеется, стойкость предложенной за- щиты относительно невелика. Однако она может быть значительно усилена. На то существует масса хитрых приемов программирования - динамическая асинхронная расшифровка, подстановка результатов сравнения вместо коэффи- циентов в различных вычислениях, по- мещение критической части кода непо- средственно в ключ и т.д. Но я хочу не предложить готовую к упот- реблению защиту (зачем? чтобы хакерам ее было бы легче изучать?), а доказать (и показать!) принципиальную возмож- ность создания самомодифицирующего- ся кода под управлением Windows 95/NT/2000. Как именно воспользоваться этой возможностью - решать читателю... Пара слов в заключение Многие считают использование самомо- дифицирующегося кода «дурным» при- мером программирования, обвиняя его в отсутствии переносимости, плохой сов- местимости с различными операционны- ми системами, необходимости использо- вания ассемблера и т.д. С появлением Windows 95/NT этот спи- сок пополнился еще одним умозаключе- нием: дескать «самомодифицирующий- ся код - только для MS-DOS; в нормаль- ных же ОС он невозможен (и поделом!)». Как показано выше, все это, мягко выра- жаясь, неверно. Другой вопрос - так ли необходим самомодифицирующийся код? Низкая эффективность существую- щих защит (обычно программы ломают- ся быстрее, чем успевают дойти до ле- гального потребителя) и огромное коли- чество программистов, стремящихся «топтанием клавиш» заработать себе на хлеб - говорят о необходимости усиле- ния защитных механизмов любыми дос- тупными средствами. В том числе - с по- мощью самомодифицирующегося кода. Низкая эффективность существующих защит говорит о необходимости усиления защитных механизмов любыми доступными средствами. В том числе - с помощью динамически изменяющегося кода. 19 г ш н> U ЗЕ U
lj_! ПРОГРАММИСТ Профессиональный журнал Александр Шевелев syleiman@mall.ru КАК НАПИСАТЬ «ХРАНИТЕЛЬ ЭКРАНА» ДЛЯ WINDOWS С точки зрения программиста, хранитель экрана (screensaver) в ОС Windows - это нормальная исполняемая про- грамма (имеющая, однако, расширение .SCR), управляемая через параметры командной строки. Однако, такая программа обязана: - поддерживать опции настройки - иметь режим предварительного просмотра - поддерживать установку и ввод пароля - не допускать запуск своей второй копии - завершать работу, если пользователь нажал клавишу или переместил мышь Попытаемся показать, как создать такое приложение с помощью Borland C++ Builder. Заметим, что наш хранитель экрана не должен мешать работе других запущенных программ. Поэтому его размер должен быть как можно меньше. Для уменьшения объема файла лучше полностью отказаться от использования визуальных компонен- тов; в нашем примере ограничимся «голым» WinAPI (заодно - припомним, как создать «старое доброе» Windows-приложение, без всяких там «новомодных штучек»: MFC, ATL и тд.). 20 С точки зрения программиста, хранитель экрана (screensaver) в ОС Windows - это нормальная исполняемая программа (имеющая, однако, расширение .SCR), управляемая через параметры командной строки. Ключи командной строки Итак, хранитель экрана управляется параметрами командной строки. Их два; первый пара- метр обозначает действие, которое должна выполнить программа, а второй - содержит хэндл вспомогательного окна и передаётся только в двух случаях: при переходе в режим предвари- тельного просмотра (preview) и при установке пароля. Возможные значения первого параметра: ♦ /А - вызов диалога ввода пароля. В этом случае нужно передать и второй параметр - хэндл окна ввода пароля. С этим ключом хранитель вызывается при нажатии кнопки «Изменить пароль» в настройках свойств заставок; ♦ /С - режим конфигурации. В этом режиме пользователь может задать параметры работы хранителя. При этом используется диалоговое окно, определяемое разработчиком. С таким ключом хранитель запускается при нажатии кнопки «Настройка» в окне настроек свойств; ♦ /Р - режим предварительного просмотра. Второй параметр - хэндл окна просмотра (изобра- жение «мини-экрана» в разделе свойств заставок). Этот ключ передаётся при выборе хра- нителя и после изменения настроек; ♦ /S - режим нормальной полноэкранной работы. Создание пустого приложения. С помощью встроенного мастера создадим пустое приложение. Для этого выберем в меню «File->New...». В появившемся окне надо выбрать пункт «Console Wizard» и нажать «Ок». В но- вом диалоге в разделе «Source Туре» следует оставить значение «C++», а во втором разделе - снять все флажки. По нажатию кнопки «Ок» создаётся приложение. Вспомогательные функции Для обеспечения работы хранителя нам понадобятся некоторые вспомогательные функции. 1. Разбор командной строки Нам понадобится функция, выделяющая отдельные параметры из командной строки. Напри- мер, такая: char cfCommandlinetchar* cCL) { // Разбор командной строки // Получили ключ управления char C=*CharUpper(CharNeHt(cCL)J; if(C—“fl" II C=="P") { // Если есть второй параметр u)hile(*(cCL=CharNeHt(cCL])! = “ // Отделили его как СТРОКУ int iSum=0; ujhilel*(cCL=CharNeHt(cCl))) // Перевели его в число iSum+=ifPou>10(strlen(cCL)-1)*(*cCL-48); hSerulUnd=(HlDNOMiSum); // Получили кзндл окна } else hSeruLUnd=NULL; return С; } Функция ifPowlO(int) позволяет получать значения целых неотрицательных степеней числа 10.
». у р » а п ПРОГРАММИСТ Профессиональный 2. Генерация последовательности псевдослучайных чисел Пусть наш хранитель экрана рисует, например, круги и квадраты произвольного цвета в про- извольном месте экрана. Для этого понадобится генератор последовательности псевдослу- чайных чисел. Можно использовать функции стандартной библиотеки C++ randomize() и гап- dom(), а можно написать свой собственный генератор. Например, на основе известной рекур- рентной формулы Энгеля: I х0- Z .где К(Р) - дробная часть Р; рй= К((х+ л) ~ Величина х принимает значения от 0 до 1. На этом же отрезке находится и база рекурсии - число z. Для его «случайной» генерации можно использовать функцию GetTickCount(), воз- вращающую количество миллисекунд, прошедшее с момента запуска системы. 3. Чтение и сохранение конфигурации В процессе работы хранитель будет использовать некоторые настроечные параметры, а именно: ♦ Тип выводимой фигуры (круг или квадрат) - глобальная переменная iFigType ♦ Максимальное количество фигур - глобальная переменная iMaxFig ♦ Задержка перед появлением очередной фигуры (в миллисекундах) - глобальная перемен- ная iTimeOut После того, как пользователь выбрал установочные параметры, надо их сохранить (желатель- но - в системном реестре). Делается это примерно так: uoid ufSaueCfgO HKEV Key; DWORD du)Size=sizeof(DWORD); DWORD dwType=REG_DWORD; char* c='"'; IF(RegOpenKeyEx(HKEV_CURRENT_USER, "SOFTWRREWSheueleuWMySS ", 0, KEV_RERD,8>Key) == ERROR_SUCCESS) { // открываем свой ключ "Sheueleu\MySS" // и задаем его поля: c=(char*)(&iMaxFig); RegSetUalueEx(Key,"FigureMax'’,NULL,du)Type,c,diuSize); c=(char*)(&ITimeOut); RegSetUalueEx(Key,"TimeOut ',NULL,dwType,c,dwSizeJ; c=(char*)(&iFigType); RegSetUalueExtKey,'FigureType',NULL,duuType,c,dwSize); RegCloseKey(Key); Загружаем параметры так: uoid ufLoadCfgO { HKEV Key; DWORD diuSize=sizeof(DWORD); DWORD dwType=REG_DWORD; char* c=“"; 21 Фом Заставка j Оформление ] Pius! ] Параметры | Свойства Экран ; Отмена | Применить Застаем E if(RegOpenKeyEx(HKEV_CURRENT_USER,"SOFTWRRE\\Sheueleu\\MySS",0, KEV_RERD,&Key) == ERROR_SUCCESS) { // есть такой ключ! if(RegQueryUalueEK(Key, "FigureMax',NULL,&dwType,c, &dwSize)==ERROR_SUCCESS) // поле есть - читаем его: iMaxFig=(int)(*(DWORD*)(c)J; else iMaxFig=50; // иначе - ставим по умолчанию iF(RegQueryUalueEx(Key1"TimeOut",NULL,G'dwType,c, &diuSize) == ERROR_SUCCESS) iTimeOut=(int)(*(DWORD*)(c)); else iTimeOut=250; if(RegQueryUalueEx(Key, "FigureType",NULL,&dwType,c, &diuSize)==ERROR_SUCCESS) iFigType=(int)(*(DWORD*)(c)); else IFigType=ROUND_RECT; RegCloseKey(Key); else {// первый запуск - ключа в реестре ещё нет! создаем его... IMaxFig=50; ITimeOut=250; // ставим все значения "по умолчанию"
ПРОГРАММИСТ Профессиональный журнал 22 iFigType=C IRCLE; if(RegCreateKey(HKEV_CURRENT_USER,"SOFTWRRE\\Sheueleu\\MySS", SKey) — ERROR_SUCCESS) { // создаем ключ и все его поля c=(char*)(f>iMaxFig); RegSetUalueEx(Key,'"FigureMax",NULL,du)Type,c,diuSize); c=(char*)(&iTimeOut); RegSetUalueExtKey.’TimeOut ",NULL,dtuType,c,diuSize); c=(char*)(&iFigType); RegSetUalueEx(Key,"FigureType“,NULL,du)Type,c,du)Size); RegCloseKey(Key); } } } Отслеживание повторного запуска При разработке хранителя экрана надо учесть, что нельзя запускать больше одной его копии. Для отслеживания факта повторного запуска я использую такой объект ядра как мьютекс (mutex). (Прим, ред.: несколько других способов проверки см. в статье «Запрет запуска дополнительных копий», в этом же номере журнала.) При первом запуске надо создать мьютекс с уникальным именем. При повторном запуске - попытка создания такого объекта закончится неудачей (он уже создан). Это и будет признаком того, что один экземпляр прило- жения уже запущен. Всё это реализуем следующей функцией: bool bfPreulnstChecktHRNDLE S-hMutex) { // Проверка - было ли запущено раньше hMutex=Creat eMutextNULL,false, "CheckMySSMutex"); if(GetLastError()==ERROR_HLRERDV_EKISFS) return false; else return true; } Если эта функция возвращает значение true, то приложение уже запущено. В конце работы мьютекс надо удалить: uoid ufRemouelnstChecktHRNDLE hMutex) { // Всегда вызывать при завершении работы приложения CloseHandle(hMutex); } Основное окно 1. Создание окна Нам нужно создавать небольшое окошко для предварительного просмотра и полноэкранное окно. Создание их лучше объединить, используя единственный класс окна: HWND hfCreateMainWindoujfHINSTRNCE hinstance,int Width,int Height,HWN ParentUJindocu) { // Создание главного окна WNDCLRSS шея; iucx.style=CS_PRRENTDC; tucK.lpfnlllndProc=MainUJndProc; // Класс окна один - и для основного окна uicx.cbClsExtra=0; // и для предпросмотра iucx.cbWndExtra=0; iucx.hlcon=0; iucx.hCursor=0; iucx.hbrBackground=CreateSolidBrush(RGB(0,0,0)); iucx.lpszMenuName=NULL; tucx.lpszClassName="SSWndClass'"; u)cx.hlnstance=hinstance; RegisterClasst&incx); // Регистрируем класс окна if(ParentWindoui) // Это - окно предварительного просмотра hMainll)nd=CreateWindouj('SSWndClass", "ScreenSauer", . WS_CHILD|WS_UISIBLEIWS_DISRBLED,0,0,Width,Height,ParentWindoiu,0,hinstance,NULL); else // Это - основное окно hMainWnd=CreateWindou)(“SSWndClass","ScreenSauer", WS_UISIBLE|WS_POPUP,0,0,Width,Height,GetDesktopWindoui(),0,hinstance,NULL); SetWindou)Pos(hMainWnd,HWND_TOPMOST, 0,0,0, SWP_NOMOUE|SWP_NOREDRRW,SWP_NOSIZE); return hMainWnd;
Профессиональный ж » • " • " ПРОГРАММИСТ * 2. Функция потока Для обновления окна хранителя можно использовать либо таймер, либо дополнительный по- ток. Я предпочитаю второй способ и создаю такую функцию потока: DUJORO LUINAPI UlinThreadProcfLPUOlB IpThreadParam) ( // Функция потока перерисовки окна ShowUJindou4hMainLUnd,Slll_SH0LU); UpdateLDindoui(hMainLUnd); iuhile(!bQuit) { InualidateRectfhMainLUnd,NULL,false); // Обновляем... Sleep(iTimeOut); // ...и засыпаем ); PostMessage(hMainLUnd,lUM_DESTROV,0,0); return 0; .} Эта функция просто заставляет изображение в нашем окне обновляться, потом «спит» неко- торое время - и обновляет изображение вновь. 3. Оконная процедура Чтобы оперировать оконными сообщениями, нам понадобится оконная процедура (описание функции bfAskPassword() приводится в п. «Проверка пароля»): LRESULT CALLBACK MainUlndProc(HU)ND huund.UINT uMsg.tUPRRRM шРагат, LPRRRM IParam) { // Оконная процедура static int iFigCount; // Счётчик фигур switch (uMsg) { case UJM_CRERTE: { iFigCount=iMaxFig; // Количество фигур не более iMawFig break; ) case IOM_DESTROV: {PostQuitMessage(0);break;} // Выходим: case IOI4_KEVDOIBN: // по нажатию клавиши case U)M_LBUTTONDO1BN: // no кнопкам мыши case IL)M_RBUTTONDOIUN: case LUM_MBUTTONDOIBN: {bQuit=bfflskPassuJord();break;} case UJM_MOUSEMOUE: { // двинули мышью: // отключаемся только в "нормальном" режиме... !!! if(!bIsPreuieiu) { //и, чтобы хранитель не отключался // при малейшем движении: if(!*iCountMoue) bQuit=bfflskPassiuord(); } break; } case IBM_PRINT: { //R вот здесь мы перерисовываем окно iftiFigCount*) ufPaint(false); else { iFigCount=iMaxFig; ufPaint(true); } break; ) default: return OefUJindowProc(hwnd,uMsg,шРагат,IParam); } return 0; } 4. Функция рисования Самая приятная часть - создание графики. Поскольку нам надо только понять, как пишется хранитель вообще - не станем сильно усложнять себе жизнь. Будем просто выводить в про- извольном месте экрана случайным цветом круги и квадраты. uoid ufPainttbool bClear) { PRINTSTRUCT Info; HOC DC=BeginPaint(hMainUJnd,&lnfo); if(bClear) // Удаление всех фигур FillRect(DC,&R,CreateSolidBrush(RGB(0,0,0))); 23 S Ш h- О s и
ПРОГРАММИСТ Профессиональный журнал else {// Делаем кисть long Color=RGB(iRandom(150),iRandom(150),iRandom(150B; HBRUSH OldBrush=SelectObject(DC,CreateSolidBrush(Color)); int B=iRandom(R.right-R.left-iSize); int V=iRandom(R.bottom-R.top-iSize); if(!ifigType) // Рисуем квадрат RoundRect(DC,B,V,R+iSize,V+iSize,iSize/2,iSize/2); else // Рисуем круг Ellipse(DC,K,V,X+iSize,V+iSize); DeleteObject(SelectObject(DC,OldBrush)); } EndPaint(hMainUlnd,&lnfo); ) 5. Функция запуска Последний шаг - создание функции запуска хранителя на полном экране: uoid ufRunFullScreenlHINSTRNCE hlnstance) { // Нормальный режим 24 MSG msg; DUIORD dwThreadld; blsPreuietu=false; iCountMoue=4; // Идентификатор второго потока // He пред-просмотр // Счётчик движений мыши HLUND hFHI=GetForegroundlUindom(); // Текущее рабочее окно nihile(ShOLuCursor(Talse)>-1); // Вбираем курсор мыши GetllJindoiDRect(GetDesktoplDindoiu(),&R); // Получили размер окна // R это размер фигур, что будут рисоваться iSize=(R.right-R.left)/20; // Создаём окно if(hfCreateMainUJindoiu(hlnstance,R.right-R.left,R.bottom-R.top,0)) { // Создаём поток CreateThread(NULL,0,WinThreadProc,NULL1018'diuThreadld); while (GetMessagei&msgjHUJNDHNULLj.B.B)) { // fl это цикл обработки сообщений TranslateMessage(Cmsg); DispatchMessagef&msg); } } ShomCursor(true); // Показываем курсор SetForegroundUlindom(hFUI); // Восстанавливаем рабочее окно } Организация предварительного просмотра Для создания окна предварительного просмотра нам нужно написать только функцию запус- ка - всё остальное (функция создания окна, оконная процедура, функции потока и рисования) у нас уже есть. Функция запуска представляет собой сокращённый вариант аналогичной функции для основного окна и выглядит так (напомню, что хэндл hServWnd мы получаем из командной строки): uoid ufRunPreuieiutHINSTRNCE hlnstance) { // Предпросмотр MSG msg; DUJORO diuThreadld; // Идентификатор второго потока blsPreuieiu=true; // Режим предпросмотра GetUlindomRect(hSeruUJnd,&R); // Получаем размеры окна iSize=(R.right-R.left)/20; // И размеры фигур // Создание окна if(hfCreateMainUJindom(hlnstance,R.right-R.left, R.bottom-R.top.hSeruUJnd)) { CreateThread(NULL,0,LUinThreadProc,NULL,0,&du)Thread I d); luhile (GetMessage(&msg,(HIDND)(NOLL),B,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } ) Поддержка пароля 1. Установка пароля Надо бы позволить пользователю устанавливать пароль. Для этого динамически подключим библиотеку MPR.DLL, в которой функция установки пароля уже есть:
Профессиональный журнал uoid ufRunSetPassiuordO { // Ставим пароль int t stdr.all *SetPassFunc)(char* cName.HWND hlmHnd,DWORD B, DWORD C); HANDLE hLib=LoadLibrary( "MPR.DLL"); // Загружаем библиотеку // Вытаскиваем из нее функцию (uoid*)SetPassFunc=GetProcAddress(hLib,'PwdChangePasscuordR ); if(SetPassFunc) // На всякий случай проверим - есть ли такая SetPassFunc("SCRSRDE“,hSeruWnd,0,0); // и установим пароль FreeLibrary(hLib); } Обратите внимание: при вызове функции установки пароля, в качестве первого параметра мы передаём строку «SCRSAVE». Она говорит о том, что мы устанавливаем пароль именно для хранителя экрана. 2. Проверка пароля Если в процессе работы хранителя в полноэкранном режиме перемещается мышь или нажи- мается кнопка, надо спросить у пользователя пароль на выход: bool bfRskPassiuordO { // Спрашиваем пароль на выход bool bResult=false; HRNDLE hLib; bool (_stdcall *GetPassFunc)(HWND hlmHnd); hLib=LoadLibrary('PRSSWORD.CPL ); if(hLib) { (uoid*)GetPassFunc=GetProcflddress(hLib, "DerifyScreenSauePtud if(GetPassFunc) { ShoujCursor(true); bResult=GetPassFunc(hMainWnd); // true - пароль верный ShoiuCursor(false); iff I bResult) iCountMoue=4; // Неверный пароль - продолжаем работу ) FreeLibrary(hLib); } return bResult; Диалог конфигурации 1. Создание диалогового окна Достаточно непростая часть - создание диалогового ресурса (мы ведь договорились не ис- пользовать визуальные компоненты). Я создал его с помощью 16-битового Resource Workshop (он есть, например, в пакете Borland C++ 4.5); сохранив файл как описание ресурса (.RC), я скомпилировал его, используя утилиту BRCC32 (BRCC32 имя_файла.гс /32). Полученный файл ресурсов я подключил к проекту. На рисунке показано созданное диалоговое окно. 2. Диалоговая процедура Для поддержки функционирования диалогового окна нам понадобится диалоговая процеду- ра. Она выглядит так: int CALLBACK ifSSDialogProcfHWND hmndDIg, DINT uMsg, WPRRRM wParam, LPRRAM IParam) { // Диалоговая процедура switchfuMsg) { case WM_INITDIALOG: { // Инициализация диалога RECT R2; // Окно - по центру экрана GetWindoiuRect(hiundDlg,&R2); SetWindowPosIhwndDIg, HWNDJTOP, (GetSystemMetrics(SM_CRSCREEN)-R2.right+R2.left)/2, (GetSystemMetrics(SM_CVSCREEN)-R2.bottom+R2.top)/2, R2.right-R2.left,R2.bottom-R2.top,NULL); // Устанавливаем состояние радиокнопок if(iFigType) SendDlgltemMessage(hu)ndDlg,102, BM_CLICK, NULL, NULL); else //в зависимости от типа фигуры SendDlgltemMessage(hiundDlg,101,BM_CLICK, NULL, NULL); // Вносим значения в поле с количеством фигур // и с задержкой char* С=“"; SendDlgltemMessage(hiundDlg,103,WM_SETTEXT,0, (LPRRRM)(itoa(iMaxFig,C,10))); ПРОГРАММИСТ — При вызове функции установки пароля, в качестве первого параметра мы передаём строку «SCRSAVE». Она говорит о том, что мы устанавливаем пароль именно для хранителя экрана. 25
- ПРОГРАММИСТ Профессиональный журнал 26 SendDlgltemMessage(hujndDlg,104,UJM_SETTEl<T,0, (LPRRRM)(itoa(iTimeOut,C, 10))); break; } case ШИ-COMMRND: { // Отлавливаем действия пользователя if(LOUJORD(wParam)==1) { char* С=""; // Нажата кнопка "Принять" GetDlgltemText(hLundDlg,103,С,250); iManFig=atoi(C); GetDlgltemText(hwndDlg,104,C,250); iTimeOut=atoi(C); EndDialog(hwndDlg,1); ) else { if(L0Ui0RD(u)Param)==2) // Кнопка "Отмена" EndDialog(hiund0lg,2); else // Сменили фигуру - квадрат if(LDLUORD(ii)Param)== 101) {iFigType=ROUND_RECT;) else if(LOUJORD(iJuParam)==102) iFigType=CIRCLE; ) break; } case 1UM_CLOSE: OestroyLUindow(hiJundDlg); // Закрываем диалог } return 0; ) 3. Функция запуска Функция запуска для режима конфигурации необычайно проста, и выглядит так: uoid ufRunConfigiHINSTRNCE hlnstance) { // Настройки // Создали диалог int iResult=DialogBox(hlnstance,"DIRL0G1 ",0,(DLGPROC)(ifSSDialogProc)); // Если нажали "Принять" - сохраняем изменения if(iResult==1) ufSaueCfgO; } Функция WinMain Последний шаг - создание функции WinMain: UIINRPI LUinMainiHINSTRNCE hlnstance, HINSTRNCE hPreulnstance, LPSTR IpCmdLine, int nCmdShotu) { MSG msg; bQuit=false; ufLoadCfgO; 11 Загружаем настройки iRandomO; // Запускаем генератор случайных чисел char cKey=cfCommandLine(lpCmdLine); // Разбор командной строки if(bfPreulnstCheck(CheckHandle)) { //Если ещё не запущена if(cKey=="S") // Ключ "S" - нормальный режим ufRunFullScreenih Instance); else if(cKey=="C") // Ключ "С" - настройки ufRunConfigih Instance); else if(cKey=="P“) // Ключ "P” - предпросмотр ufRunPreuiewth Instance); // Закрываем проверку ufPreulnstCheck(CheckHandle); } else if(cKey=="R") // Пароль - особый случай - уже запущено! ufRunSetPassuiordO; return 1; ) Подготовка хранителя к работе Готовый хранитель надо подготовить к работе; то есть - выполнить два простых шага: 1. Переименовать полученный ехе-модуль в имя-scr, где имя - название хранителя (оно будет отображаться в настройках экрана); 2. Переписать полученный файл в каталог WINDOWS\SYSTEM. ф
В этом разделе у нас - все, что касается СУБД, электронных таблиц, бухгалтерских программ; инсталляции, эксплуатации и интеграции офисных систем - и прочей «автоматизации коммерческой деятельно- сти». Кроме того, мы решили, что в этот же раздел будем помещать все, что касается программирования пользовательских интерфейсов (frameworks) - формы, документы, ием'шки; всяческие кнопочки, скроллеры, окошки, компоненты и т.д. По сложившейся традиции, раздел «Офис» у нас получается неболь- шим; не хотят что-то мастера проводок и транзакций делиться ценным опытом с собратьями по несчастью... Это им минус. Сегодня у нас - только небольшая статья про написание бухгалтерских БД (в основном - полезные общие соображения и рекомендации), до- вольно ценная для начинающих инструкция по созданию Window’bix онон извращенной формы, и обзор продуктов для написания инсталля- торов...
ПРОГРАММИСТ 28 Профессиональный журнал Леонид БОЙЦОВ itmin@narotl.ni http://ltman.narod.ru БУХГАЛТЕР, МИЛЫЙ МОЙ БУХГАЛТЕР: КАК НАПИСАТЬ БУХГАЛТЕРСКУЮ СИСТЕМУ? Как вы уже, наверное, догадались, речь пойдет о сопровождении и/или разработке бухгалтерского «софта». Мне довелось сопровождать парочку бухгалтерских систем, поэтому я рискну высказать пару рекомендаций на тему «как обустроить бухгалтерию». Хочу сразу сказать, что речь пойдет о реализациях на платформе реляционных баз данных. Мои советы адресованы в первую очередь тем заказчикам и разработчикам, которые стремятся объединить транзакционную и аналитическую базы данных. В отличие от «закрытых» систем, разработки на платформе реляционных СУБД типа Oracle, Sybase, Informix позволяют, с одной стороны, относительно легко решать оптимизационные проблемы, а с другой стороны - использовать всю мощь ОС Unix. Конечно, вы можете сразу задать вопрос - а почему возникает необходимость написания системы «с нуля»? Почему бы не построить систему, например, на базе «1С-предпри- ятия» - или его аналога? Оказывается - не все так просто, как кажется. Покупая «закры- тую» систему, вы неизбежно принимаете все действующие в этой системе ограничения. Нередко оказывается, что нужного эффекта можно достичь только, как говорится, «с тан- цами и бубнами». Кроме того, система может быть недостаточно надежной и плохо рабо- тать на больших объемах данных. Если вспомнить любимую в России 1С - не уверен, что она «выживет» на 10 млн. проводок. В отличие от «закрытых» систем, разработки на платформе реляционных СУБД типа Oracle, Sybase, Informix позволяют, с одной стороны, относительно легко решать оптимизационные проблемы, а с другой стороны - использовать всю мощь ОС Unix. Кроме того, не придется мучиться с непродуманными, ограниченными - если не сказать просто убогими - средства- ми разработки интерфейса, которыми часто отличаются «закрытые» системы. Чтобы не быть голословным, я рассмотрю не- кую воображаемую систему, похожую на ре- ально сопровождаемую мной программу. Эту гипотетическую систему я буду использо- вать для моделирования процесса взаимо- расчетов (клиринга) между фирмами, непо- средственно предоставляющими туристиче- ские услуги и агентами по продаже путевок. Все организации, участвующие в клиринге, можно поделить на четыре категории: кем- пинги (пансионаты), агентства по продаже пу- тевок, страховые компании различного про- филя и клиринговая палата (КП), координиру- ющая взаиморасчеты. Представьте себе, что в условиях царящего в России бардака, турагентства, продающие путевки кемпингов, начинают «зажимать» деньги; причем это становится повсеместным явлением. Чтобы как-то с этим бороться, кем- пинги создают акционерное общество - кли- ринговую палату (КП) для централизованной продажи путевок. КП следит за правильно- стью и своевременностью расчетов; и если, к примеру, какое-нибудь недобросовестное агентство просрочит платеж - оно может быть оштрафовано, а то и вовсе лишено права продаж. Таким образом, все расчеты происходят че- рез КП, и жестко привязаны к периодам вре- мени. Каждый месяц агентства перечисляют на счета КП в банке деньги и отчитываются о выручке; а КП, соответственно, распределяет выручку между кемпингами и агентствами (не забывая о собственных комиссионных) и рас- сылает всем участникам клиринга отчет о фи- нансовых операциях, произведенных с их уча- стием. Построение клиринговой системы с учетом всех возможных нюансов - задача довольно сложная, поэтому мы рассмотрим ее только в общих чертах. Для иллюстрации реализации бизнес-процесса можно обойтись нескольки- ми таблицами-сущностями: справочник орга- низаций, план счетов, документы, отчеты и связка отчетов с документами. Общая схема (ERwin-диаграмма) одного из возможных ва- риантов нашей модельной базы данных изо- бражена на рис. 1: Отчет о продажах путевок id отчёта агентство (FK) кемпинг (FK) тип путевки дата заезда дата выезда количество путевок цена Продажи путевок id отчёта (FK)| id документа (FK)| >0-------el- Организация id организации название Документ id документа дата документа Рис. 1 План счетов номер счета сумма на счете V тг Проводка___________ id проводки id документа (FK) счет по приходу (FK) счет по расходу (FK) сумма проводки
Профессиональный журнал Ключевыми в нашей схеме являются сущно- сти «План счетов», «Проводка» и «Доку- мент». Как известно, каждая проводка соот- ветствует финансовой операции по переведе- нию «суммы проводки» со счета, задавае- мого значением поля «счет по расходу», на счет, задаваемый полем «счет по приходу». Документ является финансовой макроопера- цией и содержит несколько проводок, выпол- няемых в заданном порядке. Если таблицы плана счетов, документов и проводок являются основным звеном практи- чески любой бухгалтерской системы, то таб- лицы «Отчет о продаже путевок» и «Продажи путевок» специфичны для описываемой схе- мы туристического бизнеса. Как я уже гово- рил, каждое агентство ежемесячно отчитыва- ется перед КП. При этом агентства, во-пер- вых, присылают сам отчет о продажах в элек- тронном или бумажном виде, а во-вторых - переводят на счет КП деньги для оплаты отче- тов. Переводы могут полностью покрывать сумму отчетов, а могут быть частичными - по- этому один и тот же отчет может оплачивать- ся в несколько приемов. Это означает, что одному отчету может соот- ветствовать несколько документов. В принци- пе, возможны ситуации, когда несколько отче- тов оформляется в виде одного финансового документа. Несложно видеть, что отношение между сущностями «Документ» и «Отчет о продаже путевок» - типа «многие ко многим». Это отношение реализуется с помощью свя- зующей таблицы «Продажи путевок». Еще раз повторюсь, что основой всей этой схемы является тройка таблиц: документы, счета и проводки; поэтому в первую очередь - нужно отладить процедуры проведения (в том числе задним числом) и отмены документов. Не забывайте, что при добавлении каждой но- вой проводки, программа должна списывать сумму проводки с одного счета и переводить ее на другой. Это должно работать при любой погоде и вне зависимости от настроения на- чальника! Лучше всего реализовать этот ме- ханизм с помощью триггеров. Но будьте бди- тельны: практически невозможно отследить план выполнения запроса внутри триггера, а изменение порядка обработки соединения таблиц в SQL-запросе может существенно увеличить время выполнения этого запроса. Забегая вперед, скажу, что лучше использо- вать подсказки оптимизатору. В схеме на рис. 1 в таблице документов, не считая поля «idдокумента», всего два атрибу- та - «дата» и «тип». Эти два атрибута являют- ся самыми важными в таблице документов. Почему? Ответ прост: индексирование по да- те и типу документу - краеугольный камень эффективного формирования отчетов. В на- шей модели вся финансовая деятельность жестко привязана к периодам. В конце отчет- ного месяца каждому агенту и кемпингу нуж- но посчитать суммы выручки и комиссии. Для этого требуется анализ документов, выпол- ненных в течение месяца; в простейшем слу- чае - суммирование всех проводок, поступив- ших на определенный счет. Например, сумма прихода на счет кемпинга «выручка» бы быть получена на языке SQL с помощью такого за- проса: SELECT $им(сумма проводки) FROM документ 0,проводка Е LUHERE О.дата документа between "начало месяца" RND "конец месяца" AND D.id документа = Е. id документа RND Е.счет по прикоду = "выручка" Чтобы получить «чистый» приход, надо вы- честь из полученной величины сумму прово- док, в которых счет «выручка» - расходный Поскольку в процессе функционирования программы количество проведенных доку- ментов может составлять сотни тысяч, и даже миллионы - без индекса по дате такое вычис- ление может затянуться на месяц... По той же самой причине необходимо поле «тип доку- мента»: для того, чтобы максимально быстро выбирать документы, относящиеся к конкрет- ному типу финансовых операций - покрытию отчетов, переводу средств между счетами, или, скажем, выполнению платежных поруче- ний (ПП). Казалось бы, совершенно тривиальные на- блюдения, но в сопровождаемой мной систе- ме они были выстраданы. Так, изначально в ней отсутствовала индексация по дате доку- мента, а документы разных типов в некото- рых случаях имели одно и то же значение по- ля «тип документа». Иногда приходилось реа- лизовывать запутанные алгоритмы определе- ния типа финансовой операции по номерам счетов проводок документа... Хочу дать еще одну «тривиальную» рекомен- дацию, которая применима не только к бух- галтерским системам: чаще используйте из- быточность представления данных. Так, в на- шем примере имеет смысл перенести в таб- лицу документов поля «агентство» и «кем- пинг» - чтобы выбирать документы по оплате отчетов некоторых заданных агентов без об- ращения к таблице «Отчеты о продажах путе- вок». Можно выразить эту простую мысль и несколько по-другому: храните промежуточ- ные значения. Одним частным, но очень важ- ным случаем промежуточных значений явля- ются величины сумм на счетах агентов и кем- пингов (так называемые сальдо). В нашей си- стеме имеет смысл отдельно хранить сальдо счетов на начало и конец каждого обработан- ного периода. к ПРОГРАММИСТ - Поскольку в процессе функционирования программы количество проведенных документов может составлять сотни тысяч, и даже миллионы - без индекса по дате такое вычисление может затянуться на месяц... 29
- ПРОГРАММИСТ Профессиональный журнал Очень важно, чтобы изменение одного куска кода не приводило к неработоспособности всей программы. Лучше до минимума сократить зависимости между модулями и таблицами, клиентским и серверным ПО. Прочитав предыдущий абзац, некоторые раз- работчики могут возразить мне, сказав, что избыточность хранения - это уход от норма- лизации базы данных и замедление выполне- ния транзакций. Сразу хочу сказать, что это справедливо только в случае, если есть две базы: транзакционная и аналитическая. Спра- шивается, а где вы видели такое в российских условиях? Кроме того, вопреки довольно рас- пространенному мнению, нормализация мо- жет иногда существенно «затормозить» даже чисто транзакционную базу данных. Простой пример: автоматическая нумерация платежных поручений (ПП). Чтобы реализо- вать возможность оплаты ПП, расширим на- шу систему новой сущностью «Платежное по- ручение» и сущностью-связкой «Оплата ПП». Соответствующий фрагмент логической схе- мы базы данных изображен на рис. 2: Раз уж речь зашла об оптимизации, нельзя умолчать о самих оптимизаторах СУБД. Ра- ботая с СУБД Sybase (далеко не худший про- дукт), я пришел к неутешительному выводу: не давайте оптимизатору не единого шанса! Как это ни печально, оптимизатор склонен «заваливаться» на элементарном соединении по двум таблицам - если, например, сисад- мин забыл обновить статистику таблиц. Поэ- тому, чтобы избежать неожиданного замед- ления работы системы, используйте подсказ- ки оптимизатору. Оптимизатор по определе- нию не знаком с предметной областью, и не всегда может знать параметры запроса; не надейтесь, что он построит план запроса луч- ше вас. Если СУБД не позволяет явно указать индекс или порядок выполнения соединения, такой продукт просто нельзя использовать - если, конечно, вы не собираетесь про- граммировать на API низкого уровня. 30 Документ______ id документа тип документа дата документа Оплата ПП id документа (FK) i lek ld ПП (FK)------>04 Платежное поручение id ПП получатель расчетный счет номер ПП рис. 2 и S е о ПП является, по сути, переводом денег с неко- торого расчетного счета (Р/СЧ) на счет полу- чателя платежа в другой организации. Каж- дая платежка обладает своим номером, на единичку превосходящим номер последнего ПП, выполненного с того же расчетного счета (Р/СЧ). Программа должна осуществлять ну- мерацию автоматически. Заметьте, что для этого требуется найти ПП, выполненное с то- го же Р/СЧ, с наибольшим номером. Если вы «забудете» построить соответствующий ин- декс, то система просто «умрет», выполняя одну-единственную операцию несколько се- кунд или даже минут! Даже если проиндексировать поля «номер ПП» и «расчетный счет» - все равно остается проблема блокировок, потому что нужно бло- кировать таблицу ПП на время выполнения операции, чтобы другой процесс не мог зане- сти туда запись - и изменить, таким образом, максимальный номер платежки. Кроме того, индекс «тормозит» занесение новых записей, если таблица большая. Как несложно видеть, все проблемы легко ре- шаются при помощи дополнительной табли- цы с двумя полями: «расчетный счет» и «мак- симальный номер ПП», в которой будет хра- ниться максимальный использованный номер ПП для каждого расчетного счета. Действи- тельно, обычно количество расчетных счетов значительно меньше количества платежных поручений, и время обновления такой табли- цы меньше времени обновления индекса. В заключение - несколько слов о сопровож- дении клиент-серверных приложений. Пос- кольку в России нормативные акты, касающи- еся финансовой деятельности в целом и буху- чета в частности, сыплются как из рога изоби- лия - программы приходится менять очень ча- сто. И процессу тестирования, как водится, много времени не уделяется. Значит - очень важно, чтобы изменение одного куска кода не приводило к неработоспособности всей про- граммы. Лучше до минимума сократить зави- симости между модулями и таблицами, кли- ентским и серверным ПО, а также уметь та- кие зависимости отслеживать. Что касается вопроса отслеживания зависи- мостей, то - увы - пока что единственным бо- лее-менее приемлемым средством отслежи- вания изменений в базе данных является «глючный» Erwin. К тому же, необходимо еще и отслеживать зависимости клиентских моду- лей от серверных. Насколько я знаю, единст- венным продуктом, позволяющим это делать, является Rational Rose. К сожалению, в Rational Rose поддержка СУБД появилась со- всем недавно, и по функциональности не «до- тягивает» до ERwin. Разумеется, любая реальная система гораздо сложнее рассмотренной модели, и проблем с ее разработкой возникает гораздо больше. Тем не менее - надеюсь, что мои заметки мо- гут быть полезны не только программистам, планирующим написать БД «с нуля», но и тем, кто сопровождает уже написанную систему, ф
Профессиональный журнал ПРОГРАММИСТ * * 1 ОКНА - ТАКИЕ РАЗНЫЕ: КРУГЛЫЕ, ТРЕУГОЛЬНЫЕ, ЗВЕЗДООБРАЗНЫЕ... Николай Больсунов Основной критерий качества ПО - его функциональность. Но в наше время разработчики коммерческого софта должны думать и о том, что принято называть «товарным видом» продукции. Как говорится, «по одежке встре- чают, по уму провожают» - здесь в роли «одежки» выступают красивая коробка, качественно изданная докумен- тация - и, конечно же. броский и удобный интерфейс программы. Тем, кто хочет создать яркий и нетривиальный дизайн для своего приложения, наверное, будет интересно исполь- зовать возможность создания окон непрямоугольной формы - круглых, овальных, многоугольных, звездчатых... Об окнах нестандартной формы знают многие программисты - сам автор ста- тьи впервые узнал о них из материалов одной эхо-конференции. Почему же тог- да разработчики так мало используют эту привлекательную возможность? Попробуем дать конструктивный ответ на этот вопрос - то есть предложим тех- нологию создания нестандартных окон. Но сначала нужно рассказать, как полу- чаются нестандартные окна, и какие ме- ханизмы Windows API для этого сущест- вуют. Если вы знакомы с концепцией ре- гиона окна и API-функциями региона, переходите сразу к последнему разделу статьи... Понятие региона окна и функций региона Любое окно в Windows можно предста- вить как прямоугольную систему коорди- нат, с точкой отсчета в левом верхнем уг- лу. Точки экранного изображения окна описываются координатами (X, Y), где X - порядковый номер точки по горизонталь- ной оси, a Y - по вертикальной. В WinAPI имеются функции для задания подмножества точек окна, путем опреде- ления координат и размеров геометриче- ской фигуры (эллипса, многоугольника и т.п.), внутри которой эти точки располо- жены. Подмножество точек окна, которое эти функции умеют определять, называ- ется регионом окна, а сами функции - функциями региона. Имена функций региона соответству- ют геометрической форме создавае- мого региона: CreateEllipticRgn() - эл- липс, CreateRectRgn() - прямоуголь- ник, CreateRoundRectRgn() - прямо- угольник со скругленными углами, CreatePolygonRgnf) - произвольный многоугольник. Функции создают реги- он (т.е. описывающую его структуру в памяти) и возвращают дескриптор соз- данного региона (region handle). Для одного окна можно определить лю- бое количество разных регионов; есть также функция CombineRgn(), позволя- ющая получить составной регион, пред- ставляющий собой пересечение, объе- динение, разность или симметрическую разность нескольких регионов. Если мы создали для нашего окна, к примеру, два региона (а значит, имеем их дескрипторы), то, запросив у этой функции операцию объединения регио- нов - получим новый регион, состоящий из точек, принадлежащих обоим нашим регионам - и тому, и другому (и обоим вместе, если они пересекаются). 31 Возможно, пользователь будет приятно удивлен, увидев на экране такое необычное круглое окно инсталлятора вашей программы Так, скомбинировав круглый и треуголь- ный регионы, можно получить на экране фрагмент окна, по форме напоминаю- щий голову в треугольной шляпе. А по- лученная через функцию CombineRgn() разность двух регионов позволяет ото- бразить на экране фрагмент окна с са- мой настоящей «дырой», сквозь кото- рую можно будет увидеть элементы зад- него плана экрана, и даже пощелкать по ним мышью! Далее, в Windows API имеется функция SetWindowRgn(), вызов которой позво- ляет ограничить область окна на экране определенным, созданным заранее ре- гионом. Определив регион необычной^
ПРОГРАММИСТ Профессиональный журнал 32 Создавая различные элементарные регионы, можно выводить на экран фрагменты окна разных очертаний: эллипс, многоугольник, скругленный прямоугольник (пунктиром показано первоначальное окно) формы, мы можем с помощью этой функции вырезать из прямоугольного экранного окна «кусок» эллиптической, полигональной и т.п. формы. Итак, резюмируя изложенные выше об- щие сведения, можно определить следу- ющий порядок создания окна с нестан- дартными очертаниями: 1. создать набор элементарных регио- нов, составляющих нужную форму окна, вызывая соответствующие функции соз- дания региона и запоминая дескрипто- ры созданных регионов; 2. если регионов больше одного, то скомбинировать созданные регионы при помощи функции CombineRgn(), полу- чив регион с требуемыми очертаниями; 3. отобразить окно в очертаниях регио- на, вызвав функцию SetWindowRgn(). Описание основных функций региона Всего существует примерно два с поло- виной десятка функций региона. Здесь мы ограничимся описанием прототипов важнейших из них (все побробности можно узнать из любого справочника по WinAPI - например, достаточно неплохо- го руководства 95guide.hlp). Прототипы функций приведены в соот- ветствии с синтаксисом языка C++ (как в файле справки). Координаты задаются в пикселах относительно левого верхне- го угла окна, также в пикселах задаются требуемые размеры. Все функции, для которых указан тип HRGN, возвращают дескриптор созданного региона. Функция создания прямоугольного региона: HRGN CreateRectRgnt // Н-координата верхнего левого угла int nLeftRect, // V-координата верхнего левого угла int nTopRect, // Н-координата нижнего правого угла int nRightRect, // V-координата нижнего правого угла int nBottomRect ); Для описания прямоугольника достаточ- но указать координаты его левой верх- ней и правой нижней вершин. Функция создания эллиптического региона: HRGN CreateEllipticRgnt int nLeftRect, int nTopRect, int nRightRect, int nBottomRect ); Здесь задаются координаты прямо- угольника, в который вписывается тре- буемый эллипс. Функция создания региона - произвольно- го многоугольника: HRGN CreatePolygonRgn( // указатель на массив координат вершин CONST POINT *lppt, // число элементов массива вершин int ePoints, // режим заполнения многоугольника int fnPolyFillMode ); Прежде чем вызывать эту функцию, нужно создать и заполнить массив коор- динат вершин многоугольника. Элемен- ты этого массива - структуры типа POINT: typedef struct tagPOINT { LONG H, LONG V, } POINT; Указатель на такой массив передается в функцию первым параметром. Вторым параметром передается число вершин многоугольника. Третий параметр осо- бого значения не имеет; может всегда задаваться константой WINDING. Функция создания региона - прямоуголь- ника со скругленными углами: HRGN CreateRoundRectRgn( // координаты прямоугольника int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, о
Профессиональный журнал ПРОГРАММИСТ — // ширина скругляющего эллипса int nUlidthEllipse, // высота скругляющего эллипса int nHeightEllipse ); Здесь, кроме координат прямоугольни- ка, нужно задать размеры осей эллипса, который будет применяться для закруг- ления углов. Функция комбинирования регионов: int CombineRgM // дескриптор результирующего региона HRGN& hrgnOest, // дескриптор первого региона-операнда HRGN hrgnSrc 1, // дескриптор второго региона-операнда HRGN hrgnSrc2, // комбинирующая операция int fnCombineMode ); Эта функция создает регион путем вы- полнения указанной теоретико-множест- венной операции над множествами то- чек двух регионов-операндов. Перемен- ная hrgnDest, в которую будет помещен дескриптор нового региона, должна быть описана в программе заранее. Воз- можны следующие операции (задавае- мые соответствующими константами): RGN_AND: пересечение двух регио- нов RGN_COPY: копия региона, заданного переменной hrgnSrd RGN_DIFF: разность двух регионов RGN_OR: объединение двух регио- нов RGN_XOR: симметрическая разность двух регионов Функция возвращает код результата, ко- торый представляет собой одну из сле- дующих констант: NULLREGION: результирующий ре- гион пуст SIMPLEREGION: результирующий ре- гион есть прямо- угольник COMPLEXREGION: результирующий ре- гион есть сложная фигура ERROR: результирующий ре- гион не был создан Функция задания региона окна: int SetLUindoinRgnt HLUND hLUnd, // дескриптор окна HRGN hRgn, // дескриптор региона BOOL bRedraiu // флаг перерисовки ); Функция устанавливает для окна, задан- ного первым параметром, регион, за- данный вторым параметром. Логиче- ский параметр bRedraw указывает, должно ли окно быть перерисовано не- медленно в очертаниях нового региона. Этих шести функций достаточно для со- здания сложных регионов и вывода на на экран окон с силуэтами соответству- ющей формы. Из функций управления внешним видом региона можно выделить следующие две. Функция закраски региона: BOOL FilIRgnf // дескриптор контекста устройства НОС hdc, HRGN hrgn, // дескриптор региона HBRUSH hbr // дескриптор кисти ); Функция закрашивает заданный регион, пользуясь параметрами заданной «кис- ти» (объекта brush). Если все получи- лось - функция возвращает True, иначе - False. ь 33 Комбинируя элементарные регионы при помощи функции CombineRgn(), можно получить самые различные силуэты окон - например, окно в виде избушки, затейливой рамки или снежной бабы с ведром на голове о
Профессиональный журнал - ПРОГРАММИСТ Если бы мы умели создать регион по контуру имеющейся у нас красивой картинки - это было бы здорово! На экране появлялось бы окно в форме этой картинки, обрезанное точно по ее контуру, и это было бы в самом деле эффектно... 34 Функция обрамления региона: BOOL FrameRgn( // дескриптор контекста устройства НОС hdc, HRGN hrgn, // дескриптор региона HBRUSH hbr, // дескриптор кисти int nlDidth, // ширина обрамления int nHeight // высота обрамления ); Функция окаймляет регион «бордюром» заданной ширины и высоты, в соответ- ствии с параметрами кисти. В Delphi для доступа к функциям регио- нов и связанных с ними типам и констан- там необходимо подключить модуль Windows (uses Windows;). Ниже приводится пример метода - обра- ботчика события создания формы для Delphi, которой выполняет установку не- стандартного региона для окна формы. При запуске приложения на экране поя- вляется не обычное прямоугольное ок- но, а неправильный четырехугольник с круглым отверстием посередине: procedure TForm1.FormCreate(Sender: TObject); uar rgn1,rgn2:HRGN; u:array [1..41 of TPoint; // массив вершин begin u[1].H:=40; u[1].V:=0; u[2].K:=200; u[2].V:=20; u[3].K:=300; u[3].V:=150; u[4l.R:=10; u[4].V:=180; rgn1:=CreatePolggonRgn(u,4,lUINDING); rgn2:=CreateEllipticRgn(100,60,150,1 10); CombineRgn(rgn1,rgn1,rgn2,RGN_DIFF); SetLDindoiuRgn(Handle,rgn1,True); DeleteObject(rgnl); // Освобождаем DeleteObJect(rgn2); // ресурсы end; Ну И ЧТО? Это, похоже, тот вопрос, который зада- вали себе все программисты, ознако- мившиеся с функциями региона. В са- мом деле,какую практическую ценность может иметь для меня возможность вы- резать из прямоугольного окна лоскут с дыркой посередине? Вид такого «окна» способен скорее вызвать отвращение, чем привлечь внимание к моему про- граммному продукту. Вот если бы мы умели создать регион по контуру имеющейся у нас красивой кар- тинки - это было бы здорово! На экране появлялось бы окно в форме этой кар- тинки, обрезанное точно по ее контуру, и это было бы в самом деле эффектно... Пользователь бы видел на экране, ска- жем, компакт-диск, или магнитолу сов- ременного обтекаемого дизайна - и это был бы не просто рисунок, а живое окно с элементами пользовательского интер- фейса! Но как это сделать? Ведь нужно опреде- лить координаты и размеры многочис- ленных «прямоугольников», «эллипсов» и «многоугольников», из которых в сово- купности состоит силуэт нашей картин- ки, чтобы затем создать соответствую- щие им регионы и скомбинировать их! Наверное, именно поэтому разработчи- ки редко используют окна нестандарт- ной формы... На мой взгляд, единственное практич- ное решение - создать программу, ав- томатизирующую создание региона по контуру рисунка; позволяющей исполь- зовать визуальную технику (подобно графическим редакторам) для наложе- ния на желаемый рисунок графических примитивов, задающих форму региона. Я разработал пример такой программы, автоматизирующей работу по созданию сложного региона («Экзотика», работа- ет под MS Excel); она позволяет выде- лить область желаемой формы, после чего - автоматически генерирует код для создания соответствующего регио- на на языке Object Pascal, который оста- ется только добавить к Delphi-проекту. Заинтересовавшиеся могут скачать программу «Экзотика» с сайта www.pro- gramme. ru. Программа распространяет- ся бесплатно. От редакции: Пожалуй, еще более инте- ресным упражнением было бы создание полностью автоматической системы оп- ределения региона - по одному лишь за- данному рисунку. Не так уж сложно написать программу, выделяющую «непрозрачные» области рисунка (например, заполненные «не- ключевыми» цветами) - и генерирую- щую код создания соответствующего региона на требуемом языке програм- мирования. Если кто-то из читателей напишет такую системку, и поделится ей - мы с удо- вольствием разместим ее как пример на нашем сайте... ф о
Профессиональный журнал ПРОГРАММИСТ СОЗДАЕМ ИНСТАЛЛЯТОРЫ — Инсталлятор солидного продукта должен внушать пользователю доверие. Кустарная программа установки, вроде самораспаковывающегося RAR-архива или дивной поделки на Delphi вряд ли вызовет у пользователя ощущение комфорта; скорее заставит призадуматься о профессионализме автора. Между тем, в природе немало софта для создания инсталляторов. Обзор таких продуктов и предлагается вашему вниманию. При написании статьи для тестирова- ния возможностей продуктов исполь- зовался мой проект текстового редак- тора, включающий в себя исполняе- мый файл, dll-библиотеку, файл со шрифтом, звуковой wav-файл, руко- водство в формате *.Н1_Р, несколько шаблонов и таблицы кодировок. Тре- бовалось создать инсталлятор, кото- рый мог бы делать следующие вещи: ♦ сохранять структуру каталогов про- екта, ♦ производить опциональную установ- ку компонентов (наборов файлов), ♦ инсталлировать шрифты, ♦ вносить изменения в реестр и сис- темные файлы инициализации, ♦ создавать группу в меню «Пуск» и помещать туда избранные элементы (ярлык приложения, документации и URL сайта продукта), ♦ создавать ярлык на рабочем столе, ♦ и, наконец - сжимать данные в са- мом дистрибутиве. Что до сжатия, то хотелось получить наименьший размер дистрибутива, общий «вес» файлов которого без ар- хивации составлял 1543 КБ. Итак - имеем довольно спартанский набор требований к тестируемым ге- нераторам инсталлятора. При написании обзора не будем под- робно рассматривать все возможно- сти больших генераторов - таких как Install Shield, Wise InstallManager - a также оставим за бортом странные программы, качество которых вызыва- ет опасения. Все описанные ниже продукты под- держивают выбор языка установки и проверку серийного номера, поэтому не будем особо оговаривать эти воз- можности. Ну что - начинаем?... Setup Factory 5.0 Производитель: Indigo Rose Corp. (www.indigorose.com) Дисковое пространство: 7.1 Мб Эргономичность превыше всего! Удоб- ный дизайн; создание проекта подра- зумевает общение с несколькими ме- неджерами: Screen Manager - операции с экра- нами. Экраны в понимании Setup Factory - это диалоговые окна, пока- зываемые при каждом «шаге» проце- дуры инсталляции. Менеджер включа- ет в себя типовые окна, вроде выбора папки установки, показа лицензионно- го сообщения (в формате Rich Text, 35 Setup Factory 5.0: Эргономичность превыше всего! Удобный дизайн, возможность задавать массу параметров, инсталляторы с полностью настраиваемым интерфейсом. В целом - отличный продукт, с которым приятно работать. о X е о
I ПРОГРАММИСТ Профе'с сиона льный журнал 36 RTF), список выбора устанавливае- мых компонентов, и прочая и прочая. При этом каждое окно можно настраи- вать: менять надписи кнопок и заго- ловков, задавать условия выполнения при помощи логических переменных. Package Manager - здесь можно задать наборы компонентов, которые будут соответствовать вариантам установ- ки: Typical, Custom, и т.д. Не забудьте предварительно вставить окно выбора варианта установки (те самые Typical, Custom и прочие) в менеджере экра- нов! Без этого окна последующее, собственно со списком набора компо- нентов, не вызовется. Для каждого из включенных в проект файлов Setup Factory позволяет за- дать массу параметров. Во-первых - принадлежность к тому или иному па- кету установки. Далее, можно указать путь назначения, опции ярлыка (если таковой нужно создать). Для шриф- тов, элементов управления ActiveX и OLE-серверов - опциональная регист- рация их в системе. Есть много раз- личных опций деинсталляции. Quick Time, файлов поддержки баз данных, Visual Basic, и т.д. Завершая разговор о Setup Factory, вкратце упомянем о других его воз- можностях. Разумеется, продукт уме- ет создавать инсталляторы с полно- стью настраиваемым интерфейсом: хотите - положите любую картинку вместо фона, используйте градиент- ную заливку, или просто сплошной цвет; можете проиграть музыку или отправить пользователя на свой сайт. Все это - в мастерах General Design и Shell Operations. Резюме: отличный продукт, с которым приятно работать. Installer VISE 3.5 Производитель: MindVision Software, Inc. (www.mindvision.com) Дисковое пространство: 20.1 Мб Этот продукт можно считать одним из лучших в данной нише ПО. Стандарт- ный набор конфигурируемых пошаго- вых экранов, к которым можно добав- лять новые, собственные - для чего есть прекрасный WYSIWYG-редактор. Installer VISE 3.5: Весьма гибкий в настройке продукт, от которого можно добиться в точности задуманного результата. Почти как Unix - можно вникнуть в любые подробности, проконтролировать и запрограммировать все лично. Условные операторы помогут выбо- рочно установить файл. Скажем, мож- но проверить наличие звуковой карты, разрешение экрана, получить доступ к переменным, содержащим пути к сис- темным папкам, имени пользователя, и так далее. В Setup Factory существу- ет набор встроенных переменных, ко- торые можно использовать для более гибкого управления проектом. Они действуют и в логических выражени- ях, и в полях менеджеров и мастеров. Runtime Support позволяет добавить в проект установку таких полезных ме- лочей, как Adobe Acrobat Reader, Чрезвычайно удобно создавать паке- ты для различных типов установки - достаточно просто пометить в списке файлов рядом с их именами check- Ьох’ы, соответствующие заданным ре- жимам инсталляции - Typical, Custom, и т.д. Предусмотрены проверки наличия ус- тановленных DirectX, QuickTime; реги- страция типов файлов; включение в проект message- и input-боксов; мощ- ная поддержка работы с внутренними переменными; ветвление процедуры установки при помощи меток и опера- торов Goto. о
Профессиональный журнал ПРОГРАММИСТ — Визуальное составление инсталляци- онного скрипта (program) путем добав- ления в него program items: диалого- вых окон, процедур всевозможных проверок, манипуляций с файловой системой и т.д. Резюме: весьма гибкий в настройке продукт, от которого можно добиться в точности задуманного результата. Почти как Unix - можно вникнуть в лю- бые подробности, все проконтролиро- вать и запрограммировать лично. Од- нако, интерфейс Installer VISE остав- ляет желать лучшего... Распространяется в двух версиях - для Windows и Mac OS. Wise InstallMaster 8.1 Производитель: Wise Solutions (www. wisesolutions.com) Дисковое пространство: 152 Мб Вот и еще один «brandname»... Попу- лярная мощная программа с передо- вым интерфейсом, состоящим из пяти следующих страниц: Files and Components - здесь задаются наборы инсталлируемых и деинстал- лируемых файлов, шрифтов, файлов поддержки Visual Basic и Visual Fox Pro, баз данных (ODBC, BDE и т.п.), библиотек DirectX, MFC и некоторых других, а также опция Smart Patch, ко- торая позволяет превратить ваш ин- сталлятор в программу установки не целого продукта, а патча. System Additions - менеджер работы с иконками, реестром, файлами иници- ализации, регистрацией типов фай- лов, и тому подобными вещами. User System Check - опции проверки системы пользователя, поиск ранее установленной версии продукта. Wizard Appearance - внешний вид инсталлятора, менеджмент пошаго- вых экранов с WYSIWYG-дизайнером окон. Последний напоминает некий гибрид IDE Delphi и Macromedia Dreamweaver, и позволяет спроекти- ровать диалоговое окно любого вида, оснастив его стандартными элемента- ми управления вроде комбобоксов, меток, кнопок и тому подобным. Advanced Functionality - задание оп- ций проверки пароля, online-инсталля- ции и различных параметров установ- ки под WinCE. Finish - тип носителя (одиночный файл, либо ориентированный на кон- кретный носитель: флоппи, CD-ROM, DVD, Zip или вообще Internet-инстал- ляция), опции сжатия дистрибутива, включения режима создания «сним- ков состояния» (snapshots) системы для Windows ME (а вдруг ваша про- грамма что-то «напортачит»?), и мно- гие другие опции. Любителям повозиться с кодом - дос- таточно перейти в Script Editor, в ко- тором доступен скрипт создания ин- сталлятора, а экспериментаторам до- ступны «мастера», которые умеют де- лать многие чудесные вещи. Напри- мер, есть мастер, осуществляющий мониторинг системы при запуске ва- шего приложения, после чего исполь- зуемые им DLL’kh и ОСХ’ы будут включены в проект инсталлятора. Говоря о продукции от Wise, нельзя обойти вниманием и другое приложе- 37 Wise InstallMaster 8.1: Популярный «brandname»; отличное качество, широкий спектр предоставляемых возможностей. Прекрасно подходит для создания дистрибутивов любых проектов, от мала до велика. и S е о
I ПРОГРАММИСТ Профессиональный журнал ние, а именно - Wise For Windows Installer Professional v3.1. Это про- граммное средство очень похоже на Wise InstallMaster, но генерирует дист- рибутивы в виде MSI или MSM-фай- лов (для стандартного Windows Installer). На всякий случай напомню, что ин- сталлятор такого вида имеют все при- ложения Win’2000, (например, MS Office 2000) - громоздкий, с древо- Astrum InstallWizard Производитель: Thraex software (www.thraexsoftware.com) Дисковое пространство: 1.4 Мб Небольшой, удобный и средний по своим возможностям продукт. Прост в использовании, умеет вклю- чать в дистрибутив файлы MP3, кото- рые играют в процессе инсталляции, а также JPEG’n для показа слайд-шоу, чтобы пользователь не скучал. 38 Astmm InstallWizard: Небольшой, удобный и средний по своим возможностям продукт. Идеальное решение для создания программы установки типичного мультимедиа- приложения: игры, программного синтезатора, редактора графических файлов. ы X e о видной структурой выбора инсталли- руемых компонентов. Интерфейс и возможности практически повторяют Wise InstallMaster, за исключением не- которых специфических для формата MSI опций, а также поддержки новой 64-битной Windows-платформы, тех- нологии Microsoft .NET framework и удобного Setup Editor’a. Последний попросту предоставляет все параметры проекта в несколько ином виде, заменяя интерфейс на бо- лее привычный, позволяющий совер- шать меньше лишних движений мы- шью. Лично мне Wise For Windows Installer показался удобнее, чем Wise InstallMaster. Резюме: генераторы инсталляторов от компании Wise отличаются отлич- ным качеством и широким спектром предоставляемых возможностей, и отлично подходят для создания дист- рибутивов любых проектов, от мала до велика. На всякий случай привожу полный перечень этих генераторов и менеджеров: Wise for Windows Installer 3, InstallMaster 8, InstallBuilder 8, InstallMaker 8, InstallManager 1.5. Обладает на редкость жизнерадост- ным интерфейсом. Свои, новые экра- ны создавать нельзя, зато можно вы- брать из уже имеющихся стандартных: какой показывать, а какой - нет. Очень удобный редактор вносимых в реестр изменений - похож на класси- ческую программу RegEdit. Большая забота проявляется о безопасности установки - в частности, отслеживает- ся конфликт версий инсталлируемых динамических библиотек, а при необ- ходимости есть возможность делать backup старых версий. Резюме: идеальное решение для соз- дания программы установки типично- го мультимедиа-приложения - игры, программного синтезатора, редактора графических файлов. GkSetup 1.92 Производитель: Gero Kuehn (www.gkware.com) Дисковое пространство: 3.6 Мб Не столь изящен, как предыдущий наш экспонат. Не так гибок, менее удобен - к интерфейсу так и липнет слово «топорный». Зато - очень даже
ПРОГРАММИСТ — Профессиональный журнал Install Maker Pro 1.3: Простой, надежный и очень быстрый генератор инсталлятора. Вероятно, лучший выбор для создания дистрибутива небольшой программки, которая будет распространяться через Internet. 39 неплохо сжимает дистрибутив, и притом бесплатен. Процесс создания инсталлятора в этой программе мало чем отличается от аналогичного в Setup Factory, и раз- бит на девять шагов - от задания ос- новных параметров проекта до посте- пенной его детализации: выбора фай- лов, работы с реестром, выполнения внешних приложений. Большой минус - нельзя выбирать от- дельные файлы. Они помещаются в проект всем скопом, из папки, кото- рую вы указываете. Даже в случае вы- борочной инсталляции, приходится складывать файлы одной категории выбора в одну папку, другой - в дру- гую, и только так. Доступа к свойст- вам установки каждого файла нет. Только к группе. Последовательность шагов-экранов инсталляции - тоже одна и неизменна. Вы можете лишь включать или отклю- чать определенные шаги. Как видим, до пакетов Setup Factory и тамошних «File Properties», восприни- маемых как должное, здесь далекова- то... Правда, имеются внутренние пе- ременные, создание автоматического деинсталлятора. Очень удобный реда- ктор ключей реестра и INI-файлов. Резюме: GkSetup может выбрать тот человек, которому нужно быстро и ка- чественно создать дистрибутив не- большого продукта с несложной стру- ктурой. Install Maker Pro 1.3 Производитель: Clickteam S.A.R.L. (www.dickteam.com) Дисковое пространство: 2 Мб Простой и надежный генератор инстал- лятора от компании, известной своим «конструктором игрушек» Klik&Play. Install Maker позволяет создать полно- ценную программу установки буквально за пару минут, причем отлично сжимая дистрибутив. Как обычно, присутствует набор видоизменяемых пошаговых эк- ранов с возможностью предварительно- го просмотра, приемлемый редактор ра- боты с реестром и INI-файлами, опции установки для каждого файла в отдель- ности, регистрация шрифтов, ОСХ-конт- ролов, инсталляция программы в качест- ве «хранителя экрана». Простенько и со вкусом. Резюме: вероятно, для создания дист- рибутива небольшой программки, ко- торая будет распространяться через Internet, лучший выбор - это Install Maker Pro. Кстати, стоит обратить вни- мание и на другие продукты от Clickteam - в частности, на бесплатную утилиту Patch Maker, предназначенную для создания дистрибутивов патчей. InstallShieid Professional 6.2 Производитель: Install Shield Corp. (www.installshield.com) Места на диске: 272 Мб Гигант из племени титанов. Умеет все - от инсталляции вместе с продуктом допол- нительных средств (вроде DirectX ил^
- ПРОГРАММИСТ Профессиональный журнал InstallShield Professional 6.2: Гигант из племени титанов. Умеет делать абсолютно все, но довольно громоздок в работе. Ориентирован прежде всего на разработчиков крупных серьезных продуктов. 40 ADO) до создания самых гибких и слож- ных программ установки. Концепция работы с ним проста, но реали- зована, на мой взгляд, слишком громозд- ко. О сути философии InstallShield - внача- ле вы собираете файлы проекта в группы, а группы подключаете к компонентам и субкомпонентам, и уж затем ассоциируете компоненты с типами установки. Идея хо- роша, но вам придется добираться к ре- зультату через тернии возникающих на пу- ти окон. Поэтому желательно иметь здоро- венный монитор - на 15" развернуть для нормальной работы окно редактора скрип- та можно, лишь уменьшив до состояния тонкой полоски панель с закладками нави- гации по элементам проекта. А панель эта - очень полезная. Нужно, на- пример, создать компоненты «Файлы про- граммы» и «Примеры» - переходим на за- кладку Components и создаем их. А у этих компонентов могут быть субкомпоненты, которые можно выбирать для установки. Более того, есть возможность прописать скрипты для событий установки компо- нента. Скриптовый язык в InstallShield мощный, похож на смесь C++ и Object Pascal, и предоставляет возможность вы- зывать функции WinAPI и внешних DLL. Именно благодаря скриптам можно соз- дать гибкий инсталлятор для сложного продукта - а для программ среднего раз- мера-со сравнительно простой структурой вряд ли стоит использовать InstallShield Pro, поскольку процесс создания дистри- бутива отнимает гораздо больше време- ни, чем в других, описанных в этом обзо- ре приложениях. Простой пример: я хочу сделать ярлык на рабочем столе для файла N. В том же Setup Factory - достаточно щелкнуть пра- вой кнопкой мыши на имени файла, и пе- рейти в свойствах на закладку Shortcut. А в InstallShield Pro тоже все вроде бы ло- гично, но громоздко: на закладке Resources, в Shell Objects I Explorer Shell I Desktop нужно войти в меню New, откуда выбрать Shortcut, и вручную заполнить ку- чу полей. Согласен, что это вполне функ- ционально. Но мне куда удобнее просто кликнуть правой кнопкой мыши, и полу- чить результат... Нужен комфорт. Посему - для большего удобства пользователей, компания пред- лагает Install Shield Express 3. А InstallShield Professional Windows Installer Edition 2.0 предназначен для тех, кто на- мерен создавать дистрибутивы формата Windows Installer. Правда, эта версия про- дукта сильно завязана на Internet Explorer и VB-скрипты, поэтому для работы важно иметь их в наличии. Резюме: продукты корпорации Install Shield (кроме Express-версии) ориентиро- ваны прежде всего на разработчиков крупных серьезных продуктов. Вот кому - раздолье, в виде 11 приложений, среди которых не только генераторы дистрибу- тивов, но и полезные утилиты. В заключение обзора - хочу привести сравнительные данные по размерам дист- рибутивов тестового проекта, полученных с помощью описанных выше продуктов: Setup Factory 5.0: 1343 кб GkSetup 1.92: 990 Кб Installer VISE 3.5: 1211 кб Wise InstallMaster 8.1: 1042 кб Astrum Install Wizard: 1073 кб Install Maker Pro 1.3: 956 кб InstallShield Pro 6.2: 1878 кб
сних программ; инсталляции, эксплуатации и интеграции офисных систем - и про- яи чей «автоматизации коммерческой деятельности». Кроме того, мы решили, что в этот же раздел будем помещать все, что касается программирования пользовательских интерф-”— “-------------------------- - менты, мевдшни; всяческие hhSiwWi, Сйрол По сложившейся традиции, эта рубрика - дл Динамично развивающейся» и «самой hobomi телекоммуникации... я по «самой нриптах, ий для расширениях HTML, программировании сайтов и локальных и глобальных сетей, и так далее. Стараясь как-то компенсировать «переизбытон» рованию в прошлых номерах, на этот раз мы сделали раздел Мы потихоньку прод| использование фреи - читателя;именно та Статья по основам безопасн но, на наш взгляд, очень даже А а следующих номерах мы у ЖЖГ“’,та8“ Раздел «Офис» у нас получается транзакций делиться ценным опытом с с амми- ным. эем публикацию циклов про Javascript и «правильное» (эти статьи рассчитаны на не слишком подготовленного «обучайки» хотят видеть многие читатели раздела «Сати»). - тоже рассчитана не на «крутых профи»; олее «серьезные» и пространства» тоже ipa проводок и астъю... Это им минус, (в ос- для на- bi, и обзор извращенной фо
в 1 ПРОГРАММИСТ пРиФессиокальный журнал Илья Басалаев а.к.a. Scarab scarab@chat.ru Club ot the developers PHP http://phpclirtuiet ПРИЕМЫ ЗАЩИТЫ ВЕБ-ПРИЛОЖЕНИЙ НА РНР Данная статья не претендует на роль всеобъемлющего руководства на тему «как сделать так, чтоб меня никто не поломал». Так не бывает. Единственная цель этой статьи - показать некоторые часто используемые приемы за- щиты веб-приложений - типа WWW-чатов, гостевых книг, веб-форумов и других приложений подобного рода. Приемы программирования рассмотрим на примере некой гостевой книги, написанной на РНР. 42 ы Первое, что необходимо сделать - это жесточайшим образом отфильтровать все данные, присланные пользователем. Первой заповедью веб-программиста, желающего написать более-менее за- щищенное веб-приложение, должно стать «Никогда не верь данным, присы- лаемым тебе пользователем». Пользо- ватели - это по определению такие злобные хакеры, которые только и ищут момента, как бы напихать в формы вво- да всякую дрянь типа РНР, JavaScript, SSI, вызовов своих жутко хакерских скриптов и тому подобных ужасных ве- щей. Поэтому первое, что необходимо сделать - это жесточайшим образом от- фильтровать все данные, присланные пользователем. Допустим, у нас в гостевой книге суще- ствует 3 формы ввода: имя пользовате- ля, его e-mail и само по себе тело сооб- щения. Прежде всего, ограничим коли- чество данных, передаваемых из форм ввода чем-нибудь вроде: cinput type=teKt name=username maKlength=20> На роль настоящей защиты это, конеч- но, претендовать не может - единствен- ное назначение этого элемента - огра- ничить пользователя от случайного вво- да имени длиннее 20-ти символов. А для того, чтобы у пользователя не воз- никло искушения скачать документ с формами ввода и подправить параметр maxlength - установим где-нибудь в са- мом начале скрипта, обрабатывающего данные, проверку переменной окруже- ния web-сервера HTTP-REFERER: <? $referer=getenu("HTTP_REFERER"); if (!ereg( ' ГШр://шшш.myseruer.com ')) { echo "hacker? he-he...\n"; . eKit; ) ?> Теперь, если данные переданы не из форм документа, находящегося на сер- вере www.myserver.com, «кул хацкеру» будет выдано деморализующее сооб- щение. На самом деле, и это тоже не может служить 100%-ой гарантией того, что данные ДЕЙСТВИТЕЛЬНО переда- ны из нашего документа. В конце кон- цов, переменная HTTP„REFERER фор- мируется браузером, и никто не может помешать хакеру подправить код брау- зера, или просто зайти телнетом на 80- ый порт и сформировать свой запрос. Так что подобная защита поможет толь- ко от Ну Совсем Необразованных Хаке- ров. Впрочем, по моим наблюдениям, около 80% процентов злоумышленников на этом этапе останавливаются и дальше не лезут - то ли IQ не позволяет, то ли просто лень. Лично я попросту вынес этот фрагмент кода в отдельный файл, и вызываю его отовсюду, откуда это возможно. Времени на обращение к пе- ременной уходит немного - а бережено- го Бог бережет. Следующим этапом станет пресловутая жесткая фильтрация переданных дан- ных. Прежде всего, не будем доверять переменной maxlength в формах ввода - и ручками порежем строку: $username=substr($username,0,20); Не дадим пользователю использовать пустое поле имени - просто так, чтобы не давать писать анонимные сооб- щения: if (empty(Susername)) { echo "inualid username "; eKit; } Запретим пользователю использовать в своем имени любые символы, кроме букв русского и латинского алфавита, знака «_» (подчеркивание), пробела и цифр: if (preg_match( 7[~(\iu)I(\k7F-\kFF)|(\s)]/ ", Susername)) { echo "inualid username"; eKit; }
ПРОГРАММИСТ - Профессиональный журнал Я предпочитаю везде, где нужно что-ни- будь более сложное, чем проверить нали- чие шаблона в строке или поменять один шаблон на другой, использовать Перл- совместимые регулярные выражения (Perl-compatible Regular Expressions). То же самое можно делать и используя стандартные РНР-шные ereg() и eregi(). Я не буду приводить здесь эти примеры - все достаточно подробно описано в ма- нуале. Для поля ввода адреса e-mail добавим в список разрешенных символов знаки «@» и «.» (иначе пользователь не сможет корректно ввести адрес). Зато уберем русские буквы и пробел: if (preg_match("/[л(\ш)|(\@)|(\.)]/", SusermaiD) { echo "inualid mail "; eKit; ) Поле ввода текста мы не будем подвер- гать таким жестким репрессиям - пере- бирать все знаки препинания, которые можно использовать, попросту лень; поэ- тому ограничимся использованием функ- ций nl2br() и htmlspecialchars() - это не даст врагу понатыкать в текст сообщения html-тегов. Некоторые разработчики, на- верное, скажут: «а мы все-таки очень хо- тим, чтобы пользователи могли встав- лять теги». Если сильно неймется - мож- но сделать некие тегозаменители, типа «текст, окруженный звездочками, будет высвечен bold'OM». Но никогда не следу- ет разрешать пользователям использо- вание тегов, подразумевающих подклю- чение внешних ресурсов - от тривиаль- ного <img> до супернавороченного <bgsound>. Как-то раз меня попросили потестиро- вать html-чат. Первым же замеченным мной багом было именно разрешение вставки картинок. Стоило учесть еще па- ру особенностей строения чата - и через несколько минут у меня был файл, в ко- тором аккуратно были перечислены IP- адреса, имена и пароли всех присутство- вавших в этот момент в чате пользовате- лей. Как? Да очень просто - чату был по- слан тег <img src=http://myserver.com/ myscript.pl>, в результате чего браузеры всех пользователей, присутствовавших в тот момент на чате, вызвали скрипт myscript.pl с хоста myserver.com (а людей, сидевших под 1упх'ом, там не было :). Скрипт же, перед тем как выдать location на картинку, свалил мне в лог-файл по- ловину переменных окружения - в част- ности QUERY_STRING, REMOTE_ADDR и других. Для каждого пользователя; с вы- шеупомянутым результатом... Посему мое мнение - да, разрешить вставку html-тегов в чатах, форумах и гостевых книгах - это красиво, но игра не стоит свеч - вряд ли пользователи пой- дут к вам в форум или чат, зная, что их IP может стать известным первому встреч- ному хакеру. Да и не только IP - возмож- ности javascript я перечислять не буду :) Для примитивной гостевой книги - пере- численных средств хватит, чтобы сде- лать ее более-менее сложной для взло- ма. Однако, для удобства книги обычно содержат некоторые возможности для модерирования - как минимум, возмож- ность удаления сообщений. Разрешен- ную, естественно, узкому (или не очень) кругу лиц. Посмотрим, что можно сде- лать здесь. Допустим, вся система модерирования книги также состоит из двух частей - страницы со списком сообщений, где можно отмечать подлежащие удалению сообщения, и непосредственно скрипта, удаляющего сообщения. Назовем их, со- ответственно, adminl.php и admin2.php. Простейший и надежнейший способ ау- тентификации пользователя - размеще- ние скриптов в директории, защищенной файлом .htaccess. Для преодоления та- кой защиты нужно уже не приложение ломать, а web-сервер. Что несколько сложнее и уж, во всяком случае, не укла- дывается в рамки этой статьи. Однако, такой способ не всегда пригоден к упот- реблению - иногда нужно все-таки про- водить авторизацию средствами самого приложения. Первый, самый простой способ - автори- зация средствами HTTP, через код 401. При виде такого кода возврата, любой нормальный браузер высветит окошко авторизации и попросит ввести логин и пароль. А в дальнейшем браузер при по- лучении кода 401 будет пытаться подсу- нуть web-серверу текущие для данного realm'a логин и пароль, и только в случае неудачи - потребует повторной авториза- ции. Пример кода для вывода требования на такую авторизацию есть во всех хресто- матиях и мануалах: if (!isset($PHP_RUTH_USER)J { Header("ШШШ-Ruthenticate: Basic realm=\"My Realm\"'); Header!"HTTP/1.0 401 Unauthorized "); eKit; ) Разместим этот кусочек кода в начале скрипта adminl.php. к Никогда не следует разрешать пользователям использование тегов, подразумевающих подключение внешних ресурсов - от тривиального <img> до навороченного <bgsound>. 43
ПРОГРАММИСТ Профессиональный журнал 44 После его выполнения, у нас бу- дут две установленные переменные: $PHP_AUTH_USER и $PHP_AUTH_PW, в которых, соответственно, будут ле- жать имя и пароль, введенные пользо- вателем. Их можно, к примеру, прове- рить по SQL-базе: *** Внимание!!!*** В приведенном ниже фрагменте кода сознательно допущена серьезная ошиб- ка в безопасности. Попытайтесь найти ее самостоятельно. $sql_statement= "select passuiord from people inhere name='$PHP_RUTH_USER '; $result = mysql($dbname, $sql_statement); Srpasstuord = mysql_result!$result,0, passiuord'); $sql_statement = “select passiBord('$PHP_AUTH_PW) $result = mysql($dbname, $sql_statement); $passiuord = mysql_result($result,0); if (Spassword != Jrpassmord) { Header! "HTTP/1.0 401 Ruth Required "); Header! "ШШШ-authenticate: basic realm=\"My RealmV"'); eKit; ) Упомянутая ошибка, между прочим, очень распространена среди начинаю- щих и невнимательных программистов. Когда-то я сам поймался на эту удочку - по счастью, особого вреда это не при- несло (не считая нескольких нецензур- ных фраз, оставленных хакером в ново- стной ленте). Итак, раскрываю секрет: допустим, ха- кер вводит заведомо несуществующее имя пользователя и пустой пароль. При этом в результате выборки из базы пе- ременная Srpassword принимает пустое значение. А алгоритм шифрования па- ролей при помощи функции СУБД MySQL Password!) - так же, впрочем, как и стандартный алгоритм Unix - при попытке шифрования пустого пароля возвращает пустое значение. В итоге - Spassword == Srpassword, условие вы- полняется, и взломщик получает доступ к защищенной части приложения. Ле- чится это либо запрещением пустых па- ролей, либо, на мой взгляд, более пра- вильным путем - вставкой следующего фрагмента кода: if (mysql_numrouis($result) != 1) { Header! "HTTP/1.0 401 Ruth Required"); Header! "ШШШ-authenticate: basic realm=\"My RealmV""); eKit; ) To есть - проверкой наличия одного и только одного пользователя в базе. Ни больше, ни меньше. Точно такую же проверку на авториза- цию стоит встроить и в скрипт admin2.php. По идее, если пользователь хороший человек - он приходит к admin2.php через adminl.php, а значит, уже является авторизованным и ника- ких повторных вопросов ему не будет - браузер втихомолку передаст пароль. Если же нет - ну, тогда и поругаться не грех. Скажем, вывести ту же фразу «hacker? he-he...». К сожалению, не всегда удается вос- пользоваться алгоритмом авторизации через код 401, и приходится выполнять ее только средствами приложения. В общем случае модель такой авториза- ции будет следующей: • Пользователь один раз авторизуется при помощи веб-формы и скрипта, который проверяет правильность имени и пароля. • Остальные скрипты защищенной ча- сти приложения каким-нибудь обра- зом проверяют факт авторизованно- сти пользователя. Такая модель называется сессионной - после прохождения авторизации откры- вается так называемая «сессия», в те- чение которой пользователь имеет дос- туп к защищенной части системы. Сес- сия закрылась - доступ закрывается. На этом принципе, в частности, строит- ся большинство www-чатов: пользова- тель может получить доступ к чату толь- ко после того, как пройдет процедуру входа. Основная сложность данной схе- мы заключается в том, что все скрипты защищенной части приложения каким- то образом должны знать о том, что пользователь, посылающий данные, ус- пешно авторизовался. Рассмотрим несколько вариантов, как это можно сделать: 1. После авторизации все скрипты за- щищенной части вызываются с неким флажком вида adminmode=1. (Не надо смеяться - я сам такое видел). Ясно, что любой, кому известен флажок adminmode, может сам сформировать URL и зайти в режиме администрирова- ния. Кроме того - нет возможности от- личить одного пользователя от другого. 2. Скрипт авторизации может каким-ни- будь образом передать имя пользовате-
Профессиональный журнал ПРОГРАММИСТ - ля другим скриптам. Распространено во многих www-чатах - для того, чтобы от- личить, где чье сообщение идет, рядом с формой типа text для ввода сообще- ния, пристраивается форма типа hidden, где указывается имя пользователя. То- же ненадежно, потому что хакер может скачать документ с формой к себе на диск и поменять значение формы hid- den. Некоторую пользу здесь может принести вышеупомянутая проверка HTTP_REFERER - но, как я уже гово- рил, никаких гарантий она не дает. 3. Определение пользователя по 1Р-ад- ресу. В этом случае, после прохождения авторизации, где-нибудь в локальной базе данных (sql, dbm, да хоть в txt-фай- ле) сохраняется текущий IP пользовате- ля, а все скрипты защищенной части смотрят в переменную REMOTE_ADDR и проверяют, есть ли такой адрес в ба- зе. Если есть - значит, авторизация бы- ла, если нет - «hacker? he-he...» :-) Это более надежный способ - не пройти авторизацию и получить доступ удастся лишь в том случае, если с того же IP си- дит другой пользователь, успешно авто- ризовавшийся. Однако, учитывая рас- пространенность прокси-серверов и IP- Masquerad'HHra - это вполне реально. 4. Единственным известным мне про- стым и достаточно надежным способом верификации личности пользователя является авторизация при помощи ran- dom uid. Рассмотрим ее более подроб- но: После авторизации пользователя скрипт, проведший авторизацию, гене- рирует достаточно длинное случайное число: mt_srand((double)microtime()*1000000); $uid=mt_rand(1,1000000); Это число он: а) заносит в локальный список автори- зовавшихся пользователей; б) выдает пользователю. Пользователь при каждом запросе, по- мимо другой информации (сообщение в чате, или список сообщений в гостевой книге), отправляет серверу свой uid. При этом в документе с формами ввода будет присутствовать, наряду с другими формами, тег вида: <input type=hidden name=uid ualue=1 234567890> Форма uid невидима для пользователя, но она передается скрипту защищенной части приложения. Тот сличает пере- данный ему uid с uid'oM, хранящимся в локальной базе и либо выполняет свою функцию, либо... Единственное, что необходимо сделать при такой организации - периодически чистить локальный список uid'oB и/или сделать для пользователя кнопку «вы- ход», при нажатии на которую локаль- ный uid пользователя сотрется из базы на сервере - сессия закрыта. Некоторые программисты используют в качестве uid не «одноразовое» динами- чески генерирующееся число, а пароль пользователя. Это допустимо, но явля- ется «дурным тоном», поскольку пароль пользователя обычно не меняется от сессии к сессии, а значит - хакер смо- жет сам открывать сессии. Та же самая модель может быть использована вез- де, где требуется идентификация поль- зователя - в чатах, веб-конференциях, электронных магазинах. В заключение стоит упомянуть и о такой полезной вещи, как ведение логов. Ес- ли в каждую из описанных процедур встроить возможность занесения собы- тия в лог-файл с указанием IP-адреса потенциального злоумышленника - то в случае реальной атаки вычислить хаке- ра будет гораздо проще, поскольку ха- керы обычно пробуют последовательно усложняющиеся атаки. Для определе- ния IP-адреса желательно использовать не только стандартную переменную REMOTE_ADDR, но и менее известную HTTP_X_FORWARDED_FOR, которая позволяет определить IP пользователя, находящегося за прокси-сервером. Ес- тественно - если прокси это позволяет. При ведении лог-файлов - необходимо помнить, что доступ к ним должен быть только у вас. Лучше всего, если они бу- дут расположены за пределами дерева каталогов, доступного через \NW\N. Ес- ли нет такой возможности - создайте отдельный каталог для лог-файлов и закройте туда доступ при помощи .htaccess (Deny from all). Я буду очень признателен, если кто-ни- будь из программистов поделится свои- ми - не описанными здесь - методами обеспечения безопасности при разра- ботке приложений для Web. P.S. Выражаю глубокую благодарность Козину Максиму (madmax@express.ru) за рецензирование данной статьи и ряд весьма ценных дополнений. л 45
ПРОГРАММИСТ Профессиональный журнал Третьяков М.Ю. Тимофеев И.Ю. Компания Ключ. ФРЕЙМЫ СОГЛАСОВАНИЕ СОСТОЯНИЯ В предыдущей статье («Проблема адресной строки», «Программист» №2) мы разобрались с неизменяемостью адресной строки браузера при использовании фреймов; теперь постараемся решить проблемы синхронизации состояния разных фреймов. Основные задачи здесь следующие: - загрузка страницы в один фрейм в зависимости от текущей страницы в другом фрейме - выделение текущего элемента во фрейме-меню в зависимости от того, какая страница загружена в основной фрейм. Как обычно, постараемся дать готовые практические решения, в двух возможных реализациях: на стороне клиен- та (с использованием JavaScript) и на стороне сервера (с помощью ASP-технологии). 46 Наш подход отличается тем, что мы передаем управление по созданию фреймовой структуры информационным страницам сайта - предоставляя каждой странице возможность самостоятельно определить необходимую ей структуру. Сначала напомним основные идеи фрей- мовой структуры. Фреймы - это независимые области, на которые разбивается окно браузера. Как правило, один из фреймов является ос- новным (в нем размещаются информа- ционные страницы), а остальные фрей- мы - вспомогательными (содержат эле- менты типа меню, заголовков, оглавле- ний). При общепринятом подходе построение структуры идет сверху вниз: сначала ок- но разбивается на области-фреймы, за- тем - для каждого фрейма указывается страница, которая будет в него загруже- на. Таким образом, сами страницы никак не влияют на свое размещение. Наш подход отличается от общепринято- го тем, что мы передаем управление по созданию фреймовой структуры инфор- мационным страницам сайта - предоста- вляя каждой странице возможность са- мостоятельно определить необходимую ей структуру. Теперь - рассмотрим структуру учебного сайта, которую мы будем использовать в этой статье: Окно делится на три фрейма: menu, toe (table of contents - оглавление) и main. Фрейм menu - статичный, и всегда со- держит страницу menu.htm, в которой описывается меню сайта. Фрейм toe со- держит оглавление основной, информа- ционной страницы, которая отображает- ся во фрейме main. Для описания такой структуры сайта ис- пользуется следующий код: <frameset roius= "4В,*'"> <frame name= "menu" src='menu.htm"> <frameset cols="4B,*‘> <frame name="toc" src="toc.htm"> <frame name="main" src="'indeK.htm"> </frameset> </frameset> Во-первых - возникает задача синхрони- зации оглавления и основной страницы: при загрузке основной страницы ей необ- ходимо определить, какая страница бу- дет загружена во фрейм toe (в данном случае мы полностью перегружаем стра- ницу во фрейме toe). А во-вторых - если наш сайт содержит материалы, принадлежащие различным разделам - надо бы предусмотреть вы- деление текущего раздела в меню (хо- чется, чтобы при загрузке страницы оп- ределенного раздела этот раздел в меню выделялся и отличался от остальных). При этом сам файл меню остается неиз- менным, а мы просто меняем параметры его отображения. Перед тем, как представить решение описанных задач - кратко напомним об- щий принцип управления фреймовой структурой. Полное описание содержит- ся в предыдущей статье («Фреймы - про- блема адресной строки»). Если вы уже знакомы с реализацией этого подхода - можете пропустить данный раздел.
Профессиональный журнал ПРОГРАММИСТ — Управление фреймовой структурой Общий принцип решения - в том, что ос- новная, информационная страница при загрузке всегда проверяет свое состоя- ние. Если страница не обнаруживает тре- буемой фреймовой структуры, то снача- ла эта структура создается, а уже затем происходит реальная загрузка страницы. В предыдущей статье мы описывали ре- шения на стороне клиента и на стороне сервера, но суть их была одна и та же. В обоих случаях создавалась отдельная функция CheckFrames, которая содержа- ла код по проверке и созданию фреймо- вой структуры. Эта функция затем вызы- валась в начале загрузки каждой инфор- мационной страницы. Таким образом, мы минимизировали изменения, вносимые в каждую страницу. Представленные ниже реализации не- много отличаются от тех, что были приве- дены в предыдущей статье - отражены изменения в структуре нашего «учебного сайта». Реализация на стороне клиента Функция CheckFrames занимается про- веркой и созданием (при необходимости) фреймовой структуры; т.к. она использу- ется во всех страницах, мы вынесли ее в отдельный файл frames.js. function CheckFrames! if (mindoiu.name != "main"){ uar PageURL = document.URL; unndow.name="root"; document.iurite("<frameset roius=’40,*’>"); document.u>rite("<frame name=’menu’ src=’menu.htm’>"); document.mrite("<frameset cols=’40,*’>"); document.write("<frame name=’toc’ src=’toc.htm’>"); document.iurite( "<frame name=’main’ src=’" + PageURL + "?embedded=yes’>"); document.mrite("</frameset>"); document.iurite("</frameset>"); } } В каждую страницу сайта между заголов- ком и телом нужно добавить следующий код: <script language="JauaScript“ src=“frames.js“> </script> <script language="Jai)a$cript"> CheckFramesO; </script> Реализация на стороне сервера Как и в случае с реализацией на стороне клиента, реализация функции проверки и создания фреймовой структуры вынесе- на в отдельный файл - frames.inc, кото- рый затем будет подключаться ко всем страницам сайта. <% function CheckFramesO if(Request.QueryString('embedded').Count=0) then CheckFrames = False PageURL = "http://" & Request.SeruerUariables("seruer_name") & Request.SeruerUariables("script_name") Response.ujrite("<frameset rotus=’40,*’>") Response.mrite("<frame name=’menu’ src=’menu.asp’>") Response.mrite(“<frameset cols=’40,*’>") Response.iurite( "<frame name=’toc’ src=’toc.asp’>") Response.mrite( "<frame name=’main’ src=’“ + PageURL + "?embedded=yes’>") Response.mrite("</frameset>“) Response.u)rite('</frameset>") else CheckFrames = True end if end function %> Если страница не обнаруживает требуемой фреймовой структуры, то сначала эта структура создается, а уже затем происходит реальная загрузка страницы. 47 В начале каждой страницы между заго- ловком и телом надо добавить следую- щий код: <!-#include uirtual="frames.inc'-> <% if not CheckFramesO then Response.End()%> Синхронизация оглавления и основной страницы Решение задачи синхронизации оглавле- ния в рамках нашего подхода оказывает- ся на удивление простым; достаточно слегка изменить код, реализующий упра- вление фреймовой структурой. Мы добавим в функцию CheckFrames па- раметр, указывающий адрес страницы оглавления, которую нужно загрузить. А каждая информационная страница будет содержать адрес «своего» оглавления, и передавать его в качестве параметра Реализация на стороне клиента Во-первых, мы изменим прототип мето- да CheckFrames следующим образом:
ПРОГРАММИСТ Пр офессиональный журнал 48 function CheckFrames(TOC_URL){ } Во-вторых, изменим способ определения страницы для фрейма toe. function CheckFrames(TOC_URL){ if (ujindoiu.name != main'H uar PageURL = document.URL; ioindoiu.name= "root"; document.iurite(''<frameset roujs=’40,*’>"); document.ujrite("<frame name=’menu’ src=’menu.htm’>"); document.ujrite( '<frameset cols=’40,*’> '); document.iurite( "<frame name=’toc’ src=’“ + TOCURL + “*>“); document.ujrite('<frame name=’main’ src=’" + PageURL + ‘?embedded=yes’> document.ujrite("</frameset>"); document.U)nte("</frameset> } ) Как видите - изменения минимальны; теперь осталось еще изменить вызов функции CheckFrames: <script language^ "JauaScript "> uarTOC_URL = "toc.htm"; CheckFrames(TOC_URL); </script> Мы добавили определение переменной, содержащей адрес страницы-оглавле- ния; ее значение потом передается в функцию, проверяющую фреймовую структуру. Предложенное здесь решение - наибо- лее простое с точки зрения программи- рования; но оно требует изменения зна- чения переменной TOC_URL в каждой информационной странице. Чтобы этого избежать, можно принять определенный стандарт именования страниц-оглавле- ний; например, добавлять префикс toc_ к названию страницы оглавления. В этом случае можно написать универ- сальный скрипт, получающий адрес страницы-оглавления из адреса самой информационной страницы; и вставлять этот скрипт во все страницы без малей- ших изменений. Можно даже восполь- зоваться для этих целей механизмом SSI (Server Side Includes - вставки на стороне сервера). Реализация на стороне сервера Решение на стороне сервера потребует тех же самых изменений, что и в случае с реализацией на стороне клиента. Мы из- меним сигнатуру функции, а также спо- соб определения адреса страницы-оглав- ления: <% function CheckFrames(TOC_URL) if(Request.QueryString('embedded').Count=0) then CheckFrames = False PageURL = “http://" & Request.SeruerUariables("seruer_name ) & Request.SeruerUariables("script_name") Response.mrite("<frameset roius=’40,*’>") Response.mrite("<frame name=’menu’ src=’menu.asp’>') Response.ujrite(' <frameset cols=’40,*’>') Response.iurite("<frame name=’toc’ src=’"+TOC_URL+“’>") Response.iurite("<frame name=’main’ src=’" + PageURL + "?embedded=yes’>') Response. mrite("</frameset>") Response.iurite("</frameset>") else CheckFrames = True end if end function %> Теперь нужно изменить основные стра- ницы, добавив на каждую адрес ее стра- ницы-оглавления: <!—#include uirtual="frames.inc"-> <% TOC_URL = “toe.asp" %> <% if not CheckFrames(TOC_URL) then Response.End()%> Выделение текущего элемента в меню В этом случае мы могли бы поступить так же, как и в случае с оглавлением - заве- дя столько страниц, сколько у нас разде- лов, и предоставив каждой странице за- гружать свой собственный вариант ме- ню. Но это не самое удачное решение - возрастает общее количество файлов на сайте, и, главное, ухудшается сопровож- даемость - ведь при любом изменении меню (вставка и удаление разделов, из- менение оформления) придется править файлы. Поэтому давайте лучше рассмотрим дру- гой способ решения этой задачи. Будем использовать только одну страницу-меню и вносить изменения в механизм ее ото- бражения - в зависимости от того, како- му разделу соответствует текущая ин- формационная страница, загруженная во фрейм main. ы
Профессиональный журнал ПРОГРАММИСТ — Каждый элемент в меню будет представ- лен в виде блока следующей структуры: <span id="topic_id"> <а href= "topic.htm ">Topic</ax/span> Для выделения текущего элемента - вос- пользуемся возможностями каскадных таблиц стилей (CSS, Cascade Style Sheets) и изменим цвет фона; а также бу- дем выводить название раздела полу- жирным шрифтом: {background-color: #FF00B0; font-weight: bold) Для того, чтобы выделение действовало только на нужный нам элемент, мы будем генерировать таблицу стилей при загруз- ке страницы меню; а для задания эле- мента, к которому мы будем применять этот стиль, воспользуемся ID-селектора- ми таблиц стилей. Именно поэтому для каждого элемента меню в теге span за- дан атрибут id, являющийся его иденти- фикатором. Получим следующий код: <style> #topic_id {background-color: #FF0000; font-weight: bold) </style> Идентификатор текущего элемента бу- дем передавать через параметр адрес- ной строки, который будет разбираться внутри страницы меню. Собственно ре- шение будет заключаться в том, что мы опять расширим функцию проверки и со- здания фреймовой структуры, а также реализуем динамическое создание таб- лицы стилей для выделения текущего элемента внутри страницы-меню. Реализация на стороне клиента Для передачи идентификатора текущего раздела в меню мы изменим функцию CheckFrames следующим образом: function CheckFrames(TOC_URL, topic){ if (window.name != "main'){ uar PageURL = document.URL; window.name= "root"; document. write("<frameset rows=’4B,*’? "); document.write("'<frame name=’menu’ src=’menu.htm?topic-"' + topic + '">"); document.write( "<frameset cols=’4B,*’> "); document.writef "<frame name=’toc’ src=’" +TOC_URL + “>”); document.iurite( '<frame name=’main’ src=”' + PageURL + "?embedded=yes’>"); document.write("</frameset>‘); document. iurite( "</frameset> ) } Мы добавили в функцию новый параметр topic, значение которого используется для формирования адресной строки страницы-меню. Изменения основных страниц - анало- гичны тем, что мы проделали в случае синхронизации оглавления с основной страницей: <script language= 'JauaScript > uarTOC_URL = "toc.htm"; uar topic - "topicl"; CheckFrames(TOC_URL, topic); </script> Страница меню 49 Новая реализация функции CheckFrames получает текущего раздела в виде пара- метра в адресной строке. Нам нужно пе- редать ей значение текущего раздела и сформировать таблицу стилей. Разместим скрипт, формирующий табли- цы стилей, между заголовком и телом страницы. <html> <head> </head> <script language="JauaScript"> </script> <body> </bodg> </html> Сначала получим значение текущего раз- дела. Для этого воспользуемся свойст- вом document.location, возвращающим текущую адресную строку; преобразова- ния выполним с помощью регулярных вы- ражений: <script language= "JauaScript"> uar гв - /\?.*topiC"([A&]*)/i; uar arr - re.exec(document.location); If ((arr I- null) && (arr.length>-2)){ uar topic arr[ 1 ]; } </script> После выполнения этого кода - в перемен- ной topic будет находиться значение теку- щего раздела. ►
1Р0ГРАММИСТ Профессиональный журнал 50 Теперь нам осталось только сформиро- вать таблицу стилей с помощью метода document.write. <script language= 'JauaScript "> uar re = /\?.*topic=([~&]*)/i; uar arr = re.enecfdocument.location); if ((arr != null) && (arr.length>=2)) { uar topicjd = arr[1 ]; document, write) "<style>"); document.write) "#" + topic + "{background-color: #FF0000; font-weight: bold)"'); document. write)"</style>"); } </script> Все. Реализация на стороне клиента завершена. Реализация на стороне сервера Изменения в функции CheckFrames полно- стью аналогичны тем, что мы внесли при реализации на стороне клиента: <% function CheckFrames(TOC_URL, topic) if(Request.QueryString) "embedded ").Count=0) then CheckFrames = False PageURL = "http://" & Request.SerueriJariables) "seruer_name") & Request.SeruerUariables("'script_name') Response.write)"'<frameset rows=’40,*’>“) Response.write) "<frame name=’menu’ src=’menu.asp?topic=’" + topic + '“>"’) Response.write) "<frameset cols=’40,*’>") Response.write) '<frame name=’toc’ src=’"+TOC_URL+'”>") Response.write) '<frame name=’main’ src=’“ + PageURL + "?embedded=yes’>") Response.write) '</frameset>') Response.write) '</frameset>") else CheckFrames = True end if end function %> Далее - повторим (ставшую уже стандарт- ной) процедуру изменения основных стра- ниц: <!-#include uirtual= "frames.inc "-> <% TOC_URL = "toe.asp'" %> <% topic = "topic" %> <% if not CheckFrames(TOC_URL, topic) then Response.End()%> Страница меню Реализация страницы меню на стороне сервера сильно отличается от ее реализа- ции на стороне клиента, благодаря серви- су, предоставляемому ASP. Представлен- ный ниже код необходимо разместить ме- жду заголовком (head) и телом страницы menu.asp: <% if not (Request.QueryString) "topic").Count = 0) then %> <style> <% topic = Request.QueryString) "topic")))) %> <% Response.write)"#” + topic + " {background-colon #FF0000; font-weight: bold)") %> </style> <% end if %> Коллекция Request.QueryString предос- тавляет доступ к параметрам,переданным в адресной строке. Благодаря этому - по- лучение значения текущего раздела суще- ственно упрощается по сравнению с реа- лизацией на стороне клиента. Затем, с по- мощью метода Response.write, мы запи- сываем таблицу стилей. Все. Реализация на стороне сервера пол- ностью выполнена... Заключение Предлагаемый метод организации фрей- мовой структуры с передачей управления на основные информационные страницы позволяет легко решать многие типичные задачи - в том числе и задачи согласова- ния состояния нескольких фреймов. Мы рассмотрели две такие задачи, для ко- торых представили реализации как на сто- роне клиента, так и на стороне сервера. Суть решений при этом оставалась одна и та же; для их реализации требовалось приложить сходные, весьма незначитель- ные, усилия. Поэтому выбор конкретной реализации зависит только от возможностей сервера, на котором будет располагаться веб-сайт и от личных предпочтений разработчика. Свои замечания и предложения по данной статье, пожалуйста, отправляйте по адре- су ask@key.kts.ru. ф о
Профессиональный журнал ПРОГРАММИСТ ПОНЯТНЫЙ JAVASCRIPT (часть 2) Михаил Мельников Web-studio "Cherry-Design" www.cherry-designspb.ni В первом номере журнала мы начали публикацию серии статей по JavaScript «для начинающих». В этом номере - разберемся с рядом более сложных вопросов. Пройдем по шагам, через все стадии - от постановки задачи до написания универсального скрипта, готового к употреблению. к г г Г L Проверка формы перед отправкой на сервер Общие соображения и HTML-код формы Проверка формы - пожалуй, одна из наи- более часто применяемых функций. Ред- кий сайт обходится без какой-либо ее ва- риации, будь то простая отсылка сообще- ния на e-mail или форма сложного заказа в интернет-магазине. Польза такого скрип- та очевидна - он проверит, что пользова- тель заполнил все нужные поля перед от- правкой, и тем самым решит проблему пу- стых писем (или, например, отсутствия контактной информации отправителя). Предположим, что форма у нас уже есть и состоит из следующих 4-х полей: имя, электронный адрес, тема сообщения и его текст. HTML-код для такой формы будет выглядеть примерно так: <form action="/cgi-bin/formmail.cgi" onsubmit= "return SendForm();"> Ваше имя: *<input tgpe="te«t" name="name"xbr> Электронный адрес: *<input tgpe="teKt" name="email"xbr> Тема сообщения: <input type="text" name= "subject "xbr> Сообщение: <te»tarea name="message"> </teKtareaxbrxbr> <input type="submit" иа1ие="0тправить"> <input tgpe="reset" иа1ие="0чистить"> * - необкодимые для заполнения поля </form> Заметьте, что в отличие от обычной фор- мы, непосредственно в теге <form> мы от- слеживаем событие onsubmit - вызывая при его наступлении функцию проверки SendForm(). Почему выбран именно такой способ вы- зова функции? Ведь можно было, напри- мер, применить событие onClick? Ответ простой - при использовании события onClick кнопку «Submit» придется заме- нить обычной кнопкой. И в случае, если в браузере отключена поддержка JavaScript, мы не сможем отправить форму (I). Если же отслеживается событие onSubmit, то даже при отключенной поддержке скрип- тов форма будет отправлена. Будем считать, что необходимых для за- полнения полей у нас всего два: имя посе- тителя и его электронный адрес. Если вы присмотритесь к HTML-коду нашей фор- мы, то заметите, что рядом с этими поля- ми я поставил звездочку, а в конце формы разместил сноску. Сделано это, разумеет- ся, для удобства и из элементарного ува- жения к пользователю. При использовании события onClick кнопку «Submit» придется заменить обычной кнопкой. Если в браузере отключена поддержка JavaScript, мы не сможем отправить форму! Если же отслеживается событие onSubmit, даже при отключенной поддержке скриптов - форма будет отправлена. 51 Функция проверки формы перед отправкой А теперь перейдем к главному - к написа- нию той самой функции, что будет непо- средственно осуществлять проверку фор- мы. Давайте подумаем, что нам от нее тре- буется? Ну, во-первых, проверить, что нужные поля заполнены, а во-вторых, ото- слать форму. В случае же, если несколько из обязательных полей пусты, нам нужно сообщить об этом пользователю - и пере- местить курсор на соответствующий эле- мент. Для начала напишем общую обвязку функции: <script language="JauaScript"> <!- function SendFormO { // Здесь мы разместим код функции return true; } //-> </script> Функция вызывается через событие onSubmit - значит, при окончании своей работы она должна вернуть логическое^
ПРОГРАММИСТ Профессиональный журнал 52 Что, если нужно проверить несколько десятков полей формы? Лучше модифицировать функцию проверки формы, так, чтобы она не зависела от количества проверяемых полей. значение - true или false. В зависимости от этого значения, форма либо будет от- правлена, либо нет. Теперь напишем саму функцию провер- ки, привязанную к данной конкретной форме. Как вы помните, необходимых для заполнения полей у нас всего два: имя посетителя и его электронный адрес. Самое простое - проверить содержимое каждого из обязательных полей на отсут- ствие текста: function SendFormO { if (document.forms[0].name.ualue == "") { а1егК‘Пожалуйста, введите ваше имя’); document.mailform.name.focusO; return false ) if (document.forms[0).email.ualue == "") { а1егП‘Пожалуйста, введите электронный адрес’); document.mailform.email.focusO; return false } return true; } Как видите, здесь два идентичных блока, различающихся только именем проверя- емого поля. Давайте прокомментируем каждую строчку в таком блоке: сначала проверяем, что данное поле является пустым; если это так, то выводим сообщение об ошибке при по- мощи встроенной функции alert(); пос- ле того, как пользователь закроет окошко, мы воспользуемся методом focus() и пере- местим курсор на ошибочное поле, и, наконец, выйдем из функции, устано- вив флажок успешности выполнения в false. В случае, если проверяемое поле не бы- ло пустым - соответствующий блок про- сто пропускается. При пропуске всех про- верочных блоков функция в качестве ре- зультата возвращает true, что свидетель- ствует об успешной проверке. Универсальная функция проверки Если нам нужно проверить всего два или три поля, то с таким методом проверки «по одиночке» еще можно смириться; но что, если их несколько десятков? А ведь такое не редкость - особенно в сложных анкетах. Поэтому мы немного модифици- руем нашу функцию, чтобы она не зави- села от количества проверяемых полей. Первым делом - создадим массив, где перечислим имена всех полей, которые требуют проверки: required = пеш ЯггауС'пате", "email"); Данный способ позволит изменять спи- сок обязательных полей, не трогая кода самой функции. Теперь добавим еще один массив кото- рый будет хранить текст ошибки для кон- кретного поля: required_shoiu = пеш НггауС'Ваше имя", "электронный адрес"); Это позволит нам выводить сообщения об ошибках в более понятном виде, не- жели просто указание имени ошибочного поля. Имея массив обязательных для заполне- ния полей, всю проверку мы осуществим в цикле. Вот как будет выглядеть моди- фицированная функция проверки: <script language="JauaScript“> <!— required = пеш RrrayC'name", "email"); required_shom = пеш НггауС'Ваше имя", "электронный адрес"); function SendForm О { uar i, j; for(j=0; jcrequired.length; J++) { for (i=0; ^document.formslB].length; i++) { if (document.forms[01.elements[i].name == requiredlj] && document.forms[0].elements[i].ualue __ Illi j { а1егЦ‘Пожалуйста, введите * + required_shoiu[j]); document.forms[0].elements[i].focus(); return false; } ) } return true; ) //-> </script> В цикле все поля формы сначала прове- ряются на совпадение с «обязательны- ми». Если оно произошло, то проверяет-
Профессиональный журнал ПРОГРАММИСТ 1 ** ся и заполненность поля (точно так же, как в предыдущем примере). Единствен- ное отличие - функция alert(), в которой сообщение об ошибке, формируется уже на основе «массива ошибочных сообще- ний», используя в качестве индекса по- рядковый номер проверяемого поля. Вот, в общем-то, и все. Данная функция вполне универсальна, и с минимальными корректировками (меняется только со- держимое двух массивов) может быть адаптирована к любой форме. Эффект RollOver Эффект, который мы сейчас рассмотрим, является, пожалуй, самым распространен- ным. Он заключается в смене изображе- ния при наведении на него курсора мыш- ки. Часто можно слышать английское на- звание эффекта - RollOver, что обычно пе- реводят как “перекатывание». Данный пе- ревод мне не кажется удачным, поэтому я остановлюсь на использовании английско- го варианта Эффект может встречаться в различных вариантах, самые распространенные из которых - это: подсвечивание пунктов меню бегающий указатель сменяющаяся картинка Несмотря на внешне различное проявле- ние этих эффектов, все они реализуются одинаково и отличаются лишь в мелочах. Давайте по порядку разберем каждый из этих вариантов. Подсвечивание пунктов меню Наиболее часто встречающаяся реализа- ция эффекта RollOver. Его смысл заклю- чается в следующем: при наведении кур- сора мышки на пункт меню изображение подменяется на другое, а когда курсор покидает его - восстанавливается перво- начальная картинка. Этот эффект можно посмотреть в дейст- вии на сайте http://www.forwarding.ru/. Сначала, естественно, нужно нарисовать каждую кнопку меню в двух вариантах: в «отжатом» и «нажатом» состояниях. Бу- дем считать, что это уже сделано, и перей- дем к рассмотрению механизма, который обеспечит нужную нам функциональность. Первым делом - надо написать HTML-код меню. Это несложно, и будет выглядеть примерно так: < а href=""ximg src="pic/pic-1.gif" name=“pic1 "x/axbr> < а href=""><img src="pic/pic-2.gif" name= "pic2 "></axbr> < a href=""xjmg src="pic/pic-3.gif" name="pic3"x/a> Это - обычное меню, составленное из графических элементов. Я опустил несу- щественные для понимания детали - та- кие, как указание высоты и ширины изо- бражения, тег ALT и т.п. Заметьте, одна- ко, что для каждой картинки указан атри- бут name, который понадобится для ссылки на картинку. Теперь добавим в меню вызов функций смены изображения. Для отслеживания попадания курсора мышки на изображе- ние воспользуемся событием onMouseOver и привяжем к нему вызов функции смены изображения: onMouseOuer= “change(‘pic1’, ’pic/pic-1-on.gif’);" А отследить уход курсора с изображения поможет событие onMouseOut, которое также вызывает функцию смены изобра- жения, но в качестве параметра, мы пе- редаем уже ссылку на исходное изобра- жение: Для реализации эффекта RollOver будем отслеживать попадания курсора мышки на изображение; воспользуемся событием onMouseOver и привяжем к нему вызов функции смены изображения. 53 onMouseOut="change(‘picr, ’pic/pic-l.gif’);" Теперь напишем всю конструкцию полно- стью на примере одного пункта меню: <а href="page.htm" onMouseOuer="change(‘pic1’, ’pic/pic-1-on.gif’);" onMouseOut="change(‘pic1’, ’pic/pic-1 .gif’); "><img src="pic/pic-1.gif" name= "pici "x/a> Осталось написать саму функцию change, которая и будет подменять изо- бражения. Для доступа к картинкам вспомним объ- ектную модель браузера’. Перед пока- зом странички на экране браузер разби- вает ее на составляющие блоки и зано- сит их в свою базу данных, предоставляя нам доступ к информации посредством объектной модели. В частности, все картинки сохраняются в объекте images, который, в свою оче- редь, входит в объект document. Для того, чтобы заменить одно изображение дру- гим, нам достаточно воспользоваться следующей конструкцией: ► 1 Подробнее об этом можно прочитать в первой части данного цикла статей ("Программист №1/2001") Контакты Услуги макЬ О компании ш о
ПРОГРАММИСТ Профессиона льный журнал 54 Т)оли>1А. HcJ&ocvUTa. Смллк!*. Наи*.и, ^о^хо^м. I^Atc и_АС< мАТлЛТ^ document.images["pid "J.src = "pic/pic-1-on.gif"; Обратите внимание, что для ссылки на конкретное изображение (ведь на стра- ничке их может быть много), мы исполь- зуем имя картинки, которое указали в ат- рибуте лате тега <img>. Кстати, точно таким же образом мы мо- жем обратиться и к другим атрибутам картинки: ширине (width), высоте (height), поясняющему тексту (alt) и т.д. В нашем же случае - достаточно будет изменить только ссылку на картинку, т.к изображе- ния «обычной» и «нажатой» кнопки име- ют одинаковые размеры. Не следует забывать о том, что объект- ные модели разных браузеров могут от- личаться, поэтому в некоторых ситуациях надо определить тип и версию браузера (об этом тоже подробно написано в пер- вой статье цикла). А вот код на JavaScript, который осущест- вляет подмену изображений. В качестве параметров мы передаем ему имя рисун- ка и ссылку на изображение «нажатой» (или «отжатой») кнопки: function change(img, ref) ( if (broiuser_ok == ‘true’) { document.images[img].src = ref; ) } Бегающий указатель Чем отличаются варианты RollOver с «подсвечиванием пунктов меню» и «бега- ющим указателем»? Только тем, что во втором случае у нас есть всего две кар- тинки, используемые для всех пунктов меню - пустая и с изображением указате- ля. Пример использования данного эф- фекта - можно посмотреть на сайте http://www.child.org.ru/pages/news.html. В этом варианте RollOver нам придется слегка изменить HTML-код, описываю- щий меню, т.к. перед каждым пунктом меню мы добавляем изображение пусто- го указателя: покидании ее, слегка отличаются. Мы на- зовем эти функции соответственно over() и out(). Во-вторых, заметьте, что атрибут пате тега <img> переместился из описа- ния пункта меню в описание указателя - ведь теперь мы подсвечиваем не меню, а указатель! Число параметров, передаваемых функ- циям, можно сократить - достаточно ог- раничиться именем картинки (атрибут name), т.к. ссылки на изображения, фор- мирующие указатель,нам известны зара- нее. Код теперь выглядит так: function ouer(img) ( if (broiuser_ok == ‘true’) { document.images[img].src = "pic/pointer-on.gif"; } ) function out(img) { if (brouuser_ok == ‘true’) { document.images[img].src = "pic/pointer.gif"; ) ) Осталось разобраться с третьим вариан- том эффекта... Сменяющаяся картинка Пример использования данного эффекта можно наблюдать непосредственно на моем собственном сайте (http://www.cher- ry-design.spb.ru/). Его отличие от ранее рассмотренных ва- риантов - в том, что используется одна изменяющаяся картинка, но вариантов замены - больше чем два. Заметим так- же, что скрипт может вызываться из од- ной части странички, а смена картинки - происходить совершенно в другой. Начнем с HTML-кода. В данном варианте он будет состоять из двух фрагментов: описание изображения, которое будет ме- няться, и описание областей, при попада- нии на которые вызывается скрипт. Это может быть, к примеру, то же самое ме- ню: <img src=“pic/pointer.gif" name='pici "><a href="neius.htm" onmouseouer=“ouer(‘picr);" onmouseout="out(‘pict’);"ximg src="pic/pic-1.gif"x/a> Обратите внимание на несколько особен- ностей. Во-первых, вместо одной функ- ции смены изображения нам понадобятся две - действия, производимые при попа- дании курсора в область пункта меню и <img src="pic/default.gif" name="pic"> <а href="page_1.htm" onmouseouer="ouer(‘pic/image- 1.gif’);" onmouseout="out();"> <img src="pic/pic-1.gif"x/axbr> <a href= "page_2.htm" onmouseouer="ouer(‘pic/image-2.gif’);" onmouseout="out();"> <img src="pic/pic-2.gif"x/axbr> <a href="page_3.htm"
Профессиональный журнал ПРОГРАММИСТ!_?_! onmouseouer="ouer(‘pic/image-3.gif’);" onmouseout="out();"> <img src=“pic/pic-3.gif"x/a> Рассмотрим поведение функций. В функ- цию over(), которая вызывается в резуль- тате попадания курсора в активную об- ласть, достаточно передать только ссыл- ку на заменяющее изображение. Вызов же функции out() осуществляется вовсе без параметров: function ouer(ref) { if (brotuser_ok == ‘true’) { document.images[img].src = ref; } } function outt) { if (browser_ok == ‘true’) { document.images[img].src = "pic/default.gif"; ) Новое t И Готовые рабогы УслуЗ» студии С гагьм гю web -дизайну Попетые ссылки Глоссарий web-термина Контактная информации Новости Готовые работы Услуги сгудж Статьи по web дизайну Полезные ссыпки Глоссарий web-термина Кон так r^t информации Вот мы и разобрались со всеми вариан- тами скриптов для реализации эффекта RollOver. Осталось рассмотреть еще один очень важный вопрос: Предварительная загрузка изображений Я специально выделил этот момент в от- дельный подраздел - он очень важен и сильно сказывается на качестве эффек- та и визуальной загрузке странички. Для чего используется предварительная загрузка изображений? Как известно, любая web-страничка состоит из набора файлов, которые загружаются одновре- менно с основной страничкой. Но есть одна особенность - загружаются только картинки, которые видны на экране. Наш же эффект построен на подмене изобра- жений и соответственно часть картинок при загрузке не видна. Предварительная загрузка заключается в том, что мы заранее скачиваем невиди- мые картинки в кэш компьютера. Иначе при смене картинки будет происходить заметная задержка - связанная с тем, что картинка будет загружаться непо- средственно с сервера. Механизм предварительной загрузки изображений осуществляется при помо- щи следующего фрагмента кода: if (brotuser_ok == ‘true’) { а!=пеш Image; a 1.src="pic/pic-1-on.gif; а2=пеш Image; a2.src=“pic/pic-2-on.gif"; аЗ=пеш Image; a3.src=“pic/pic-3-on.gif"; ) Мы просто создаем объект Image для ка- ждой невидимой в данный момент кар- тинки и указываем адрес изображения. В результате - дойдя до этого фрагмента кода, браузер загрузит эти изображения точно так же, как и обычные видимые картинки. К концу загрузки странички - абсолютно все картинки окажутся в кэ- ше, и будут отображаться мгновенно. Таким способом нужно кэшировать все невидимые на страничке картинки. Обра- тите также внимание, что я проверяю корректность браузера - в случае, если это достаточно старая версия, которая не поддерживает смену изображений, лиш- няя графика просто не грузится - тем са- мым несколько ускоряется загрузка. При грамотной «нарезке» страничек сай- та - можно использовать часть изобра- жений первой страницы во внутренних. Подобный механизм реализован на моем собственном сайте. Если вы к нему вни- мательно присмотритесь, то обнаружите, что почти вся необходимая графика гру- зится на первой страничке, и открытие внутренних разделов сайта происходит почти мгновенно. В частности, оказыва- ются уже загруженными фрагменты ло- готипа разных цветов и графика состав- ляющая пункты меню. При грамотной «нарезке» страничек сайта - можно использовать часть изображений первой страницы во внутренних. Вся необходимая графика грузится на первой страничке, и открытие внутренних разделов сайта происходит почти мгновенно. 55 Надеюсь, что описанные выше скрипты и механизмы работы помогут вам реализо- вать свои задумки... Архивы с примерами скриптов, рассмот- ренных в данной статье (и в прошлой то- же) вы можете скачать по адресу: http://www.cherry-design.spb.ru/soft/js_all.zip (продолжение следует...) £
Здесь, нам обычно, уютно расположились статьи, прямо или косвенно связанные с понятием «мультимедиа», 20 и ЗОграфина, звук, видео, и прочие прелести; алгоритмы, форматы, библиоте- ки и рабочие инструменты, все вопросы разработки мультимедийных и игровых про- грамм - будут потихоньку здесь рассматриваться. Недавно в редакцию пришло письмо: «Ваш раздел Мульти мог бы быть гораздо интереснее - ведь есть так много тем, о которых еще никто не писал. Например звук - это то направление, которое больше всего меня интересует. Есть же библиотеки, форматы и все такое. Но информации об этом мало (по крайней мере мне не попадалось)» Наш ответ Керзону: в этом номере тани есть статьи про компьютерный звук Аж целых две. Первая - общий обзор средств Windows, используемых для вывода зву- ка [в следующем номере думаем напечатать продолжение, с более подробным опи- санием DirectSound - наиболее мощным из аудио-интерфейсов Windows). Вторая статья посвящена основам самостоятельной генерации волновых сэмплов, (слышали на дискотеке «дынц-дынц-дынц»? - это, в общем, типичные сэмплы). По многочисленным просьбам читателей - продолжаем публикацию цикла статей «30-графика». От простейших вопросов типа «нан повернуть нубин» или «кан от- сечь невидимые части сцены» мы уже добрались до более-менее сложных (и инте- ресных) вещей - нынче речь пойдет о «натягивании» текстур на объекты. Некото- рые познэния в математике очень помогут для понимания материала...
Профессиональный журнал ПРОГРАММИСТ "_" ВЫВОД ЗВУКА В WINDOWS Великанов И. В. flay® polytech Jvanovo.su http:\\ext2jiarodju ICQ 12202990 Когда приходится осваивать новую область, самое трудное - это первый шаг. Так что, когда я решил разобрать- ся с выводом звука в среде Windows, труднее всего было выбрать - с чего начать. Средства есть самые разные, от простейших функций типа PlaySound до мощных библиотек типа DirectX. Что же лучше всего подойдет для вашего конкретного приложения?... Наиболее известных «звуковых» Windows- интерфейсов три: • Media Control Interface (MCI) - простей- шее управление мультимедиа- устройст- вами • Waveform Audio - набор WinAPI-функ- ций для вывода «волнового» (wave) зву- ка • DirectSound (компонент DirectX) - низ- коуровневый интерфейс для игр и муль- тимедийных приложений (требующих особо высокой производительности). Далее - приведу краткое описание работы с этими интерфейсами (с минимальными примерами на Delphi); постараюсь отме- тить плюсы и минусы каждой технологии. Media Control Interface MCI - это очень высокоуровневый интер- фейс; команды посылаются устройствам в виде достаточно общих инструкций - на- пример, текстовых строк: {проиграть 2-й трзк; по окончании - известить) mciSendString ("play cdaudio 2 to 3 notify", nil, 0, handle); Если работать с текстовыми строками не- удобно - есть параллельная функция mciSendCommand, принимающая те же параметры «в числовом виде» (соотвест- вующие константы). Интерфейс MCI вполне годится для про- стейших операций (например, запустить проигрывание avi-файла или CD-трэка), но в целом - довольно ограничен, и вряд ли подходит для серьезного программирова- ния. Поэтому подробно его рассматривать не будем. WaveForm Audio Этот интерфейс входит в стандартный Windows API и позволяет воспроизводить программно генерируемые звуки. Первым делом нужно выбрать устройство, которое будет воспроизводить звук. Функ- ция waveOutGetNumDevs() вернет общее количество таких устройств в системе, а waveOutGetDevCaps() опишет возможно- сти конкретного устройства. Впрочем, я бы советовал оставить этот выбор на усмотрение пользователя (для этого нужно при вызове функции Wave OutOpen() вместо идентификатора уст- ройства указывать флаг WAVE_MAPPER). Перед началом вывода звука надо от- крыть устройство: WaveOutOpen(), а за- тем - выводить блоки данных последова- тельными вызовами WaveOutWrite(); при- чем, если вы начинаете вывод следующе- го блока, пока еще не доигрался предыду- щий - звук будет непрерывным. Экспери- ментальным путем я установил, что звук обычно начинает прерываться, если блок не успели вывести более чем за 200 мс до конца предыдущего (эта величина зависит от драйвера звуковой платы). Следова- тельно, нельзя достичь непрерывности звука, если используются блоки менее чем по 200 мс. Каждый выводимый блок данных нужно сначала подготовить к воспроизведению функцией waveOutPrepareHeader(), а по- сле того, как он отзвучал - освободить функцией waveOutllnPrepareHeader(). Сколько времени звук воспроизводится - выясняет функция waveOutGetPosition(). Функция waveOutSetVolume() регулирует громкость. Для приостановления вывода звука и его продолжения - есть функции waveOut Pause() и waveOutRestart(). Вызов WaveOutClose() завершает вывод звука. Контроль над воспроизведением сводится к оповещению программы в момент окон- чания проигрывания очередного блока; Windows умеет это делать тремя способа- ми: • послать сообщение заданному окну • возбудить указанное событие • вызвать указанную callback-функцию (типа WaveOutProc) . MCI - это очень высокоуровневый интерфейс; в целом - он довольно ограничен, и вряд ли подходит для серьезного программирования. 57 WaveForm Audio позволяет воспроизводить звуки в волновой форме; годится для большинства приложений, но монополизирует вывод звука, лишая другие приложения этой возможности.
ПРОГРАММИСТ Профессиональный журнал 58 Способ оповещения задается в момент от- крытия устройства функцией WaveOut Ореп(). Советы: . Лучше не использовать функции wave OutSetPitchQ и waveOutSetPlayback Rate() - они не поддерживаются боль- шинством звуковых карт. . Все функции возвращают значение MMSYSERFLNOERROR в случае ус- пешного выполнения и другие значения в случае ошибки. Всегда проверяйте возвращаемые функциями значения; если что - текстовое описание произо- шедшей ошибки можно запросить функцией waveOutGetErrorText(). . Большинство функций WaveOutxxx имеют парные функции Wavelnxxx, ко- торые предназначены для записи зву- ка; работа с ними - абсолютно анало- гична. Имейте в виду: функции Waveform Audio «монополизируют» вывод звука, лишая другие приложения этой возможности. Ес- ли хотите этого избежать - используйте DirectSound. DirectSound - наиболее мощный инструмент работы со звуком. Он позволяет проигрывать на одном звуковом устройстве произвольное количество звуков разной дискретизации и форматов. DirectSound DirectSound - очень мощный инструмент работы со звуком. Он позволяет несколь- ким приложениям одновременно проигры- вать на одном звуковом устройстве неог- раниченное (в разумных пределах) коли- чество звуков разной дискретизации и форматов. К сожалению, нельзя одновременно ис- пользовать функции DirectSound и Waveform Audio. При установке Windows9x содержит следу- ющие версии DirectX: • Windows 95 OSR2 - DirectX 3.0 • Windows 98 - DirectX 5.0 • Windows ME - DirectX 7.0 Более новую версию можно скачать прямо с сайта www.microsoft.com. Для серьезного программирования советую установить DirectX SDK, в котором содержится полная документация и примеры использования интерфейсов. Работать с DirectSound на Deplhi можно с помощью экспорта функций из библиоте- ки DSOUND.DLL; но я бы посоветовал ис- пользовать готовые заголовки к интер- фейсам функций, которые легко найти в интернете (если будете искать - не пере- путайте версии DirectX; заголовки могут немного отличаться). Начать работу следует с создания СОМ- объекта типа «интерфейс DirectSound»: uar OS: I DirectSound; DirectSoundCreate(NIL, DS, NIL); После этого обязательно установить при- оритет вывода звука: DS.SetCooperatiueLeueKhlDnd, DSSCL_PRIORITV); Затем создается произвольное количест- во буферов (отдельный буфер для каждо- го звукового канала): uar DSB1 ,DSB2: IDirectSoundBuffer; DSBD: TDSBufferDesc; DS.CreateSoundBufferiDSBO, DSB 1,NIL)); DS.CreateSoundBuffertDSBD, DSB2.NIL)); Перед заполнением буфера данными нуж- но заблокировать его функцией Lock(), а после заполнения - разблокировать функ- цией UnLock(). В отличие от Waveform Audio, вы можете изменять данные в бу- фере прямо во время его проигрывания. Единственное ограничение: если вы попы- таетесь писать в проигрываемую в данный момент позицию (или менее чем в 20 мс после нее) - услышите довольно неприят- ное хрюканье. Чтобы записать данные в заведомо еще не проигранном месте - при вызове функции Lock устанавливайте флаг DS BLOCK_FROMWRITECURSOR (это вернет для записи позицию, которая только начнет проигрываться). Заполнив буфера каналов, можно начи- нать их проигрывать: DSB1 .Р1ау(0,0,0) DSB2.Play(0,0,0) и, соответственно, останавливать: DSBI.Stop; DSB2.Stop; Освобождение созданных СОМ-объектов произойдет автоматически при выходе из программы; но можно это форсировать конструкцией DS := NIL; Простое присваивание вызовет уменьше- ние количества ссылок на объект, и если оно станет равным нулю, то объект будет жесточайшим образом уничтожен. Теперь про некоторые полезные детали: Если нужно будет управлять буфером «в процессе», при его создании надо указать дополнительные флаги: • для регулировки громкости функцией DSB1.SetVolume() - флаг DSBCAPS_CTRL VOLUME
Профессиональный журнал ПРОГРАММИСТ — • для регулировки частоты воспроизведе- ния функцией DSB1. SetFrequency() - флаг DSBCAPS_CTRLFREQUENCY, и т.д. Для динамического управления проигры- ванием звука есть интерфейс (Direct Sound Notify. С его помощью можно соз- дать в буфере точки, при достижении ко- торых будет возбуждаться определенное событие (не забудьте включить флаг DSB- CAPS.CTRLPOSITIONNOTIFY!). Тут есть небольшая особенность: событие наступа- ет примерно на 20 мс раньше, чем на са- мом деле. Это из-за того, что на самом де- ле событие возбуждается не в момент собственно проигрывания точки, а в мо- мент переноса данных (блоками по 20 мс) на звуковое устройство. Чем это чревато? Если вы установите точку на 43 мс, и по приходу события попросите вызвать Stop, то услышите только 43 - 20 = 23 мс. Будь- те бдительны! Узнать текущую точку проигрывания мож- но функцией GetCurrentPosition(). Эта функция возвращает реально проигрыва- емую позицию в буфере; рекомендую ус- танавливать флаг DSBCAPS_GETCUR- RENTPOSITION2. Советы: • По умолчанию звук воспроизводится только пока ваше приложение в фоку- се. При переключении на другое прило- жение - проигрывание звука приоста- новится. Чтобы этого избежать, при со- здании буфера включайте флаг DSB- CAPS_GLOBALFOCUS. • Все функции возвращают значение DS_OK в случае успешного выполнения и другие значения в случае ошибки. Всегда проверяйте возвращаемые функциями значения; текстовое описа- ние ошибки - функция DSErrorString(). Имейте в виду: • При изменении частоты проигрывания буфера в процессе воспроизведения вы услышите неприятные щелчки. • Если вы используете DirectSound, запаз- дывание выводимых данных не превы- шает 20 мс (в отличие от 200 мс при Waveform Audio). Т.е. DirectSound позво- ляет писать приложения, где звук прак- тически синхронен с изображением или любыми программными событиями. • Если при выводе звука входная частота дискретизации не равна выходной, про- исходит сглаживание звуковой кривой (т.е. можно проигрывать звук низкого качества с более высоким, и наоборот). Вот; для более описания - рекомендую обратиться к help’aM, по Microsoft SDK и DirectX SDK соответственно... Л 59 Е
- ПРОГРАММИСТ Профессиональный журнал 60 Дмитрий Сазонов dsteuz@mallru.com Наиболее распространенные и часто используемые формы волны: синусоидальная (sine), квадратная (square), пилообразная (saw), треугольная (triangle) и шум (noise). ПРОГРАММНАЯ ГЕНЕРАЦИЯ ЗВУКОВЫХ СЭМПЛОВ Зачем это надо, спросите вы? Ну, например, у вас есть простенький синтезатор, который вы повседневно используете; но со временем - его ха- рактеристик вам начинает катастрофически не хватать. И что - бежать, покупать новый? Зачем, если можно соз- давать звуки и добавлять звуковые эффекты самостоятельно? Еще - программная генерация сэмплов может пригодиться для программ, которые не могут занимать много ме- ста в памяти или на внешних носителях; для создания звуковых realtime-эффектов (заданное искажение звука в реальном времени). А для создания звуковых редакторов или утилит - просто необходимо понимать волновые характеристики звука, и уметь с ними работать. В общем, начнем... Сэмпл (на русский обычно переводится как «инструмент») - это некий звуковой фраг- мент, обычно представляющий собой коротенькую музыкальную фразу (или ноту с не- которым типичным тембром). Звук можно представить в виде бесконечного количества волн различной частоты и ам- плитуды. Волны, в свою очередь, могут иметь практически любую форму. Вот самые распространенные и чаще всего используемые формы волны: синусоидальная (sine), квадратная (square), пилообразная (saw), треугольная (triangle) и шум (noise). Сначала попробуем разобраться с основными параметрами волны: частотой и амплитудой. Амплитуда - это абсолютная величина волны (она же - громкость сэмпла). Частота (измеряется в герцах) - количество периодов (повторений исходного участка) волны в секунду. Например, при частоте 44100Hz количество периодов в одну секунду равно 44100. Теперь перейдем непосредственно к алгоритмам генерации. I. ГЕНЕРАЦИЯ ВОЛН РАЗНОЙ ФОРМЫ Сначала - определим общие значения (их можно задавать константами, или определить как параметры соответствующих функций): #define samplelength 16384 short bufferfsamplelength]; float samplerate; float mauefrequencg; float waueuolume; // для длины сэмпла = 16kb // зарезервированная память для сэмпла // частота сэмпла // частота волны // громкость волны short uol; //переменная для хранения вычисленного значения волны Например, для 16-килобайтного сэмпла синусоидального звука, с частотой 1000Hz и ка- чеством сэмпла 44100Hz - наши параметры должны иметь следующие значения: samplerate=44100, wavefrequency=1000, samplelength=16384. Особенного пояснения требуют параметр wavevolume и формат представления звука. Для дискретного представления звука записывают значение громкости (амплитуды вол- ны) в определенный момент времени; мы будем пользоваться для этого переменной vol. Естественно, качество воспроизведения звука пропорционально зависит от битности его представления (8-bit, 16-bit, 24-bit и т.д.). Для 8-bit - значения vol от 0..255, для 16-ти - 0...65536, для 24-х - 0...16777216. Какой вариант выбрать? Смотря какая у вас задача; но меньше 16-ти я бы не советовал (хотя бывают и исключе- ния.- когда надо сократить объем сэмпла взамен качеству). Все приведенные здесь при- меры расчитаны на 16-битный звук (поэтому и vol, и buffer[ ] - типа short, 16-bit) 1) Синусоидальная волна (sine) S
Профессиональный журнал ЦР0|*РД|У||^||^0У а //вычисляем полупериод волны: float period = samplerate/iuauefrequency/2; float pi=3.14; //число pi forlint a=0;a<samplelength;a++] { // цикл по всей длине сэмпла // вычисляем очередное значение синусоидальной волны: uol = iuaueuolume*sin(a*pi/period); bufferla] = uol; // и заносим его в буфер } 2) Пилообразная волна (saw) float sr_2m=samplerate/Luauefrequency; // Вычисляем период волны int с=0;//Специальная переменная для проверки на окончание периода for(int a=0;a<samplelength;a++] { //цикл по всей длине сэмпла if(c>=sr_2m) с=0; //если период закончился, то начать следующий //вычисление волны и занесение вычисленного значения в буфер: uol = Luaueuolume*(c/period)-tuaueuolume; buffer[a]= uol; C++; } 3) Треугольная волна (triangle) 61 float period=samplerate/iuauefrequencg/2; // полупериод волны int c=period*2; // период волны int c2=-1; // индикатор полупериода волны float sr_2m=period; float sr=samplerate/iJuauefrequency/4; // четверть периода волны foriint a=0;a<samplelength;a++) { //цикл по всей длине сэмпла //если прошел полупериод, инвертируем волну: if(c>sr_2m) c=sr_2m, с2=-1; // если прошел период: if(c<0) с=0,с2=1; //вычисление значения волны и занесение его в буфер bufferla] = iuaueuoliime*(c/sr)-ujaueuolume; с+=с2; } 4) Квадратная волна (square) Е
ПРОГРАММИСТ Профессиональный журнал float period=samplerate/wauefrequency/2; //вычисляем полулериод волны int psc=0; //индикатор полупериода forfint a=0;a<samplelength;a++) { //цикл по всей длине сэмпла //если начался второй полупериод, инвертировать громкость if(psc>period) psc=0,u>aueuolume~=u)aueuolume; //заносим громкость в буфер buffer[a]=u)aueuolume; psc++; } 5) Шум (noise) srand(iuaueuolume); // инициализируем генератор случайный чисел s1=samplerate/wauefrequency; // период psc=s1; // дублируем переменную в psc шаиеио1ите»=7; // делим громкость на 128 forfint a=0;a<samplelength;a++) { //цикл по всей длине сэмпла if(psc>s 1 )//если уже начался новый период, вычислить новую амплитуду { psc-=sl; // квадратная волна со случайной амплитудой: int п=256*((гапб()%(шаиеио1ите+1))-шаиеио1ите/2); } //заносим полученное значение в буфер buffer[al=n; psc++; } II. МОДУЛЯЦИЯ ВОЛНЫ ПО ОГИБАЮЩЕЙ (ENVELOPE). Модуляция - это искажение волны относительно чего-либо. Огибающая - графически заданная форма модуляции волны. Огибающая предназначена для более разнообразного изменения звука. Она может быть практически любой формы, но для большего удобства придумали некий стандарт, который мы сейчас и разберем. Hold Attack. \oecay у \ Sustain ^Release Delay / Стандартная огибающая (применяется в картах Creative) состоит из следующих фаз: Delay - пауза Attack - усиление Hold - поддержка Decay - спад Sustain - задержка Release - понижение S
Профессиональный журнал ПРОГРАММИСТ _ Приведем пример использования огибающей на амплитуду на примере синусоидной волны short enuelope[sampielengthl; // буфер для значений огибающей ... // здесь надо выполнить расчет огибающей ... // обычная линейная интерполяция // от одной точки до другой float period=samplerate/iuauefrequency/2; //вычисляем полупериод волны forfint a=0;a<samplelength;a++) { //цикл по всей длине сэмпла uol = enuelope[a]*sin(a*pi/period); // sine-волна плюс огибающая buffer[a]=n; // в буфер ) 63 А теперь попробуем использовать огибающую на частоту (задача немного посложнее). float pi_2=pi*2; // два пи float ualue; int psc=0; //счетчик начала периода sr=pi_2/samplerate; for(int a=0;a<samplelength;a++) { //цикл по всей длине сэмпла // новое значение волны: ualue=u)aueuolume*poiij(2,-2+float(enuelope[a])/16384); if(psc>=pi_2) psc-=pi_2; //если начался новый период, //изменить частоту относительно огибающей buffer[a]=u)aueuolume*sin(psc); // вычисленное значение - в буфер psc+=sr*ualue; //вычисление частоты для следующего значения волны I. СЛОЖИИЕ НЕСКОЛЬКИХ ВОЛН. После того, как мы создали несколько волн, их можно сложить. Для этого применяется обычная операция сложения. Например: short buffer![samplelength]; // память первого сэмпла short buffer2[samplelengthl; // память второго сэмпла #define MR8_U0L 32767 // ограничения для 16-битного звука tfdefine MINJJOL -32768 ь
- ПРОГРАММИСТ Профессиональный журнал 64 for(int a=R;a<samplelength;a++) { // по всей длине сэмпла //складываем волны: int ио132 = bufferl [a] + buffer2[a]; //если значение вышло за максимальный предел, ограничить его: if(uol32 > MRH_UOL) UOI32 = MRK_UOL; //то же для минимального предела: if(uol32 < MIN_U0L) UOI32 = MIN_UOL; bufferla] = (short) oo!32; // в буфер ) IV. ДИСТОРШН СЭМПЛА (DISTORTION). Дисторшн - сильное увеличение амлитуды сэмпла; стандартный эффект для «хард-рок»- электрогитары. Дисторшн бывает ограниченный (limit) и круговой (wrap). Попробуем разо- браться с этим эффектом. 1) Ограниченный- значения обрубаются, если выходят за пределы максимальной громкости int оо132; int ouerdriue; //значение дисторшна (чем больше, тем сильнее) for(int a=R;a<samplelength;a++) { // цикл по всей длине сэмпла ио132 = buffer[a]+float(buffer(a]*ooerdrioe/16); //если надо, обрубаем значение if(uo!32 > MRR_UOL) UOI32 = MRK_UOL; if(oo!32 < MIN_UOL) 00132 = MIN_UOL; bufferla] = (short)ool32; // значение - обратно в сэмпл ) 2) Круговой - при выходе значения за пределы максимальной громкости - направление уси- ления меняется в обратную сторону. int lorapooerdrioe; // значение кругового дисторшна // (чем оно больше, тем сильнее искажение сэмпла) for(a=0;a<sampleiength;a++) {// цикл по всей длине сэмпла int оо132 = buffer[a]+float(buffer[a]*ujrapooerdrioe/16); //если вычисленное значение вышло за заданные пределы, // модифицировать его, пока оно не будет в заданный пределан UJhile((uol32 < -MRR_U0L)||(ool32 > MRH_UOL)) { if(uol32 < -MRK_UOL) 00132 = -MRK_UOL +(-UOl32 - MRK_UOL); else 00132 = MRK_UOL +(-ool32 + MRH_UOL); } bufferla] = (short) oo!32; // полученное значение - обратно в сэмпл ) В принципе, вот и вся основа wave synthesis; с ее помощью можно создать любой звук. На- пример, mp3 формат представляет собой массив простых волн... И напоследок несколько советов: 1) Для получения более насыщенного звука, складывайте волны с различными формами: sine+square, triangle+saw, или вот такой вот монстр: saw+square+triangle+saw. 2) Перкуссия (различные высокочастотные «ударные» сэмплы) лучше всего получается пу- тем сложения noise+sine. 3) Фоновые сэмплы хорошо получаются путем сложения различных волн с частотой, отлича- ющейся на 1; т.е. если sine-волна - 65Hz, то saw-волне хорошо присвоить частоту 64Hz или 66Hz. 4) Bass drum (бас-бочка, та, что мешает нам спать по ночам) получается путем искажения синусоидальной волны по амплитуде и частоте. ф
Профессиональный журнал ПРОГРАММИСТ — ПРОГРАММИРОВАНИЕ 30-ГРАФИКИ: ТЕКСТУРИРОВАНИЕ Итак - мы продолжаем цикл статей о программировании ЗО-графики. Напомним, что в первой статье речь шла об основных понятиях, таких как грани, проекции, камеры. Во второй статье мы рассказали об отсечении невиди- мых частей ЗО-сцены. Теперь перейдем к способам текстурирования - «раскрашивания» трехмерных объектов заданными текстурами. Андрей Аксёнов Задача текстурирования формулируется так: есть грань (согласно предположени- ям, треугольная) с линейно наложенной на нее текстурой (то есть - каждая точка грани окрашена цветом соответствую- щей ей точки в текстуре); есть точка эк- рана с координатами на экране (sx,sy), принадлежащая проекции грани. Требуется - найти цвет точки текстуры, соответствующей этой точке экрана; то есть найти «координаты текстуры» для точки, проекцией которой на экран явля- ется наша (sx,sy). Рассмотрим основные методы решения этой задачи. Точное текстурирование Пусть вершины грани есть точки A(Ax,Ay,Az), B(Bx,By,Bz) и C(Cx,Cy,Cz), а соответствующие им точки текстуры - (Au,Av), (Bu,Bv) и (Cu,Cv). Найдем коор- динаты текстуры для точки, проекцией которой является (sx,sy). Напомним, что Xsize и Ysize - размеры экрана, a dist - расстояние от него до центра координат. Для точек (x,y,z), проекцией которых яв- ляется (sx,sy), имеем: sx = XSize/2+x*dist/(z+dist) sy = YSize/2-y*dist/(z+dist) Для упрощения формул будем использо- вать обозначения: i = sx-XSize/2 j = YSize/2-sy Z = z+dist Рассмотрим точку D, принадлежащую грани. D однозначно задается парой чи- сел (a,b): D = А+а*(В-А)+Ь*(С-А) Для нее координаты текстуры (из того, что текстура накладывается линейно) бу- дут такие: Du = Au+a*(Bu-Au)+b*(Cu-Au) Dv = Av+a*(Bv-Av)+b*(Cv-Av) Пусть проекция D на экран как раз и имеет координаты (sx,sy), тогда для нее выполнены написанные выше соотноше- ния: i*DZ = Dx*dist j*DZ = Dy*dist Мы опустим некоторые математические выкладки. Скажем только, что их смысл заключается в получении системы двух линейных уравнений, из которой можно найти а и Ь. Введем векторы М = ВА (Мх = Вх-Ах и т.д.) и N = СА и обозначе- ние d = dist; опустим знаки умножения (все это для краткости записи); получим: _i(AZNy-AyNz)+j(AxNz-AZNx)+d(AyNx-AxNy) Э i(MyNz-MzNy)+j(NxMz-MxNz)+d(MxNy-NxMy) b _ i(AZMy-AyMz)+j(AxMz-AZMx)+d(AyMx-AxMy) i(MyNz-MzNy)+j(NxMz-MxNz)+d(MxNy-NxMy) Отсюда вычисляются и и v. Формулы для Du и Dv получатся длиной в несколько строк; но из этих формул видно кое-что интересное: u = (C1*sx+C2*sy+C3) / (C4*sx+C5*sy+C6) v = (C7*sx+C8*sy+C9) I (C4*sx+C5*sy+C6) Где С1...C9 - просто какие-то коэффи- циенты, зависящие от грани. То есть, можно посчитать эти коэффициенты один раз для каждой грани, а потом счи- тать u, v по написанным парой строк вы- ше формулам. Однако - все равно полу- чается как минимум одно деление на точ- ку. Это слишком трудоемко. Поэтому все методы текстурирования на самом деле используют приближенные вычисления (хотя именно по этим формулам). Отметим, что на самом деле - грань не обязательно должна быть треугольной; можно взять любые три вершины много- угольной грани (это справедливо и для остальных описанных методов наложе- ния текстур). Аффинное текстурирование Этот метод основан на приближении и и v линейными функциями. Итак, пусть и - линейная функция, u = k1*sx+k2*sy+k3. Можно посчитать k1, k2, k3, исходя из то- го, что хотя бы в вершинах грани и долж- но совпадать с точным значением - это даст нам три уравнения, из которых нахо- дятся коэффициенты. Но этот способ вы- числения и все равно медленный - два умножения на пиксел. к Задача текстурирования - найти цвет (координаты) точки текстуры, проекцией которой является данная точка экрана. 65
ПРОГРАММИСТ Профессиональный журнал 66 В с • Будем рисовать грань по строкам - это общепринято, просто, и не доводит до умопомешательства кэш-память процес- сора. Вершины грани заранее отсортиру- ем по sy (например, A.sy <= B.sy <= C.sy). Для каждой строки можно посчи- тать начальное значение х и и (так же, как и для х - ведь и по любой прямой ме- няется тоже линейно), а также длину этой строки. (рис1) В нарисованном случае, например, x_start = R.sx+(current_sy-fl.sy)*(C.sx- fl.sx)/(C.sy-R.sy) u_start = R.u+(current_sy-fl.sy)*(C.u- R.u)/(C.sy-R.sy) x_end = fl.sx+(current_sy-B.sy)*(B.sx- B.sx)/(B.sy-fl.sy) length = x_end - x_start Какие вершины использовать в этих формулах - это уже проблемы рисования треугольника, а не текстурирования. Лично я просто храню x_start, x_end, u_start, и на каждом переходе вниз на строчку прибавляю к х delta_x start = (C.sx-A.sx)Z(C.sy-A.sy). Аналогично счи- таются приращения для x_end, u_start. Надо только аккуратно следить за тем, какая сторона правая, какая - левая, и на каком мы сейчас промежутке нахо- димся - АВ или ВС, и соответственно из- менять приращения. Впрочем, все это - уже обыкновенное рисование треуголь- ника. В примерах просто дано решение «в лоб» - проверяем, какой участок - АВ или ВС - пересекает текущая строка, считаем x/u/v на обоих концах, считаем длину строки и берем соответствующие левому концу (то есть - меньшему х) зна- чения и и V. Итак: посчитали начало строки, длину строки, и в начале строки. Осталось за- метить, что раз уж u = k1*sx+k2*sy+k3, то при переходе к следующему пикселу строки и изменится на к1 (также извест- ный как du/dsx). Это число и надо как-то посчитать. Например, так: (рис 2) x_start = R.sx+(B.sy-R.sy)*(C.sx- fl.sx)Z(C.sy-R.sy) x_end = B.sx u_start = R.u+(B.sy-R.sy)*(C.u-R.u)/(C.sy-R.sy) u_end = B.u du_dsx = (u_start-u_end)/(x_start-x_end) du/dsx - просто число, оно не меняется на всем треугольнике; поэтому просто считаем его там, где удобно, и берем по- считанное значение. v (из тех же соображений) считается точ- но так же, надо только во всех приведен- ных формулах и заменить на v. Теперь осталось только взять и нарисо- вать саму строку: // ... u = u_start; и = u_start; for (current_sx = x_start; current_sx <= x_end; current_sx++) { putpixel(current_sx, current_sg, texturel(int)u][(int)u]); u += du_dsx; и += du_dsx; } // ... Пройдясь по всем строкам грани - т.е. пробежавшись current_sy по значениям от A.sy до C.sy (вершины отсортирова- ны!), получим текстурированную грань. Перспективно-корректное текстурирование Этот метод основан на приближении и и v кусочно-линейными функциями. Кратко говоря, при рисовании каждая строка разбивается на куски (обычно несколько кусков длиной 8/16/32 пикселов плюс ос- таток), в начале и конце каждого куска и и v считаются точно, а внутри куска они интерполируется линейно. Точные значения и и v можно считать по формулам точного текстурирования, но обычно используют более простой путь. Он основан на том факте, что значения 1/Z, u/Z и v/Z зависят от sx, sy линейно (доказательство мы не приводим). Зна- чит, достаточно для каждой вершины по- считать 1/Z, u/Z, v/Z - и линейно их ин- терполировать; точно так же, как интер- полируются и и v в аффинном методе. Причем, так как зависимость линейная - интерполяция дает не сильно приближен- ные результаты, а абсолютно точные! Сами же точные значения u, v считаются как u = (u/Z) / (1/Z) v = (v/Z) I (IfZ) Дальше все становится совсем просто. При рисовании треугольника - на ребрах интерполируем не и и v, как в аффинных формулах, a 1/Z, u/Z, v/Z. Кроме того, за- ранее считаем d(u/Z)/dsx, d(v/Z)/dsx, d(1/Z)/dsx (то есть, изменения этих са- мых u/Z, v/Z, 1/Z, соответствующие шагу по dsx на 1) - так, как считали du/dsx; это будет нужно для быстрого вычисле- ния точных значений u, v. Каждую линию рисуем кусками по 8/16/32 пикселов (на
Профессиональный журнал ЦРЦ^РДЩЦУЦ^^У а самом деле, кусками любой длины; про- сто, если длина - степень двойки, то при вычислении du/dx и dv/dx для текущего куска можно деление на длину куска за- менить сдвигом вправо); если надо, рису- ем оставшийся хвостик. Для расчета точ- ных значений u, v - в конце каждого кус- ка пользуемся посчитанными значения- ми d(u/Z)/dsx, d(v/Z)/dsx, d(1/Z)/dsx; раз значения u/Z, v/Z, 1/Z в начале куска из- вестны, меняются они линейно и длина куска известна (либо 16 пикселов, либо длина остатка), то в конце куска они счи- таются очень просто. Все вместе будет выглядеть примерно так: // ... current_sx = K_start; length = x_end - x_start; // расчет u/Z, u/Z, 1/Z, u, и в начале // самого первого куска uZ_a = uZ_start; uZ_a = uZ_start; Z1_a = Z1_start; //это 1/Z u_a = uZ_a / Z1_a; u_a = uZ_a / Z1_a; // рисуем куски по 16 пикселов while (length >= 16} { // расчет u/Z, u/Z, 1 /Z, u, и в конце куска uZ_b = uZ_a + 16 * duZ_dsx; uZ_b = uZ_a + 16 * duZ_dsx; Z1_b = Z1_a + 16 * dZ1_dsx; u_b = uZ_b / Z1_b; u_b = uZ_b / Z1_b; // начинаем текстурир. с начала куска u = u_a; и = и_а; // с fixedpoint можно сделать » 4 du = (u_b - u_a) / 16; du = (u_b - u_a) / 16; // рисуем 16 пикселов старым добрым // "аффинным" методом 1еп = 16; while (len—) { putpiHel(current_sK, current_sy, texture[(int)ul((int)u]); u += du; и += du; current_sx++; } length -= 16; // конец кдска становится началом // следующего куска uZ_a = uZ_b; uZ_a = uZ_b; Z1_a = Z1_b; u_a = u_b; u_a = u_b; ) // дорисовываем "хвост" линии, если он // непуст if (length != 0) { uZ_b = uZ_a + length * duZ_dsx; uZ_b = uZ_a + length * duZ_dsx; Z1_b = Z1_a + length * dZ1_dsx; u_b = uZ_b / Z1_b; u_b = uZ_b / Z1_b; // начинаем текстурирование с начала // куска u = u_a; и = и_а; du = (u_b - u_a) / length; du = (u_b - u_a) I length; // рисуем остаток пикселов старым // добрым "аффинным" методом while (length-) { putpixel(current_sx, current_sy, textureluHul); u += du; и += du; current_sx++; ) } // ... 67 Пройдемся подобным куском кода по всем строкам грани, не забыв вместо «...» вставить интерполяцию всяких там [u/v/1]Z_start (подобно интерполяции u_start)... и - о чудо - получается тексту- рированная с учетом перспективы грань! Осталось сказать еще пару слов о кое- какой оптимизации. Во-первых, два де- ления при расчете и и v в цикле прори- совки можно (и нужно) заменить на одно - посчитать tmp = 1/Z, дальше u = uZ * tmp, v = vZ * tmp. Во-вторых, немного поменяв местами блоки расчета очередной пары точных значений и и v и прорисовки очередного куска линии, можно добиться того, что это самое одно деление, нужное для рас- чета и и v для следующего куска, будет находиться сразу перед прорисовкой те- кущего куска. А в этом случае деление может исполняться в сопроцессоре одно- временно с отрисовкой куска линии в процессоре. То есть - единственная мед- ленная операция будет считаться «на ха- ляву»! Получим перспективно-коррект- ное текстурирование, которое (по идее) будет работать ненамного медленнее аффинного. В-третьих, деление на length при дори- совке хвостика длиной от 1 до 15 пиксе- лов можно заменить на умножение на ►
_л ПРОГРАММИСТ Профессиональный журнал 68 1/length, заранее посчитав табличку для значений 1/length. И, наконец, мелкие треугольники можно текстурировать аффинным методом, а большие - методом с коррекцией. Раз- мер треугольника можно определять хотя бы по длине самой длинной горизонталь- ной линии (все равно мы ее считаем для расчета du_dsx или duZdsx и пр.): K_start = fl.sK+(B.sy-R.sy)*(C.SK- R.sK)/(C.sy-fl.sy) K_end = B.sh longestjength = K_end - K_start Параболическое текстурирование Этот метод основан на приближении u, v квадратичными функциями, то есть пара- болами. Для каждой строки строится приближающие u, v квадратичные функ- ции, дальше с их помощью они интерпо- лируются по строке. Нам понадобятся точные значения u, v в трех точках - на- чале, середине и конце строки. Их счита- ем точно так же, как в предыдущем спо- собе. Пусть у нас есть точные значения и в на- чале, середине и конце строки, то есть на расстоянии 0, length/2 и length пикселов от начала этой строки; обозначим их как ua, ub, и ис соответственно. Мы пытаем- ся приблизить и квадратичной функцией, то есть полагаем, что и = А*х‘х + В*х + С, где х - расстояние от текущей точки до начала строки. Тогда, подставив в фор- мулу ua, ub, uc и соответствующие им х, получаем: R = (2*(uc - ua) - 4*(ub - ua)) / (leng th*length) В = (4*(ub - ua) - (uc - ua)) / length C = ua Найдем теперь du и ddu. Начальные зна- чения u, du, ddu будут такими: u = С du = R + В ddu = 2*R А по известным начальным значениям последовательно вычисляем значения и в любой точке: u(0), du(0), ddu - известны u(1) = u(0) + du(0), du(1) = du(0) + ddu u(2) = u(1) + du(l), du(2) = dull) + ddu u(3) = u(2) + du(2), du(3) = du(2) + ddu Для v все делается полностью аналогич- но. Таким образом, рисование строки будет выглядеть примерно так: // ... // считаем и, и для начала, середины и // конца строки ua = uz_start / z1_start; ua = uz_start / z1_start; ub = (uz_start + uz_end) / (z1_start + z1_end); ub = (uz_start + uz_end) / (z1_start + z1_end); uc = uz_end / zl_end; uc = uz_end / z1_end; // считаем начальное du и ddu pa = 2 * ((uc - ua) - 2 * (ub - ua)) / (length * length); pb = (4 * (ub - ua) - (uc - ua)) / length; pc = ua; u = pc; du = pa + pb; ddu = 2 * pa; // считаем начальное du и ddu pa = 2 * ((uc - ua) - 2 * (ub - ua)) / (length * length); pb = (4 * (ub - ua) - (uc - ua)) / length; pc = u_a; и = pc; du = pa + pb; ddu = 2 * pa; // рисуем кусок tuhile (length-) { putpiHel(current_SK, current_sy, teKturefuHu]); u += du; и += du; du += ddu; du += ddu; ) // ... По сравнению с перспективно-коррект- ным текстурированием, здесь внутрен- ний цикл оказывается медленнее, но для длинных строк меньше делений. Расчет ua, va и иже с ними можно сделать с по- мощью всего трех делений; деления на length и (length*length) можно заменить умножениями на 1/length и 1/(length*length), а эти значения можно брать из заранее посчитанной таблички. Таким образом, на любую строку прихо- дится три деления независимо от ее дли- ны. Качество получается более-менее приемлемое; а для коротких строк можно использовать обычную линейную интер- поляцию, точно так же, как и в случае с перспективно-корректным текстурирова- нием. Так что этот метод вполне конку- рентоспособен.
Профессиональный журнал ПРОГРАММИСТ — Билинейная фильтрация текстур Да-да, это именно тот метод; с помощью которого смазывают текстуры всякие ус- корители типа 3Dfx. Итак, пусть у нас есть какая-то текстура. Текстура - это 2D картинка, а 2D картинка, в свою очередь - набор замеров цвета через какие-то промежутки. В реальной жизни цвет ме- няется не скачкообразно, а довольно плавно. При обычном текстурировании мы получаем координаты в текстуре, ок- ругляем их до ближайшего целого числа и выбираем нужный цвет из текстуры (т.е. берем значение цвета в ближайшей точке текстуры) - поэтому цвет меняется резко, оставаясь непрерывным между уз- лами сетки; возникает эффект больших квадратов. При билинейной фильтрации цвет линей- но интерполируется между узлами сетки замеров. То есть: пусть в текущей точке у нас получились координаты текстуры u, v - какие-то нецелые, вообще говоря, чис- ла. Тогда по целым частям u, v определя- ется, между какими узлами сетки (если угодно, между какими пикселами тексту- ры) находится наша точка, а по дробным - как близко она находится к каждому из узлов (см. рис. 3). Здесь 1, 2, 3, 4 - «окружающие» точку пикселы текстуры (они же узлы сетки за- мера цвета). Пусть iu, iv - целые части координат текстуры точки; fu, fv - дроб- ные части. Тогда 1, 2, 3, 4 имеют коорди- наты в текстуре (iu.iv), (iu+1,iv), (iu,iv+1), (iu+1,iv+1). Интерполируем какую-то компоненту цвета (R, G или В) по прямым 1-3 и 2-4: а.с = 1.с + (З.с - 1.с) * fv b.c = 2.с + (4.с - 2.с) * fv то есть а.с = c[iu][iv] + (c[iu][iv+1 ] - c[iu][iv]) * fv b.c = c[iu+1 ][iv] + (c[iu+1 ][iv+1 ] - c[iu+1][iv]) * fv Теперь интерполируем цвет по прямой ab в нашей точке: с = а.с + b.c * fu Интерполировав по этой формуле каж- дую компоненту цвета, получим, наконец, готовый результат - цвет точки; но уже с учетом билинейной фильтрации. Здесь у нас получилось по три умноже- ния на компоненту. То есть, в сумме - де- вять умножений на пиксел. Можно, ко- нечно, честно считать по этим формулам, делая девять умножений для каждого пи- ксела. Но можно заменить все умноже- ния на выборки по таблице, u, v обычно - это fixedpoint; fu, fv - тоже (кстати, в слу- чае с fixedpoint целые и дробные части вычисляются ровно одной операцией and). Пусть мы используем 24-битный цвет и 16:16 fixedpoint; тогда одна компо- нента цвета занимает 8 бит, а дробную часть можно одним сдвигом перевести в 24:8 fixedpoint. Получаем 256 возможных значений для любой компоненты цвета и 256 возможных значений для дробной ча- сти, то есть - табличка 256x256. Если цвет 15/16-битный, или используется бо- лее грубое (скажем, до пяти бит) округле- ние дробной части - табличка становится еще меньше. Памяти, конечно, не жалко, но кэш-память пока не резиновая, так что чем меньше lookup-таблица, тем оно луч- ше для скорости. Вот и все. Осталось только упомянуть, что лучше занести в табличку не байты, а слова (для данного примера это будет 8:8 fixed- point), и складывать все результаты тоже как слова, а потом сдвигом переводить обратно в целые числа. Иначе (особенно в случае 15/16-битных режимов) будет заметен небольшой шум на текстуре, по- являющийся из-за ошибок округления. Мипмэппинг Если полигон относительно сильно уда- лен от камеры или повернут - так, что со- седним пикселам на экране соответству- ют сильно разнесенные точки текстуры, то возникают всякие неприятные эффек- ты. Причина в следующем: при текстури- ровании мы выбираем лишь какую-то од- ну точку текстуры, а реально в экранный пиксел будет проецироваться несколько точек текстуры. Вообще, идеально было бы провести до пересечения с гранью ЗО-пирамиду с вершиной в камере и ос- нованием-пикселом, выбрать все точки текстуры, попадающие в наш пиксел, и усреднить значения их цветов. Вот толь- ко вычислительные затраты на одну точ- ку в этом случае окажутся просто фанта- стическими. Поэтому для борьбы с этим явлением ис- пользуют значительно более простую вещь, а именно - мипмэппинг: для каж- дой текстуры заранее создается несколь- ко ее копий уменьшенного размера (1/2, 1/4, и так далее), а далее при текстуриро- вании используется либо сама текстура, либо подходящая уменьшенная копия. Памяти при этом расходуется на 25-33% больше, чем без мипмэппинга, но зато, вроде бы, увеличивается качество изо- бражения. Как создать уменьшенную в два раза ко- пию текстуры? Здесь мы опишем три ме- тода, два из них очевидны, третий поза- имствован у Crystal Space. Методы рас- положены в порядке уменьшения скоро- сти и увеличения качества уменьшенной текстуры. Метод 1. Выкинуть все пикселы текстуры с нечетными координатами. Самый про- а в а • Ь а □ (рис. 3) 69
— ПРОГРАММИСТ Профессиональный журнал стой, самый быстрый, но дает не очень хорошо выглядящие результаты. Метод 2. Оставить точки с четными коор- динатами, в каждой точке усреднить зна- чения цвета в этой точке и ее трех сосе- дях (справа, снизу и справа-снизу). Метод 3. Оставить точки с четными коор- динатами, использовав в каждой точке фильтр, заданный вот такой матрицей: 1/16 1 2 1 242 1 2 1 70 Последовательно применяя любой из описанных методов, мы можем построить набор уменьшенных текстур. Остается выяснить, какую именно выбрать при текстурировании. Здесь опять будет опи- сано два достаточно простых метода; а вообще, конечно, их можно придумать значительно больше. Метод 1: полигональный мипмэппинг. В этом случае мы считаем площадь поли- гона в пикселах на экране и в текстуре (последнюю обычно можно посчитать за- ранее), определяем по ним примерное количество пикселов текстуры, соотвест- вующих одному пикселу экрана, и выби- раем нужный уровень уменьшения тек- стуры по следующей формуле: mipleuel = floor(log2(screenflrea / textureArea) / 2); здесь screenArea - площадь грани на экране TextureArea - площадь грани в текстуре Log2() функция двоичного логарифма (для Watcom С стандартная) Miplevel - уровень уменьшения; выбира- емая текстура должна быть сжата по обе- им осям в (2miPlevel) раз Поскольку бесконечное количество уменьшенных копий текстуры никто хра- нить не будет, да и увеличенные тексту- ры тоже обычно не хранят, a miplevel мо- жет получится любым действительным числом - значение надо обрубить: mipleuel = floor(log2(screenArea / textureArea) / 2); if (mipleuel < 0) mipleuel = 0; if (mipleuel > MRKMIPLEUEL) mipleuel = MRKMIPLEUEL; screenArea и textureArea проще всего посчитать по формуле Герона для пло- щади треугольника. В целом - метод практически не требует вычислительных затрат, так как все опе- рации проделываются один раз на грань. С другой стороны, здесь используется один и тот же уровень уменьшения (он же уровень детализации, LOD, level of detail) для всего полигона, а разным пикселам экрана вообще может соответствовать разное количество пикселов текстуры. Есть и более неприятное следствие - уровни уменьшения для двух соседних полигонов меняются скачком, а это не очень хорошо выглядит. Метод 2: попиксельный мипмэппинг. В этом случае нужный уровень уменьше- ния считается для каждого пиксела и вы- бирается на основе максимального шага в текстуре: textureStep = max( sqrt(dudx * dudx + dudx * dudx), sqrt(dudy * dudy + dudy * dudy)); mipleuel = floor(log2(textureStep)); Подобную операцию для каждого пиксе- ла проводить, конечно, накладно. Но при аффинном текстурировании dudx, dvdx, dudy и dvdy постоянны для всех пиксе- лов, так что попиксельный мэппинг ста- новится полигонным, только с другой ме- тодикой расчета уровня уменьшения. Для перспективно-корректного текстури- рования dudx, dvdx, dudy и dvdy посто- янны для всех пикселов одного кусочка (span’a), так что уровень уменьшения считается раз в несколько пикселов. Впрочем, даже раз в несколько пикселов подобное (два корня и один логарифм) считать будет достаточно медленно. Поэ- тому займемся небольшой оптимизаци- ей: во-первых, для скорости можно сде- лать упрощение и считать, что: textureStep = sqrt(dudx * dudx + dudx * dudx); Далее, заметим,что Iog2(sqrt(x)) = Iog2(x) I 2, откуда mipleuel = floor(log2(dudx * dudx + dudx * dudx) / 2); Осталась, практически, одна трудоемкая операция - взятие логарифма. Но и ее можно убрать. Дело в том, что числа с плавающей запятой (float’bi) как раз и хранятся в логарифмической форме, и floor(log2(x)) можно посчитать вот так: float х; int floor_log2_x; х = 123456; // чистый С floor_log2_x = ((*((int*)&x)) - (127 « 23)) » 23; // C++ floor_log2_x = (((int&Jx) - (127 « 23)) » 23; Теперь floor(log2(sqrt(x))) = floor(log2(x) 12) считаем как // чистый С mipleuel = ((*((int*)&x)) - (127 « 23)) » 24; // C++ mipleuel = (((intO)x) - (127 « 23)) » 24; Естественно, что этот трюк можно приме- нить и в случае полигонного мипмэпинга для полного устранения медленных опе- раций типа sqrt(), Iog2(). Вот, в общем-то, и все. W
Здесь мы рассуждаем о программистском «фундаменте»; о базовых вещах, без которых никакая разумная профессиональная деятельность невозможна. м предпоч что UNIX - п знаменитым как им заниматься; основные принципы, методы, алгоритмы; самая что ни на есть «теория и методология» А во-вторых - это рабочие инструменты, с помощью которых, собственно, и. Средства разработки, компиляторы, полезные утилитки... то. посредством чего мы с вами «пашем и сеем». Сегодня мы рассказываем как раз о полезных инструментах. Для лиц, привыкших к комфорту и визуальным решениям - продолжение статьи о серии продуктов длля разработчика компании Rational. А для тех, кто зндные строки, опции и хитрые ключи [и вообще считает, , а остальное - наоборот :] - описание тонкостей работы со щиком GNU Make. Многие (судя по всему, только начина е программировать) snraTenv просят начать публиковать что-то типа учебника по основам программирования вообще; пона мы успешно сопротивляемся - напирая на то, что такой материал для нас будет великоват по объему и гораздо проще все-тани купить соответствующую книжку (благо в продаже их - пруд пруди...) Хотя уже начинаем сомневаться... напишите нам. что вы думаете по зтому поводу!
ПРОГРАММИСТ Профессиональный журнал НОВИЧКОВ А.Н rational@intertace.ru wwwjntertacem ЭФФЕКТИВНАЯ РАЗРАБОТКА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ С ИСПОЛЬЗОВАНИЕМ ТЕХНОЛОГИЙ RATIONAL (окончание) В предыдущем номере (№2) мы начали рассказ о продуктах компании Rational Software, которая является одним из лидеров в области создания инструментальных средств и методологий разработки ПО. Инструментарий Rational действительно широко применяется софтверными компаниями - особенно крупными - для эффективной организации процесса разработки ПО. В первой статье мы рассказали об общей идеологии, предлагаемой разработчикам (Rational Unified Process), о продуктах Rational Rose. SoDA и Requisite PRO. На этот раз - поведаем об остальных инструментах Rational. 72 Поскольку проект постоянно меняется (исправляются ошибки, дорабатываются и добавляются элементы), жизненно важно знать статистику того, что менялось, кем, и в какое время. Итак - предположим, проект наш запущен, и начал жить собственной жизнью. Наи- большая головная боль на этом этапе - из- менения в проекте. Поскольку проект по- стоянно меняется (исправляются ошибки, дорабатываются и добавляются элемен- ты), руководителям и менеджерам жиз- ненно важно знать статистику того, что менялось, кем, и в какое время. В большинстве случаев нужно не просто сохранять «уведомления» о внесении из- менений - хочется иметь полный объем необходимой информации в какой-либо базе данных - например, Oracle. Здесь по- может программа ClearQuest. ClearQuest ClearQuest является мощным средством управления запросами на изменение (change request management - CRM), спе- циально разработанным с учетом динами- ческой и сложной структуры процесса раз- работки ПО. В течение всего жизненного цикла продукта ClearQuest отслеживает и управляет действиями любого типа, при- водящими к изменениям - помогая, тем самым, создавать ПО более предсказуе- мым (и правильным) образом. ClearQuest позволяет: ♦ Внедрить надежный и проверенный про- цесс CRM, либо изменить уже существу- ющий процесс для удовлетворения спе- цифическим требованиям ♦ Управлять изменениями, возникающими в ходе процесса разработки ПО ♦ Оптимизировать путь прохождения за- просов на изменения, а также связан- ные с ним формы и процедуры ♦ Через World Wide Web поддерживать связь внутри команд, разделенных тер- риториально ♦ Используя богатые возможности графи- ческого представления информации и отчетов - визуально анализировать по- лученный прогресс проекта ♦ Интегрировать в проект средства конфи- гурационного управления (такие как Rational ClearCase), что позволит созда- вать логические связи между запросами на изменениеч и процессом развития кода Среди положительных черт данного про- дукта - его адаптивность (то есть продукт можно доработать на месте с учетом кон- кретной функциональности), масштабиру- емость, простота в администрировании и использовании. В работе с программой каждый участник проекта может заходить в базу и опреде- лять собственные запросы. Запросы на изменения проходят цикл из нескольких состояний (states) - от подачи запроса до его разрешения. Например, только что поданный запрос находится в состоянии «Подан» (Submitted). После пе- редачи запроса конкретному сотруднику он переходит в состояние «Назначен» (Assigned). Начало работы над запросом переводит его в «Открытое» состояние (Open), и вся команда может видеть, что кто-то обрабатывает запрос. Наконец, ко- гда запрос проверен и закрыт - он прохо- дит соответственно стадии «Проверяется» (Verify) и «Закрыт» (Resolved). Таким образом - любой участник проекта, от подчиненного до руководителя, может пользоваться базой в собственных целях. Продукт направлен на всех участников ко- манды. S
Профессиональный журнал ПРОГРАММИСТ Когда проект в общих чертах сформи- рован (требования установлены; стру- ктура ясна; известно, как управлять изменениями) - наступает время соз- дания, собственно, программного про- дукта. За дело берутся программисты. Для облегчения жизни разработчика и улучшения производимого им кода, компания Rational также предлагает ряд программных решений. К сожалению, в арсенале компании пока нет собственной среды разработ- ки (редактора/компилятора); но проду- кты Rational легко интегрируются со многими популярными инструменталь- ными средствами ведущих компаний (таких как Microsoft, Oracle, IBM и др.). И если вы пользуетесь такими инстру- ментами - технологии Rational, бес- спорно, помогут. Прежде всего - чтобы получить каче- ственный код, разработчик должен вычистить явные ошибки, и настроить код на максимальную производитель- ность. После чего - передать тести- ровщикам для вылавливания логиче- ских (высокоуровневых) ошибок. Следующие 4 программы помогут раз- работчику существенно повысить ка- чество кода, снизив при этом время, необходимое на его детальную прора- ботку. Quantify Quantify предоставляет полную стати- стическую выкладку по всем вызовам (внешним и внутренним), невзирая на размеры тестируемого приложения и время его тестирования. Сбор данных осуществляется посредством техноло- гии OCI (Object Code Insertion); суть способа - в подсчете всех машинных циклов путем вставки счетчиков в код для каждого функционального блока тестируемой программы (все циклы приложения просчитываются реально, а не при помощи произвольных выбо- рок, как в большинстве пакетов тести- рования). При этом тестируется не только сам исходный код, но и все ис- пользуемые компоненты, (например: библиотеки DLL, системные вызовы); и для подобного анализа совсем не- обязательно иметь исходные тексты тестируемого приложения (правда, в этом случае нет возможности отсле- живать внутренние вызовы). Статистическую информацию по вы- зовам можно перенести в Microsoft Excel, где легко строятся графики и сводные таблицы для разных запусков программы. Заметим, что тестируемое приложе- ние можно перекомпилировать и запу- стить повторно; при этом Quantify за- помнит все предыдущие вызовы и даст сравнительную оценку. Продукт ориентирован на разработчиков. Профайлер Rational Quantify генерирует в табличной форме список всех вызываемых в процессе работы приложения функций, указывая временные характеристики каждой из них. 73 Разработчик постоянно сталкивается с проблемой увеличения производи- тельности собственного приложения в сжатые сроки и с максимально воз- можной эффективностью. Проблема нетривиальная - разнообразные про- файлеры не всегда предоставляют удобный интерфейс и достаточный на- бор функций. Purify Rational Quantify - достаточно гибкое, мощное и удобное средство для сбора информации о производительности приложений. Rational Purify поможет решить проблемы, связанные с утечками памяти и run-time ошибками. Quantify генерирует в табличной фор- ме список всех вызываемых в процес- се работы приложения функций, ука- зывая временные характеристики ка- ждой из них. Этот продукт поможет решить пробле- мы, связанные с утечками памяти и run-time ошибками. Не секрет, что многие программные продукты ведут себя «не слишком скромно», замыкая на себя во время работы все систем- ные ресурсы без большой на то необ- ходимости. А это может привести сис- тему к краху в самый ответственный момент. Подобного рода ошибки трудно отсле- дить стандартными средствами разра- ботки. Дело в том, что в подавляющем большинстве случаев проектные сро- ки вынуждают смотреть «сквозь^
ПРОГРАММИСТ пр°Фесси°нальнь|й журнал 74 пальцы» на такие «мелкие», но труд- ноустранимые неточности. В общих чертах, работа Purify сводит- ся к сбору и выводу детальной стати- стики об использовании памяти при- ложением. Программа собирает дан- ные о любых потерях в памяти - ба- нальном невозвращении блока, неис- пользованном указателе; умеет выво- дить состояние среды при run-time ошибках. Purify позволяет разработчику не только видеть состояние исполнения (предупреждения, ошибки), но и пере- ходить к соответствующему месту в исходном тексте (естественно, только для внутренних вызовов - исходные тексты динамических библиотек про- граммистам недоступны). Очень мощный продукт, обладает дос- таточно простым интерфейсом и осва- ивается специалистом за 2-3 дня. Продукт ориентирован на разработчиков. Pure Coverage собирает статистику о тех участках программы, которые во время тестирования не были выполнены (пройдены). Программа также считает так называемые «хиты» - наиболее активно исполнявшиеся строки. Pure Coverage Основное и единственное назначение продукта Rational Pure Coverage - вы- явление участков кода, пропущенных при тестировании приложения. Очевидно, что даже при самом дотош- ном тестировании программы специа- листу не удается проверить абсолютно все ее функции. Но согласитесь - дос- таточно обидно получить системную ошибку после просьбы заказчика ткнуть мышью в одну из бесчисленных кнопок на экране. После такого прова- ла уже не доказать, что немало бес- сонных ночей проведено в детальном тестировании программы, и что только эта, одна-единственная, функция при- водит к сбою... ЕсДи же разработчик воспользуется средством Rational Pure Coverage - не- выполненного кода в программе про- сто не останется. Pure Coverage собирает статистику о тех участках программы, которые во время тестирования не были выполне- ны (пройдены). Программа также счи- тает так называемые «хиты» - наибо- лее активно исполнявшиеся строки. Разработчик может не просто оценить, сколько раз вызывалась та или иная функция - а сколько раз исполнилась каждая строка, составляющая ее. Имея подобную статистику, очень про- сто выявить не исполнившиеся строки и проанализировать причину, по кото- рой они не получили управления. Такие строки Pure Coverage подсвечи- вает красным цветом - тем самым указывая на наличие «черных дыр» непроверенного кода в программе, и давая разработчику хорошую пищу для размышлений. Продукт направлен на разработчиков От тестирования кода программы пе- рейдем к функциональному и нагру- зочному тестированию. Это следую- щий этап в разработке ПО - когда все требования к системе определены, на- писана какая-то работоспособная вер- сия программы, и ее необходимо про- тестировать. Ниже описаны две основ- ные программы, направленные на вы- сокоуровневое тестирование - функ- циональное (т.е. интерфейса и пользо- вательских функций) и нагрузочное (необходимое в основном для прило- жений клиент-сервер). Каждая из программ способна созда- вать специальные скрипты для после- дующего повторного использования - например, чтобы узнать, как сказа- лись на функциональности те или иные изменения, внесенные разработ- чиком в программу. Имея набор по- добных тестовых программ, можно пе- рейти от широко практикуемого кус- тарного тестирования к профессио- нальной тестовой лаборатории. Пос- леднее, в свою очередь, даст сущест- венный скачок в качестве выпускае- мых приложений. Robot Rational Robot - средство функциональ- ного тестирования, базирующееся на объектно-ориентированной технологии.
Профессиональный журнал ПРОГРАММИСТ Это позволяет существенно превзойти традиционные средства GUI-тестирова- ния (тестирования графического интер- фейса) - так как здесь проверяются сотни и тысячи свойств всех (даже скрытых) объектов приложения - всех вместе и каждого по отдельности. Программа может работать в двух ре- жимах - автоматическом и ручном. В ручном режиме - пользователь сам задает на специальном языке сценарий тестирования (скрипт); в автоматиче- ском - пользователь тестирует прило- жение, a Robot автоматически генери- рует скрипт для дальнейшего повторно- го тестирования. Rational Robot поддерживает широкий спектр языков программирования и ERP-решений. Rational Robot позволяет редактировать, отлаживать и настраи- вать скрипты. Возможно тестирование сложных систем клиент/сервер на плат- форме Windows. Продукт ориентирован на разработчи- ков и тестировщиков. LoadTest LoadTest - средство автоматизирован- ного тестирования производительности распределенных сетевых приложений на платформах Windows и Unix. При этом тестировании важно нагрузить сервер большим количеством «вирту- альных пользователей». Например, это можно сделать так: уста- новить таймер для одного пользователя, чтобы определить, сколько времени зай- мет выполнение запроса, когда тысячи других пользователей будут одновре- менно посылать запросы на тот же са- мый сервер. Термин «тесты производительности» включает нагрузочные, стрессовые, конкурирующие и конфигурационные тесты. Совокупность этих тестов позво- ляет ускорить цикл тестирования про- изводительности и достигнуть значи- мых и точных результатов. Нагрузочное тестирование с использо- ванием LoadTest выполняется и для то- го, чтобы определить время отклика серверов или клиентских приложений при изменяющейся нагрузке. Его мож- но применить для подсчета максималь- ного количества транзакций, которое может выполнить сервер за определен- ный временной отрезок. Если клиент/серверная система ис- пользует распределенную архитектуру или средства балансировки нагрузки - нагрузочное тестирование может быть использовано для того, чтобы прове- рить правильность выбранных методов для балансирования (или выбранной архитектуры). Нагрузочное тестирование выполняет- ся как с использованием режима толь- ко виртуальных пользователей (когда измеряется время отклика только сер- верной части, или комбинации вирту- альных пользователей), так и в режиме использования графического интер- фейса (для измерения времени отклика системы на запросы конкретного кли- ентского приложения). Таким образом, данная программа поз- воляет проверять производительность любой клиент/серверной системы . Продукт направлен на тестировщиков, разработчиков, WEB-разработчиков. Итак, мы рассмотрели средства, пред- назначенные для отладки и тестирова- ния ПО. Осталось упомянуть только один программный продукт - использу- емый на всех этапах проекта: от идеи и до внедрения. Подробно описывать его мы не будем, но упомянуть «для полно- ты картины» - желательно. Rational ClearCase - это средство конт- роля версий и конфигураций, которое сохраняет всю историю каждого файла проекта с целью возможного сравнения изменений, включая миграцию файлов между независимыми проектами. Рассмотренные продукты - не полный список того, что предлагает Rational для построения эффективного произ- водства ПО. Но даже такого набора хватит с лихвой, чтобы более профес- сионально подходить к выпуску высококачественного программного обеспечения. А Два основные вида высокоуровневого тестирования - функциональное (интерфейса, пользовательских функций) и нагрузочное (производительности при больших нагрузках - необходимо, в основном, для приложений клиент-сервер). 75
ПРОГРАММИСТ Профессиональный журнал Владимир Игнатов ignatov@inlosec.ni http//vtadJgnaloviorg Эффективное использование GNU Make Часть 1 Широко распространенная утилита GNU Make обладает массой уникальных возможностей, которые могут здорово об- легчить разработку вашего проекта. Есть прекрасно написанное и довольно объемное руководство по GNU Make (дос- тупное также on-line). Однако, как водится, не все программисты находят время внимательно его изучить. А зря... В настоящей серии статей я рассмотрю некоторые приемы эффективного (на мой взгляд) использования GNU Make. 76 В качестве примера будем использовать не- кий «гипотетический» проект. Пусть это бу- дет, например, текстовый редактор - состо- ящий из нескольких файлов с исходным текстом на языке C++ (main.cpp1, Editor.cpp, TextLine.cpp) и нескольких заголовочных файлов (main.h, Editor.h, TextLine.h). В исходный файл main.cpp будут включать- ся (#include) файлы main.h и Editor.h, в файл Editor.cpp - файл Editor.h, а в файл TextLine.cpp - файл TextLine.h. Заголовоч- ный файл Editor.h включает в себя файл TextLine.h. Начнем с «обычного» make-файла. Как и «обычный стиральный порошок», наш make-файл будет прост и незатейлив. Пред- положим, что все файлы проекта располо- жены в одном каталоге: example_1-traditional / main.cpp main.h Editor.cpp Editor.h TextLine.cpp TextLine.h Makefile Для make-файла проекта я выбрал имя Makefile, рекомендуемое руководством по GNU Make - см. раздел «Имена make-фай- лов» (What Name to Give Your Makefile). Этот файл будет выглядеть следующим об- разом: # example_1-traditional/Makefile # Example 1. Пример "обычного" make- # файла # (С) Владимир Игнатов, 2000-2001 CFLRGS := -03 -fomit-frame-pointer .PHON?: clean rebuild iEdit: main.о Editor.o TextLine.o gcc -pipe $(LDFLHGS) -о $@ %.о: %.cpp gcc -pipe -c $(CFLRGS) $< main.o: main.h Editor.h TextLine.h Editor.o: Editor.h TextLine.h TextLine.o: TextLine.h clean: -rm *.o iEdit rebuild: clean iEdit 'хотя в Unix-подобных средах для исход- ных текстов на C++ принято использовать расширение «.С» или «.схх», я все-таки предпочитаю использовать «.срр» Обратите внимание: все строки make-фай- ла, являющиеся командами, обязательно должны начинаться с символа табуляции! Именно наличие этого символа (как прави- ло, невидимого!), является тем признаком, по которому make отличает строки с коман- дами от прочих строк make-файла. Так, в приведенном выше примере - строки gcc -pipe $(LDFLHGS) $Л -о $@ gcc -pipe -с $(CFLRGS) $< -rm *.о iEdit начинаются с символа табуляции! Теперь подробно, строчка за строчкой, раз- берем содержимое нашего make-файла. Переменная CFLAGS будет хранить опции компиляции. По умолчанию - она получит указанное в make-файле значение. Хотя имя этой переменной можно выбрать произ- вольным, я предпочел использовать «обще- принятое» имя, которое используется для этой цели в большинстве make-файлов - см. раздел руководства «Variables Used by Implicit Rules» («переменные, используемые в неявных правилах»). Далее, при желании, можно будет задать другие опции компиляции, указав их при вы- зове make, например: make CFLRGS:="-O0 -g” При этом все попытки make-файла присво- ить переменной CFLAGS другое значение будут игнорироваться. Обратите внимание, что из-за использова- ния оператора (вместо =’), переменная CFLAGS становится упрощенно-вычисляе- мой (simply expanded). Я предпочитаю все- гда использовать переменные этого типа - они кажутся мне более удобными и надеж- ными. Вдобавок, это более эффективно, так как значение переменной не вычисляется заново каждый раз при ее использовании. Рекурсивно вычисляемыми (recursively expanded) переменными я пользуюсь в ред- ких случаях, и только там, где это действи- тельно необходимо. Подробно две разно- видности переменных описаны в разделе руководства «The Two Flavors of Variables» («две разновидности переменных»). Следующая конструкция make-файла - пра- вило со специальной целью .PHONY - объ- являет, что цели clean и rebuild являются аб- страктными (phony). Теперь make не будет пытаться найти на диске файлы clean и
Профессиональный ж , » н . п ПРОГРАММИСТ ‘2 rebuild, что положительно отразится на про- изводительности и надежности работы make-файла. Абстрактные цели подробно описаны в разделе руководства «Phony Targets» («абстрактные цели»). Дальше в make-файле находится явное пра- вило с целью iEdit; оно описывает, каким об- разом должен получаться исполняемый файл iEdit (компоноваться из перечислен- ных объектных файлов). Чтобы пользова- тель мог изменить параметры компоновки, используется «стандартная» переменная LDFLAGS. Так, вызвав: make LDFLRGS:=-static можно будет получить «статический» вари- ант исполняемого файла. Из полезных оп- ций стоит упомянуть -pipe, которая застав- ляет компилятор использовать каналы об- мена (а не временные файлы) для связи своих подсистем. Это позволяет повысить скорость работы. Имена компонуемых объектных файлов и имя исполняемого файла передаются ком- пилятору с помощью автоматических пере- менных $л («список всех пререквизитов правила») и $@ («имя цели правила») соот- ветственно. Подробно автоматические пе- ременные обсуждаются в разделе руковод- ства «Automatic Variables» («автоматиче- ские переменные»). Использование автома- тических переменных не только упрощает запись, но и повышает надежность работы make-файла (заметьте, что пререквизиты и цели правила могут находиться не только в текущем каталоге!). Следующая конструкция make-файла - %.□: %.срр gcc -pipe -с $(CFLRGS) $< представляет собой шаблонное правило, описывающее процесс получения объектно- го файла из соответствующего ему файла с исходным текстом. Имя конкретного исход- ного файла, к которому в данный момент применяется это правило, передается компи- лятору с помощью автоматической перемен- ной $< («имя первого пререквизита прави- ла»). Упомянутая выше переменная CFLAGS передает компилятору требуемые опции компиляции. Кроме шаблонных правил, в GNU Make име- ется и другой способ задания неявных пра- вил - так называемые «суффиксные прави- ла» (suffix rules). С использованием суф- фиксных правил, процесс компиляции'исход- ных текстов можно было бы описать так: .срр.о: gcc -pipe -с $(CFLRGS) $< Из-за наличия в GNU Make механизма шаблонных правил, суффиксные правила можно считать устаревшими - их синтаксис менее нагляден, а возможности - гораздо беднее. В своих проектах я не пользуюсь суффиксными правилами. Руководство по GNU Make также не рекомендует их использовать. Следующие несколько правил make-файла: main.о: main.h Editor.h TentLine.h Editor.o: Editor.h TextLine.h TextLine.o: TextLine.h посвящены описанию зависимостей отдель- ных объектных файлов от заголовочных. Указывать такие зависимости обязательно нужно - в процессе разработки программы заголовочные файлы могут меняться до- вольно часто (описания классов, например, традиционно размещаются в заголовочных файлах). Если не указывать зависимости объектных файлов от соответствующих за- головочных файлов, то может сложиться си- туация, когда разные объектные файлы программы будут скомпилированы с ис- пользованием разных версий одного и того же заголовочного файла. А это, в свою оче- редь, может привести к частичной или пол- ной потере работоспособности собранной программы. Составление подобных зависимостей «вручную» требует довольно кропотливой работы: недостаточно просто открыть файл с исходным текстом и перечислить имена всех заголовочных файлов, подключаемых с помощью #include. Дело в том, что одни заголовочные файлы могут, в свою оче- редь, включать в себя другие заголовочные файлы, так что придется отслеживать всю «цепочку» зависимостей. Далее в make-файле следует «традицион- ное» правило для очистки каталога проекта: clean: -rm *.о iEdit 77 В данном случае - удаляются все объект- ные файлы и исполняемый файл програм- мы. Обратите внимание на символ *-’ в нача- ле строки с командой. Он заставляет make игнорировать ошибки, которые могут воз- никнуть в ходе исполнения команды. Следующее правило (с целью rebuild) я за- вел для удобства. Предположим, я хочу пе- рекомпилировать программу, задав «-О2» в качестве опции компилятора. В этом случае я должен выполнить что-нибудь вроде: make clean make CFLRGS:=-02 Первая команда удалит все скомпилирован- ные ранее (со «старыми» опциями) объект- ные файлы. Следующая команда выполнит перекомпиляцию программы. Правило rebuild выполняет те же самые действия, но только в «автоматическом» режиме. Так, следующая команда перекомпилирует про- грамму с опцией «-О2» и получением стати- ческого варианта исполняемого файла: make rebuild CFLRGS:=-02 LDFLAGS:=-static >
ПРОГРАММИСТ Профессиона л ьный журнал А с помощью команды: make rebuild CFLRGS:="=-S -uerbose-asm" можно получить ассемблерные листинги всех исходных файлов (правда, без созда- ния исполняемого файла). Описанный make-файл вполне справляется со своей работой; но посмотрим на него критически - так ли он хорош? Пожалуй, один из главных его недостатков - «привя- занность» к конкретному проекту. При по- пытке использовать его для сборки другой программы - придется не только заново со- ставлять список объектных файлов, но и (что гораздо более неприятно) заново соз- давать многочисленные правила, описыва- ющие зависимости объектных файлов от заголовочных файлов программы. Даже в ходе разработки одного проекта - надо будет постоянно редактировать подоб- ный make-файл, чтобы он отражал текущее состояние программы. Все это очень не- удобно и - что еще хуже - чревато ошибка- ми. К счастью, богатые возможности утилиты GNU Make позволяют облегчить себе жизнь. Попробуем сделать первый шаг к ав- томатизации - возложим на GNU Make обя- занность построения списка объектных файлов. Разумеется, простой «трюк», наподобие: iEdit: *.о gcc -pipe $(LDFLRGS) $Л -о не будет работать - такое правило будет оз- начать, что файл iEdit зависит от всех объ- ектных файлов, существующих в данный момент, что, очевидно, неверно. Поскольку каждый объектный файл зависит от соответствующего ему файла с исход- ным текстом, желаемой цели можно добить- ся другим путем - получить список исход- ных файлов и преобразовать его в список объектных файлов. Для получения списка исходных файлов можно использовать функцию wildcard. Эта функция описана в разделе руководства «Functions for File Names» («функции для об- работки имен файлов»). Она возвращает список файлов, удовлетворяющих заданно- му шаблону. Например, конструкция: source_files := $(iuildcard *.срр) поместит в переменную source_files список всех файлов с расширением «.срр», находя- щихся в данный момент в текущем катало- ге. Обратите внимание, что шаблон задает- ся в формате «командного интерпретатора» -то есть, например, нужно писать «*.срр», а не «%.срр» (как в шаблонном правиле). Теперь остается преобразовать полученный список исходных файлов в список объект- ных файлов. Это можно сделать с помощью функции patsubst. Эта функция описана в разделе руководства «Functions for String Substitution and Analysis» («функции анали- за и подстановки строк»). Она находит в указанном тексте слова, соответствующие шаблону, и заменяет их заданным образом. Мы будем действовать так: object_files := $(patsubst %.срр, %.о, $(source_files)) Теперь переменная objectjiles будет содер- жать список объектных файлов программы! Вот как может выглядеть новый вариант make-файла: # eKample_2-auto_obj/Makefile # Enample 2. Автоматическое построение # списка объектный файлов # (С) Владимир Игнатов, 2000-2001 CFLHGS := -03 -fomit-frame-pointer .PHONV: clean rebuild iEdit: $(patsubst %.cpp,%.o,$(mildcard *.cppi) gcc -pipe $(LDFLHGS) $Л -о $@ %.o: %.cpp gcc -pipe -c $(CFLRGS) $< main.o: main.h Editor.h TeKtLine.h Editor.o: Editor.h TeKtLine.h TeKtLine.o: TeKtLine.h clean: -rm *.o iEdit rebuild: clean iEdit В целях сокращения записи - я не стал заво- дить отдельные переменные для хранения списков исходных и объектных файлов. Итак, make-файл стал более универсальным - список объектных файлов теперь строится автоматически и не требует нашего вмеша- тельства. Остается, однако, главная пробле- ма - необходимость задания зависимостей от заголовочных файлов. О том, как можно переложить эту неприят- ную работу на плечи компилятора GCC - я расскажу в следующей статье... Полезные ссылки: «Домашняя страничка» программы GNU Make http://www.gnu.org/software/make/make.html Страничка с on-line руководствами по про- граммам GNU http://www.gnu.org/manual/ Моя домашняя страничка, где находится перевод руководства по GNU Make http://www.geocities.com/SiliconValley/Office/ 6533 Домашняя страничка Пола Смита, кото- рый в данный момент отвечает за разра- ботку и сопровождение программы GNU Make. http://www.paulandlesley.org/ Проект Cygwin. Перенос программ GNU в среду Win9x/NT http://cygwin.com/ W
Раньше сие называлось «Real Life». Но впредь - из соображений стилисти- ческих и патриотических - решили именовать этот раздел просто «Жизнь». Здесь, как обычно - все, что с программированием связано довольно кос- венно, зато с программистами - очень даже прямо. Объявления, работа, подписка, web-обзоры, ваши письма... и прочие радости Жизни. Например, к этому номеру - мы освоили жанр интервью :) Нак вы помните, в новостях нашего самого первого номера мы сообщали, что система «НонсультантПлюс» первой среди российских программ прошла сертификацию на совместимость с Windows'2000 - и по сей день остается единственной, имеющей такой сертификат. Нас очень заинтересовало - почему, зачем и как это делается; и вот, воору- жившись диктофоном, мы проникли на усердно охраняемую ВОХРом терри- торию некогда секретного Акустического Института. Где-то в недрах его ла- биринтов нас встретили гостеприимные сотрудники того самого «Нонсуль- тантПлюса», которые и поведали нам все тайны процесса сертификации, с леденящими душу подробностями читайте на здоровье. А еще мы расскажем о довольно популярном способе заработка для про- граммиста - написании и распространении ЗЬвпеи/аге-приложений. Подно- готная процесса, подводные намни - и прочее. Да; не забудьте заглянуть в раздел объявлений! [н моменту сдачи номера мы даже успели получить несколько сообщений для «ярмарки вакансий»...]
ПРОГРАММИСТ Профессиональный журнал СЕРТИФИКАЦИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ Мы взяли интервью у сотрудников компании ‘Консультант Плюс", которая первой среди российских программ полупила сертификат о совместимости с Windows 2000. На наши вопросы отвечали Николай Потемкин (ведущий программист Windows-версии КонсультантПлюс) и Георгий Поляков (заместитель руководителя отдела программирования по программным технологиям). 80 В: Прежде всего - что такое серти- фикация ПО для Windows?Kro ее проводит и по каким правилам? Сразу хотелось бы подчеркнуть: тес- тирование и сертификацию осуществ- ляет не фирма Microsoft. Этим занима- ется независимая компания VeriTest. Помимо проверки ПО на совмести- мость с Microsoft Windows, она прове- ряет софт (и аппаратное обеспечение) на совместимость с продуктами дру- гих фирм. Услугами VeriTest пользуют- ся, например, Compaq, Novell, Oracle... Это - авторитетная международная организация. По каждой программе сертификации существует набор критериев, которым должно удовлетворять тестируемое ПО или железо. Например, для серти- фикации на совместимость с Windows 2000 план тестирования - это 400- страничная книга. Мы прошлись по всем критериям еще до сдачи про- граммы КонсультантПлюс на тестиро- вание, и отловили несоответствия са- ми - так что тестировщики из VeriTest, тщательно проверив нашу программу, не нашли ничего криминального. В: Какие классы программных про- дуктов подлежат сертификации? Есть программы сертификации поль- зовательских приложений, серверных приложений, больших серверных про- дуктов, программа “Designed for Microsoft Office”, и другие. В: А какую программу сертифика- ции прошел КонсультантПлюс? Мы проходили “Certified for Microsoft Windows 2000 - Windows 2000 Professional”. Это - самая ходовая сер- тификация, которую могут проходить пользовательские приложения для Windows 2000. Кстати, эта программа сертификации для нас уже третья. В 1997 году мы ус- пешно прошли сертификацию по про- грамме “Designed for Microsoft Windows 95/NT”, а через год, одновре- менно с выпуском русской версии Windows 98, мы получили сертификат “Designed for Microsoft Windows 98/NT”. Но самым трудным для нас оказался последний сертификат - под Windows 2000 - очень уж возрос объем предъя- вляемых к программе требований. В: Кстати, продукты самой фирмы Microsoft имеют сертификат на сов- местимость с Windows 2000? Office тестировался, он получил серти- фикат чуть раньше нас - с некоторы- ми замечаниями, которые Microsoft либо счел некритическими, либо по- обещал исправить к следующему ре- лизу. Думаю, нам за такие замечания серти- фикат могли бы и не дать... A Office прошел, хотя и не с первого раза (сер- тифицирован ServicePack 1). Certified For Windows 2000 Professional В: Какие еще продукты прошли сертификацию Windows 2000? На момент получения сертификата на- ми в мире было всего 79 таких проду- ктов, от 58 фирм. Из них порядка 10 - продукты Microsoft. Какие еще? Ну, например, PC Anywhere. Сейчас их, кажется, около 100. А в России мы пока единственные. В: Зачем нужен сертификат пользо- вателю? Что он ему гарантирует? Как подчеркивает сама фирма Microsoft, сертификат не гарантирует, что в данной программе нет ошибок. Он гарантирует, что программа совме- стима с Windows 2000 и успешно про- шла все тесты на взаимодействие с данной ОС: в критических ситуациях, в условиях перегрузки, при нехватке места на диске, с файлами, именован- ными достаточно критически, на обо- рудовании экзотической конфигура- ции. Еще Microsoft гарантирует благо- получную жизнь программы в ее окру- жении - она точно не испортит окру- жение ОС и других приложений. Кро- ме того, такая программа соответству- ет некоторым правилам, принятым в Windows: она правильно размещается сама и размещает свои данные, не ле- зет, куда не надо. При переходе на Windows 2000 с других операционных систем - программа останется в рабо- чем состоянии (если она, конечно, ра- ботала под предыдущей системой). Более того - на будущих версиях ОС Windows она тоже будет работать. В: А зачем сертификат понадобил- ся вам? Что это вам дало? Мы считаем сертификацию независи- мым подтверждением качества наших систем. Если ни один из русских про- дуктов не прошел сертификацию, а мы прошли - значит, у нас работают хорошие профессионалы, а наш про- дукт достаточно надежный, качествен- ный. Нам стало легче сопровождать про- дукт; сертификация на совместимость с ОС сильно упрощает техническую поддержку - многие возможные при- чины проблем можно просто исклю- чить. .. Кроме того, по данным опросов - на- дежность и совместимость очень важ- ны для пользователей справочно-ин- формационных систем. На первом ме- сте для пользователей таких продук- тов стоит полнота информации, а на втором месте не цена системы (как можно было бы ожидать), а ее совме- стимость с другими программами, ус- тановленными у пользователя (в том числе - и с ОС). Так что сертификация - это очень сильный маркетинговый ход. В: Перейдем к технической стороне вопроса. Наш журнал читают про- граммисты, и им будет интересно узнать, что значит подготовка к сертификации для разработчика.
а Профессией альный ж у р Для нас дело началось с изучения сай- та www.veritest.com. Мы изучили, ка- кие требования есть у данной про- граммы сертификации, скачали кон- кретные документы (напомню, что в нашем случае один из документов был 400-страничной книгой). Дальше начинают исправляться те места в программе, которые явно не подходят. Например, если есть 16-разрядный код - его надо исключить (к счастью, мы сделали это еще 5 лет назад). До- пускается только 32-разрядный код. В: Интересует ли тестировщиков исходный код? Нет, только исполняемый. Следующий этап - разворачивание тестовой сре- ды. Это - компьютер весьма причуд- ливой конфигурации, в котором два CD-ROM’a разных типов, две клавиа- туры, две мыши разных типов (PS/2 и USB), два монитора, много логических дисков, разделов разных форматов. Программная конфигурация компью- тера тоже весьма причудлива. На дис- ках - файлы и директории с именами по 256 символов, такие же имена у принтеров. Создается среда, крайне неудобная для работы программы. Но в рамках правил. В таких экстремаль- ных условиях и гоняется программа. Есть и часть тестов, где мы смотрим на поведение кода из дебаггера. Та- кая самостоятельная работа оправды- вает себя: мы действительно находи- ли тонкие, ненадежные места и испра- вляли их. Если бы ошибки всплыли на контрольном тестировании в Veritest, то пришлось бы доплачивать за по- вторное тестирование и потерять мно- го времени. В: А у вас были ошибки на реаль- ном тестировании? Нет. В этом можно убедиться на сайте www.veritest.com. Кстати, во всем ми- ре принято получать информацию о сертификации продуктов именно на сайте производителя или сертифика- тора. И данная программа вообще не предусматривает выдачи сертифика- та в виде официального документа с печатями. А вот в нашей стране почему-то всем хочется увидеть красивую бумагу с личной подписью Билла Гейтса... В: Много ли времени заняла подго- товка? Да, потому что объем тестов велик. Только подготовка тестовой станции согласно спецификациям заняла око- ло двух недель. Фактически, у нас бы- ла целая куча компонентов, из кото- рых мы собирали разные конфигура- ции. Все это с трудом помещалось на столе, и заходившие в комнату с удив- лением спрашивали: “А что это у вас такое?”. А сами тесты заняли еще больше времени - три месяца. В: Есть ли какие-нибудь типичные вещи, с которыми столкнутся все разработчики, решившиеся на тес- тирование? Есть тонкие места, связанные с моди- фикацией интерфейсов программиро- вания Microsoft. Например, до Windows 2000, чтобы снять показания курсора мыши, использовался один макрос. С поддержкой двух монито- ров и отрицательными координатами для этого пришлось использовать сов- сем другие средства. Что еще...Один из этапов тестирова- ния посвящен инсталляции продукта. Требуется использование стандартно- го Windows Installer, поддержка режи- ма рекламной инсталляции (“устанав- ливать по первому вызову”). А Windows 2000 не стоит на месте, выхо- дят сервис-паки, какие-то вещи меня- ются - и это надо учитывать. В общем - пришлось и посидеть над документацией, и почитать конферен- ции - нетривиальные вещи были. Было и немало забавных случаев во время подготовки и тестирования. На- пример, мы проверяли программу на корректность работы с экранными ре- жимами, предназначенными для лю- дей с плохим зрением. И была там конфигурация “Высококонтрастная тыква” (High Contrast Pumpkin scheme), при которой все элементы экрана ста- новятся огромного размера и весьма необычных красно-желтых цветов. За- ходившие в комнату очень смеялись, глядя на нашу “тыкву". Для слабовидящих есть и такой эле- мент, как экранная лупа. На непосвя- щенных очень сильное впечатление производит буква, увеличенная до по- ловины экрана... В: Для соответствия программе сертификации достаточно следо- вать рекомендациям Microsoft по разработке ПО, или есть подводные камни? Определенные противоречия есть. Например, популярная библиотека MFC уже сама по себе содержит несо- ответствия. И в документации к ней — ПРОГРАММИСТ кое-где так и сказано: дескать, “из двух зол мы выбираем меньшее”. Это самое “меньшее зло” порой и не соот- ветствует некоторым критериям сер- тификации. Речь идет, например, о плавающем toolbar’e, в котором некор- ректно работает кнопка закрытия ок- на. А на двух мониторах плавающие toolbar's, вообще работают криво. В: Сертификат получила версия 6.30 программы КонсультантПлюс. Получат ли сертификат следующие версии “по наследству”? По правилам следующая версия полу- чает сертификат автоматически - в случае, если в ней нет функциональ- ных изменений. Косметические изме- нения допускаются. По крайней мере, так было с предыдущими программа- ми сертификации. Думаю, что все так и осталось. В: Отличается ли программа серти- фикации для локализованных про- дуктов, например русскоязычных? Хотя наш продукт русскоязычный, он успешно работает и в английской вер- сии Windows (с установленной под- держкой русского языка). Однако по правилам требуется тестирование именно на русской версии системы. Поэтому, в случае с Windows 98 - на- ша программа около месяца томилась в лаборатории, пока не вышла офици- альная русская версия операционной системы. Кстати, о русском языке: в ближайшем к нам (парижском) отде- лении VeriTest русскую программу протестировать не смогли; поэтому пришлось проходить тесты в Лос-Анд- желесе. В: Вы, как разработчики, получили какое-то удовлетворение или ощу- тимое улучшения работы после пе- ределки вашего продукта под сер- тификационные стандарты? Огромное моральное удовлетворение - безусловно. С одной стороны - про- грамма стала сложней, она поддержи- вает такие вещи, о которых мы не ду- мали раньше. А с другой стороны - код стал более чистым и “причесан- ным”. И к следующей сертификации нам будет проще подойти. В: Значит, вы и дальше будете сер- тифицировать новые версии своей программы? Обязательно! ф 81 .а z со S
!_! ПРОГРАММИСТ Профессиональный журнал Sidecut ru7@nifu ЗАРАБОТОК ДЛЯ ПРОГРАММИСТА 0 СЕТИ Вот, наконец, на свет появился результат бессонных ночей и испорченных нервов - новая программка. Вроде, хо- рошая получилась; и самому нравится, и знакомым. ..туги возникает мысль: «а что, если мне её продвинуть в массы?»; а за ней другая: «хм... а если это делать за деньги - наверное, неплохо заработаю?!». Окрыленный гениальной идеей, начинаешь работать серым веществом... и, после непродолжительного разду- мья, приходишь к мысли, что всё не так-то просто. Практически все способы распространения требуют больших денег (производство копий, продажа, реклама...) - которых, естественно, нет. Но если есть хотя бы доступ к интернет, то лучший рецепт - продавать свои программы именно там. Правда, в этом деле много подводных камней; так что я попробую поделиться своим опытом, дабы читатель не набивал се- бе лишних шишек... 82 п X ЭЕ Заработок для программиста в сети Чтобы распространять программу в се- ти, нужно добиться ее соответствия не- которым требованиям. Прежде всего, надо «подвести програм- му под конечного пользователя», т.е. сделать так, чтобы пользователю было приятно и удобно. Понятный интерфейс, не режущий глаз дизайн... На первых порах лучше «подглядывать», как оформлены другие известные програм- мы. Я не предлагаю заниматься плагиа- том, просто всегда стоит равняться на более опытных и подмечать интересные детали и идеи. Далее - раз уж программа будет не со- всем бесплатная - нужно разработать ограничительный механизм и механизм снятия ограничений (о способах ограни- чения написано ниже). Важно определиться с тем, на какую ау- диторию рассчитана программа-т.е. на «наших» людей или на иностранцев. Яс- но, что для буржуинов делать всё гораз- до выгоднее, они платят чаще и в боль- шем объеме. Если программа не сугубо локальная (как, например, карта Мо- сквы), то стоит сделать её многоязыко- вой - намного больше шансов, что пользователь купит или зарегистрирует программку на своем родном языке. Так что, в случае, если основная цель - по- лучение денег, стоит оптимизировать программу именно для иностранцев. Также надо бы позаботиться о совмес- тимости; программа, которая прекрасно работает под Win'9x - совсем не обяза- тельно будет работать под NT или Win’2000, не говоря уж про MacOS или UNIX. А пользователи по всему миру пользуются разными ОС, и в идеале ни у кого не должно возникнуть никаких проблем. Ну и, конечно, программа должна быть лучше всех конкурентов (если таковые имеются). Стоит внимательно посмот- реть на соперников, взять у них лучшее и добавить своего, чтобы не оставить им не единого шанса; а после релиза, в процессе «технической поддержки» и разработки новых версий - обязательно учитывать замечания и пожелания пользователей. Теория Ну вот; программа готова и ориентиро- вана на конечного пользователя. Можно прямо сейчас выкладывать её в сеть и радоваться. Люди будут пользоваться программой, слать благодарственные письма, рассказывать знакомым... Это всё безусловно хорошо; но ведь мы вроде бы собирались делать деньги?... Значит, такой способ нам не очень под- ходит. Лучше поступить так, как делают уже тысячи людей: сделать свою про- грамму условно-бесплатной (share- ware). Это означает, что программа бес- платна для скачивания и использова- ния, но есть некоторые ограничения по времени работы, количеству запусков, функциональности и т.д. Одним словом, программа ненавязчиво вынуждает пользователя за себя заплатить. Например, скачал человек программку, начал ей пользоваться, привык - и вдруг программка ему говорит, что бес- платный срок в 30 дней истек, и пора бы вообще за нее заплатить (зарегистри- роваться). И если пользователь уже не хочет жить без программы - придется ему регистрироваться; после чего поль- зователь получит ключ (код), который надо сообщить программе, чтобы та
Профессиональный журнал ПРОГРАММИСТ — продолжила свою работу. Метод этот доступный, быстрый и простой в реали- зации. Ну вот; идея, вроде, очевидна... теперь перейдем от теории к практике. Практика Для начала стоит решить, какое именно ограничение стоит применить в про- грамме. Самых распространенных не- сколько, и у каждого есть свои преиму- щества и недостатки. Прежде всего, это относится к способности ограничения подтолкнуть пользователя к регистра- ции. Самый часто применяемый способ - ог- раничение по времени работы; оно да- ет пользователю полностью оценить все возможности программы, что значи- тельно увеличивает шанс приобрете- ния. На первый взгляд - большое преи- мущество; но оно же оказывается и главным слабым местом такого рода программ - ибо защита элементарно ломается, и полнофункциональная про- грамма уходит в «дикую природу». Можно, конечно, попытаться написать крутую защиту, но всё равно у взлом- щика меньше времени уйдет на её сня- тие, чем у программиста на написание. Лучше всего поступать так: делать ми- нимальную защиту от простых пользо- вателей, вооруженных монитором рее- стра или файлов, и позаботиться о та- ких простых трюках, как, например, пе- ревод даты. Опытный же взломщик все равно сведет на нет любую защиту, так что применять сложные защитные меха- низмы, по моему скромному мнению, смысла нет. Лучше потратить то же са- мое время на улучшение программы... Часто программы распространяют как demo; у таких программ нет части функ- ций - и, если человеку программа при- шлась по душе, он может заказать себе полную версию (за деньги, разумеется), и получить её, например, на свой e-mail. В общем, этот способ идеален в плане защиты, так как части кода просто нет, а дописывать его возьмется только ума- лишенный; так что «крэков» под demo- программу пользователь не найдет при всем желании. Конечно полная версия может всплыть на пиратских сайтах, но такие обычно долго не живут, и к тому же их достаточно сложно найти. Каза- лось бы, в плане защиты demo - один большой плюс; но у медали есть и обо- ротная сторона: пользователь не может в полной мере почувствовать все воз- можности программы, что уменьшает шанс покупки. Так что нужно найти тон- кую грань: какие функции вырезать, а какие оставить. Если вырезать что-то значительное, то пользователь может просто не оценить всей пользы програм- мы; а вырезав не очень важное - даешь пользователю возможность спокойно работать с демкой, даже не думая о при- обретении полной версии. Лучше всего опираться на психологию - класть кусок сыра прямо перед лицом, но не давать до него дотянуться; т.е. сделать так, что- бы пользователь видел результаты ра- боты, но не мог ими воспользоваться. Например, мог перевести текст, но ни- как не мог сохранить перевод. Какой способ применять? Мощные и многофункциональные программы луч- ше, наверное, выпускать как demo. Та- кие обычно стоят немало; и потери от одного пользователя, скачавшего «крэк», будут весьма значительными. Кроме того, можно найти множество ва- риантов для ограничения - ведь функ- ций-то много. Напротив, простые и средние программы стоит выпускать как Shareware, так как пользователю нужно в полной мере оценить (и так не- богатые) возможности оной. Я же лично считаю, что лучший способ распространения - AdWare; т.е. «спон- сированное программное обеспече- ние». На первый взгляд - такие про- граммы бесплатны, т.е. распространя- ются свободно, содержат полный набор функций и не требуют регистрировать- ся. Но при этом - в главном окне про- граммы размещается реклама (баннер), за показы которого будут платить ре- альные деньги. К тому же, пользователь может (как и в случае с Shareware) за- регистрироваться - и убрать рекламу из своей копии. То есть выгода очевидна: во-первых, два источника дохода; во-вторых, раз программа бесплатна - ей будет поль- зоваться много людей, что само по себе способствует. А теперь посчитаем - допустим, про- граммой одновременно пользуются 1000 человек, каждому из них програм- ма покажет примерно 10 баннеров в день, и за один показ платят примерно 0,2 цента... вот и получается 1000*0,2*10 = 2000 центов = 20$ в день. Неплохо? И это практически минимум; если программа хорошая, да ещё и внешне бесплатная - то, возможно, ей будут пользоваться десятки тысяч чело- век. А если еще вспомнить о людях, ко- торые захотят зарегистрироваться, да- бы убрать рекламу... 83 (§@Р©СО(§ ГОРОДА л Z со X 36
I ПРОГРАММИСТ Профессиональный журнал 84 в z co X 36 Radiate Теперь про минусы этого способа. Пер- вое - программа должна быть потенци- ально популярной в широких массах; из-за этого в качестве AdWare распро- страняются в основном крупные проек- ты (не имеет особого смысла показы- вать рекламу аудитории в 100 человек). Второе - в последнее время люди стали беспокоиться о программах, которые устанавливают AdWare-продукты. Это, действительно, потенциально вредные программы, т.к. они постоянно сидят в памяти и используют интернет когда за- хотят (такие программы еще называют SpyWare). В последнее время появи- лись утилиты (например, OptOut), кото- рые обнаруживают и удаляют SpyWare. Для получении информации по разме- щению баннера в программе стоит зай- ти на сайты www.radiate.com, www.cydoor.com, www.everad.com. Итак, вроде бы - дверь указана; но вой- ти в неё нужно самостоятельно: решать, каким способом распространять свои программки, всё равно предстоит вам, граждане читатели... Теперь поподробнее рассмотрим собст- венно Распространение Для начала нужно создать WWW сайт, с которого программу можно будет ска- чать. Но сайт - это не только место для файла; на нем должна располагаться разного рода информация о программе, авторах, регистрации и т.д. Сайт - это во многом лицо программы, так что сто- ит уделить его созданию особое внима- ние (впрочем, это тема для отдельной статьи, так что здесь не будем её рас- сматривать). Наконец, надо где-то разместить этот самый сайт; для этого в сети существу- ют куча платных и бесплатных хостин- гов. Если проект масштабный, то стоит завести собственный домен - типа www.myprog.ru или www.mycompany.com; это выглядит солиднее, но стоит некото- рых денег. Для большинства проектов на первых порах достаточно домена третьего уровня, типа www.myprog. server.com; конечно, не так красиво, за- то бесплатно - подобный домен можно зарегистрировать почти на любом бес- платном сервере (их элементарно найти любым поисковиком, по фразе «бес- платный хостинг»). Кстати, лучше не размещать сайт у сво- его интернет-провайдера - в случае ухода от оного можно столкнуться со многими неприятностями, связанными с переездом. Итак, сайт создан, программа размеще- на; теперь надо сделать так, чтобы о программе узнало как можно больше людей. Самый лучший и распростра- ненный способ - разместить информа- цию о своей программе на софт-серве- рах; там находится информация о тыся- чах программ, и пользователи заходят туда специально, чтобы найти для себя что-то полезное. Оптимально зарегист- рироваться на 5-6-ти наиболее извест- ных серверах (искать их можно по фра- зе «каталог+софт»). Самое важное там - придумать своей программе яркое рекламное описание, чтобы выделяться из толпы и привлечь внимание наибольшего числа пользова- телей. Еще можно разместить ссылки непосредственно на сайт программы, для чего воспользоваться линк-катало- гами (найденными, например, по фразе «каталог+ссылки»). Методы раскрутки, описанные выше - самые несложные и общедоступные; но можно заняться рекламой своей про- граммы в полном смысле этого слова. Для этого существуют службы обмена баннерами. Они работают следующим образом: показываешь баннер на своей странице (на нем крутится реклама дру- гих участников), а за это - у других уча- стников обмена появляется твоя рекла- ма. Еще можно просто заказать опреде- ленное число показов баннеров в сети за деньги. Таких проектов очень и очень много, и западных, и отечественных; до- статочно поискать по фразе «обмен+ба- нер» на русских и «banner+exchange» на западных поисковиках. Баннеры - хороший способ рекламы; но для того, чтобы он был эффективным, требуется очень большое количество показов. Бесспорно, намного более эф- фективно действует заказная реклама на известных сайтах - когда программе отводят специальное место, и всячески рекомендуют её посетителям; но это обойдется в кругленькую сумму... такую рекламу стоит применять для уже рас- крученных проектов, чтобы выжать из них всё по максимуму. Подводим итоги: чистая реклама - хо- роший способ раскрутки, но всё же не такой простой, доступный и эффектив- ный, как размещение в тематических каталогах. Для крупных проектов стоит запастись деньгами и раскручивать по
ПРОГРАММИСТ L2 Профессиональный журнал полной программе, а для остальных - это лучше делать по мере возможно- сти... Получение денег Итак, мы подошли к наиболее приятно- му моменту; ради которого всё, собст- венно, и затевалось - к получению де- нежек. Для начала нужно завести счет в каком- нибудь банке; обычно это обходится не более чем в 10$. Далее, нужно опреде- литься с ценовой политикой - т.е. ре- шить сколько будет стоить программа (регистрация). Здесь всё зависит от сложности продукта; чем он больше и мощнее, тем дороже. Например, для не- большой, но полезной утилитки опти- мальной ценой будет 10-15$. Лучше, по понятным причинам, сделать программу дешевле, но в разумных пределах - во первых, на Западе многие судят о каче- стве программы по её цене; во вторых - можно немножко пожадничать, т.к. лишние деньги пока ещё никому ни ме- шали. Для получения денег существуют два оптимальных способа - один для наших клиентов, другой для западных. Начнем с западных, так как в нашем случае они более перспективны как по- купатели. На самом деле, с буржуинами работать даже проще, чем с соотечест- венниками - они поголовно обладают таким благом цивилизации, как кре- дитная карта. С ее помощью клиент мо- жет заплатить за программу прямо не выходя из сети, и получить ключ на свой e-mail. Для того, чтобы провернуть эту схему, лучше воспользоваться помо- щью специальных агентств: они берут на себя все хлопоты по получению де- нег от клиента, отправке ему ключа, и, конечно, пересылке денег на твой счет. За свои услуги такие агентства берут порядка 10% от суммы продаж; у них всегда можно выбрать способ получе- ния денег: прямой перевод на счет в банке, чек, и т.п. На первый взгляд, луч- ше получать деньги прямо на счет; но, так как практически все агентства нахо- дятся на Западе - при переводе будут сдирать очень приличную сумму. Нас- только приличную, что лучше пользо- ваться переводом с помощью чека - или, по крайней мере, сначала накапли- вать приличную сумму, а потом уже пе- реводить её на свой счет (ставка опла- ты перевода, как правило, фиксирован- ная, не зависящая от его суммы). Много времени может уйти на регистра- цию в агентстве. Иногда при контакте с западными агентствами всплывают сплошные проблемы - одни не отвеча- ют на письма, другим что-то постоянно не нравится и т.д. Кроме того, с ними бывает трудно контактировать - все-та- ки мы разговариваем на разных языках. Тут лучший выход - отечественные агенства, или отечественные пред- ставительства западных агентств. Я лично пользовался русским представи- тельством западного агентства www. softunion.org, и остался доволен. Другие агентства можно найти по фразе «shareware + sell + agency» для запад- ных и «агентство + продажа + share- ware» для русских поисковиков. Ну вот; с иностранцами, вроде, разо- брались; теперь перейдем к получению денег с соотечественников. Есть один хороший способ - конечно, не такой удобный, как с кредитной картой, но в наших условиях оптимальный. Схема такова: пользователь перечисляет день- ги на указанный рублевый счет, и отсы- лает автору на e-mail уведомление, в ко- тором сообщает своё имя (на базе кото- рого будет сгенерирован ключ) и рекви- зиты перевода. Остается проверить, действительно ли деньги были перечис- лены - и отослать клиенту ключ. Как водится, у этого способа есть ми- нус: можно проверить, поступили ли деньги, но нет возможности установить, кем именно они были перечислены. При достаточно большом количестве поку- пателей это несколько добавит хлопот. И еще: лучше пользоваться услугами банков, в которых можно прямо в сети совершать операции со своим счетом (например, посмотреть баланс и опре- делить, поступили ли деньги) - так как каждый раз бегать в отделение банка, чтобы узнать баланс, слишком уж не- удобно. Итоги Ну что же... мой рассказ подошел к ло- гическому завершению. Надеюсь, он поможет вам организовать свой бизнес в сети. Если есть другие идеи, как программи- сту в сети зарабатывать деньги, пиши- те, пожалуйста, на ru7@ru.ru - или на ICQ#71672637. А пока что - разрешите откланяться... Ш 85 MasterCard] Exclusives' ONLINE VISA a s n X X
ПРОГРАММИСТ Итак - выходит уже третий наш номер. Потихоньку налаживается обратная связь с читателями. Мы достали из чулана мешок с вашими письмами (ладно, насчет мешка мы загнули; на самом деле - это отдельная папочка в почтовой программе...) и начали их любовно перебирать. Спасибо вам - писем приходит много. Мы стараемся на все отвечать, хотя это и не всегда получает- ся. Много замечаний и пожеланий обнаруживается и в форуме на нашем сайте (www.programme.ru). По-прежнему много поздравлений новому журналу; все-таки два номера - это еще несерьезный возраст для печатного издания. Но уже набралась и масса пожеланий и советов: в какую сторону нам развиваться; о чем писать, а о чем не надо... Вот, например, упрекают нас в том, что до уровня профессионального журнала мы не дотягиваем: На днях купил второй номер вашего журнала. Чест- но скажу, мне он понравился меньше, чем первый - но это, возможно, потому, что темы там рассматри- вались несколько более далекие от меня. Сильно я порадовался статье про CVS - у меня никак руки не доходили почитать доку на это дело. Сильно _не_ понравился обзор дельфовых компонент - уж очень сильно напоминает статьи в пионерских журналах а-ля «хацкер». Чуть ли не в первую очередь прочи- тал отзывы читателей - не могу не согласиться с не- которыми. Откровенный уклон в программирование в Win API не есть хорошо. Статьи про css и осталь- ные веб-технологии тоже как-то не очень - какой смысл перепечатывать «умные книжки»? Вызывают двойственные чувства новости. Оно, конечно, хоро- шо, но странно читать в середине февраля о выхо- де ядра Linux 2.4.0, когда - во-первых - оно уже ме- сяц как выкачено и установлено, а во вторых - через два дня вышло уже 2.4.1 Единственное, пожалуй, чего хотелось бы, и что, скорее всего не выполнимо, так это ориентирование на действительно профессионалов. Хочется видеть статьи на малоизвестные темы, потому что читать в десятый раз про «SSI в примерах» не очень инте- ресно (не в обиду автору - статья хорошая, но тема избитая). Хотелось бы также видеть раздел, осве- щающий новые или альтернативные средства раз- работки. А кому-то, наоборот, хочется видеть доступные статьи, где все объяснено до мельчайших под- робностей; Я считаю, что все-таки нужно печатать полные лис- тинги программ, описываемых в статьях, т.к. не все- гда удается самому собрать программу. Конечно, многие (в том числе и вы) считают, что, если не пи- сать комментарий или листинги, то начинающему программисту только на пользу. Но это не так, хотя и излагаю собственное мнение. Я знаю по себе, бы- вает так, что никак не можешь собрать программу, хотя автор сказал: «соберете! это только на поль- зу.» И еще мнение: «комментарии новичкам - враги! пусть молодой программист сам найдет, что делает каждая строчка - тогда из него выйдет настоящий программер!» - это неправильно! Опять же сужу по себе: нашел какую-нибудь прогу, из которой нужен только кусок, и вклиниваешь его, порой не понимая, а что тут что делает... вот еще один довод в пользу обязательной публикации полного листинга' с ком- ментариями. Купил второй номер вашего журнала, и был страш- но разочарован! Вы же сами сказали, что в дальней- шем будете учитывать пожелания читателя, а ока- зывается вам на них просто наплевать. Я вам писал, что считаю нужным публикацию статей с полными исходниками; программы (в рубрике «Система») под DOS либо под DOS/unix, но вам было наплевать на мое мнение! Поймите, что вас читают не только крутые программисты, но и начинающие. И им недо- статочно описания программы. И, кстати, програм- ма «hello world» из первого номера у меня под Лину- ксом так и не заработала. Поэтому именно для та- ких случаев и нужны полные листинги. В вашем журнале в некоторых рубриках вы описываете не очень простые вещи, и к ним желательны исходни- ки, полные комментарии, чего у вас и в помине нет! это нехорошо. Можете обозвать меня ламером (хо- тя, к слову сказать, я сам изучил Pascal, basic,del- phi), но я не понимаю стратегию вашего журнала, вы слишком уперлись в теоретическое описание, и UNIX. => побольше практики, и помните, что мало- вато народу пишет из UNIX. Если к третьему номеру вы не поменяете свою пози- цию, то вы потеряете еще одного читателя - меня Некоторые просят немедленно осветить доволь- но экзотические темы: После покупки своего Palm-a я увлекся программи- рованием под него. Я считаю, что у PDA очень боль- шое будущее. Мне очень нравятся эти маленькие машинки и будет просто здорово, если вы продол- жите раздел PDA (возможно, что не только про Palm) и будете рассказывать не только про сами компьютеры, но и про программирование под эти чудесные машинки. Вся моя жизнь связана с Linux-ом. Я на нем рабо- таю, он стоит на домашнем компьютере, он просто везде, (разве, что Palm пока еще под PalmOS, но скоро и там будет mCLinux:) ) Поэтому, когда я уви- дел имена файлов boot.S, head.S, start.c, то ото-
ПРОГРАММИСТ рваться я уже не мог. И будет здорово, если вы про- должите раздел системного программирования, а может быть создать раздел ‘Linux Inside’, или ‘Kernel Inside’, а? Увидел в киоске журнал «Программист», купил его и прочитал Весьма интересно! Надеюсь, следующие номера будут еще лучше! Не планируется ли как-ли- бо освещать в журнале такой вопрос, как програм- мирование на ассемблере Z80? А то я мог бы, так сказать, поделиться опытом... И еще: знаете о та- ком замечательном компьютере, как ZX Spectrum? Думаете, все Спектрумы уже давно на помойке? А на чем же, по-вашему, я пишу это письмо? :-) Есть и люди, которые точно знают, как наш жур- нал должен выглядеть :) Я себе представляю этот журнал так: статьи на тему программирования вычислительных, прикладных и системных задач на разных АЯП (алг. языках про- граммирования). Никогда не забывайте про ассемб- лер. Но и в Бейсик слишком глубоко не уходите. Ду- маю, что Ассемблер, Delphi (Pascal) и С(С++) - золо- тая середина. Все должны быть этим удовлетворе- ны. Мне кажется, что надо уделить больше внима- ния (расширить) такие рубрики, как: Система(осно- ва основ) и Матчасть (тоже никуда в серьезных за- дачах). Я бы хотел, чтобы в рубрике СИСТЕМА пи- сали программы, интересные и с практической сто- роны. неплохо было бы продолжить про написание ОС. Можно заняться написание драйверов простых устройств и т.д. Можно много чего интересного при- думать. Предлагаю открыть рубрику типа «статья по заяв- кам». Т.е. человек в письме пишет, о чем ему хоте- лось бы почитать, а вы по очереди на такие письма отвечаете (статьей). Сколько людей - столько и мнений! Мы понима- ем, что все вы очень разные, и то, что одному ка- жется замечательным - другому скучно, неинте- ресно или непонятно. Однако, нам по-прежнему хочется учитывать все ваши интересы. Наш идеал - та самая «золотая середина», «понемногу обо всем». Мы стараемся освещать самые разные вопросы; у нас, навер- ное, будут открываться новые разделы; будем печатать материалы для читателей разного уровня подготовки. Но полные листинги в бу- мажной версии, увы, не поместятся; ведь 100 страниц - это не так уж много... Надеюсь, вы не будете на нас за это обижаться :) Главное - очень много читателей, которых в на- шем журнале все устраивает и даже радует: Здрасьте уважаемая редакция журнала «Програм- мист»! Сегодня, попав на работу, в куче всякого рекламного хлама и прочей ерунды, я выудил Ваш Ж-У-Р-Н-А-Л... Мужики, я, конечно, вполне нормальный; но после того, как я пробежал глазами пару строк, я чуть не прослезился! Ну, НАКОНЕЦ-ТО! Появился в России единственный нормальный журнал. Я его схватил в руки, и под шумок свалил в серверную, где и провел остаток дня за чтением этого поистине настоящего, я еще раз подчеркиваю - настоящего! - журнала о тех, кто ваяет и ваяет; кто видит в программирова- нии отдушину и получает от этого удовольствие (и деньги конечно:-)))) ) Ваш журнал это «новое слово в науке и технике» :-))) После пафосного GAME.EXE, после философски- задумчивой КОМПЬЮТЕРРЫ, и жутко «мудрого» BYTE, вы просто отдушина для нормальных про- граммеров. Интересно абсолютно ВСЕ, читаешь взахлеб (мне уже начали в дверь стучаться, пока я читал, и требо- вали прекратить и вернуться к своими обязанностя- ми), поначалу просто листаешь и думаешь - а что будет дальше? какая тема? Но, пролистав все - от- крываешь первую страницу - и пошел, строка за строкой, страница за страницей. P.S. Пишите больше, становитесь толще и прият- ней! Не опускайте руки и оставайтесь профессио- нальным изданием, а аудитория Вам обеспечена, ведь Россия никогда не была обделена талантливы- ми людьми. Удачи Вам. Приятно было бы ощущать за спиной какую-ни- какую историю. Вот недавно - в редакции зазво- нил телефон, и приятный женский голос спро- сил: «А где можно купить ваши последние три номера за прошлый год?». К сожалению, нигде - нас тогда еще не было... W И в письмах - тоже часто спрашивают: Скажите пожалуйста , где можно достать первый номер?! - очень-очень нужен. Приобрели и с удовольствием прочитали N 2 журна- ла «Программист». Где купить № 1? В киосках уже нет! Отвечаем: в Москве у нас есть дружественный книжный магазин «Мир печати». Адрес - 2-я Тверская-Ямская д.54, недалеко от метро «Бело- русская-кольцевая». Там точно есть все номера. Кстати (военная тайна!) - там еще и дешевле...
I ПРОГРАММИСТ Профессиональный журнал ID Software - Archives - Source http://www.idsoftware.com/ archives/sourcearc.html Бесценный подарок всем, кто интересуется любыми ас- пектами написания игр. Ле- гендарная ID Software выло- жила исходники своих не ме- нее легендарных детищ: Quake I, DOOM (для Linux) и Wolfenstein 3D. Ура - теперь знаменитые 30-движки и прочую начинку мож- но, что называется по- щупать своими рука- ми. А значит - в нашем полном распоряжении, на- верное, лучший на свете учебник по ЗО-графике! 90 «Королевство Дельфи» - виртуальный клуб программистов http://delphi. vitpc.com а. о и ш о со Ш S Один из самых известных сай- тов, посвящённых программи- рованию. Существует уже бо- лее двух лет. Несмотря на свое название, в Королевстве вы сможете найти информацию не только по Delphi. Содержимое сайт весьма разнообразно: ста- тьи и обзоры на темы програм- мирования, список информаци- онных ресурсов и on-line журна- лов для разработчиков про- граммного обеспечения, обзо- ры книг по программированию. Отзывы, информацию о том где можно приобрести книги. Ста- тьи по темам прикладного хара- ктера. Обучающие циклы и при- меры решений прикладных за- дач с объяснениями (некоторое подобие лекций). Обзор различ- ных инструментов, которые об- легчают работу программиста (CASE средства, средства по- строения справочных систем, эксперты, и многое другое). Различные математические функции и просто оригиналь- ные решения известных задач. Интересен раздел «Круглый стол» - конференция для реше- ния проблем, связанных с про- граммированием. Хочется отме- тить исключительную пользу «Круглого стола» - скачав архив вопросов за всю историю его существования, вы с вероятностью 99% най- дёте ответ на свой воп- рос. На сайте есть ещё не- сколько полезных on-line разде- лов, которые делают сайт не простым сборником статей и ис- ходников, а именно Клубом - местом, куда возвращаешься вновь и вновь. Music Studio.ru http://www.musicstudio.ru/ Российский портал, посвя- щённый современным техно- логоиям создания музыки. Очень профессионально сде- ланный сайт, на котором есть всё для тех, кто интере- суется созданием и обработ- кой звука. Отдельно стоит отметить работу автора по освящению новинок аудио • WAP Catalog Media Newt БлеУ-ЧЧчи IV-Siaijon* Reference Dictlonarie» Travel Gutdw Search Traffic Infpt Business Shopping Auctions С<упрлп1М <sllLiJi9p.com —> ALL WAP-phones in Europe! - - ' > -'.-55 Pklures"• * > i tlckherol индустрии - каждый день по- является по нескольку ново- стей про новые продукты, со- держащие подробнейшие комментарии. На сайте ле- жат статьи о звуковом оборудовании, которые были опубликованы в различных независимых журналах начи- ная с 1997 года. В статьи ре- гулярно вносятся коммента- рии, которые отражают сего- дняшнее положение вещей. Gixom international WAP catalog http://www.gixom.com Отправная точка для тех, кто интересуется WAP (Wireless Application Protocol - стандарт предоставления служб, в том числе и доступа к интернет, для различных мобильных устройств). Этот сайт почти не содержит (пока!) конкретной информации по решению тех- нических вопросов и програм- мированию - основу его со- ставляют ссылки на ресурсы, посвящённые WAP, и всему, что с ним связано. Правды ра- ди, стоит отметить, что в разделе «Downloads» собраны ссылки на различные конвертеры (GIF, JPEG, BMP в WBMP), редакторы, SDK (Ericsson WAP IDE SDK) и т.п., что повы- шает полезность сайта. Так- же, тем кто интересуется, сто- ит наверное взглянуть на рус- скоязычный ресурс «WAP фо- рум» - http://www.aspid.ru/wap. Российский клуб вебмастеров http://www.webclub.ru Настоящему вебмастеру предст)йвлять этот сайт не нужно. Для тех же, кто только встал на этот тернистый путь, и действительно желает стать Вебмастером с большой бук- вы, разъясним: здесь присут- ствует 99% всего, что может понадобиться. Одно слово - мощно. Содержание - начи- ная от раздела для начинаю- щих («Детская») и юмора, за- канчивая вещами типа • «Хрестоматия по про- • граммированию на С в Unix» и «Web-разра- ботки на языке PERL с ис- пользованием Oracle на плат- форме Linux». Огромное ко- личество ссылок на полезные ресурсы, обсуждение проблем безопасности, интересные но- вости и ненавязчивый юмор. Как написано на сайте, одной из тайных целей Клуба явля- ется подъем российского Ин- тернета до мирового уровня как по количеству, так и по ка- честву предоставляемой ин- формации. Надеюсь, к юмору это не относится.
П р о ф е с с и о н а л ь н ы й журнал ПРОГРАММИСТ Клуб сертифицированных специалистов http://www.cna.ru Основной задачей Клуба яв- ляется повышение популярно- сти сертификации IT специа- листов, а также предоставле- ние специалистам площадки для общения, обмена опытом и отдыха. Полезная информа- ция о тестах, обсуждения, бир- жа труда, и т.д. Интересный сайт. Как для тех, кто уже име- ет свой кровный, лежащий в ящике стола или висящий в рамочке на стене, так и для тех, у кого его нет. Я про сертификат. Библиотека программиста http://bib.com.ua/ Обширный сайт для разра- ботчика. Информация по ASM, Delphi, C++, Perl, Pascal. Большое количество исходных текстов программ, написанных на этих языках. На сайте есть 8 фору- мов, чат. Полностью согласен с автором сайта, которому при- надлежат следующие слова - «Когда уйти некуда, когда го- лова болит от невозможно- сти понять, что творится с программой. Когда баги и глюки сыпятся один за дру- гим, приходиться в итоге об- ращать свой взгляд на доку- ментацию и исходники...» Документации немного, од- нако подборка довольно ори- гинальна. В разделе «Ста- рье» лежат шедевры типа «СПРАВОЧНИК по системе программирования ТУРБО АССЕМБЛЕР 2.0». Журнал «Codemanual» http://www.codemanual.com Программы самые язык программиро- назначение про- почти не имеют Добротный сайт для разра- ботчика. Основу составляют примеры программ с исход- никами, разные; вания и граммы значения; главное чтобы программа была интересной. Можно найти исходники про- грамм от тетриса до алгорит- мов сжатия изображения без потерь, превосходящие по характеристикам GIF и даже JPG. Среди приславших свои программы проводится конкурс с денежными при- зами. Visual Basic на русском http://vbrussian.com/ Настоящая находка для про- граммиста на VB! Здесь мож- но найти статьи и обзоры по- священные Visual Basic, сове- ты по оптимизации кода, хит- рости и FAQ, ссылки на рус- скоязычные VB сайты и про- граммы для VB программи- стов, обзоры книг и многое другое, что, несомненно, заин- тересует любого VB-разработ- чика. Сайт регулярно обновля- ется. Я, признаться, поддавшись снобистскому мнению обще- ственности, считал, что VB не такой развитой и многогранный язык. И был приятно поражён! DELPHI UNDOCUMENTED http://delphi.aga va .ru/delphi/i ndex.html Любителям «фич» и «примо- чек» посвящается. Как понят- но из названия, сайт содержит информацию о недокументи- рованных возможностях про- граммирования на Delphi. Правда, к сожалению, боль- шинство таких «недокумен- тированных возможностей Delphi» являются по сути доку- ментированными возможно- стями Win32 API. Однако это ни в коей мере не умаляет до- стоинств сайта. И если вы ищете эти самые возможно- сти, вы их найдёте. Есть ин- формация о не-Вог1апб’овских компонентах, а также некото- рые общие сведения о программировании под Windows - например, о работе с реестром. На сайте есть оригинальная кол- лекция примеров программ, а также - on-line тестирование для программистов. Но не су- дите слишком строго.
ПРОГРАММИСТ Л р 0 ф е с с и о н а л ь н ы й журнал Русская информация об ОС Linux http://www.linux.org.ru Целью этого проекта является создание основного информа- ционного ресурса о операци- онной системе Linux в России. Сайт наполнен различной Linux-ориентированной ин- формацией - последними но- востями, ссылками, докумен- тацией и другими ресурсами. Библиотека алгоритмов http://www.chat.ru/~alglib/ SCRIPTS La Vision http://la-vision.net I inu\ Hof'.bi Iи АЫАвыи в— Каких только алгоритмов здесь нет... Начиная от рабо- ты с матрицами, заканчивая полиномами и дифференци- альными уравнениями. Плюс - критография, геометрия, сор- тировка, псевдослучайные числа. Основу составляют ма- тематические алгоритмы, од- нако и «компьютерщики» най- дут тут много полезного для себя. Учтите - алгоритмы представлены в виде блок- схем. Для них автор напи- сал специальный реда- ктор, который умеет конвертировать схему в код на паскале. co Для интересующихся про- граммированием графики. Основу составляют примеры реализации алгоритмов гра- фических 20-эффектов (эф- фект огня, фонтан и т.п), ста- тьи и примеры по ЗО-графике (Direct3D,OpenGL,Glide), а так же обзоры, движки, алгорит- мы, которые могут пригодить- ся при программировании игр, и т.п. Сайт оставляет приятное впечатление и обновляется с завидной регулярностью. Увидев пример реализации алгоритма огня, сразу вспомнил, как N-e количе- ство лет назад сидел пе- ред монитором, заворожён- но глядя на экран, и думал «Как же это сделано?...». Эх, молодость... приятно ПО- добавляется Новостная лента ражает - в день около 5-7 серьёзных и дейст- вительно интересных ново- стей, касающихся только Linux. Новости можно проком- ментировать, что и делают по- сетители сайта. Занятны темы обсуждений на форуме - «MS просит сенат США запретить Linux», или «Ужасные предска- зания от Линуса Торвальдса». Хочется отметить, что сайт не- коммерческий. АП about shareware http://shareware.al.ru/ Каждый начинающий (и не только) разработчик shareware просто обязан посетить этот сайт! На этом сайте вы узнае- те, как писать, раскручивать, продавать ваше программное обеспечение в интернете. Вы узнаете, на какую тему лучше разрабатывать приложение, получите информацию о том, что и как защищать (основные рецепты и как не надо делать), найдёте ссылки на сайты по shareware - разработка пра- вильного интерфейса, бета-те- стирование, архивы shareware, где искать взломанные версии ваших программ, и т.п. Сайт не слишком навороченный, но попадает в самое яблочко. Ни- чего лишнего, только то, что реально нужно. DESK - Международный клуб программирующих на JavaScript http://deskclub.newmail.ru/ma in.html Неплохой сайт для JavaScript- программистов. Поделён на два главных раздела «JavaScript» и «HTML». Этот сайт поможет начинающим JavaScript-программистам лучше узнать JavaScript, осво- ить технику программирова- ния на этом языке, а также по- лучить ответы на интересую- щие их вопросы. Профессио- нальные JavaScript-програм- мисты смогут также получить ответы на самые сложные во- просы (если на них есть от- вет). По крайней мере, так fek утверждают авторы сайта. В Неплохая подборка при- меров скриптов. Вибрация и все, все, все. http://vibration.narod.ru/ Специализированный сайт. Включены русские ресурсы в Интернете по диагностике со- стояния оборудования по виб- рации. Присутствуют: про- граммы диагностики обо- рудования, программные тренажёры, ссылки на фир- мы, работающие в этой облас- ти, людей, сравнение прибо- ров (измерение общего уров- ня вибрации, сборщики сигна- лов), статьи, вэйвлетам и тает форум «Имеется ли рования износа шлицевого со- единения зубчатого коле- са?»). Занятен дизайн сайта. Информация по спектрам. Рабо- (вопросики типа опыт диагности-
й о н а л ь н ы ПРОГРАММИСТ Професси журнал Visual FoxPro club http://nsvisual.com/foxpro Несомненно нужный ресурс для программистов на Visual FoxPro. Для них тут есть уж ес- ли не всё, то почти всё - ана- литические статьи (общие описания технологий, нов- шеств), FAQ, конференция, новости, чат, решения кон- кретных задач. Раздел про глюки. Ещё из интересного - раздел, посвященный реше- нию сложных, нетривиальных, олимпиадных задач. Сайт ос- тавляет приятное впечатле- ние, регулярно обновляется. Иногда появляются интерес- ные предложения по постоян- ному трудоустройству для специалистов 4ЙК& Visual FoxPro. Клуб любителей C++ http://cppclub.newmail.ru/ Сайт, посвящённый програм- мированию на C++. Он ещё довольно молод, но содержит много интересного, причём не только для разработчика на C++. Среди статей, размещён- ных на сайте, можно найти ув- лекательнейшие материалы по программированию под Windows вообще. Есть инте- ресные материалы по парал- лельным вычислениям, рабо- те с реестром, COM, ActiveX. Сайт можно порекомендовать тем разработчикам, у которых есть проблемы с (или просто для тех кто интересуется) Win32 API, т.к. часть размещённых матери- алов представляет со- бой переводы различ- ной документации по Windows API, в основном, конечно MSDN. Также интересны при- меры программ, иллюстриру- ющие различные «недокумен- тированные» возможности и непростые приёмы. Архив компьютерной документации http://www.bookcase.ru/ Если ищете документы - не- пременно загляните сюда. К настоящему времени на сайте набралось более трёх тысяч книг, статей и документов по следующим темам: програм- мирование, операционные си- стемы, форматы файлов, ин- тернет-технологии, базы »а»лл. /й&ичла.. п«кмммте чт» акта, зачти, т» вастмтежьво рег+мткдтты эосттап. страхачкуПуЧШУ Втанахм. вам здесь поиравмттл. в вы мАдете ос-да еще а»х»иыи IWWiWJieiX» Гры-иммгрсезние магическим up feeteh i Архипы ?Л1Ч Л1Ш кем. »• в ............ ««»• Л/*зжЙУ{ I&- Добро пожаловать! данных, безопасность, графи- ка и дизайн, программные ру- ководства, периферия и т.д. Всего и не перечислишь. Здесь не составит проблемы найти описание редкого фор- мата, или, если вас вдруг за- интересовали нейронные сети, откопать что-нибудь типа «Идентификация колебатель- ного звена методом группово- го учёта аргументов и искусст- венной нейронной сетью с ге- нетическим алгоритмом обу- чения». QQQMUI 0*Ш Е оеш □меж. юа. сна. ш». «юаа» Программирование магических игр http://pmg-ru.narod.ru/ Здесь вы найдете множество переводов, статей, списки книг и ссылок по следующим темам: двумерная и трехмер- ная графика (DirectX, OpenGL), искусственный ин- теллект (в том числе поиск пу- ти, нечеткая логика, нейрон- ные сети, экспертные систе- мы, агенты), а также игровые алгоритмы. Для тех, кто инте- ресуется разработкой игр - этот сайт будет большим под- спорьем, особенно, если вы только начинаете. Есть ссылки на 260 ресур- сов, посвящённых теме программирования игр, или связанным темам. Пос- леднее обновление, правда, было 4 января. Очень жаль. Сайт-то хороший I 93 Whiz Kid Technomagic http://www.geocities.com/Silic on Valley/Heights/7394/ Всё о программирова- нии на ассемблере под Windows! Есть: сами ассемблеры и DDK, примеры программ, примеры DLL, созданных на ассемблере, описания нюан- сов программирования на ассемблере под Windows, хитрости. Есть несколько плагинов для Adobe Photoshop, сделанных на ас- семблере. Конечно, ко всему прилагается исходный код. Этот ресурс - домашняя страничка автора.