Idáig csak előkészületeket tettünk a saját file-kezelés megírására. Most, hogy – remélhetőleg – meg van minden kedves T&T rovat Olvasónak a megfelelő elméleti alapja elkezdjük a saját file-kezelés megírását. A legkönnyebb kezelhetőség - átláthatóság, és persze a profi megoldás végett objektumból építjük fel az egész file-kezelést. Az eddig megismert file kezelő rutinok neve hasonlóan a fő objektum metódusai lesznek. Persze ha új operációs rendszerünk alapját akarná képezni a most közlendő file-kezelés el kell szomorítanom benneteket, hisz az OP rendszerek file-kezelését assembly-ben illik megírni. Másrészről nem is valósítunk meg minden DOS file-kezelő rutint lévén, hogy jó részük elavult (FCB-s utasítások), a file-írás rutinjait annak bonyolultságuk és összetettségük miatt csak nagyobb Olvasói érdeklődésre közölném (PC-XUSER@freemail.c3.hu, Subject: "T&T rovat file-írás"). Továbbá rovatunknak célja a logika, a szerkezeti felépítés közlése a túlzott részletekbe merülés nélkül, hisz nem mindenkit érdekel egyaránt az ilyen szintű rendszerprogramozás.

Mivel már tudjuk, hogy milyen elemei vannak a DOS file-kezelésének megpróbálhatjuk azt utánozni, újraírni saját magunknak, okulva a DOS hibáiból.

A korszerű objektum orientált irányzatot követve vázoljuk a megvalósítás lépéseit:

  1. DOS megvalósítás:

  2. Read From File or Device - Olvasás file-ból vagy device-ról
    AH = 3Fh
    BX = a file handle (előzőleg megnyitott file, melyhez érvényes handle tartozik)
    CX = a beolvasandó byte-ok száma
    DS:DX - buffer a beolvasáshoz
    INT  21h
    Visszatérési érték:
    Ha CF = 0 akkor sikeres volt a művelet, és:
       AX = a beolvasott byte száma  (Ha = 0 akkor EOF volt a rutin meghívásakor.)
  3. Saját Objektumos megvalósítás:

  4. / TR4sFileDOS = Object(TObject) /

    function ReadFromFile(Handle: Word; var ByteToRead: Word; var Buf; BufSize: Word): Boolean; virtual;

Láthatjuk, hogy a két megvalósítás (az eredeti DOS és a saját) között a különbség csak annyi, hogy az egyiket (a DOS-ét) assembly-ből a másikat meg simán programból hívhatjuk meg. Egyiknél sem kell azzal törődnünk, hogy hogyan és mit csinál bent magában a megszakításban, ill. az objektumban. Ha a paraméterezésnél – is – megtartjuk a 100%-os kompatibilitást akkor azok akiknek már voltak assembly-n keresztül működő DOS file-kezelő rutinjai egyszerűen a assembly betétek objektum hívásra történő kicserélésével átírhatják programjukat a saját file-kezelés használatára. Nézzük sorba mit, hogyan csinál az objektum metódusa a megszakítás mintájára. Nos ilyen megfontolás alapján kell felépíteni a fő objektumunkat.

Nem kívánom részletezni minden metódus paraméterezésének logikáját, hiszen kézenfekvő, így csak közlöm:

  PR4sFileDOS = ^TR4sFileDOS;
  TR4sFileDOS = Object(TObject)
  public {Original DOS functions}
{R} constructor Init(ADriveObj: PAbstractDrive);
    destructor  Done; virtual;
{R} procedure SelectDefaultDrive(DriveImageFile: String); virtual;               {210E}
{R} function  OpenFileByFCB(var FCB: TFCB): Boolean; virtual;  {UnOp}            {210F}
{R} function  CloseFileByFCB(var FCB: TFCB): Boolean; virtual; {  Op}            {2110}
    function  FindFirstByFCB(FCB: TFCB): Boolean; virtual; {UnOp}                {2111}
    function  FindNextByFCB(FCB: TFCB): Boolean; virtual;  {UnOp}                {2112}
    function  DeleteFileByFCB(FCB: TFCB): Boolean; virtual;{UnOp}                {2113}
{R} function  SeqReadByFCB(var FCB: TFCB): Boolean;virtual;{Op,Return data in DTA 2114}
    function  SeqWriteByFCB(FCB: TFCB): Boolean; virtual;  {Op,Expect data in DTA 2115}
    function  CreateFileByFCB(FCB: TFCB): Boolean; virtual;{UnOp,overw. if exist  2116}
    function  RenameFileByFCB(RFCB: TRFCB): Boolean; virtual;                    {2117}
{R} function  GetCurrentDrive: String; virtual;                                  {2119}
{R} procedure SetDTA(Address: Pointer); virtual;           {Disk Transfer Area    211A}
    procedure GetAllocationInformationForDefDrive(var SecClus: Byte;    {211B}
                var ByteSec: Word; var TotClus: Word; var MediaID: Byte); virtual;
    function  GetDPBForDefDrive: PDriveParameterBlock; virtual;                  {211F}
    function  ReadRandomRecByFCB(FCB: TFCB): Boolean; virtual;   {Op,Return data in DTA 2121}
    function  WriteRandomRecByFCB(FCB: TFCB): Boolean; virtual;  {Op,Except data in DTA 2122}
    function  GetFileSizeByFCB(var FCB: TFCB): Boolean; virtual; {UnOp   2123}
    procedure SetRandomRecNumbForFCB(var FCB: TFCB); virtual;    {Op     2124}
    function  RandomBlockReadByFCB(var NumbRecToRead: Word; var FCB: TFCB): Boolean; virtual;
              {Op,Return data in DTA 2127}
    function  RandomBlockWriteByFCB(var NumbRecToRead: Word; var FCB: TFCB): Boolean; virtual;{Op,Except data in DTA 2128}
    function  PraseFileNameToFCB(var FCB: TFCB; Str: String; var UnPrasedPos: Byte): Boolean; virtual; {UnOp 2129}
{R} function  GetDTA: Pointer; virtual; {212F}
    function  GetFreeDiskSpace(Drive: String; var SecClus, FreeClus, ByteClus, TotalClus: Word): Boolean; virtual; {2136}
    function  MkDIR(Name: TASCIIZ): Boolean; virtual; {2139}
    function  RmDIR(Name: TASCIIZ): Boolean; virtual; {213A}
{R} function  ChDIR(Name: TASCIIZ): Boolean; virtual; {213B}
    function  CreatFile(Name: TASCIIZ; Attributes: Word; var Handle: Word): Boolean; virtual; {213C}
{R} function  OpenFile(Name: TASCIIZ; AccessMode: Byte; var Handle: Word): Boolean; virtual;  {213D}
{R} function  CloseFile(Handle: Word): Boolean; virtual; {213E}
{R?}function  ReadFromFile(Handle: Word; var ByteToRead: Word; var Buf; BufSize: Word): Boolean; virtual; {213F}
{N} function  WriteToFile(Handle: Word; var ByteToWrite: Word; var Buf): Boolean; virtual; {2140}
    function  DeleteFile(Name: String): Boolean; virtual; {2141}
{R} function  SeekFile(Handle: Word; SeekType: Byte; var Pos: Longint): Boolean; virtual;  {2142}
{R} function  GetFilePos(Handle: Word): Longint; {2142xx} {if Return = $FFFFFFFF then It means Error !}
{*} function  GetFileAttributes(Name: String; var Attributes: Word): Boolean; virtual; {214300}
    function  SetFileAttributes(Name: String; Attributes: Word): Boolean; virtual;     {214301}
    function  DuplicateFileHandle(Handle: Word; var NewHandle: Word): Boolean; virtual;  {2145}
    function  GetCurrentDIR(Drive: Byte; var ASCIIZPath: TASCIIZ): Boolean; virtual; {2147}
{R} function  FindFirstFile(Name: TASCIIZ; Attribute: Byte): Boolean; virtual; {Return FF DataBlock in DTA  214E}
{R} function  FindNextFile: Boolean; virtual; {EXCEPT previous FF DataBlock in DTA  214F}
    function  RenameFile(OldName, NewName: TASCIIZ): Boolean; virtual; {2156}
{R} function  GetFilesTimeAndDate(Handle: Word; var Time, Date: Word): Boolean; virtual; {215700}
    function  SetFilesTimeAndDate(Handle: Word; Time, Date: Word): Boolean; virtual;     {215701}
    function  CreateTempFile(var Name: TASCIIZ {\ !}; Attribute: Word; var Handle: Word): Boolean; virtual; {215A}
    function  CreateNewFile(var Name: TASCIIZ; Attribute: Word; var Handle: Word): Boolean; virtual;        {215B}
    function  NameExpand(Name: TASCIIZ; var OutNameBuffer: TNameBuffer): Boolean; virtual; {Canonicalize filename or path 2160}
    function  FlushFileDatas(Handle: Word): Boolean; virtual; {2168}
{*} function  GetDiskSerialNumber(Disk: String; var DiskInfo: TDiskInfo_Serial): Boolean; virtual; {216900}
    function  SetDiskSerialNumber(Disk: String; DiskInfo: TDiskInfo_Serial): Boolean; virtual;     {216901}
    function  ComitFile(Handle: Word): Boolean; virtual; {216A}
{}  function  ExtendedOpenOrCreateFile(Name: String; OpenMode, Flags: Byte; Attribute: Word;
              Action: Byte; var Handle: Word): Boolean; virtual; {216C00}
  public {DOS Extensions}
    function  GetFileSize(Handle: Word): Longint;
  {...
   private {internal procs}
   ...}
end;
 

Nos idáig eljutottunk:

De ne felejtsük el, hogy NEM egy teljes OP rendszer file-kezelését írjuk meg, így meg kell adni ennek az objektumnak, hogy milyen Drive-ra legyen értelmezve az-az, hogy saját file-kezelésünket min használjuk.

Most felvetődhet a kérdés:

  1. Használhatjuk először egy IMAGE file-ra ami annyit tesz, hogy készítünk egy másolatot, pl. egy floppy lemezről szektorról – szektorra (sáv a 0. fejjel, majd 1-ő fejjel, majd sáv+1 a 0. fejjel, stb.). Ez egy olyan Image file, amit a lemezmásoló programok is készítenek. Ez a DOS számára egy kezelhetetlen 1.44 MB-os adattömeg, nem más mint a floppy-nak a byte-ok szerinti másolata. Ezt a másolatot már nem meghajtóként, hanem kutya közönséges adatként – file-ként fogja a DOS kezelni.

  2. Mi a saját file-kezelésünkkel viszont kezelhetjük már egy való meghajtóként ezt az image-file-t.
  3. A második még egyenlőre nem ajánlatos használati mód a fizikai meghajtó kezelése saját rutinjainkkal.

  4. Értem ezt arra, hogy a floppy-t ne DOS-sal, hanem saját file-kezelésünkkel olvassuk. Ezt egyenlőre a jelenlegi rutinokkal nem ajánlom, mert még nem gyorsabb mint a DOS (sőt néha lassabb).
Hogyan valósítható meg ez egy objektummal, hogy különböző médiákat kezeljünk ?

Gondolkozzunk logikusan, és objektumok szintjén !

Minden médiát, legyen az fizikai vagy egyéb: szektor szinten kezelünk ! Kell egy ős – absztrakt – objektum, mely tartalmazza az alapvető szektor szintű utasításokat. Ezen absztrakt ős objektum típust adjuk meg a file-kezelő TR4sFileDOS objektumnak, ugyanis a fő objektumnak nem kell törődnie azzal, hogy hogyan pl. olvasson be egy szektort, hanem csak meghívja a TxDrive objektum ReadSectors… metódusát. (Tehát a TR4sFileDOS objektumnak csak az a lényeg, hogy kapjon egy beolvasott szektort, a tényleges beolvasást a drive objektum végzi.) Mivel szabványosítottunk egy ős – absztrakt objektumot, az ebből származtatott utód-objektumok metódusainak ugyanaz lesz a neve, csak másképp fog működni. Lássuk a szabványosítást, azaz az őst:

  PAbstractDrive = ^TAbstractDrive;
  TAbstractDrive = Object(TObject)
  public
    LastStatus: Byte;
    CF        : Boolean; {Carray Flag}
    Drive     : Byte;
    constructor Init(ADrive: Byte);
    destructor  Done; virtual;
    function  ResetDisk(ADrive: Byte): Byte; virtual; {Ret: Status} {1300}
    function  GetStatusOfLastOperation(ADrive: Byte): Byte; virtual;  {Ret: Status} {1301}
    function  ReadSectorsIntoMemory(var Buf; ADrive, Head: Byte; Track: Word; Sector,
               SecCount: Byte; var Status, ReadedSecCount: Byte): Boolean; virtual; {1302}
    function  WriteSectors(var Buf; ADrive, Head: Byte; Track: Word; Sector,
               SecToWrite: Byte; var Status, SecWrited: Byte): Boolean; virtual; {1303}
    function  VerifySectors(var Buf; ADrive, Head: Byte; Track: Word; Sector,
               SecToVerify: Byte; var Status, SecVerified: Byte): Boolean; virtual; {1304}
    function  ReadLogicalSectorIntoMemory(var Buf; ADrive: Byte; StartSec: Longint; SecCount: Byte;
                var Status, ReadedSecCount: Byte): Boolean; virtual; {1301}
    procedure GetDriveDatas(var AFATStart, AFATSize, ARootStart, ARootSize, ADataStart: Word; var AMaxEntryPerSec: Byte;
                var ABootInf: TBootSecInforms);
    function  GetDriveNumber: Byte;
  private
  {...}
  end;

Nos íme az interface, ami a szektor szintű rutinokat tartalmazza. Azért ezt az absztrakt őst adjuk meg a TR4sFileDOS Init-jében mert az ebből származtatott összes őst elfogadja akkor majd ide paraméterének.

Tehát ha ebből az absztrakt ősből származtatunk egy objektumot mi fizikailag kezeli a lemezt:

TPhisicalDrive = Object(TAbstractDrive)

Akkor azt közvetlenül megadhatjuk a TR4sFileDOS Init-jének, hisz kompatíbilis az absztrakt őssel, s szektor metódusok itt már működnek. (TEHÁT az absztrakt őst NEM lehet az Init-be megadni hisz ez még nem egy működő objektum csak a közös ős mely a metódusneveket tartalmazza. Ha ezt tennénk Runtime error 211 elszállnánk, jelezve, hogy nem használható absztrakt objektumot használunk.)

Tehát ugyanígy használhatnánk a TAbstractDrive-ból származtatott TImageDrive-ot is.

TImageDrive = Object(TAbstractDrive)

Persze a lényeg az utód objektumok implementációjában, a virtuális metódusok kifejtésében van. Az egyik objektum pl. a TPhisicalDrive fizikai lemezkezeléssel olvas be / ír ki szektorokat az INT 13h-mal, míg a TImageDrive pedig az image-file-ból DOS file-kezeléssel olvas be egy szektornyi byte-ot. Ennek áttanulányozására ajánlom az R4s_FDOS.INT file-t. (Azért nem kívánom T...Drive objektumokat részletesen kifejteni, mert ez nem kapcsolódik közvetlenül a file-kezeléshez, és a fizikai /INT 13h/ lemezkelést meg már kiveséztük. A cél csak a felépítés elmagyarázása volt.) (Azt azért megjegyezném, hogy mindig a logikai szektorokat fog a TR4sFileDOS olvasni / írni ( ReadLogicalSectorIntoMemory() ) és nem pedig fizikailag fej/sáv/szektor...)

Tehát ezen deklarált objektumokat adjuk át a TR4sFileDOS Init-jének. Persze a T...Drive objektumoknak is van Init-je ahol meg kell adni pl. a TPhisicalDrive-nak a fizikai lemezszámot, ill. a TImageDrive-nak az a file-nevet melyben az image-file van. Íme:

/TPhisicalDrive/
constructor Init(ADrive: Byte);

A fizikai lemez – szektor kezelést megvalósító TPhisicalDrive Init-jében csak a fizikai drive számot kell megadni, ahol 00h A:, 01h, B ... 80h az első HDD, 81h a második HDD ... A másik:

/TImageDrive/
constructor Init(ADrive: String);

Ha image-ből történik a file-kezelés, (az-az már egy floppy lemezt előzőleg lementettünk szektorról-szektorra HDD-re egy file-ba) akkor a TImageDrive objektumot használjuk. Így egy virtuális meghajtót kapunk az image-file-ból a TR4sFileDOS objektum segítségével. A TR4sFileDOS-nak az Init-jébe csak megadjuk a TImageDrive-ot. Annak Init-jébe pedig a file-nevét ami a floppy-lemez image-ét tartalmazza.

Lássuk konkrét példát a TR4sFileDOS inicializálására:

Fizikai lemezkezelés:

var FDOS: PR4sFileDOS;
New(FDOS, Init( New( PPhisicalDrive, Init( a_drive_száma_pl_0 ) ) ) );
Image file-kezelés: var FDOS: PR4sFileDOS;
New(FDOS, Init( New( PImageDrive, Init( az_imagefile_neve_pl_"proba.img" ) ) ) );
Nos, hogy ilyen szépen eljutottunk idáig lássuk hogyan kell image-file-t készíteni. (Inkább leírom most, nehogy Nektek kelljen megerőltetni magatokat. J )

Image-file készítése

Egyáltalán nem egy ördöngős dolog, pár dolgot kell figyelembe vennünk:

  1. A lemezt nyílván INT 13h-mal fogom beolvasni.
  2. Logikus, hogy nem szektorról szektorra olvasom be, mert maga a hardware úgy is egy sávot olvas be egyszerre. Ezért, hogy minél gyorsabb legyen egyszerre egy sávot olvasunk be.
  3. A mérési eredmények – s a józan ész – azt diktálja, hogy a beolvasás így történjen:

  4. x. sáv a 0. Oldalról, x. sáv az 1-ő oldalról, majd a x+1. sáv a 0.-ról, és az x+1. az 1-ről, stb.
    Tehát egyik sáv az egyik fejről, majd ugyanazon sáv a másik fejről, majd a következő sáv a 0-dikról ...
Lássuk a megvalósítást:

BEGIN
  Drive:= 0; {A fizikai lemez száma 0 = A}
  Assign(F, ParamStr(2)); {És ParamStr(2)-ben van a file neve amibe mentjük az image-t}
  ReWrite(F, 1);
  GetDriveInfo(Drive, DrvInf); {A lemezinformációk lekérdezése}
  for Cylinder:=0 to DrvInf.CylinderNumber do {Egymás után olvassa a lemez sávjait}
   for Head:=0 to DrvInf.HeadNumber do        {Külnböző oldalról olvassa az azonos sávot}
   begin
     ReadDiskSector(B, Drive, Head, Cylinder, 1, DrvInf.SectorPerCylinder, i, j);
     BlockWrite(F, B, Word(DrvInf.BytePerSector)*j, int);
     GotoXY(1, 1);
     Writeln('Drive: ', Drive, ' Head: ', Head, ' Track: ', Cylinder,
             ' Readed Sector: ', j, ' ');
   end;
  Close(F);
END.
 

Használjátok egészséggel a SAVEDISK.pas forráskódot. Ezzel egész jó image-file-t lehet készíteni.

Térjünk vissza a TR4sFileDOS objektumunkhoz pár sorra. Az Init ill. Done utasításokat vegyük végig:

constructor TR4sFileDOS.Init(ADriveObj: PAbstractDrive);
var Mem, Disk: Longint;
begin
  New(FAT);

  DriveObj:=ADriveObj;
  DriveNum:=ADriveObj^.GetDriveNumber;

  SelectDefaultDrive(TheDrive);
  if Error <> 0 then begin Error:=R4sFDOS_DriveNotReady; Fail; end;
  Is12bitFAT:=HasDrive12bitFAT;
  SetDTA(Ptr(PrefixSeg, $80));
end;

procedure TR4sFileDOS.SelectDefaultDrive(DriveImageFile: String);
var
  TempW: Word;
  TempB: Boolean;
begin
  ClearAllLocalVariable;
  DriveObj^.GetDriveDatas(FATStart, FATSize, RootStart, RootSize,
                          DataStart, MaxEntryPerSec, BootInf);

  CurDIR.Sector:=RootStart;
  CurDIR.Path:=':\';
  if IOResult <> 0 then Error:=R4sFDOS_DriveNotReady
                   else if Not ReadFAT then Error:=R4sFDOS_FATToBig;
end;
 

Térjünk vissza a TR4sFileDOS.Init()-hez: Ha már az Init()-et végig nyálaztuk, akkor lássuk a destructor Done;-t is:

destructor  TR4sFileDOS.Done;
begin
  ClearAllLocalVariable;
  Dispose(FAT);
  DriveObj^.Done;
  Inherited Done;
end;

Nos mára ennyi, a TR4sFileDOS alapfelépítését, a hozzá csatolódó objektumokat – típusokat elmondtam. Legfontosabbnak a dolgok LOGIKÁjanak, felépítésének elmagyarázását tartottam, hisz eme rovatot nem a teljesen kezdők olvassák és akit érdekel annak csak egy gondolatmenet kell, és már maga is meg tudná írni. Ha valakinek összekuszálódtak volna soraim akkor kérem írjon, segítek.

Következő számunkban a tényleges file olvasásról, meg hasonló nyalánkságokról lesz szó.

Egy kérdés: Akit érdekel a Win95 – WinNT hosszú file-nevek rendszere az írjon egy levelet a PC-XUSER@freemail.c3.hu –ra, s ha elegen vagytok írhatok róla. (Nem olyan ördöngős. A FAT32 DOS 6.22 alatti olvasása, meg stb-ről lenne szó.)

A cikkhez csatolt R4sFDOS.INT segítséget nyújt a fentiek megértéséhez !

 
Bérczi László
eMail: PC-XUSER@FREEMAIL.C3.HU, Subject: "T&T rovat"
BELA@MI.STUD.PMMFK.JPTE.HU