http://sulfurzona.ru/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Wing-Thunder Game (fly simulator)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Скачать игру Крыло-Гром (Wing-Thunder) бесплатно
 
 

Паскаль для новичков (часть 28)

 

Спрашивали? Отвечаю…

 

640К для Паскаля не предел

(продолжение)

 
Автор: Владислав Демьянишин
 
Паскаль для новичковНу что, продолжим?
Функция GetXMS выполняет все необходимые действия по выделению EMB размером Size килобайт, инициализирует структуру указателя XPtr, помещая идентификатор выделенного EMB в поле Handle, в поле Allocated заносит единицу. Если процессор работает в реальном режиме, то функция пытается заполучить линейный адрес выделенного EMB, и если это удается, то заносит его 32-битное значение в поле LPtr. Если же процессор находится в режиме V86, то при вызове функции GetLinePointer может произойти ошибка выполнения задачи, так как режим работы V86 процессора свидетельствует о том, что загружен драйвер EMM386 либо программа работает в сеансе MS-DOS под Windows. Дело в том, что в этих двух случаях линейный адрес скорее всего не совпадет с физическим адресом EMB, да и обратиться к нему по нулевому селектору в сегментном регистре и линейному адресу из программы в режиме V86 практически невозможно, так как произойдет нарушение защиты.
 
Если функция возвращает нуль, то операция выделения EMB прошла успешно, иначе нет.
Помимо этого, данная функция выполняет копирование двух байт из памяти произвольного адреса, например, $0:$0 в область полученного EMB, чтобы восстановить в кэше дескрипторов предел в 4Гб для нулевого селектора. Это полезно сделать на тот случай, если программа выполняется в реальном режиме. Тогда при загрузке нулевого значения селектора в сегментный регистр DS или ES (кроме FS и GS) и 32-битного смещения, коим может быть линейный адрес EMB, в любом 32-битном регистре EAX, EBX, ECX, ESI, EDI, EBP можно обратиться непосредственно к области EMB для пересылки данных, минуя функцию CopyEMB драйвера, что даст четырехкратное ускорение пересылки данных при условии, что пересылка будет выполняться двойными словами (по 4 байта).
 
{Size - в Кбайтах
Return: 0 – ok, <>0 - error}
function GetXMS( var XPtr : TXMSPtr; Size : word ) : word;
var P : pointer;
begin
GetXMS := $0ff;
XPtr.Allocated := 0;
if GetEMB( Size, XPtr.Handle )<>0 then exit;
P := Ptr( 0, 0 );
with EMBCopy do begin
         Counter := 2;
         SrcHandle := 0;
         SrcPtr.AsPtr := P;
         DstHandle := XPtr.Handle;
         DstPtr.AsInt := 0;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then exit;
if not PMode then
 if GetLinePointer(XPtr.Handle,XPtr.LPtr)<>0 then;
XPtr.Allocated := 1;
GetXMS := 0;
end;
 
Процедура FreeXMS позволяет освободить EMB с идентификатором XPtr.Handle, который был ранее выделен функцией GetXMS. При этом выполняется разблокировка EMB, затем его освобождение и сброс в нуль поля Allocated структуры XPtr. При этом размер EMB указывать нет необходимости, так как драйвер самостоятельно ведет учет всех выделяемых EMB. Возможная ошибка игнорируется.
 
procedure FreeXMS( var XPtr : TXMSPtr );
begin
UnlockEMB( XPtr.Handle );
FreeEMB( XPtr.Handle );
XPtr.Allocated := 0;
end;
 
Теперь мы подобрались непосредственно к функциям пересылки данных.
Процедура MoveMemToXMS копирует четное количество Count байт из нижней DOS-памяти Buf (источник) в EMB, представленный структурой Dst (получатель), по смещению DstOffs байт относительно начала EMB. Возможная ошибка игнорируется.
 
procedure MoveMemToXMS( Dst : TXMSPtr; var Buf; DstOffs, Count : longint );
begin
with EMBCopy do begin
         Counter := Count;
         SrcHandle := 0;
         SrcPtr.AsPtr := @Buf;
         DstHandle := Dst.Handle;
         DstPtr.AsInt := DstOffs;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then;
end;
 
Процедура MoveXMSToMem копирует четное количество Count байт из EMB, представленного структурой Src (источник), со смещением SrcOffs байт относительно начала EMB в нижнюю DOS-память Buf (получатель). Возможная ошибка игнорируется.
 
procedure MoveXMSToMem( Src : TXMSPtr; var Buf; SrcOffs, Count : longint );
begin
with EMBCopy do begin
         Counter := Count;
         SrcHandle := Src.Handle; 
         SrcPtr.AsInt := SrcOffs;
         DstHandle := 0;
         DstPtr.AsPtr := @Buf;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then;
end;
 
Процедура MoveXMS копирует четное количество Count байт из EMB, представленного структурой Src, со смещением SrcOffs байт в EMB, представленный структурой Dst со смещением DstOffs байт. Возможная ошибка игнорируется.
 
procedure MoveXMS( Src, Dst : TXMSPtr; SrcOffs, DstOffs, Count : longint );
begin
with EMBCopy do begin
         Counter := Count;
         SrcHandle := Src.Handle;
         SrcPtr.AsInt := SrcOffs;
         DstHandle := Dst.Handle;
         DstPtr.AsInt := DstOffs;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then;
end;
 
Пришло время продемонстрировать на практике, как вся эта кухня работает ;o)
Для этого объявим XMS-указатели Ptr и Ptr2, для которых будет выделено по 1.5Мб на каждый (а кого нам стесняться? ;o) ).
После того, как нам удалось заполучить два EMB, выделяем память для динамической переменной Buf. В строку s1 заносим текст, который затем будем копировать куда не лень, а строку s2 делаем пустой.
 
После такой, казалось бы, громоздкой прелюдии, приступаем к перемещению данных. Сперва копируем образ строки s1 в Buf^, затем из Buf^ эти же данные копируем в EMB, представленный структурой Ptr. Потом следует наглядный пример, как можно копировать данные из одного EMB в другой EMB. После этого копируем данные из EMB, представленного структурой Ptr2, в строку s2 и отображаем ее содержимое на экран. У меня все сошлось, а у вас? ;o)
 
До этих пор было продемонстрировано копирование с нулевым смещением относительно начала EMB. Но бывает необходимость, когда данные следует копировать не с самого начала EMB, а с определенной позиции в нем. Тогда пригодится фрагмент примера, который копирует из EMB, представленного структурой Ptr, последние 8 символов в строку s3, после чего производится установка элемента строки s3[0], отвечающего за длину строки, и содержимое строки отображается на экране.
 
И в качестве последнего штриха освобождаем всю ранее полученную память. Вот собственно и код примера:
 
{$G+}
Uses XMS;
 
type TBuf = array [0..1000] of byte;
 
var Ptr, Ptr2 : TXMSPtr;
       s1, s2, s3 : string;
       SOffs : integer;
       Buf : ^TBuf;
 
begin
{ определяем тип центрального процессора }
 case TestX86 of
   0: s1 := 'CPU 8086';
   1: s1 := 'CPU 80286';
 else s1 := 'CPU 80386 or higher';
 end;
Writeln( s1 );
 
{ определяем текущий режим работы процессора }
if PMode then Writeln( 'CPU Mode: Protected (V86).' )
   else Writeln( 'CPU Mode: Real.' );
 
{ проверяем наличие драйвера в памяти }
if not InitXMS then begin
   Writeln( 'Error: XMS driver not found.' );
   halt;
   end;
 
{ проводим ревизию свободной XMS памяти }
Writeln( MemXMSAvail, ' kbytes' );
 
{выделяем 1500 Кб для нашего буфера в XMS-памяти}
if GetXMS( Ptr, 1500 )<>0 then begin
   Writeln( 'Error: Not enough memory.' );
   halt;
   end;
 
{ то же самое для Ptr2 }
if GetXMS( Ptr2, 1500 )<>0 then begin
   Writeln( 'Error: Not enough memory.' );
   halt;
   end;
 
{ динамический буфер в DOS-памяти }
GetMem( Buf, SizeOf( TBuf ) );
 
{ инициализируем строки перед пересылкой данных }
s1 := 'Hello friends!';
s2 := '';
Writeln( 's1= ', s1 );
 
{ копируем строку в Buf^ }
move( s1, Buf^, 16 );
 
{ так просто выглядит копирование образа строки из Buf^ в EMB }
MoveMemToXMS( Ptr, Buf^, 0, 16 );
 
{ так же просто копировать данные из одного EMB в другой }
MoveXMS( Ptr, Ptr2, 0, 0, 16 );
 
{ и не менее просто получить данные из EMB в строку }
MoveXMSToMem( Ptr2, s2, 0, 16 );
 
{ Ну что у нас получилось? Выглядит неплохо }
Writeln( 's2= ', s2 );
 
SOffs := 7;
{ копируем окончание строки из EMB с указателем Ptr, начиная со смещения SOffs, в строку s3 }
MoveXMSToMem( Ptr, s3[1], SOffs, 8 );
 
{ устанавливаем новую длину строки }
s3[0] := char( 8 );
 
{ Проверяем результат }
Writeln( 's3= ', s3 );
 
{ Освобождаем выделенные ранее EMB и проверяем освобождение ресурсов }
FreeMem( Buf, SizeOf( TBuf ) );
FreeXMS( Ptr );
FreeXMS( Ptr2 );
Writeln( MemXMSAvail, ' kbytes' );
end.
 
Теперь, я думаю, что на счет длинных пересылок данных все ясно. Правда, на веку программиста встречаются разные задачи, и может статься так, что в EMB будет целиком размещен, например, файл BMP (Windows BitMap) с его заголовком, и возникнет необходимость прочесть лишь отдельные поля заголовка, например, поля (по два байта) ширины и высоты графического образа. Ну не копировать же все 54 байта (ровно столько занимает заголовок BMP) по такому случаю, да и каждый раз готовить переменную-получатель такой длины не целесообразно. Гораздо проще читать по два байта прямо из EMB, правда если такие манипуляции будут выполняться в цикле многократно, то на максимальную производительность рассчитывать не стоит.
 
Для чтения слова из EMB со смещением Offs байт, составим функцию ReadXMSWord, возможную ошибку игнорируем.
 
function ReadXMSWord( Src : TXMSPtr; Offs : longint ) : word;
var W : word;
begin
with EMBCopy do begin
         Counter := 2;
         SrcHandle := Src.Handle; 
         SrcPtr.AsInt := offs;
         DstHandle := 0;
         DstPtr.AsPtr := @W;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then;
ReadXMSWord := W;
end;
 
Для записи слова W в EMB послужит процедура WriteXMSWord.
 
procedure WriteXMSWord( Dst : TXMSPtr; Offs : longint; W : word );
begin
with EMBCopy do begin
         Counter := 2;
         SrcHandle := 0; 
         SrcPtr.AsPtr := @W;
         DstHandle := Dst.Handle;
         DstPtr.AsInt := offs;
         end;
if CopyEMB( ofs( EMBCopy ) )<>0 then;
end;
 
Основываясь на двух предыдущих подпрограммах составим еще две подпрограммы для “ленивых”, то бишь на случай пересылки побайтно. Тогда функция ReadXMSByte читает байт со смещением Offs байт от начала EMB.
 
function ReadXMSByte( Src : TXMSPtr; Offs : longint ) : byte;
var w : word;
begin
w := ReadXMSWord( Src, Offs);
ReadXMSByte := byte( w );
end;
 
В свою очередь, процедура WriteXMSByte заносит байт B в EMB со смещением Offs байт.
 
procedure WriteXMSByte( Dst : TXMSPtr; Offs : longint; B : byte );
var w : word;
begin
w := ReadXMSWord( Dst, Offs );
w := (w and $0ff00) or B;
WriteXMSWord( Dst, Offs, w );
end;
 
Аналогичным образом, коим построены подпрограммы ReadXMSWord и WriteXMSWord, можно составить подпрограммы ReadXMSLong и WriteXMSLong для пересылки данных типа Longint, при этом счетчик пересылаемых байт должен быть равен четырем.
  
Чтобы понять, как эти функции работают, объявите глобальную переменную J : integer и добавьте в предыдущий пример перед кодом освобождения ресурсов следующий код:
 
for j := 1 to Length( s1 ) do
      Write( char( ReadXMSByte( Ptr, j ) ) );
 
  

А на последок я скажу…

  
Помимо способа пересылки данных при помощи функции CopyEMB есть еще один способ копирования данных, через прямой доступ к памяти EMB. Его можно осуществить посредством загрузки нулевого селектора в любой из сегментных регистров DS или ES (кроме FS и GS, которые появились в i80386), и обращения к памяти по смещению в любом 32-битном регистре из EAX, EBX, ECX, ESI, EDI, EBP. Второй способ является наиболее производительным (в два и более раз), но имеет один существенный недостаток, а именно то, что такой способ будет работать только если процессор работает в реальном режиме, то есть когда линейный 32-битный адрес EMB соответствует его физическому адресу. Если же процессор работает в режиме V86 (при EMM386 или под Windows), то ни EMM386, ни Windows не захотят вернуть линейный адрес EMB. К тому же, попытка обратиться к памяти по смещению в 32-битном регистре превышающему 64К ($FFFF) повлечет за собой прерывание, а точнее, исключительную ситуацию нарушения защиты, под номером $0D. Все дело в том, что исключительная ситуация (сбой) обрабатывается процессором не так как обычное прерывание. То есть адрес возврата из обработчика сбоя (помещенный в стек) указывает на команду, сгенерировавшую данный сбой, а не на команду, следующую за ней. Сам же обработчик прерывания с номером $0D состоит из единственной машинной команды IRET. Таким образом, управление возвращается команде, вызвавшей сбой, и компьютер зацикливается (зависает).
 
Поэтому, несмотря на некоторую медлительность функции CopyEMB, все же следует использовать ее и подпрограммы, работающие через нее, которые были описаны выше. Это позволит создавать программы, стабильно и успешно работающие как в чистом MS-DOS (в реальном режиме процессора) и под драйвером EMM386 (режим V86 процессора), так и в сеансе MS-DOS под Windows.
 
А нам с Паскалем море по колено, да и горы по плечо! ;O)
 

Литература

1.     Диалоговая справочная система Norton Guide.
2.     Д-р Джон М. Гудмэн. Управление памятью для всех – К.:Диалектика, 1996. – 520 с.
 
Продолжение следует…
 
© Владислав Демьянишин
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ.
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 28): 640К для Паскаля не предел 2 (продолжение)
 
 
 
 
 
 
На главную страницу На предыдущую страницу На начало страницы