;---чтение данных
MOV CX,2 ;число читаемых записей
NEXT_REC: MOV AH,14H ;функция чтения файла
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;читаем одну запись
CMP AL,0 ;все в порядке?
JE CONTINUE ;
CMP AL,2 ;проверка на ошибку
JE READ_ERROR ;
.
.
CONTINUE: ADD DI,256 ;увеличиваем указатель
MOV DX,DI ;DX указывает на новую DTA
MOV AH,1AH ;функция установки DTA
INT 21H ;устанавливаем DTA
LOOP NEXT_REC ;идем на чтение следующей записи
;---позднее, закрываем файл
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,10H ;функция закрытия файла
INT 21H ;закрываем файл
CMP AL,0FFH ;проверка на ошибку
JE CLOSE_ERROR ;
Метод дескриптора файлов:
Функция 3FH прерывания 21H может читать данные из файла после-
довательно. Эта функция используется для любого чтения из файла с
помощью метода дескриптора файлов, включая файлы прямого доступа.
Файл должен быть открыт функцией 3DH прерывания 21H с кодом 0 в
AL, если он открывается только для чтения, и с кодом 2 - если он
открывается для чтения и записи. При открытии файловый указатель
автоматически устанавливается на первый байт файла. Функция чте-
ния из файла указывает сколько байтов должно быть считано и после
того как это сделано файловый указатель указывает на байт, сле-
дующий за последним считанным байтом, подготавливая следующее
обращение к функции. Отметим, что файловый указатель уникален для
каждого файла - операции над другими файлами не меняют его пози-
цию.
Программа может создать небольшой временный буфер, размером,
скажем, 512 байт, и постоянно вызывать функцию чтения, не забо-
тясь о позиции файлового указателя. Другой метод состоит в считы-
вании всего файла прямо в то место памяти, где он должен быть
расположен. В этом случае надо просто потребовать, чтобы функция
прочитала больше байтов, чем реально содержится в файле, так как
чтение прекращается при достижении последнего байта файла. Однако
Вам необходимо знать точную длину файла, чтобы знать где кончают-
ся данные в буфере, в который Вы считали файл.
Размер файла можно определить, сдвинув файловый указатель на
конец файла. Это надо сделать сразу же после открытия файла.
Поместите в AL код 2 и вызовите функцию 42H, для того, чтобы
сдвинуть указатель на конец файла. CX и DX должны содержать 0,
так как в противном случае указатель будет сдвинут с конца файла
на величину, которая содержится в этих регистрах. При возврате
DX:AX будут содержать новую позицию указателя, как смещение отно-
сительно начала файла, т.е., в данном случае, длину файла. Hе
забудьте снова вернуть файловый указатель на начало файла, перед
тем как читать его; это делается точно таким же образом, за иск-
лючением того, что в AL надо поместить 0. Если при выполнении
функции 42H возникает ошибка, то устанавливается флаг переноса, а
в AX возвращается 1, если неверен номер функции, и 6 - если ука-
зан неверный номер файла.
Теперь программа готова для чтения файла. Hадо поместить номер
файла в BX, а требуемое число байтов в CX и выполнить прерывание.
При возврате AX будет содержать число реально прочитанных байтов.
Если AX равен нулю, то достигнут конец файла. При других ошибках
устанавливается флаг переноса, а AX содержит 5 - при ошибке обо-
рудования и 6 - если указан неверный номер файла. В следующем
примере в буфер памяти считывается весь небольшой файл. Для у-
добства буфер располагается в сегменте данных, что существенно
увеличивает размер программы на диске. В своих программах лучше
создавать буфер, используя технику распределения памяти, описан-
ную в [1.3.1].
;---в сегменте данных
PATH DB 'A:FILENAME.EXT'0 ;строка пути к файлу
DATA_BUFFER DB 1000 DUP (?) ;буфер данных
HANDLE DW ? ;номер файла
FILESIZE DW ? ;размер файла
;---открываем файл
LEA DX,PATH ;DS:DX указывают на путь
MOV AL,0 ;код открытия для чтения
MOV AH,3DH ;функция открытия файла
INT 21H ;открываем файл
JC OPEN_ERROR ;проверка на ошибку
MOV HANDLE,AX ;запоминаем номер файла
;---устанавливаем файловый указатель на конец файла
MOV AH,42H ;функция установки указателя
MOV AL,2 ;код для конца файла
MOV BX,HANDLE ;номер файла
MOV CX,0 ;смещение равно нулю
MOV DX,0 ;
INT 21H ;устанавливаем указатель
JC POINTER_ERROR1 ;обработка ошибки
MOV FILESIZE,AX ;запоминаем размер (меньше 64K)
;---возвращаем указатель на начало
MOV AH,42H ;номер функции
MOV AL,0 ;код для начала файла
MOV CX,0 ;смещение равно нулю
MOV DX,0 ;
INT 21H ;устанавливаем указатель
JC POINTER_ERROR2 ;обработка ошибки
;---читаем весь файл
MOV AH,3FH ;номер функции чтения файла
MOV BX,HANDLE ;номер файла
MOV CX,FILESIZE ;число считываемых байтов
LEA DX,DATA_BUFFER ;DS:DX указывают на буфер
INT 21H ;читаем файл
JC READ_ERROR ;обработка ошибки
;---позднее, закрываем файл
MOV BX,HANDLE ;номер файла
MOV AH,3EH ;функция закрытия файла
INT 21H ;закрываем файл
JC CLOSE_ERROR ;обработка ошибки
5.4.5 Запись в файлы прямого доступа.
Физически файлы прямого доступа ничем не отличаются от после-
довательных файлов, они отличаются только режимом доступа. Файл
прямого доступа предполагает, что его данные организованы в виде
записей фиксированной длины, таким образом положение каждой запи-
си может быть вычислено (в последовательных файлах n-ный элемент
ищется путем подсчета разделителей между элементами, начиная с
начала файла). Операционная система автоматически выполняет эти
вычтсления. Однако любая программа может выполнять эту работу
сама, устанавливая файловый указатель на нужную позицию и считы-
вая последовательно такое число байтов, которое образует запись.
Высокий уровень.
В [5.3.3] объяснен формат открытия файдов прямого доступа в
Бейсике. В отличии от последовательного файла, файл прямого дос-
тупа может читаться и записываться в одно и то же время, без
закрытия и повторного его открытия. Оператор OPEN завершается
числом, дающим размер записи файла. Hапример, OPEN "R", 1, "NEW-
DATA", 20 устанавливает для файла NEWDATA размер записи в 20 байт
(при этом файл открывается как файл #1).
После того как файл открыт, его записи могут быть разбиты на
составляющие переменные с помощью оператора FIELD. Оператор FIELD
указывает сколько байтов записи отводится под каждую переменную.
Hапример, запись длиной 20 байт может быть разбита оператором
FIELD 1, 14 AS LASTNAME$, 2 AS DEPOSIT$, 4 AS ACCTNUM$. В этом
операторе первая цифра 1 указывает, что данный оператор FIELD
описывает разбиение записи для файла, открытого под номером #1.
Данные располагаются в записи точно в том порядке, в каком они
описаны в операторе FIELD. Опреаторы RSET и LSET сдвигают данные
в полях, выравнивая их по правому (RSET) или левому (LSET) краю и
заполняя остающиеся пустые места пробелами. Hапример, для того,
чтобы вставить фамилию "SMITH" в 14-байтное поле с именем LASTNA-
ME$, надо записать RSET LASTNAME$ = "SMITH", или если переменной
N$ было присвоено значение "SMITH", то RSET LASTNAME$ = N$. Вмес-
то RSET может быть использовано LSET. Kогда впоследствии данные
считываются из поля в переменную, то переменной присваиваются все
14 байтов. При использовании RSET программа удалит все лишние
пробелы в начале строковой переменной, однако если будет исполь-
зоваться LSET, то пробелы будут удаляться справа.
Отметим, что все имена переменных в операторе FIELD относятся
к строковым переменным. В файлах прямого доступа Бейсик рассмат-
ривает все переменные - включая числовые - как строковые. Число-
вая переменная должна быть преобразована в специальный вид, преж-
де чем ее значение может быть присвоено полю, а когда она затем
считывается из поля то необходимо обратное преобразование. Слово
преобразование стоило бы заключить в кавычки, поскольку Бейсик на
самом деле не меняет способ представления числа в памяти; он
просто обрабатывает число особым образом. Числовые поля требуют
двух байтов для целых чисел, четырех байтов - для чисел с обычной
точностью и восьми байтов - для чисел с двойной точностью. В
точности такое же число байт требуется для представления этих
чисел в памяти. Для преобразования их в строковую форму надо
использовать функции MKI$, MKS$ и MKD$, которые осуществляют
преобразование число-строка для целых, вещественных и чисел с
двойной точностью, соответственно. Обычно эти функции комбини-
руются с операторами RSET или LSET, например, RSET = ACCTNUM$ =
MKI$(X), где X - целая переменная, если полю ACCTNUM$ было отве-
дено два байта в операторе FIELD.
После того как поля заполнены операторами RSET и LSET, запись
записывается на диск с помощью оператора PUT#. PUT #1, 245 поме-
щает данные в запись номер 245, файла открытого под номером #1.
Hомер записи может быть опущен, в этом случае данные записываются
в запись с номером на единицу больше, чем номер последней запи-
санной записи (начиная с записи 1). Записывается вся запись цели-
ком, даже если не все поля были заполнены данными. Отметим, что
поля буфера не очищаются при выполении операции PUT, поэтому
элементы данных, такие как текущая дата, могут помещаться в буфер
только один раз, а затем они будут записаны во все записи, кото-
рые будут записываться в течение данной сессии. Функция LOC возв-
ращает номер последней записанной в файл записи. Если файл был
открыт под номером #3, то напишите X = LOC(3).
Функция LOF (длина файла) возвращает длину файла в байтах. Для
определения числа записей, содержащихся в файле, надо разделить
это значение на длину записи. Добавление 1 к этому значению дает
номер записи, который надо использовать, чтобы добавить к файлу
новые записи. Если файл был открыт под номером #2, а длина его
записей равна 32 байтам, то требуемое значение вычисляется как
RECORDNUM = LOF(2)/32 + 1.
В следующем примере файл прямого доступа открывается с длиной
записи 24 байта, причем запись разбита на три переменные. Пользо-
ватель программы запрашивается о содержимом всех трех полей, а
когда все они введены, то запись добавляется к файлу. В строке
120 вычисляется начальный номер записи. Отметим, что данные могут
не записываться физически на диск каждый раз при выполнении опе-
ратора PUT. В выходном буфере могут накапливаться несколько запи-
сей, прежде чем это будет сделано.
100 OPEN "R", 1, "A:NEWDATA.DAT", 24 'открываем файл
110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT
120 R = LOF(1)/24 + 1 'номер последней записи + 1
130 CLS 'чистим экран
140 INPUT "Enter name:",N$ 'получаем имя (строка)
150 INPUT "Enter age:",A% 'получаем возраст (целое)
160 INPUT "Enter weight:",W! 'получаем вес (вещественное)
170 RSET LASTNAME$ = N$ 'помещаем в поле имя
180 RSET AGE$ = MKI$(A%) 'помещаем в поле возраст
190 RSET WEIGHT$ = MKS$(W!) 'помещаем в поле вес
200 PUT #1, R 'записываем запись
210 R = R + 1 'увеличиваем счетчик
220 PRINT: PRINT "Do another (y/n)?" 'запрос пользователя
230 C$ = INKEY$: IF C$ = "" THEN 220 'ожидаем ответа
240 IF C$ = "y" THEN CLS: GOTO 130 'если да, то на начало