Оригинал статьи можно найти по адресу www.usenix.org/publications/login/1997-11/win32.html
В ноябрьском номере (1997 г.) журнала ;login, который издается ассоциацией пользователей ОС UNIX Соединенных Штатов Америки Usenix была опубликована статья Дэвида Корна относительно впечатлений, которые произвела на него двухлетняя работа по воспроизведению API UNIX с использованием API Win32. Мне кажется, что это должно быть интересно и людям из мира UNIX, и людям, связанным с технологиями Microsoft. Дэвид Корн - это одна из легендарных фигур в сообществе UNIX в основном благодаря тому, что он является создателем одного из наиболее популярных языков и командных интерпретаторов семейства shell Korn-shell (ksh). Перевод статьи на русский язык и ее публикация сделаны с разрешения Дэвида.
Заметим еще, что любой человек может стать членом Usenix и, в частности, получать журнал ;login. Информация об условиях членства размещена на Web-сервере Usenix.
Поскольку статью переводил я сам, хочу сделать еще одно замечание по поводу терминологии. Известно, что компания Microsoft очень любит вводить свои собственные термины, трудно переводимые на русский язык. Одним из таких терминов является "handle". По смыслу совершенно понятно, что это такое, но соответствующего (и не перегруженного) русского термина я не нашел. Поэтому (хотя мне это самому очень не нравится) я использую транслитерированный термин "хендл". Если кто-нибудь имеет на этот счет собственное мнение, сообщите мне, пожалуйста. А то ведь, не дай Бог, приживется...
С уважением, Сергей Кузнецов
Автор работал более двух лет, пытаясь написать интерфейс ОС UNIX для Windows NT и Windows 95 на основе обеспечиваемого компанией Microsoft API (Application Programming Interface) Win32.
Результатом работы явилось создание программного обеспечения U/WIN. Информация о U/WIN доступна в Internet по адресу http://www.research.att.com/sw/tools/uwin. Копии U/WIN для целей обучения, исследований или оценки можно свободно взять в Сети. Коммерческие версии U/WIN распространяются компанией Global Technologies Ltd., которая является владельцем торговой марки U/WIN и лицензирует программное обеспечение U/WIN у AT&T.
В этой статье описывается опыт автора по использованию API Win32, что показалось хорошим, что плохим, и что - неприятным. У автора не было опыта работы с предыдущими системами DOS и Windows, а опыт работы с Win32 ограничен подмножеством функций, требуемых для обеспечения функциональности, специфицированной X/Open, и не включает графические пользовательские интерфейсы. В статье выражается собственное мнение автора, не представляющее официальной точки зрения AT&T или какого-либо еще.
Следует сделать одно предупредительное замечание относительно Win32. Windows NT и Windows 95 не поддерживают один и тот же API Win32. На самом деле, интерфейсы различаются от выпуска к выпуску за счет добавления функций. Более важно то, что многие части интерфейса не реализованы в Windows 95, а в некоторых случаях имеют иную семантику.
Ряд аспектов программирования в среде Win32 показался автору достаточно полезным. Ассоциирование "хендлов" (handle) с большинством объектов было довольно удобным. Хендлы похожи на дескрипторы файлов в ОС UNIX за исключением того, что на них не распространяется свойство упорядоченности, присущее дескрипторам файлов. Как доступ к файлу, так и доступ к любому процессу, любой нити (thread) и любому объекту синхронизации производится через хендл. Хендлы можно "подменить" за пределами границ процесса, поскольку функция DuplicateHandle() принимает в качестве аргумента как хендл выпускающего процесса, так и хендл принимающего процесса.
С хендлами ассоциированы дескрипторы безопасности, которые определяют владельца, группу и права доступа к объекту. В дополнение к обычным правам чтения, записи и выполнения объект может иметь способность к синхронизации. Хендл со способностью к синхронизации может находиться в состояниях "готов" и "не готов". Процесс может заблокироваться до тех пор, пока от одного до 64 хендлов не перейдут в готовое состояние.
Одним из средств Win32, которое отсутствует в большинстве UNIX-систем, - это возможность оповещения при изменении файлов. В Win32 имеется примитив для создания хендла каталога со способностью к синхронизации; это хендл переходит в состояние "готов" при любом изменении файлов в этом каталоге или его подкаталогах.
Автору также понравился интерфейс нитей. Его было просто использовать, и он обеспечивал необходимую функциональность без излишних расширений интерфейса. Одним из новых свойств, доступных только на Windows NT, является возможность процесса создать нить внутри другого процесса.
На автора произвела сильное впечатление производительность ввода/вывода Windows NT при работе на Pentium Pro. Вряд ли пользователи Linux почувствуют какие-либо различия в производительности ввода/вывода.
Автор интенсивно использовал компилятор Visual C/C++ в режиме командной строки и испытывал сравнительно мало затруднений. Компилятор производит впечатление совместимого со стандартом ANCI-C.
Наконец, автор нашел некоторые полезные источники информации о Win32. Множество полезной информации содержится в книге Jeffrey Ritcher "Advanced Windows NT" (Microsoft Press, 1993); несколько дополнительных полезных указаний можно обнаружить в книге Matt Pietrek "Windows 95 System Programming Secrets" (IDG Books Worldwide, 1995).
Web-сайт www.sysinternals.com, поддерживаемый Mark Russinovich и Bryce Cogswell, является отличным местом для получения полезных инструментальных средств; набор средств, входящих в Microsoft Development Kit, на этом сайте умышленно отсутствует.
Многие аспекты Win32 показались автору плохими. Первым среди них была сложность интерфейса. Похоже, что сложная модель защиты является причиной проблем с безопасностью, поскольку, вероятно, только эксперты в состоянии применить эту модель корректно.
Список экспортируемых имен показывает, что библиотека kernel32 включает около 675 функций; библиотека advapi32, определяющая интерфейс безопасности, содержит приблизительно 400 функций; библиотека users32, обеспечивающая интерфейс пользователя, содержит примерно 600 функций. Набор средств разработки включает более 225K строк включаемых файлов и поставляется вместе с 400 дополнительными динамически компонуемыми библиотеками.
Одним из примеров чрезмерной сложности является интерфейс CreateFile(). Этот интерфейс обеспечивает функциональность функции ОС UNIX open(). У функции CreateFile семь аргументов:
Имеется по меньшей мере 33,554,432 возможных комбинаций опций для данного путевого имени, не считая дескриптора безопасности и хендла файла.
Функция CreateProcess(), которая заменяет семейство функций fork() и exec() ОС UNIX, принимает десять аргументов; некоторые из них являются структурами, содержащими много полей. При наличии большого числа возможных комбинаций флагов отсутствует установка флага, которая обеспечивала бы функциональность семейства функций exec. Эти функции заменяют текущий процесс некоторым новым процессом. Реализация семейства exec ОС UNIX была одной из наиболее хитроумных задач.
Другим примером сложности может служить реестр (registry). Реестр служит для сбора в одном месте параметров конфигурирования и обеспечения к ним быстрого доступа. Реестр структурирован подобно файловой системе, но доступ обеспечивается оотдельным набором функций API. Реестр обладает бесчисленным количеством ключей, и их организацию часто трудно предсказать, тем более, что она различна для Windows 95 и Windows NT.
В дополнение к сложности, вторым плохим свойством API Win32 является его неполнота. Хендлы могут наследоваться одним процессом от другого или дублироваться, но отсутствует функция API, которая позволила бы по заданному хендлу определить его тип. Хотя функции ReadFile() и WriteFile() могут выполнять через хендл асинхронный ввод/вывод (только в Windows NT), этот хендл должен быть создан через вызов CreateFile() с флагом FILE_FLAG_OVERLAPPED. Более того, если хендл был создан с использованием этого флага, то синхронный ввод/вывод через этот хендл невозможен. При наличии этих ограничений отсутствуют функции API, которые позволили бы переустановить флаги или хотя бы узнать, какие флаги использовались при создании хендла.
Другим примером неполноты является невозможность управлять полным пространством имен файлов. В дополнение к отсутствию возможности создания каталогов и, в меньшей степени, файлов с именами, различающимися регистром букв, в интерфейсе Win32 не допускаются такие имена файлов как aux.c и com0.sh. Невозможно обеспечить доступ или создать файлы, которые заканчиваются точкой. API Win32 не обеспечивает вызов для создания жесткой ссылки, хотя в самой файловой системе NTFS такая возможность имеется.
Третим аспектом Win32, который автор считает плохим, является отсутствие согласованности. Этот аспект можно было бы считать "неприятным", а не "плохим", если бы он не являлся источником ошибок и не приводил к потребности траты времени на чтение руководств. В ОС UNIX тоже часто встречается несогласованность, и похоже, что разработчики Win32 не старались учиться на чужих ошибках. Одна из областей несогласованности API Win32 - это значения, возвращаемые функциями. Например, CreateFile() возвращает -1 при ошибке и хендл в противном случае. Функция CreateEvent() возвращает 0 при ошибке и хендл в противном случае. Функция RegCreateKey() возвращает код ошибки, а передается ей адрес хендла.
Использование аргументов тоже является несогласованным. Первым аргументом функции OpenProcess() является желаемый режим доступа. Таким же образом обстоят дела с OpenEvent(). Однако в функции OpenProcessToken() желаемый режим доступа передается в качестве второго параметра.
В отличие от Windows 95, в интерфейсах Windows NT используется UNICODE, что позволяет хранить в этой кодировке имена файлов и других ресурсов, управляемых системой. К сожалению, вместо того, чтобы применять кодировку переменной длины UFT8, выбран способ представления символов в кодировке постоянной длины (16 бит). В результате отсутствует совместимость с 7-битовой кодировкой ASCII. Кроме того, приходится компилировать программы либо в расчете на UNICODE, либо в расчете на ASCII вместо того, чтобы применять некоторую унифицированную версию.
Другими плохими аспектами Windows NT, по мнению автора, являются планирование загрузки, необходимость в частых перезапусках и менталитет однопользовательской системы. Примером последнего является то, что когда пользователь входит в систему в рамках сессии telnet, то ftp приглашает ввести пароль на консоли, а не на терминале пользователя.
В дополнение к "плохим" свойствам Win32, многие аспекты этого интерфейса автор характеризует как "неприятные". По мнению автора, многие соглашения и приемы программирования в API Win32 выбраны не лучшим образом.
В предыдущем разделе перечислялись некоторые несогласованности, которые автор относит к плохим свойствам. Имеются и такие несогласованности, которые автор называет неприятными, например, несогласованный подход к именованию функций. Многие функции, такие как CreateFile(), именуются путем соединения глагола и существительного. Автора не беспокоит это соглашение, поскольку связанные функции рассеяны по всему руководству программиста. (Да, помогает гипертекст!) Однако в Win32 это соглашение не используется согласованным образом. Для функций RegCreateKey(), DeviceControl() и DosDateToFileTime() соглашение не соблюдается.
Во многих файлах заголовков, требуемых для построения приложений Win32, наблюдается большая свобода именования, что затрудняет программирование. Хотя некоторые подобные деффекты присутствуют и в API UNIX, в новых стандартах применяются тщательные меры, чтобы избежать этой проблемы. Имена в файлах заголовков Win32, похоже, используются случайным образом. Такие слова как IN, OUT, FAR, TRUE, FALSE, DELETE, UNALIGNED, VOID, IGNORE, INFINITIVE и MAXCHAR определены в стандартных файлах заголовков. Имена других символических констант, таких как SP_BAUD, GPRT, PST_FAX, GHND и LPTR (упомянем лишь несколько), трудно запомнить.
Многие имена функций в интерфейсе Win32 кажутся чрезмерно длинными. Это усложняет и написание, и чтение программы. Использование длинных английских имен не приносит пользу неанглоязычным программистам. Примером длинного имени функции может служить GetFileInformationByHandle(). Эту функцию можно было бы назвать GetFileInfo(), потому что в API нет никакой функции, позволяющей получить информацию о файле, кроме той, которой сообщается хендл. Из-за длинных имен и большого числа аргументов для вызова функции часто требуется несколько строк, что затрудняет поиск ошибок при чтении программы.
Автору не нравится соглашение о включении в имя переменной информации о ее типе. Например, имя структурной переменной dwProcess должно означать, что элемент Process является двойным словом. Это та информация, в которой автор не нуждается и которую не хочет помнить. Представим себе, что в будущей системе для Process потребуется 64 бита. В этом случае имя, по всей видимости, будет изменено, и придется изменять тесты программ.
Автор строго предпочитает соглашения UNIX по именованию типов с суффиксом _t, чем использование только прописных букв для имен типов. По соглашению, символические константы именуются только прописными буквами, и их использование затрудняет чтение программы.
Автору не нравится определение по крайней мере двух, а иногда и трех типов для каждого абстрактного типа. Например, в дополнение к типу идентификатора объекта SID имеется тип PSID, который означает всего-навсего SID*. Для типа FILETIME имеются два дополнительных заклинания - PFILETIME и LFILETIME.
Автор заключает статью тем, что после двух лет программирования для Win32 он находит интерфейс нескладным и трудным для использования. Ему по-прежнему часто приходится обращаться к руководству. Часто приходится писать небольшие тестовые программы, чтобы определить, что будет делать библиотечная подпрограмма в конкретных условиях. Как кажется, API UNIX гораздо проще, и благодаря U/WIN можно программировать с использованием этого интерфейса и запускать программы на системах Windows.