int21h

Lepší práce s paměťovými médii

Asi si říkáte, proč probírat takové věci. Všichni přeci víme, že na disk se přistupuje přes BlockRead a BlockWrite (je snad ještě někdo, kdo to čte po 1 bytu?!?), v paměti se pracuje s GetMem (popř. New) a FreeMem (atd.), a na CD-ROM se přeci přistupuje stejně jako na obyčejný disk (HDD). No, možná pro Vás budu objevovat Ameriku, ale možná, že se také dozvíte něco nového. Sice jsem psal, že zvukovými kartami končím, ale ještě mne napadlo něco, o čem bych mohl napsat. Tak do toho... Následující kódy by měly fungovat jak v TP7 (po zmenšení některých array či GetMem), tak i ve FP. Pokud zde najdete chybu, tak si ji prosím opravte. Mým cílem není napsat Vám kompletní jednotky, ale naučit Vás, jak si je udělat sami... opravou chyb se také hodně naučíte.

Urychlení a zjednodušení práce s diskem

Nemá cenu se zabývat udržováním několika bloků najednou, např. několika částmi stejného souboru, protože to nám zaručuje správce diskové cache pod Windows nebo SmartDrive (či jiný) pod DOSem. Pokud vyžadujete mít v paměti několik bloků současně, tak použijte naši metodu a daná data si vždy někam uschovejte. Nám tedy stačí, když si uvědomíme, co často děláme a jak si to zjednodušit. Naším hlavním cílem je, abychom se nemuseli starat o správu souboru, a mohli z něj číst třeba jen po jednom bytu, aniž bychom tím zpomalovali aplikaci.

Napíšeme si tedy proceduru, která bude využívat svůj vlastní buffer o velikosti 32 kB, s tím, že pokud budeme vyžadovat nějaká data, procedura načte ze souboru celý blok (pokud navíc soubor ještě není otevřen, zajistí jeho otevření a zavření případného předchozího) o velikosti 30 kB od požadované pozice (a 2 kB před současnou pozicí, kdyby se bylo potřeba vracet) a z něj teprve poskytuje data. Můžeme tedy žádat i jen jeden byte, a přesto bude práce s ním rychlá. Navíc procedura zajišťuje i spožděný zápis, tedy můžeme data zapisovat i a číst několikrát po sobě, a přesto se zapíší až když se bude zavírat soubor nebo měnit blok dat (zápis by šel optimalizovat, kdybychom si podobně jako u banků ve VESA udržovali interval offsetů v bufferu, kam se zapisovalo a pak uložili jen tato data, ale nebudeme si to komplikovat - procedura by neměla být tak složitá, aby její zpracování nezdrželo počítač déle než zápis těch pár kB dat na disk navíc). Procedura pracuje jen s netypovými soubory (prakticky kterýkoliv), a když si daný kód napíšete jako jednotku, tak se sama nainstaluje při startu Vašeho programu a také sama skončí, když Vy ukončíte svůj program:

unit FastFile;
{$I-}	{...kdo to ještě neví}

interfrace

type	TJmeno = string[8+1+3];

{vrací chybový kód:
	0	OK
	1	OK, dosažen konec souboru
	2	soubor nelze otevřít
	3	chyba při čtení dat z disku (nebo za koncem souboru)
}
function CtiSoubor(Jmeno : TJmeno; var Buffer : array of byte; Pozice,Pocet : longint) : byte;

{0 = OK, 4 = nelze založit nový soubor, 5 = chyba při zápisu dat na disk}
function ZapisData(Jmeno : TJmeno; var Buffer : array of byte; Pozice,Pocet : longint) : byte;
{může přidávat data na konec souboru (po 32-2 kB blocích), ale ne za konec}
{pokud potřebujete přidat méně než 30 kB, požadujte nejprve data na začátku souboru
 a pak nahrajte blok NOVAVEL-30 kB, počítač načte blok <=NOVAVEL-32 kB a při zápisu
 zapíše 32 kB tak, že velikost souboru bude ta Vámi požadovaná}

implementation

const	VelBufferu = 32768;

var	DiskBuffer : record
			Prostor : pointer;	{kde jsou uložena naše data}
			Soubor : TJmeno;	{aktuální soubor, '' = žádný, zavřený}
			Handle : file;		{ukazatel na soubor}
			Offset : longint;	{akt.pozice bufefu v souboru}
			Vel : longint;		{velikost celého souboru}
			PocetDat : longint;	{počet skutečně přeč./zaps.dat}
			Zapis : boolean;	{bylo zapisováno?}
		     end;
	Cesta : string;				{kde jsou uloženy soubory}
{pokud změníte velikost souboru na menší pomocí Seek a Truncate, musíte provést:
	DiskBuffer.Vel := FileSize(Handle)}

function CtiSoubor(Jmeno : TJmeno; var Buffer : array of byte; Pozice,Pocet : longint) : byte;
var	PuvRezim : byte;
	PoziceBuf : longint;
	AktVel : longint;
begin
 with DiskBuffer do
 begin
  if Soubor <> Jmeno then	{chceme pracovat s jiným souborem}
   if Soubor <> '' then
   begin
    Close(Handle);
    if IOResult <> 0 then begin end;
    Soubor := '';
   end;
  if Soubor = '' then		{ještě nebyl přiřazen}
  begin
   if Cesta[Length(Cesta)] = '' then Delete(Cesta,Length(Cesta),1);
   Assign(Handle,Cesta+''+Jmeno);
   PuvRezim := FileMode;
   FileMode := 0;		{pouze pro čtení - kdyby měl bit ReadOnly}
   Reset(Handle,1)
   FileMode := 2;		{i pro zápis; pokud je RO, vrátí BW chybu později}
   if IOResult <> 0 then
   begin
    CtiSoubor := 2;
    Exit;
   end;
   Vel := FileSize(Handle);	{zjistíme velikost}
   Offset := FilePos(Handle);	{aktuální pozice = 0}
   Zapis := False;
   Soubor := Jmeno;		{neotevře znovu soubor v ABCx, pokud je již otevřen XYx}
  end;  
  PoziceBuf := 0;		{kam zapisujeme do externích dat}
(* --- *)
  while Pocet > 0 do		{dokud je co číst, čteme}
  begin
   {máme již požadovaná data načtená?}
   if not ((Pozice >= Offset) and (Pozice < Offset+VelBufferu)) then
   begin			{ne, musíme je načíst}
    if Pozice >= Vel then
    begin
     CtiSoubor := 3;		{překročili jsme konec souboru}
     Exit;
    end;
    if Pozice > 2048 then	{nastavíme počátek bufferu v souboru}
     Offset := Pozice-2048 else
      Offset := 0;
    Seek(Handle,Offset);	{skočíme na danou pozici a přečteme soubor}
    BlockRead(Handle,Prostor^,VelBufferu,PocetDat);
    if (IOResult <> 0) or (PocetDat = 0) then
    begin
     CtiSoubor := 3;
     Exit;
    end;
    Zapis := False;
   end;				{poskytneme data}
   if Pozice+Pocet >= VelBufferu then AktVel := VelBuffer-Pozice
    else AktVel := Pocet;	{kolik dat budeme nyní přenášet}
   Move(Ptr(Seg(Prostor^),Ofs(Prostor^)+Pozice-Offset)^,Buffer[PoziceBuf],AktVel);
   Inc(Pozice,AktVel);		{posuneme pozici v souboru, kdyby bylo více dat}
   Inc(PoziceBuf,AktVel);
  end;				{podáme hlášení o výsledku konce souboru}
(* --- *)
  if Pozice = Vel then CtiSoubor := 1 else CtiSoubor := 0;
 end;
end;

function ZapisData(Jmeno : TJmeno; var Buffer : array of byte; Pozice,Pocet : longint) : byte;
begin
 with DiskBuffer do
 begin
  if Soubor <> Jmeno then
   if Soubor <> '' then
   begin
    Close(Handle);
    if IOResult <> 0 then begin end;
    Soubor := '';
   end;
  if Soubor = '' then
  begin
   if Cesta[Length(Cesta)] = '' then Delete(Cesta,Length(Cesta),1);
   Assign(Handle,Cesta+''+Jmeno);
   PuvRezim := FileMode;
   Reset(Handle,1)
   if IOResult <> 0 then
   begin
    Rewrite(Handle,1);		{pokud nejde otevřít, zkusíme ho znovu založit}
    if IOResult <> 0 then
    begin
     ZapisData := 4;
     Exit;
    end;
   end;
   Vel := FileSize(Handle);
   Offset := FilePos(Handle);
   Zapis := False;
   Soubor := Jmeno;
  end;  
  PoziceBuf := 0;		{odkud čteme z externích dat}
(* --- *)
  while Pocet > 0 do
  begin
   if not ((Pozice >= Offset) and (Pozice < Offset+VelBufferu)) then
   begin
    if Zapis then		{aktuální blok byl změněn, musí se zapsat}
    begin
     Seek(Handle,Offset);
     BlockWrite(Handle,Prostor^,VelBufferu,PocetDat);
     if (IOResult <> 0) or (PocetDat = 0) then
     begin
      ZapisData := 5;
      Exit;
     end;
    end;
    if Pozice > Vel then	{na konec souboru přidávat můžeme}
    begin
     ZapisData := 3;
     Exit;
    end;
    if Pozice > 2048 then
     Offset := Pozice-2048 else
      Offset := 0;
    Seek(Handle,Offset);	{načteme data kvůli opětovnému zápisu}
    BlockRead(Handle,Prostor^,VelBufferu,PocetDat);
    if (PocetDat <> Vel-Offset) and
       (Pocet <> VelBufferu) then
     begin
      ZapisData := 3;
      Exit;
     end;
   end;
   if Pozice+Pocet >= VelBufferu then AktVel := VelBuffer-Pozice
    else AktVel := Pocet;
   Move(Buffer[PoziceBuf],Ptr(Seg(Prostor^),Ofs(Prostor^)+Pozice-Offset)^,AktVel);
   Inc(Pozice,AktVel);
   Inc(PoziceBuf,AktVel);
   Zapis := True;		{tento blok dat byl změněn}
  end;
(* --- *)
  ZapisData := 0;
 end;
end;

var	KonecObsl : pointer;

{ukončíme naši jednotku}
procedure KonecFR; far;
begin
 ExitProc := KonecObsl;
 if (DiskBuffer.Jmeno <> '') and DiskBuffer.Zapis then
 begin					{zapíšeme případná zbývající data}
  Seek(Handle,Offset);
  BlockWrite(DiskBuffer.Handle,DiskBuffer.Prostor^,
             DiskBuffer.VelBufferu,DiskBuffer.PocetDat);
  Close(DiskBuffer.Handle);
  if IOResult <> 0 then;
 end;
 FreeMem(DiskBuffer.Prostor,VelBufferu);
end;

{automatická instalace při startu programu}
begin
 KonecObsl := ExitProc;
 ExitProc := @KonecFR;
 GetMem(DiskBuffer.Prostor,VelBufferu);	{to snad má dneska každý}
 FillChar(DiskBuffer,SizeOf(DiskBuffer),0)
 Cesta := '';
end.

Urychlení alokace paměti

Tento článek se nás netýká, pokud nám stačí paměť, kterou získáme přes VAR. Pokud ovšem potřebujete dynamickou správu paměti, jistě na ni používáte GetMem. Pokud alokujete jeden, dva bloky, nemá cenu, abyste používali následující příklad. Pokud však během programu často alokujete malé bloky paměti a zase je uvolňujete, je zdlouhavé používat neustále GetMem, neboť tato procedura je totiž velice pomalá! Vyřešíme to po svém. Necháme naši jednotku, aby alokovala velký blok paměti (v TP7 je to omezeno na 65520 bytů, takže to trochu postrádá smysl, ale pokud děláte např. spojový seznam s ukazateli, kde každá položka má pár (desítek) bytů, bude se to i tak hodit), a v něm bude provádět operace s pamětí. Ve Free Pascalu by se dalo zařídit, aby Váš buffer mohl dynamicky růst, ale jak říkám, nebudeme si komplikovat život.

Funkce podporují i fragmentaci, tj. budou obsazovány všechny volné bloky od začátku bufferu, i když se jedná jen o "díry". Můžete si doplnit defragmentaci, kdy budete volné bloky přesouvat na konec a ty plné na začátek, kdy Vám stačí pouze měnit ukazatel na blok (a data v nich, samozřejmě). Funkce Vám vrací ukazatel na data, kterým můžete číst data normálně přes Bloky[Handle].Data^, ovšem pokud jste alokovali více než je min.velikost bloku, a paměť je fragmentovaná, můžete bez obav přečíst jen velikost prvního bloku (což je v našem případě 16 kB). Aby toto fungovalo i mimo jednotku, musíte strukturu BLOKY z VAR přesunout do INTERFACE části (můžete si udělat i vlastní funkci, které když předáte aktuální blok (ne handle), tj. ten, na nějž ukazuje POINTER (pokud je první), tak Vám vrátí pointer na následující blok stejného vlastníka (Seg(Buffer^):Ofs(Buffer^)+DalsiBlok*VelBloku), tj. můžete číst dalších 16 kB; pokud už byl toto poslední blok (došlo se na konec seznamu), vrátí NIL (a namísto bloku, který jste jí předali, vrátí číslo bloku, kterému patří vrácený ukazatel - toto číslo pak použijete zase jako parametr pro další hledání). Takto můžete najít všechny bloky. Funkci zjednodušíte pátrání, když do RECORDu zavedete položku PrvniBlok, což bude číslo bloku (dosadí funkce ALOKUJ) ve VOLNEBLOKY, na který ukazuje pointer - od něj začnete hledat další bloky, které pak použijete zase jako výchozí pro další hledání). Můžete však využít vestavěné funkce, které načtou/uloží data z/do Vašeho bufferu do této paměti korektně, i pokud jsou data fragmentovaná. Můžete si dokonce určit i offset v daném handle (v bytech).

unit FastMem;

interface

{0 = OK, 1 = nedostatek volné paměti (málo bloků), 2 = už nejsou volné handle}
function Alokuj(var Handle : word; Vel : longint) : byte;

{0 = OK, 3 = dané handle neexistuje (nebylo alokováno)}
function Uvolni(Handle : word) : byte;

{0 = OK, 3 = handle není alokované, 4 = překročen limit velikosti handle}
function Zapis(Handle : word; var Data : array of byte; Vel,Offset : longint) : byte;

{0 = OK (buf.naplněn), 3 = handle není alokované, 4 = překročen limit velikosti}
function Precti(Handle : word; var Data : array of byte; Vel,Offset : longint) : byte;

implementation

const	VelBufferu = 8*1048576;	{1024*1024 = 1 MB}
	Bloku = 64;		{kolik maximálně "pamětí" můžete alokovat; max. 65534}
				{kvůli tomu, že $ffff označuje blok bez vlastníka}
	VBloku = 512;		{na kolik bloků je paměť rozdělená}
				{pak bude možné min. alokovat VelBufferu div VBloku}
				{tj. zde 16 kB}

var	Buffer : pointer;
	Bloky : array[0..Bloku-1] of record		{seznam bloků paměti}
				      Bloku : word;	{0 = není alokované}
				      Vel : longint;
				      Data : pointer;	{pro 0 je zde NIL}
				     end;
	VolneBloky : array[0..VBloku-1] of word;	{seznam částí volné paměti}
	VolnychBloku : word;

function Alokuj(var Handle : word; Vel : longint) : byte;
var	VelBloku : longint
	PocetBloku : longint;
	i,j : word;
begin
 VelBloku := VelBufferu div VBloku;
 if Vel > VolnychBloku*VelBloku then	{málo volné paměti}
 begin
  Alokuj := 1;
  Exit;
 end;
 PocetBloku := Vel div VelBloku;
 if Vel mod VelBloku <> 0 then Inc(PocetBloku);
 for i := 0 to Bloku-1 do		{najdeme volné handle}
  if Bloky[i].Bloku = 0 then
  begin
   Bloky[i].Vel := Vel;			{obsadíme volné bloky}
   for j := 0 to VBloky-1 do
    if VolneBloky[j] = $ffff then
    begin
     VolneBloky[j] := i;
     Dec(VolnychBloku);
     Inc(Bloky[i].Bloku);
     if Bloky[i].Bloku = PocetBloku then Break;
    end;
   Alokuj := 0;				{OK}
   Exit;
  end;
 Alokuj := 2;				{žádné volné handle}
end;

function Uvolni(Handle : word) : byte;
var	i : word;
begin 
 if Bloky[i].Bloku = 0 then		{toto handle nebylo použito}
 begin
  Uvolni := 3;
  Exit;
 end;
 Uvolni := 0;
 Bloky[i].Data := NIL;			{nastavíme handle na volné}
 for i := 0 to VBloku-1 do		{uvolníme všechny bloky, které si nárokovalo}
  if VolneBloky[i] = Handle then
  begin
   VolneBloky[i] := $ffff;		{uvolníme daný blok}
   Dec(Bloky[i].Bloku);
   Inc(VolnychBloku);
   if Bloky[i].Bloku = 0 then Break;	{už jsou všechny}
  end;
end;

function Zapis(Handle : word; var Data : array of byte; Vel,Offset : longint) : byte;
var	AktVel : longint;
	VelBloku : longint;
	i : word;
	AktBlok : word;
	Zdroj : longint;
begin
 if Bloky[i].Bloku = 0 then
 begin
  Zapis := 3;
  Exit;
 end;
 if Vel+Offset > Bloky[i].Vel then	{některá data jsou mimo limit}
 begin					{nebo Offset je větší než počet bytů v handle}
  Zapis := 4;
  Exit;
 end;
 VelBloku := VelBufferu div VBloku;
 AktBlok := 0;
 Zdroj := 0;
 for i := 0 to Bloky[i].Bloku-1 do	{začneme přenášet data do bloků}
 begin
  if Vel > VelBloku then AktVel := VelBloku
   else AktVel := Vel;			{kolik nám ještě zbývá}
  Dec(Vel,AktVel);
  while Offset > VelBloku do		{najdeme první blok, od kterého vede offset}
  begin
   while VolneBloky[AktBlok] <> Handle do Inc(AktBlok);
   Dec(Offset,VelBloku);		{zároveň hledáme jen v našich blocích}
  end;
  if Offset > 0 then
  begin					{poprvé musíme zohlednit offset}
   Move(Data[Zdroj],Ptr(Seg(Buffer^),Ofs(Buffer^)+AktBlok*VelBloku+Offset)^,AktVel);
   Offset := 0;
  end
   else
    Move(Data[Zdroj],Ptr(Seg(Buffer^),Ofs(Buffer^)+AktBlok*VelBloku)^,AktVel);
  Inc(Zdroj,VelBloku);			{přesuneme data a aktualizujeme ukazatel}
  if Vel = 0 then Break;		{už není požadavek na přenos zbytku}
 end;
 Zapis := 0;
end;

function Precti(Handle : word; var Data : array of byte; Vel,Offset : longint) : byte;
var	AktVel : longint;
	VelBloku : longint;
	i : word;
	AktBlok : word;
	Cil : longint;
begin
 if Bloky[i].Bloku = 0 then
 begin
  Precti := 3;
  Exit;
 end;
 if Vel+Offset > Bloky[i].Vel then
 begin
  Precti := 4;
  Exit;
 end;
 VelBloku := VelBufferu div VBloku;
 AktBlok := 0;
 Cil := 0;
 for i := 0 to Bloky[i].Bloku-1 do
 begin
  if Vel > VelBloku then AktVel := VelBloku
   else AktVel := Vel;
  Dec(Vel,AktVel);
  while Offset > VelBloku do
  begin
   while VolneBloky[AktBlok] <> Handle do Inc(AktBlok);
   Dec(Offset,VelBloku);
  end;
  if Offset > 0 then
  begin
   Move(Ptr(Seg(Buffer^),Ofs(Buffer^)+AktBlok*VelBloku+Offset)^,Data[Cil],AktVel);
   Offset := 0;
  end
   else
    Move(Ptr(Seg(Buffer^),Ofs(Buffer^)+AktBlok*VelBloku)^,Data[Cil],AktVel);
  Inc(Zdroj,VelBloku);
  if Vel = 0 then Break;
 end;
 Zapis := 0;
end;

var	KonecObsl : pointer;

{$F+}	{far je v FP zbytečný, ale ani nepřekáží}
procedure KonecFR;
begin
 ExitProc := KonecObsl;
 FreeMem(Buffer,VelBufferu);
end;
{$F-}

begin
 KonecObsl := ExitProc;
 ExitProc := @KonecFR;
 GetMem(Buffer,VelBufferu);
 FillChar(VolneBloky,SizeOf(VolneBloky),$ff);
 FillChar(Bloky,SizeOf(Bloky),0);
 VolnychBloku := VBloku;
end;

Ovládání CD-ROM mechaniky

Nám v této kapitole nepůjde ani tak o čtení souborů z CD-ROM, protože to je možné stejně jako u HDD, ale přímo ovládání samotné mechaniky. Toužíte se naučit mechaniku vysouvat a zasouvat, či si udělat vlastní přehrávač Audio CD (za předpokladu, že máte povolený vstup CD na zvukové kartě na její výstup, viz. minulý seriál)? Tak jste tu správně. Tady bude trochu více funkcí a navíc v assembleru.

První věc, kterou musíte udělat na začátku Vašeho programu, je tzv. inicializovat CD-ROM mechaniku. Prakticky si pouze zjistíte informace od ovladače. CD-ROM totiž pracuje nezávisle na počítači, je možné ji tedy spustit (PLAY) a pak ukončit program. Ona bude hrát dál. Pokud jí budete chtít zastavit, spustíte znovu program, Init a pak STOP (ten raději proveďte 2x (příkaz, ne program) za sebou, některé mechaniky jsou na to háklivé). Nejprve budeme potřebovat vytvořit nějaké ty proměnné:

var	Pozadavek : record
			Velikost : byte;
			Podjednotka : byte;
			Prikaz : byte;
			StatusLo : byte;
			StatusHi : byte;
			Rezerva : array[0..7] of byte;
			Zbytek : array[0..242] of byte;
		    end;
	Informace : record
			Verze : word;
			Pocet : word;
			Mechanika : word;
			MinStop : byte;
			MaxStop : byte;
			Prvni : longint;
			Posl : longint;
		    end;
	Subkanal : array[0..255] of byte;

Pokud Vám není jasný význam z jejich jmen, poznáte ho během jejich využívání. První, trochu delší funkce je již zmíněná inicializace. Ta zjistí, zda se používá správná verze ovladače (MSCDEX) a zda je mechanika CD-ROM (a připravena):

function Init : boolean; assembler;
asm
	mov	ax,$1500	{viz. ATHelp, multiplex}
	xor	bx,bx		{počet CD-ROM mechanik}
	clc			{or	ax,ax}
	int	$2f
	xor	ax,ax		{chyba}
	jc	@chyba
	mov	informace.pocet,bx
				{počet a číslo první mechaniky, 0=A, 1=B, atd.}
	mov	informace.mechanika,cx
	mov	ax,$150c
	xor	bx,bx		{verze ovladače}
	clc
	int	$2f
	xor	ax,ax
	jc	@chyba
	mov	informace.verze,bx
	cmp	bx,512		{potřebujeme verzi 2.01}
	jbe	@chyba
	mov	ax,$150b	{je to skutečně CD-ROM}
	xor	bx,bx		{můžete si vybrat i jinou, pokud jich je více}
	mov	cx,informace.mechanika
	clc			{tento bit se většinou nemusí mazat}
	int	$2f
	mov	cx,ax
	xor	ax,ax
	jc	@chyba
	cmp	bx,$adad	{značka pro MSCDEX}
	jne	@chyba
	or	cx,cx		{mechanika není CD}
	je	@chyba
	mov	pozadavek.prikaz,$d
	call	proved		{otevřeme zařízení - ne dvířka}
	xor	ax,ax
	jc	@chyba		{zjisti stav}
	mov	subkanal+0.byte,6
	call	ioinput
	xor	ax,ax
	jc	@chyba		{stav}
	mov	bl,subkanal+1.byte
	test	bl,$10		{podporuje Audio CD?}
	jz	@chyba
	inc	ax
@chyba

end;

Uf, je to za námi. Jistě jste si všimli, že tu voláme dvě jakési funkce. Ve skutečnosti jsou vlastně celkem tři, a umožňují komunikovat s danou jednotkou. První funkce vyžaduje od jednotky provedení určitého příkazu, další dvě do ní (nebo z ní) posílají určitá data (informace):

procedure Proved; assembler;
asm
	mov	ax,$1510	{požadavek na ovladač}
	mov	cx,informace.mechanika
	mov	bx,seg pozadavek
	mov	es,bx
	mov	bx,offset pozadavek
	clc
	int	$2f
	jc	@chyba
	mov	al,pozadavek.statushi
	add	al,al		{nastavíme Carry při chybě}
end;

function IOinput : word; assembler;
asm
	mov	pozadavek.prikaz,3	{IO vstup}
	mov	ax,offset subkanal	{adresa pro přenos}
	mov	pozadavek+$e.word,ax
	mov	ax,seg subkanal
	mov	pozadavek+$10.word,ax
	call	proved
end;

function IOoutput : word; assembler;
asm
	mov	pozadavek.prikaz,$c
	mov	ax,offset subkanal
	mov	pozadavek+$e.word,ax
	mov	ax,seg subkanal
	mov	pozadavek+$10.word,ax
	call	proved
end;

Teď budeme potřebovat funkci pro ukončení obsluhy ovladače, kterou zavoláte na konci svého programu, pak funkci pro reset mechaniky (ta se moc nepoužívá, ale kdybyste chtěli), a pak několik podpůrných funkcí: jedna zastavuje přehrávání, druhá ho obnovuje po pauze, třetí přepočítává čas z červené knihy na sektor a čtvrtá zahajuje přehrávání. Upozorňuji, že to jsou (kromě Konec a Reset) pouze podpůrné funkce. Můžete je volat buď přímo a nebo pomocí pozdějších funkcí, které Vám ještě zjistí, zda proběhla chyba či nikoliv:

function Konec : boolean; assembler;
asm
	mov	pozadavek.prikaz,$e
	call	proved
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

function Reset : boolean; assembler
asm
	mov	subkanal+0.byte,2
	call	iooutput
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

function StopAudio : byte; assembler;
asm
	mov	pozadavek.prikaz,$85
	call	proved
end;

function ResumeAudio : byte; assembler;
asm
	mov	pozadavek.prikaz,$88
	call	proved
end;


function Vypocti : longint; assembler;
{data vložíme do DX:AX}
asm
	mov	bx,ax	{BH=vteřiny, BL=snímky, DX=minuty}
	mov	ax,60
	mul	dx
	mov	dx,ax	{minuty * 60}
	xor	ah,ah
	mov	al,bh
	add	dx,ax	{+vteřiny}
	mov	ax,75
	mul	dx
	xor	bh,bh
	add	ax,bx	(* 75 + snímky *)
	adc	dx,0
	sub	ax,150	(* -150 *)
	sbb	dx,0	(* DX:AX=logický sektor *)
end;

function PlayAudio : byte; assembler;
asm
	mov	pozadavek,prikaz,$84	{hraj}
	mov	pozadavek+$d.byte,0	{HSB formát}
	mov	ax,informace.prvni+0.word
	mov	dx,informace.prvni+2.word
	call	vypocti
	mov	pozadavek+$e.word,ax
	mov	pozadavek+$10.word,dx
	mov	ax,informace.posl+0.word
	mov	dx,informace.posl+2.word
	call	vypocti
	mov	ax,pozadavek+$e.word
	mov	dx,pozadavek+$10.word
	mov	pozadavek+$12.word,ax
	mov	pozadavek+$14.word,dx
	call	proved
end;

Definitivní funkce najdete už zde: samozřejmě, pokud Vám nejde o chybu, můžete si předchozí funkce přepsat jako procedury, neboť z nich nezjisíte chybu. Tak to jde alespoň u STOP a RESUME. PLAY bude trochu horší. Takže nic nezkazíte, když budete používat jen tyto tři následující funkce (zvláště ta poslední je přímo lahoda):

function CDstop : boolean; assembler
asm
	call	stopaudio
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

function CDresume : boolean; assembler;
asm
	call	resumeaudio
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

function CDplay(Skladba : byte) : boolean; assembler;
asm
	mov	subkanal+0.byte,$a	{audio informace}
	call	ioinput
	jc	@chyba
	mov	al,subkanal+1.byte
	mov	informace.minstop,al
	mov	al,subkanal+2.byte
	mov	informace.maxstop,al	{poslední stopa}
	mov	ax,subkanal+3.word
	mov	informace.posl+0.word,ax
	mov	ax,subkanal+5.word
	mov	informace.posl+2.word,ax
	mov	al,skladba
	mov	subkanal+0.byte,$b	{informace o stopě}
	mov	subkanal+1.byte,al
	call	ioinput
	jc	@chyba
	mov	al,subkanal+6.byte	{řídící byte stopy}
	and	al,$d0			{vymaže COPY bit}
	cmp	al,$40			{audio nebo data?}
	je	@chyba
	mov	ax,subkanal+2.word
	mov	informace.prvni+0.word,ax
	mov	ax,subkanal+4.word
	mov	informace.prvni+2.word,ax
	call	stopaudio
	call	playaudio
	jc	@chyba
	mov	ax,1
	jmp	@konec
@chyba:	xor	ax,ax
@konec:
end;

Pokud Vám ovšem stačí jen to zmiňované (asi jste fakt skromní) zasouvání a vysouvání dvířek :-), tak i na to jsou zde funkce (pravda, pochopit celý systém asi budete muset sami, sám toho totiž o ovládání CD-ROM moc nevím):

function Zasunout : boolean; assembler
asm
	mov	subkanal+0.byte,5
	call	iooutput
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

function Vysunout : boolean; assembler
asm
	mov	subkanal+0.byte,0
	call	iooutput
	xor	ax,ax
	jc	@chyba
	inc	ax
@chyba:
end;

Kdyby Vám to nefungovalo a nevěděli jste, co s tím, nebo Vám to bylo dokonce málo, doporučuji se podívat na originál jednotku CDROM.ASM od Guenthera Klaminga. Kdybyste jí snad nenašli, tak zkuste hledat jednotku CDROM.PAS od Grega Estabrookse, která je psaná převážně v Pascalu. No, tak to bychom měli. Příště se podíváme, jak si udělat svůj vlastní bootovací "operační systém" (zatím z diskety) a pak už budu mít snad konečně volno :-)

2006-11-30 | Martin Lux
Reklamy:
„Láska vdaných žen je nejcennější na světě, manželé o tom ovšem nevědí.“ Oscar Wilde