О библиотеках. 0. Вместо вступления За время написания эта статья трижды меняла свое название и содержание. Сначала я хотел просто написать небольшую заметку о своей интерфейсной библиотеке ICL, которую я собирался опубликовать. Но при написании статьи оказалось, что есть еще два вопроса, имеющие непосредственное отношение к этой теме. Я дописал разделы о свободно распространяемых библиотеках и о принципах написания конфигурабельного кода. Но к моменту окончания этой работы выяснилось что, во-первых, есть ряд причин по которым я в данный момент не могу точно сказать: буду я публиковать ICL или нет. А во-вторых, выяснилось что то, что я хотел опубликовать в качестве примера к статье об упаковке анимаций постепенно принимает вид законченной библиотеки, причем как раз такой, какой я бы хотел видеть хорошую библиотеку. Поэтому пришлось еще раз все поменять, и в результате описания ZXA library и Memory Management Library были выделены в отдельные статьи, а эта статья была полностью посвящена моим размышлениям на тему "Возможность создания полноценной библиотеки в условиях Спектрума" :) Прошу не воспринимать все изложенное ниже как мою попытку навязать вам свою точку зрения на то, как надо писать код. Это просто попытка поделиться своим опытом, накопленным за годы работы на нескольких платформах. 1. Свободно распространяемые библиотеки В тот момент, когда я начинал писать эту статью, ситуация с ассемблерами на Speccy была просто удручающая. Среди кодеров были распространены около десятка ассемблеров практически абсолютно несовместимых друг с другом. Причем несовместимых не на уровне формата исходных текстов (что решалось бы простыми конверторами), а на уровне самого главного - синтаксиса! Если набор мнемоник самого ассемблера Z80 был, слава богу, еще одним и тем же (здесь "отличился" только STORM), то даже мало-мальские расширения уже были свои у каждого ассемблера. Здесь, чувствуется, авторы ассемблеров просто соревновались в оригинальности - кто из них придумает больше фич, которых нет в других ассемблерах. И при этом, подчас забывая о главном - о реализации тех фич, что уже были сделаны в других ассемблерах. Так, например, в половине ассемблеров не поддерживаются скобки в выражениях. Мне интересно - а авторы хоть раз пытались написать более-менее сложное выражение без скобок? По-видимому, нет. А, тем не менее, поддержка скобок в выражениях была (если брать только современные ассемблеры) еще в TASM v2.0 аж 1993-го года выпуска! Или выделение старшего и младшего байтов слова в тех же выражениях. Я знаю, по меньшей мере, 3 ассемблера в которых такая функция есть, но ее синтаксис различен во всех трех! И, главное, авторы ассемблеров все как один забили большой болт на внедрение в свои детища хоть каких-нибудь более важных наворотов касающихся возможностей самого компилятора. В конце концов, скорость компиляции, хоть и немаловажный, но далеко не самый важный критерий в оценке качества компилятора. Часто при работе над большим проектом вдруг оказывается, что кодер просто не в состоянии контролировать те объемы исходных текстов, с которыми ему приходится работать. Уже никакая скорость компиляции не сможет помочь, если человек физически не сможет упомнить все тонкости в работе собственного кода. В этом случае зачастую проект может просто погибнуть. Здесь могут помочь только дополнительные инструменты, предоставляемые компилятором и призванные облегчить организацию работы над большим проектом. По части наворотов в самом компиляторе единственным светлым пятном до сих пор оставался только TASM v4.12 by RST7/CBS. Только в нем кодер получает в свои руки такие мощные инструменты как макросы и условную компиляцию. А плюс к этому метки неограниченной длины и нормальный механизм работы с main file'ами, да навороты в редакторе вроде клавиатурных макросов - все это делает TASM безусловным лидером в ряду Спектрумовских ассемблеров. Конечно и в нем не все гладко - отсутствие вложенной условной компиляции и небольшая таблица меток создают определенные неудобства, но, по крайней мере, в этом ассемблере можно создавать и вести больше проекты. К сожалению, большинство Спектрумовских кодеров не понимает всю важность этих вещей. Им гораздо важнее высокая скорость компиляции, или, к примеру, поддержка многотекстовости. Поэтому наши славные кодеры сидят в ALASM'е, STORM'е или XAS'е. Но теперь ситуация, к счастью, изменилась в лучшую сторону. Появился приемлемый для большинства компромисс - ALASM v4.2. Эта версия, доработанная KVA/E-Mage имеет поддержку практически всех расширений TASM'а - макросы, условная компиляция, скобки в выражениях. Причем, что очень важно - синтаксис всех этих расширений совместим с TASM'ом! Конечно, как это всегда бывает, совместимость не полная, но вполне достаточная для того, чтобы обеспечить переносимость исходников между TASM'ом и ALASM'ом. Правда, из-за различия в наборе функций этих двух ассемблеров, для обеспечения переносимости придется соблюдать ряд правил. К примеру, в TASM'е не поддерживаются двоичные числа, нет вложенной условной компиляции, нет части функций ALASM'а используемых в выражениях, нет конструкции DUP-EDUP. С другой стороны в ALASM'е отсутствует функция DISPLAY, а локальные метки и работа с main file'ами сделаны неверно. Но это уже мелочи по сравнению с тем что теперь у нас появилась возможность писать код с использованием нормальных инструментов, и этот код сможет использовать, по меньшей мере, половина всех Спектрумовских кодеров (в основном за счет ALASM'а). Теперь необходимо пояснить - к чему я, собственно, завел весь этот разговор. Дело в том, что на Speccy, как на платформе, практически отсутствует одна очень хорошая вещь, широко распространенная на других платформах (особенно в среде UNIX-подобных OS). Я говорю о таком явлении как свободно распространяемые в исходниках библиотеки. Вспомните, как часто вам приходилось сталкиваться с подобными библиотеками? Скорее всего, нечасто, а большинству - так и вообще никогда. Точнее они сталкивались с ними, но в несколько ином варианте - в виде опубликованной в каком-нибудь журнале статьи, в которой описывался тот или иной набор процедур. Лично я за все время видел только штук 5 подобных библиотек и немного больше коллекций процедур, которые можно с натяжкой назвать библиотеками. В качестве подобных коллекций могу привести известные пакеты SuperCode. В качестве же примера полноценной библиотеки можно привести Hrust Library Димы Пьянкова. У некоторых людей может возникнуть вопрос типа: "Зачем нам все это? Мы и сами все напишем не хуже и даже лучше!" С одной стороны это правильно. Но с другой стороны как раз именно из-за отсутствия практики выпуска и, главное, использования подобных библиотек мы можем наблюдать огромное количество примеров хорошего по своей сути софта, загубленного как раз по причине того, что в какой-то области автор оказался неспециалистом. За примерами далеко ходить не надо - любой вспомнит массу программ, которые он бы использовал, если бы не... Дальше можно поставить кучу вещей - убогий до невозможности интерфейс, жуткий метод работы с диском, неправильная работа с джойстиком и мышью, отсутствие поддержки или неправильная работа с расширенной памятью и многое другое. Вспомнили? А теперь подумайте - если бы автор подобной программы не изобретал велосипед, а просто использовал бы готовую библиотеку, которая бы реализовывала все необходимые функции в какой-либо области. Причем делала бы это на высоком уровне, то есть: - если это библиотека интерфейса, то она обеспечивала бы легкое создание интерфейса удобного в использовании и содержащего все необходимые интерфейсные элементы. - если это библиотека работы с диском, то она корректно бы работала со всеми видами дисковых накопителей и правильно бы обрабатывала все дисковые ошибки. - если это библиотека работы с джойстиком и мышью, то она обеспечивала бы работу со всеми распространенными схемами, имела бы возможность как автодетекта, так и ручной настройки и т.п. - если это библиотека работы с памятью, то она обеспечивала бы корректную работу со всеми распространенными расширениями памяти, автодетект типа и объема памяти и прочие подобные вещи. В этом случае автору: 1. Не пришлось бы ломать голову над тем, как правильно сделать ту или иную вещь так, чтобы это работало если не у всех, то у подавляющего большинства. 2. Не пришлось бы тратить время на то чтобы придумать, написать и отладить то, что уже до него придумывалось, писалось и отлаживалось не один раз, причем зачастую это работало лучше, чем у него. А кроме всего прочего его программы будут выглядеть и работать лучше при намного меньших затратах сил и времени. Для тех, кто, прочитав написанное выше, подумал нечто вроде: "Нафиг мне это надо, еще разбираться с чужими исходниками! Я лучше сам все напишу!" я хочу сказать что: 1. Вы не до конца представляете себе тот объем работ, который необходимо сделать для реализации некоторых вещей. Кроме того подумайте: "а обладаю ли я достаточным набором знаний, опыта и времени, чтобы писать это самому?". Приведу несколько примеров: допустим, вы пишете графический редактор для PC (или для AMiGA, неважно). Каждый нормальный редактор обязан иметь поддержку кучи графических форматов. Так вот, возьметесь ли вы писать поддержку таких форматов как JPEG или PNG? Или все же воспользуетесь готовыми, отлаженными, свободными и поставляемыми в исходниках библиотеками jpeglib и pnglib? Для того чтобы дать вам немного информации приведу статистические данные по объемам исходных текстов этих библиотек: jpeglib: 57 files, 816541 bytes pnglib : 21 files, 550314 bytes По-моему комментарии здесь излишни. Или другой пример (для Speccy, чтобы было понятнее) - возьметесь ли вы писать поддержку какого-либо девайса, которого у вас нет, и который вы в глаза никогда не видели? Да еще так, чтобы это правильно работало? Или, возьметесь ли вы писать, например, компрессор, не будучи спецом в области компрессии данных? А вы уверены, что ваш компрессор будет сжимать данные лучше, чем Hrust? Или вы все-таки возьмете бесплатную, поставляемую в исходниках и имеющую хорошее описание библиотеку Hrust Library? 2. Вы не до конца понимаете суть того, что вкладывается в понятие "библиотека". - Хорошая библиотека написана человеком, который очень хорошо разбирается в той области для которой он создает библиотеку. Так что возможности и качество кода, включенного в эту библиотеку, будут заведомо лучше чем то, что могут написать большинство тех, кто разбирается в этом вопросе хуже, чем автор библиотеки. - Хорошая библиотека написана, исходя из соображения, что код будут использовать другие люди. Т.е. хорошая библиотека - это не просто набор процедур. Это хорошо комментированный код и хорошо продуманная интерфейсная часть. Функции библиотеки должны обеспечивать если не все, то, по крайней мере, большинство из того набора функций, который требуется от данного типа кода. Т.е. если кто-то сел, написал для себя несколько процедурок, а потом решил их опубликовать - это будет не библиотека, а просто очередной набор процедур. - Хорошая библиотека снабжена подробным описанием своих возможностей, принципов работы каждой из интерфейсных функций, всех необходимых структур данных и т.п. Также, как правило, с хорошей библиотекой поставляются несколько программ - примеров использования. - Код хорошей библиотеки конфигурируем в широких пределах. Об этом будет рассказано подробнее в следующем разделе. - Интерфейсная часть хорошей библиотеки понятна человеку, не вникавшему детально в принципы работы этой библиотеки. Это, например, означает, что все идентификаторы (метки), которые будет использовать кодер для работы с этой библиотекой должны быть читаемыми для любого человека, а не только для автора библиотеки. К примеру в хорошей библиотеке не должно быть процедур и/или переменных с именами типа: VXSTPD, PROC1, ZXCVBNM и т.п. Любой нормальный ассемблер должен иметь поддержку меток любой длины (как TASM и ALASM), так что не стоит мучить человека, задавая ему загадки типа "что значит метка CRRLSTB?". Гораздо лучше, если эта метка будет иметь имя CREATE_ROLL_SPRITES_TABLE, что по сути одно и то же, только второй вариант будет понятен сразу всем, тогда как над значением первого варианта метки любой человек хорошенько поломает голову. Кроме того что идентификаторы должны быть понятными - они должны быть также хорошо систематизированы с тем, чтобы человек при первом же взгляде на имя метки получил общее представление о том, что именно это за метка. Приведу пример набора правил, по которым построены идентификаторы в моей библиотеке ICL: - Все имена интерфейсных процедур (через которые программа работает с библиотекой) начинаются с префикса ICL_, чтобы в коде программы сразу были видны все обращения к функциям библиотеки. Пример: ICL_OPEN_WINDOW - Идентификаторы полей структур данных образуются по следующей схеме: [Сокращение имени структуры].[Имя поля] Пример: Поля структуры данных для пункта меню (Menu Item) имеют вид: MI.X - X координата пункта меню MI.FLAGS - флаги пункта меню MI.HANDLER - процедура-обработчик Пример использования: LD A,(IX+MI.X) - Идентификаторы масок для выделения битов во флаговых байтах имеют префикс '_' перед именем. Пример: флаги во флаговом байте пункта меню (Menu Item Flags): _MIF_ACTIVE - пункт меню активен _MIF_SELECTED - пункт меню выбран Пример использования: LD A,(IX+MI.FLAGS) AND _MIF_ACTIVE JR Z,INACTIVE_MENU_ITEM - Идентификаторы номеров битов для флагов во флаговых байтах имеют префикс 'B_'. Пример: флаги во флаговом байте пункта меню (Menu Item Flags): B_MIF_ACTIVE - пункт меню активен B_MIF_SELECTED - пункт меню выбран Пример использования: BIT B_MIF_ACTIVE,(IX+MI.FLAGS) JR Z,INACTIVE_MENU_ITEM Я не говорю что эти правила идеальны, я привел их только в качестве иллюстрации. Важно то, что если все идентификаторы в библиотеке построены по одному принципу - с ней будет значительно легче работать. В качестве примера библиотеки, во многом отвечающей этим правилам я могу привести ZXA library. Это не потому что я хочу сам себя похвалить, а потому что я специально писал ее с расчетом на то чтобы показать, как можно создать хорошую библиотеку на Speccy. И одним из самых важных моментов в ней является ее полная конфигурабельность. Вот о ней мы и поговорим далее. 2. Принципы написания конфигурабельного кода. Как обычно поступает Спектрумовский кодер если ему необходимо, имея набор процедур, изменить их функциональный набор? Т.е. к примеру, если у него есть библиотека для создания интерфейса и ему нужно взять из нее только часть. Что он будут делать в этом случае? Я так думаю, что большинство ответит что-нибудь вроде "полезет в код и будет разбираться" или "оставит как есть" или "плюнет на это и напишет все сам". А причиной этого служит только одно - как правило, Спектрумовские кодеры пишут неконфигурабельный код! Т.е. действуют по принципу "каждую программу пишу с нуля". Или, пытаясь использовать часть ранее написанного кода, сталкиваются с проблемой того, что им легче написать все заново чем разобраться "что же я там такое написал и как все это работает". Действительно, как правило, взявшись просматривать код даже полугодичной давности, бывает очень трудно вспомнить - что именно делает, к примеру, мнемоника BIT 3,(IX+7)? Имеется ввиду, конечно, "что же это за данные достаются с ее помощью?" :) Естественно так бывает не всегда, хотя достаточно часто. И приходится, плюнув на все, писать весь код заново, по сути делая уже сделанную работу второй раз. Но вернемся к конфигурабельности кода. Что означает этот термин? Это возможность изменения функционирования кода без его редактирования. Подчеркну еще раз - БЕЗ его редактирования! Т.е. для того чтобы убрать какую-то часть кода или для того чтобы изменить какие-то параметры работы сам код трогать не нужно! А это в свою очередь означает, что для того, чтобы использовать этот код в своих программах (а значит подстраивать его под свои нужды) совершенно необязательно копаться в коде и досконально изучать принципы его работы. Достаточно просто знать 2 вещи: 1) Набор "интерфейсных" процедур. Это те процедуры, через которые осуществляется работа. 2) Набор конфигурационных переменных для настройки кода под конкретную задачу. Как же создавать такой код? Используя языки высокого уровня это сделать довольно легко т.к. они, кроме всего прочего предоставляют набор инструментов для написания подобного кода и, к тому же там исходный текст мало связан с тем, как конкретно будет выглядеть исполняемый код. Написание конфигурабельного ассемблерного кода сложнее, но тоже вполне возможно. Просто здесь приходится самому выступать в роли компилятора и еще при написании учитывать то, как должен выглядеть код в различных конфигурациях. Правда спектрумовские ассемблеры серьезно уступают профессиональным компиляторам по набору директив компиляции, но даже с тем, что есть, писать конфигурабельный код вполне реально. И даже достаточно просто, хотя для некоторых людей это может быть несколько непривычно. Вот несколько моментов, учитывая которые при написании кода можно добиться его конфигурабельности: 1. Любая вещь, которую можно позволить изменить надо позволить изменить. Если это параметр - его значение задается не в коде, а в EQU. Соответственно все значения которые зависят от этого параметра, должны быть заданы не константой, а выражением. 2. Код пишется исходя из соображения, что любая его часть может впоследствии быть изменена. Особенно это касается структур данных состоящих из нескольких полей. В компиляторах языков высокого уровня этот пункт реализуется сам по себе т.к. генерация реально исполняемого кода происходит только на этапе компиляции, а значит все индексы (смещения) полей в структурах данных тоже просчитываются компилятором самостоятельно. При работе с ассемблером всю работу по заданию индексов полей структур данных приходится проделывать вручную. Если эти индексы заданы в программе явно - то при изменении состава и/или размеров полей в структуре все индексы "поплывут" и их придется пересчитывать заново. Допускать этого нельзя, особенно если планируется управлять генерацией кода извне. Поэтому индексы всех полей структур данных используемых в коде тоже необходимо задавать через EQU с тем, чтобы в случае изменения набора полей и/или их размеров не возникла необходимость вручную править код. Также подобный подход подразумевает отказ от ручной оптимизации кода переходов между полями структур данных при их обработке. Например, у нас есть следующая структура: STRUCTURE DEFB 0 ;FIELD 1 DEFW 0 ;FIELD 2 DEFW 0 ;FIELD 3 Если нет полной уверенности, что при всех возможных конфигурациях кода эта структура останется неизменной, то в коде нельзя использовать конструкции подобные такой: LD HL,STRUCTURE LD A,(HL) ;GET FIELD 1 INC HL LD E,(HL) ;GET FIELD 2 INC HL LD D,(HL) Также нельзя менять INC HL на INC L, если нет полной уверенности что перехода через границу блока (256 байт) не будет во ВСЕХ возможных конфигурациях кода. Если же использование подобных конструкций почему- либо необходимо - эти моменты необходимо отдельно оговаривать (к примеру, поместить в этом месте кода комментарий с пометкой, при каких условиях этот участок кода будет работать правильно). 3. Код должен быть построен в виде набора независимых или слабо связанных компонент. Под слабой связью я подразумеваю то, что один компонент взаимодействует с другим так же как внешний код взаимодействует с этими компонентами, т.е. только через интерфейсные процедуры (которые можно при необходимости легко эмулировать). Тогда отсутствие какой-нибудь компоненты либо не повлияет на остальные компоненты, либо придется писать альтернативные куски кода которые работают без удаленной компоненты. 4. Т.к. практически любой код требует для своей работы выделения участков памяти под личное использование - при создании такого кода не обойтись без механизма выделения памяти. О, том как это можно сделать - читайте мою статью о Memory Management Library. Если вы внимательно прочитали то что было написано выше - вы понимаете, что в полной мере реализовать все это можно только используя условную компиляцию. Только так можно реализовать подмену участков кода во время компиляции. Макросы тоже очень нужны для обеспечения конфигурабельности кода - с их помощью можно реализовать множество нужных функций. Соответственно, учитывая все написанное выше, можно сказать - написание подобного кода на данный момент возможно только для двух ассемблеров: TASM v4.12 и ALASM v4.2. Но, как я уже говорил выше - суммарно эти ассемблеры охватывают около половины всех Спектрумовских кодеров, а значит подобные библиотеки найдут своего пользователя. 3. Распространение Основные моменты того описания, что вы только что прочитали, я уже успел обсудить с некоторыми достаточно известными людьми на Speccy. И, к своему удивлению, услышал, что все они придерживаются одного мнения: MiniSoft avec NRJ/ACL Вопрос о том, будет ли распространяться Doors2000 в исходниках. > Doors будет pаспpостpаняться только как ОТКОМПИЛИРОВАННЫЙ >модуль и никак иначе! > Мне бы не хотелось, что бы всякие пионеры в ней ковырялись и >начали плодить глюки. D-Man/EI Вопрос о том, как EI относятся к идее распространения исходников (на примере библиотеки ICL). > Насчет ICL я даже и не знаю. Распространив ее ты породишь на >свет множество одинаковых прог разных ламеров, которым влом >писать оболочку. > Мы, например, не хотим релизить исходники Napalm'а именно по >этой причине, т.к. после публикации мы породим кучу однотипных >скучных дем. Все это нужно не кодерам, т.к. уважающий себя >кодер никогда не будет использовать чужие исходники. Т.е. авторы боятся, что их код будет использован людьми, которым самим не под силу написать то же самое и которые просто "украдут" их код, сделав некачественную программу и наплевав на все copyright'ы. Да, такая опасность, безусловно, есть. Но есть несколько моментов, о которых нельзя забывать. Во-первых, практически любая библиотека достаточно объемна. Т.е. объем ее исходных текстов составляет, допустим 100кб, а то и больше. Скажу вам честно, что человеку, особенно неподготовленному (т.е. ламеру), будет просто чрезвычайно сложно каким-либо образом "перековырять" код этой библиотеки так, чтобы существенно его изменить и при этом сохранить в рабочем состоянии. И вряд ли ламер с этим справится. Если же справится - значит ваш код во многом помог его развитию и от этого всем будет только польза - возможно в дальнейшем он напишет много классных программ. Вспомните - все мы начинали со взлома и изучения чужого кода, нет ничего плохого в том что кто-то начнет с изучения исходников написанных профессионалами. Во-вторых, все почему-то забывают о том, что кроме ламеров, которых надо держать в клетке пока из них не получится что-нибудь путное, есть еще и более опытные люди. И то, что уважающий себя кодер все должен писать сам - достаточно спорное мнение. В начале этой статьи я уже приводил ряд примеров, когда даже опытному кодеру было бы лучше использовать готовый код, а не писать что-то свое. Потому что если это "что-то свое" будет хуже чем то, что он мог бы использовать в своей программе, то от этого проиграют конечные пользователи этой самой программы, т.е. мы сами. Простейший пример: всем известный Screen Optimizer. Очень нужная и полезная вещь, но за такой интерфейс лично мне хочется оторвать автору руки (шутка!). Или демы с multiloader'ами, которые не работают на половине дисководов? Или игры, которые нельзя запустить из-за неправильного опроса Kempston mouse? Вспомните, ведь все эти вещи были как раз написаны самими авторами программ! И, как оказалось - написаны плохо. А пострадали от этого мы все. Неужели не лучше было бы, если бы все они использовали вместо этого готовые, хорошо написанные и отлаженные библиотеки? А теперь что касается дем. Здесь, конечно каждый все пишет сам т.к. сейчас демы в подавляющем большинстве случаев пишутся не просто так, а для участия в demo compo на какой-нибудь party. Но, например, лично я бы не отказался воспользоваться некоторыми процедурами из того же Napalm'а в своей деме. И вовсе не из-за того, что я сам не могу написать того же. Нет, просто я не против воспользоваться каким-нибудь super fast inner loop'ом texturemapper'а или процедурой сортировки полигонов просто потому, что, если моя процедура будет работать медленнее - от этого, в конце концов, пострадают зрители. Правда это уже не будет библиотекой, но тоже касается вопроса распространения исходников. И, наконец, давайте посмотрим на другие платформы. Практика создания библиотек, распространяемых в исходниках там довольно сильно распространена. Более того - как правило такие библиотеки являются также кроссплатформенными, т.е. собираются и работают на многих платформах, в среде многих компиляторов и OS. Примеров можно привести массу, назову хотя бы уже ранее упомянутые мной jpeglib и pnglib, а также библиотеку работы с .zip архивами - zlib. Кроме того, нельзя не вспомнить такие вещи как OpenPTC (многоплатформенная библиотека для унификации работы с видеопамятью) и Crystal Space (мощный 3D движок с кучей дополнительных утилит для создания 3D игр практически под все платформы). Все они, как и многие другие библиотеки, свободно распространяются в исходных текстах и, кроме того, разрешается их использование в коммерческих программных продуктах. Их наличие помогает многим людям создавать качественные программы, и в результате все от этого только выигрывают! Чем же Speccy хуже? Почему бы и нам не начать подобную практику - уверен, что нам всем это тоже пойдет только на пользу!