Сперва — о том,
зачем создавать библиотеки именно для ZXDev, ведь есть чудесный кросс-ассемблер SjASMPlus.
Во-первых, для быстрого макетирования часто используют ZX-Basic, но это имело больше смысла при разработке на самом Спектруме, которая уже никогда не будет актуальной — мощные хост-платформы сверхдоступны. Но зато возрастает потребность в структурировании методов разработки, поэтому макетирование на Spectrum-Basic выглядит наивно. Нужно использовать другой язык, более подходящий для структуризации данных. В этом свете видится единственно возможное решение — язык высокого уровня с высоким качеством кодогенерации. В ZXDev это есть, и даже больше — это Си и Оберон-1 и 2.
Во-вторых, нужен гибкий инструмент для разработки библиотек и эффективного их использования (в идеале с возможностью вызывать процедуры, работая как на языке высокого уровня, так и на ассемблере; чтобы в целевую программу включались только лишь реально использованные фрагменты кода; и, наконец, была бы гибкая конфигурация параметров библиотек). И всё это тоже есть в ZXDev.
Вот какой механизм конфигурирования библиотек предлагается. Допустим, у нас есть графическая библиотека, и в ней процедура вывода точки по заданным координатам. При аналогичном случае использования данной процедуры рассмотрим 3 варианта, которые могут быть предусмотрены её конфигуратором:
1. Пишется компактный загрузчик. Нужно выводить точки только в верхней части экрана, быстродействие процедуры вывода точки не важно.
2. Пишется очень большая игра, использующая всю память; необходим вывод точки максимально быстрый, но ради экономии памяти не использующий заранее рассчитанные таблицы данных.
3. Демка среднего размера, которой нужна максимальная скорость рисования точки. Ввиду наличия свободной памяти можно использовать таблицы данных для ускорения работы.
Предлагаемый механизм конфигурирования библиотек основан на препроцессоре языка Си (но может использоваться и для разработки на Обероне), годится для всех трёх случаев и не требует перекомпиляции библиотеки (которая может быть довольно длительной по времени). Файл конфигурации помещается в папку с исходниками и выглядит так:
Код: "C"
/* Configuration file of the library Graph */
//#define PLOT_ROM
//#define PLOT_FAST_NO_TABLE
#define PLOT_FAST_TABLE
#define TableStart 0xF000 // If table used
А вот как достигается эффект “без перекомпиляции”. В самом заголовочном файле:
Код: "C"
#ifdef PLOT_ROM
#define Graph_Plot Graph_Plot_ROM
#endif PLOT_ROM
#ifdef PLOT_FAST_NO_TABLE
#define Graph_Plot Graph_Plot_NoTab
#endif PLOT_FAST_NO_TABLE
#ifdef PLOT_FAST_TABLE
#define Graph_Plot Graph_Plot_Tab
#endif PLOT_FAST_TABLE
Подобным же образом можно задать тип аргументов для вывода точки (байт — если достаточно вывода только в пределах экрана; и слово — если нужен вывод точек за пределами экрана), какие использовать алгоритмы рисования линий, кругов, заливки замкнутого контура, вывода спрайтов, тайлов и т.д. Или каким способом выводить символьную информацию — с помощью RST #10 (ПЗУ) или напрямую в видеопамять. Отдельно можно ввести опции, задающие какие управляющие коды будет понимать процедура вывода, будет ли прокрутка при достижении низа экрана и т.п. Таким же способом можно задать режим работы программы — IM0/IM1, IM2 или DI. Понятное дело, что для возможности переключать такое большое количество опций без перекомпиляции потребуется дублировать много кода в библиотеке, но это неважно. Главное, что будет достигаться единостильность. А в случае кросс-платформенной разработки с помощью такого конфигуратора будут скрываться различия между платформами.
В будущем хотелось бы иметь визуальный редактор свойств модулей и компонентов, подобный дельфийскому. Возможно, такой инструмент появится в среде XDev.
Надеюсь, я вас убедил, что накопление библиотек в стиле, задаваемом XDev, имеет плюсы помимо простоты разработки на Обероне. Ведь массив библиотек может быть использован и для разработки на Си, и на ассемблере, и об этом читайте ниже. Приступаем к делу. Нам понадобится базовое знание ассемблера Z80.
Адаптируем для ZXDev из
YS MegaBasic V4.0 процедуру INVERT без параметров. Запустите MegaBasic и наберите INVERT. Она инвертирует весь экран Спектрума — меняет местами засвеченные и погасшие точки. Примем рабочую гипотезу, что где-то в процедуре должна быть загрузка в регистр начального адреса экранной памяти: #4000. В дебаггере эмулятора EmuZWin вызываем инструмент “Поиск”(Find). Будем искать два байта 00 40 (В Z80 первым хранится младший байт). Опытным взглядом сразу находим участок кода по адресу #C053:
Код: "ASM"
C053 ; ---------------------------------------------------------------------------
C053
C053 INVERT:
C053 ld hl, 4000h
C056 ld bc, 1800h
C059
C059 loc_C059: ; CODE XREF: C061
C059 ld a, (hl)
C05A xor 0FFh
C05C ld (hl), a
C05D dec bc
C05E inc hl
C05F ld a, b
C060 or c
C061 jr nz, loc_C059
C063 ret
C064 ; ---------------------------------------------------------------------------
(Я использовал лучший дизассемблер всех времён и народов — IDA, который понимает и процессор Z80!)
Создадим такой Оберон-интерфейс:
Код: "OBERON"
MODULE Mega;
(* -------------------- *)
(* MEGA BASIC for ZXDev *)
(* by Oleg N. Cher *)
(* VEDAsoft, 2013 *)
(* -------------------- *)
PROCEDURE INVERT*; END INVERT;
END Mega.
Сохраним его в ZXDev/Lib/Mod/Mega.odc и скомпилируем нажатием F11. У нас создались файлы:
ZXDev/Lib/Obj/Mega.c
ZXDev/Lib/Obj/Mega.h
ZXDev/Lib/Sym/Mega.sym
Скопируем файлы ZXDev/Lib/Obj/Mega.h и ZXDev/Lib/Obj/Mega.c в ZXDev/Lib/C
Это делается затем, что папка Obj содержит автоматически сгенерированные Ofront’ом сишные исходники, а у нас ручная работа. Т.е. нам от интерфейсного пустого модуля нужен лишь символьный файл, своеобразный биндинг между Обероном и Си/ассемблером.
Отредактируем исходник ZXDev/Lib/C/Mega.c (для редактирования программ на Обероне, Си и PHP я часто использую редактор
Syn Text Editor). Он примет такой вид:
Код: "C"
/* -------------------- */
/* YS MegaBasic v4.0 */
/* (c) 1984 Mike Leaman */
/* MEGA BASIC for ZXDev */
/* by Oleg N. Cher */
/* VEDAsoft, 2013 */
/* -------------------- */
#include "SYSTEM.h"
#include "Mega.h"
export void Mega_INVERT (void);
/*================================== Header ==================================*/
/* "INVERT" - инвертирование экрана: тон становится фоном, а фон - тоном. */
void Mega_INVERT (void)
{
__asm
LD HL, #0x4000
LD BC, #0x1800
INV_LOOP$:
LD A, (HL)
XOR #0xFF
LD (HL), A
DEC BC
INC HL
LD A, B
OR C
JR NZ, INV_LOOP$
__endasm;
} //Mega_INVERT
Как видите, я перевёл ассемблерный текст в заглавные буквы (традиция ZX). Пришлось также поправить числа — в ассемблере sdas, использованном в ZXDev, шестнадцатиричные числа задаются как 0xЧИСЛО, а решёточка # ставится перед всеми вычисляемыми выражениями (и числами). Знак $ после имени метки обозначает, что она локальная. Примеры смотрите в поставке ZXDev.
ZXDev/Lib/C/Mega.h приведём в такой вид:
Код: "C"
#ifndef Mega__h
#define Mega__h
#include "SYSTEM.h"
import void Mega_INVERT (void);
#define Mega__init()
#endif
Определение пустого Mega__init() необходимо если не требуется инициализация модуля (если требуется, то это будет процедура инициализации).
Откроем наш интерфейс ZXDev/Lib/Mod/Mega.odc и соберём библиотеку нажатием F12. Если ошибок нет, то консольное окно после трансляции исчезнет. Удобно, не надо заострять на нём лишний раз внимание.
Напишем небольшой модуль для тестирования нашей процедуры INVERT:
Код: "OBERON"
MODULE DemoInvert;
IMPORT Console, Mega;
BEGIN
Mega.INVERT;
Console.WriteStr(" To Be Inverted!");
Mega.INVERT;
Console.WriteStr(" To Be Inverted!");
Mega.INVERT;
END DemoInvert.
Работает хреновина! В следующих постах расскажу о процедурах с параметрами, там при сопряжении Си и Оберона есть тонкости.
Вопросы, предложения.