Начало см. PC Week/RE, N 16/2004, c. 30; N 18/2004, с. 30.
В предыдущих номерах мы обсудили общие вопросы архитектуры Mac OS X, поговорили о ядре системы Darwin и базовом объектном API (точнее, Framework) Cocoa, пришедшем в систему из мира компьютеров NeXT. Теперь расскажем о нескольких других важных API "десятки" - Classic, Carbon и Java.
Classic. Традиции программ "с человеческим лицом"
Одна из самых больных для любой вновь создаваемой ОС тем - исполнение старых программ. Ведь именно от того, насколько корректно они работают, во многом зависит ее первый успех у обычных пользователей, так как поначалу число "родных" программ относительно невелико.
В Mac OS X за выполнение приложений, написанных для классической Mac OS (т. е. с номерами версий меньше 10), отвечает подсистема Classic. При попытке ее создания возникли весьма сложные задачи - ведь "старые" приложения обращались к механизмам кооперативной многозадачности и, пользуясь отсутствием защиты памяти в классической Mac OS, довольно часто очень вольно обращались с областями памяти "чужих" программ, портами ввода-вывода и отображаемой в общее адресное пространство памятью отдельных адаптеров. Что еще более неприятно - некоторые стандартные функции классической Mac OS изначально были задуманы так, что корректную их работу в среде с вытесняющей многозадачностью и защитой памяти реализовать оказалось вообще невозможно.
В "десятке" при запуске одного или нескольких "старых" приложений для них всех порождается общая среда исполнения, в которой "загружается" обычная классическая операционная система Mac OS (на сегодня это Mac OS 9.2.2). При этом обеспечивается работа всех подсистем Mac OS, включая графическую, звуковую, сетевую, печати и т. д., а для прикладных программ создается полная видимость исполнения на отдельном Маке под классической Mac OS. Такой подход обеспечивает очень высокий уровень совместимости "старых" приложений с новой системой, однако наследует все недостатки традиционной Mac OS. К примеру "старые" приложения, работающие под Classic, разделяют время процессора (одного, если не используют специального MP-ориентированного API Mac OS) между собой при помощи невытесняющей кооперативной многозадачности; да и адресное пространство всех этих программ является общим. В этих условиях любой серьезный сбой какого-либо из приложений может привести к невозможности исполнения всех остальных запущенных под Classic приложений и аварийному завершению работы самой подсистемы Classic.
Следует подчеркнуть, что Classic - это не программа-эмулятор, а скорее некая специфическая виртуальная машина. Разница между программами-эмуляторами и программами, реализующими виртуальные машины, оказывается принципиальной, поскольку в данном случае виртуальная и реальная машины имеют сходную архитектуру. Это позволяет выполнять программы в рамках виртуальной машины напрямую, без столь неэффективной эмуляции, и лишь при попытке исполнения некорректных или потенциально опасных с точки зрения "десятки" действий перехватывать их, используя механизмы защиты и обработки исключительных ситуаций, и подменять корректными вызовами системы. В результате потеря производительности при запуске "старого" приложения под "десяткой" в большинстве случаев составляет не более 10%.
С точки зрения Mac OS X подсистема Classic представляет собой полноценное "десяточное" приложение, так что любые катастрофы, происходящие внутри Classic, никак не сказываются на выполнении остальных (Carbon и Cocoa) задач. Более того, преимущества "десятки" позволяют работать с огромным объемом динамически распределяемой виртуальной памяти, одновременно запускать под Classic сколь угодно много "старых" приложений, причем отводя статически каждой из них очень большой пул "собственной" памяти (этого требует подсистема памяти классической Mac OS).
Цена перехода на Mac OS X
Подавляющее большинство приложений, написанных для классической Mac OS, совершенно нормально выполняется и в среде Classic операционной системы Mac OS X. Однако существуют и программы, работа которых под Classic невозможна, - именно для них в комплект "десятки" был включен полный дистрибутив Mac OS и предусмотрен режим переключения между операционными системами (подобно тому, как в среде Windows 9x некоторые программы могут выполняться только после перезагрузки компьютера в режиме MS-DOS). Правда, все Маки, выпущенные за последний год, загружаться в "чистой" классической Mac OS уже не способны, поскольку развитие аппаратной части идет вперед, далеко обгоняя возможности уже "замороженной" Mac OS 9, для которой лишь выпускаются минимальные пакеты модернизации и "заплаты" для устранения брешей, обнаруженных в подсистеме безопасности.
Что же может помешать "старой" программе корректно действовать в среде Classic Mac OS X? Как правило, это обращение к какой-либо низкоуровневой функции или попытка работы с оборудованием напрямую:
- попытка приложения рисовать за пределами отведенного ему окна пресекается; однако подобное поведение допускается в полноэкранном режиме;
- работа с экранной памятью напрямую, минуя графический API QuickDraw, при несоблюдении определенных правил может привести к временной порче изображения в окнах Aqua;
- любая попытка прямого обращения к отображаемым в адресное пространство портам ввода-вывода приводит к немедленному завершению программы из-за нарушения режима защиты памяти;
- приложения, использующие практику перехвата системных вызовов (в стиле драйверов MS-DOS), могут испытывать проблемы. Например, перехват вызовов к файловой системе позволит такому "драйверному" приложению отслеживать только задачи, исполняемые под Classic;
- не следует обращаться к дисковой подсистеме при помощи низкоуровневого драйвера или Device Manager API. Как следствие, все созданные для классической Mac OS X дисковые утилиты (например, Norton Disk Doctor) не смогут работать под "десяткой".
В России есть еще один потенциальный источник проблем, связанный с русификацией системы. Дело в том, что в стародавние времена "независимыми локализаторами" Mac OS был выбран "хакерский" метод добавления в систему русского языка, резко противоречащий самим принципам поддержки многоязычности в Mac OS. Этот метод обеспечивает некоторые преимущества в случае применения многих не совсем корректно построенных для функционирования в многоязычных средах программ и потому довольно широко используется (впрочем, наряду с "правильным" методом). В результате при переходе на Mac OS X под Classic некоторые из подобных "русификаторов" работать отказываются. Впрочем, их создатели достаточно оперативно отреагировали на проблему, выпустив новые версии своих программ.
С "железом", поддерживаемым в режиме Classic, складывается аналогичная ситуация: совместимость обеспечивается для всего нового оборудования (которое можно встретить в сертифицированных для Mac OS X конфигурациях), однако некоторые устаревшие и экзотические устройства окажутся недоступными для приложений среды Classic:
- не работает почти вся аппаратура, использующая шину ADB (Apple Desktop Bus - появившаяся в 1987 г. и устаревшая сегодня шина для подключения устройств ввода); в частности, не будут подключаться графические планшеты, джойстики, калибраторы мониторов и ключи аппаратной защиты, имеется лишь ограниченная поддержка базовой клавиатуры и мыши;
- не поддерживаются последовательный порт и коммуникационный интерфейс LocalTalk (фирменное название интерфейса RS-422, более скоростного и "дальнобойного" варианта RS-232); доступ к PPP из Classic-приложений корректно транслируется в соответствующие вызовы Mac OS X;
- не работает внутренний гибкий диск, ранее бывший неотъемлемой принадлежностью каждого Мака;
- не существует драйверов для "специализированных" PCI-адаптеров (т. е. всех, кроме поддерживаемых Mac OS X по умолчанию сетевых, видео- и SCSI-адаптеров).
Кое-что из перечисленного устаревшего оборудования при этом может оказаться совместимым с приложениями на базе более новых API Carbon или Cocoa.
Кроме того, из среды Classis "видны" далеко не все поддерживаемые Mac OS X файловые системы, однако для основных, с точки зрения пользователя Маков, их типов (HFS, HFS+, AFP, ISO 9660, UDF) поддержка обеспечена.
Несмотря на все эти замечания, процесс улучшения совместимости "старых" приложений и "десятки" продвинулся очень далеко. В Mac OS X Server 1.0, по сути, по многим параметрам являющейся "нулевой" версией Mac OS X, "старые" приложения выполнялись в отдельном экранном поле, так что одновременно можно было увидеть либо только "новые", либо только "старые" приложения, причем между этими двумя группами задач не было почти никакой связи (разве что посредством общих дисков и разделяемых сетевых интерфейсов). Сегодня же интеграция нового со старым стала гораздо теснее, и все программы выполняются в едином экранном поле с поддержкой общих буферов обмена и технологии Drag-and-Drop. Теперь уже решается не проблема возможности исполнения "старых" программ вообще, а текущие вопросы работоспособности отдельных важных "ведущих себя неправильно" приложений и неполного соответствия интерфейсов.
Соотношение функций, входящих в API Carbon и Classic
Как уже неоднократно говорилось, в родных "десяточных" приложениях используется графический интерфейс пользователя Aqua, довольно сильно отличающийся от классического Маковского, поэтому идентифицировать окна "старого" и "нового" приложений не составляет особого труда. Во-первых, "новые" имеют иной внешний вид обрамления окна и несколько другую структуру строки меню. Во-вторых, "старые" не способны использовать элементы интерфейса в стиле Aqua. И, пожалуй, самое главное: "старые" приложения не могут работать со всеми богатыми возможностями графической подсистемы Quartz, включая сглаживание, полупрозрачность и т. д.
Carbon. Если сделать это по-быстрому
Как уже говорилось, Carbon представляет собой эволюционный API, созданный на базе стандартного API классической однопользовательской кооперативной Mac OS. Однако в отличие от своего предка он позволяет создавать приложения для многопользовательской системы с полноценной защитой памяти и вытесняющей многозадачностью.
Очевидно, что для всех подобных "средств переходного периода" существует одна общая проблема. С одной стороны, они должны позволять использовать большинство новых функций системы, с другой - обязаны в максимальной степени допускать использование всех старых наработок и максимально упростить их перенос на новую платформу.
От такого паллиативного API, как Carbon, сложно ожидать того же уровня использования всех возможностей Mac OS X, какой характерен для исторически "родного" API Cocoa. Но старания разработчиков Carbon привели к тому, что и этот API по целому набору параметров ничуть не уступает последнему:
- Carbon позволяет добиться очень высокой степени стабильности отдельных приложений и системы в целом, решая массу проблем, характерных для классической Mac OS;
- "карбонизированные" приложения полностью вписываются в идеологию вытесняющей многозадачности Mac OS X, что спасает от многих проблем с некорректно работающими программами и заметно упрощает создание приложений "почти реального времени";
- все ресурсы в Carbon распределяются динамически, в отличие от классической Mac OS, где многие ресурсы (в первую очередь память) распределялись статически. Каждое "карбонизированное" приложение может динамически выделить себе до 4 Гб памяти;
- программа на базе Carbon может полноценно использовать интерфейс Aqua.
Но и тут есть небольшой подвох, связанный с необычным составом Carbon. На сегодня этот API включает четыре больших блока функций, имеющих совершенно разное происхождение.
- Первый блок объединяет функции API классической Mac OS, способные нормально исполняться и в многопользовательской многозадачной среде с защитой памяти. На остальные, "плохие" с точки зрения Mac OS X функции в средней программе приходится 5-10% вызовов, а если программист еще в "классические времена" скрупулезно следовал всем рекомендациям Apple, - то и того меньше. Всего в Carbon вошло около 70% функций классического API.
- Второй блок составляют отдельные из оставшихся 30% "плохих" функций классического API Mac OS, которые удалось приспособить для работы в Mac OS X путем некоторой модификации. Слегка изменились, скажем, функции, связанные с планированием исполнения приложений.
- Третий блок составляют совершенно новые функции, которые могут выполняться как при работе под Mac OS X, так и под классической Mac OS (где они, по сути, расширяют традиционную функциональность ОС). Примерами могут служить функции подсистем Core Foundation и Carbon Event Manager.
- Наконец, четвертый блок составляют функции, специфические для Mac OS X и не имеющие никаких аналогов в классической Mac OS.
В настоящий момент доля функций четвертого блока в API Carbon довольно невелика, однако можно ожидать роста их числа.
Какие же функции классического API не вошли в Carbon?
- Прежде всего это функции, в принципе неприменимые по каким-либо причинам к Mac OS X. Примерами могут служить специфические для старых процессоров Motorola серий 680x0 вызовы или же функции работы с общим системным пулом памяти (концепция защищенной памяти "десятки" привела к тому, что такого пула попросту нет).
- Вторым блоком "плохих" функций являются функции прямого доступа к оборудованию. Carbon предполагает наличие некого абстрактного уровня работы с аппаратурой, так что прямой доступ к ней запрещен.
- Третий блок - функции, обслуживающие устаревшие объекты ОС, обычно у них есть более современные аналоги.
- Кроме того, в "карбонизированных" программах запрещено использовать вставки кода для процессоров серии 680x0 и перехватывать вызовы при помощи Patch Manager и Trap Table. Заметно ограничен и прямой доступ на запись ко многим структурам данных, так что теперь для этого приходится либо использовать специальные функции, либо после каждого изменения данных вызывать специальную процедуру оповещения, синхронизирующую данные между приложениями.
Теперь становится понятно, что на самом деле существуют совершенно разные Carbon-приложения. Одни из них - классические, перенесенные "малой кровью" на новый API и не использующие никаких новых возможностей Carbon. Другие - те, что полноценно применяют все новые функции и при этом избегают специфичных для Mac OS X вызовов, сохраняя теоретическую обратную совместимость с классической Mac OS. А некоторые задействуют почти весь Carbon, поставив крест на работе под классической Mac OS... Соответственно, функциональность таких приложений будет совершенно разной, как и степень того, насколько "родными" по сути они будут являться для "десятки".
Для упрощения переноса программ под Carbon (их "карбонизации") в состав поставляемых с каждым экземпляром Mac OS X средств разработки Xcode включена полная поддержка этого API (впрочем, как и всех остальных). Немаловажно, что эти средства не ограничиваются простым набором исполняемых в командной строке компиляторов, редакторов связей и программ-библиотекарей, а представляют собой вполне зрелый, а в чем-то и принципиально новаторский продукт (см. PC Week/RE, N 46/2003, c. 40). Большим подспорьем в переносе программ с классического API на Carbon является свободно доступная диагностическая утилита Carbon Dater, анализирующая текст программы, выявляющая "нехорошие" вызовы API и дающая рекомендации по возможной их замене на "хорошие" вызовы; часто Carbon Dater фиксирует и прямой доступ к объектам в "нижней" памяти или к данным в общем системном пуле памяти.
Вездесущий Carbon
Очень важно, что "карбонизированные" программы могут выполняться не только на "десятке", но и на классической Mac OS (версии 8.1 или старше). Иными словами, перед нами редкий случай обратной совместимости при изменении операционной системы. Правда, создавать такие универсальные приложения несколько сложнее, чем чисто "десяточные" - ведь приходится ограничивать себя использованием только общих для "девятки" и "десятки" функций.
Такая обратная совместимость, строго говоря, неполна: для работы "карбонизированных" приложений под классической Mac OS придется использовать специальную библиотеку CarbonLib (впрочем, входящую в комплект поставки всех последних версий системы). Эта библиотека содержит функции двух видов. Первый из них представляет собой собственно функции API Carbon, а второй - точки доступа к внешним по отношению к Carbon функциям, реализованным в библиотеке InterfaceLib.
Эта обратная совместимость оказалась важным подспорьем при переходе на Mac OS X, когда разработчикам ПО надо было создавать программы под "десятку" и при этом продолжать развивать вышедшие ранее версии программ под "девятку". Однако сегодня, когда количество "родных" приложений для Mac OS X превзошло число программ для классических версий Mac OS и основная масса пользователей полностью перешла на Mac OS X (или хотя бы применяет "старые" программы только в режиме Classic), это уже не столь важно.
Под "десяткой" вызовы к API Carbon направляются к соответствующему пакету, называемому Carbon.framework. Этот пакет, аналогично библиотеке CarbonLib, включает как "полноценные" функции, реализованные в самом пакете, так и функции-заглушки, транслирующие вызовы в другие пакеты (в первую очередь в пакеты Foundation.framework и AppKit.framework API Cocoa).
Обеспечить такую универсальность может традиционный механизм динамического связывания. Используемая при создании программы библиотека CarbonLibStub содержит стандартизованные для обеих операционных систем описания функций. Ее вызовы в процессе работы подменяются в зависимости от среды исполнения вызовами CarbonLib или Carbon.framework.
Кофе с ароматом Какао
Какие бы ни дули с разных сторон ветры, судьба Java на Маках всегда складывалась неплохо. Вероятно, следуя принципу "враг моего врага - мой друг", Apple в самого начала поддерживала Sun в ее борьбе против многочисленных "еретических" течений в Интернет-программировании. В настоящий момент Java в Apple считается одним из основных API, и на его развитие брошены очень значительные силы. В результате Java для Mac OS X превратился в весьма интересный продукт, вполне заслуживающий подробного рассмотрения.
Реализация Java для Мака (Mac OS Runtime for Java, MRJ) появилась почти сразу после стабилизации API Java. Каждая новая версия классической Mac OS давно уже включает в себя и MRJ, причем степень ее соответствия всем деталям спецификации Sun очень и очень велика. Однако подход к реализации MRJ под классической Mac OS был вполне обычным, и в результате виртуальная Java-машина исполнялась поверх традиционных для этой ОС API. Как следствие, излишними быстродействием и стабильностью она не отличалась.
Под "десяткой" все стало гораздо интереснее. Во-первых, там уже функционирует новая реализация - Java 2 Standard Edition 1.4 (включая виртуальную машину HotSpot). Во-вторых, и в главных, Java в "десятке" очень тесно интегрирована в систему - несоизмеримо теснее, чем это возможно в классической Mac OS или, скажем, в Windows XP.
Ярким признаком такого единения Java и Mac OS X служит тот простой факт, что Java-приложения под "десяткой" могут использовать все функции графического интерфейса Aqua. При создании интерфейса Java-приложения даже можно воспользоваться теми же средствами проектирования интерфейса из Xcode и оформить все элементы интерфейса в виде характерных для Cocoa интерфейсных nib-файлов (кстати, это означает, что при необходимости перенос приложения с API Cocoa или Carbon на API Java несколько упростится). Более того, даже обычные Java-приложения, вообще не адаптированные к работе под Mac OS X, но использующие стандартный для Java GUI на базе API Swing, приобретают Aqua-интерфейс, причем не только традиционные визуальные "look and feel" (аналогичные возможности настройки вида есть и для Windows - именно для этого Swing в свое время и создавался Sun), но и все уникальные для Quartz свойства. Правда, как всегда, найдется и небольшая часть Java-приложений, страдающих от такого "косметического" вмешательства; но для них легко можно вернуться к стандартному виду интерфейса.
AppleWorks 6 - пример Carbon-программы, одинаково хорошо работающей и под Mac OS, и под Mac OS X. Правда, интерфейс таких программ приходится разрабатывать дважды...
Единение Java и Mac OS X идет гораздо дальше простого сходства интерфейсов. Графическая подсистема Java, API Java 2D, оказывается несколько похожа на Quartz: обе представляют собой скорее объектно-ориентированные подсистемы, и обе базируются на подходах, родственных Display Postscript. Вполне понятно было желание разработчиков Mac OS X транслировать высокоуровневые по своей сути вызовы Java 2D напрямую в столь же высокоуровневые вызовы Quartz, а не разбирать их на "графические кванты" (тем более что потом их все равно пришлось бы передавать в Quartz, да и аппаратного ускорения операций при работе с такими "графическими квантами" почти не наблюдается). Как следствие, при исполнении сложных графических Java-приложений в Mac OS X было получено пятикратное (!!!) преимущество в производительности по сравнению с функционированием под Java 2D, реализованной по обычной схеме. Обратной стороной медали является то, что Java-приложения, использующие "старую", в стиле Java 1.1, графику (т. е. библиотеку Java AWT), действуют заметно медленнее, чем при традиционном подходе.
Аналогичная операция была проделана и над Java 3D: большинство вызовов этого API трехмерной графики напрямую транслируются в вызовы OpenGL, а не "разбираются на кирпичики". В результате под "десяткой" на Java вполне можно написать что-то вроде игры Quake...
Для полноты картины стоит упомянуть и подсистему QuickTime for Java, позволяющую из Java-программ получать доступ к управлению мультимедийными ресурсами "десятки". Применение этой подсистемы превращает создание Java-программы для работы с любыми видео-, звуковыми или потоковыми данными в детскую забаву.
Еще одна очень важная особенность реализации виртуальной Java-машины HotSpot под Mac OS X - ее многопоточность. Потоки (threads) Java преобразуются в потоки ядра Mach системы Mac OS X, что позволяет резко повысить эффективность их исполнения.
Наконец, реализация Java для Mac OS X полностью поддерживает Unicode.
Средства разработки программ Xcode поддерживают и Java, так что и тут перед программистами открываются очень интересные перспективы. Для обеспечения "бесшовной" интеграции Java-программ в "десятку" входят специальные версии Foundation framework и AppKit framework, представляющие собой Java-пакеты (com.apple.cocoa.foundation и com.apple.cocoa.application соответственно) с реализацией интерфейсов базовых иерархий API Cocoa для Java. Структура Java-иерархий классов com.apple.cocoa. foundation и com.apple.cocoa.application вблизи их корней заметно отличается от иерархий "родных" Foundation.framework и AppKit.framework API Cocoa, что, впрочем, не мешает обеим этим иерархиям мирно уживаться под одной крышей.
(Продолжение)