Bagaglio Pesante - by Dreadnaut (c) 2002

-- Come portarsi dietro files. Scritto un sabato pomeriggio che la spiaggia no, Calcolo Numerico neanche -- ver 0.2
Capita spesso (non so' se volentieri) di dover accompagnare altri file ai nostri eseguibili, a volte semplicemente file e font BGI, altre volte dati. Meno spesso, ma non di rado, vorremo che questi file non si perdessero per il file system, ma possibilmente facessero parte dell'eseguibile stesso. Come portarceli dietro ?

-- Piccoli file (fino a 64kb) --

Assieme al TP7 viene un piccolo e malvagio tool chiamato binobj, che nessuno degna mai di uno sguardo ma sta' li in \bin... questo programma permette di trasformare un file qualunque (per tipo, non per dimensioni...) in un file OBJ, cioe' `oggetto', che puo' essere linkato nell'eseguibile utilizzando la direttiva {$L <nomefile>} [ da non confondere con {$L +/-} ] In teoria $L andrebbe usato per inserire blocchi eseguibili binari (a.e. procedure scritte in asm), ma funziona benissimo anche per i files di dati ! In pratica si fa' cosi': 1) binobj <filedati> <fileoggetto.OBJ> <oggetto>

con questo comando convertiamo il nostro file di dati (speranzosamente piccolo) in un file oggetto con estensione OBJ. Dato che in teoria il binobj e' fatto per essere applicato a file contenenti codice, ci fornisce anche il terzo parametro, che sara' l'identificatore con cui faremo riferimento al nostro file all'interno del programma.

2) procedure <oggetto>; far; external; {$L <fileoggetto.OBJ>}

nel nostro codice linkiamo il file creato e prepariamo un riferimento ad esso: <oggetto> sara' il nostro identificatore, e con @<oggetto> otterremo la posizione in memoria dell'oggetto stesso, al quale potremo accedere come facciamo per qualsiasi variabile in memoria. Ovviamente non ci sono controlli sui puntatori, quindi superare la dimensione del nostro file ci portera' dritti dritti a leggere (o peggio ancora a scrivere) in mezzo al codice del programma.

[--- esempio: prendiamo un file info.txt di dimensione <size> e lo trasformiamo in info.obj specificando che l'oggetto creato si chiami INFOTEXT. Questo programma mostrera' l'intero contenuto del file che adesso si trova all'interno dell'eseguibile... inserendo un qualche `marcatore' alla fine del file txt si potrebbe rendere il programma indipendente da <size>
    {$X+} { per l'es mi servono le operazioni sui puntatori }
    procedure INFOTEXT; far; external;
    {$L info.obj}

    var
     c: ^char;
     n: word;

    begin
     c := @INFOTEXT;
     for n := 1 to <size> do
      begin
       write(c^);
       inc(c);
      end;
    end.
!! tentare di eseguire la procedura INFOTEXT !! !! piantera' sicuramente qualunque programma !! ---] I files linkati in questo modo entreranno a far parte del segmento *codice* del modulo (programma principale o unit) in cui sono linkati, senza influire percio' sullo spazio riservato ai dati... ovvio che se il programma e' molto complesso si rischia dal lato opposto di riempire il segmento codice e beccarsi a link-time un bel "Error 48: code segment too large" -- Font e driver BGI -- Questi file sono un caso particolare di file piccolo, in quanto la unit graph ci mette a disposizione due procedure per portarceli dietro ed utilizzarli senza doverli estrarre: RegisterBGIDriver e RegisterBGIFont. Utilizzando binobj possiamo linkare il driver/font che ci interessa allo eseguibile e `registrarlo' a run-time: verra' restituito un intero da passare alla InitGraph/SetTextStyle per fare riferimento ad esso, oppure la costante grInvalidDriver/Font se il file non e' di tipo corretto. L'help del TP7 offre gia' qualche bella e densa pagina su queste due funzioni e in piu' nella dir \examples\bgi c'e' un esempio completo (bgilink.pas) dal quale prendere spunto [e codice], quindi mi sembra inutile ricopiarli qua :-) -- Grandi file (piu' di 64kb) -- Tormentati dal problema dei segmenti e dei 64kb, non possiamo infilarli nel codice, ne linkarli in alcun modo che ne preveda il caricamento in memoria. Quindi non possiamo metterli nell'eseguibile... ma cos'e' un eseguibile ? Il formato EXE e' un'evoluzione dei file COMpilati: a differenza di questi un EXE non si compone semplicemente di codice che puo' venir caricato in blocco ed eseguito, ma contiene un header che ne specifica le richieste, il metodo di caricamento in memoria e le dimensioni. Le `dimensioni' ? eh si ! "offset 04-05: Number of blocks in the file that are part of the EXE file" E se noi aggiungessimo bytes all'eseguibile e non cambiassimo l'header ? L'effetto e' che al caricamento soltanto la parte `prevista' dell'eseguibile viene effettivamente copiata in memoria, mentre il resto rimane li, su disco e non viene considerato. File grandi (e per grandi intendo GRANDI a piacere) possono venir comodamente accodati all'eseguibile senza pregiudicarne il funzionamento: abbiamo trovato un modo per portarci dietro dati ! - Problemi (e per quanto possibile, soluzioni) a) accodare i dati all'eseguibile beh questo e' facile... potremmo scrivere da noi un programma che lo faccia, ma il semplice comando copy ci offre gia' questa feature: copy /b filea + fileb filec costruisce filec saldando assieme (con /b se sono file binari) filea e fileb b) accedere all'eseguibile per leggere i dati anche questo non e' troppo difficile: ParamStr(0) ci ritorna il nome dell' eseguibile, che possiamo aprire come un qualsiasi file. La cosa piu' fastidiosa e' pero' che i dati si trovano in fondo all'eseguibile, ma per poterci accedere dobbiamo sapere quanto sia grosso l'exe senza dati e codificarlo nell'exe stesso... cosi' ci mordiamo la coda e siamo costretti a mettere una costante, compilare, poi modificarne il valore con la dimensione dell'exe ottenuto, ricompilare... e questo ad ogni modifica del sorgente... noiosetto :-) Una soluzione e' lavorare al contrario sull'eseguibile: aggiungere un altro header `in fondo' che contenga informazioni su come i dati sono attaccati all'exe, ma sempre facendo riferimento alla fine del file invece che all'inizio. L'implementazione base di questo metodo e' molto semplice: basta scrivere in fondo ai dati la dimensione dei dati stessi, e poi saldarli all'exe: qualunque sia la dimensione dell'eseguibile (anche zero) e possibile accedere ai dati con relativa facilita'. +---------------------+--------------------------------------------+-+ | | | | | Eseguibile | Dati ||| | | ||| +---------------------+--------------------------------------------+-+ | | || | | || +-- offset: 0 +-- <exesize> <exesize+datasize> --+| Inizio exe Fine exe - Inizio dati Fine dati | Inizio header | | Header: 4 byte per memorizzare <datasize> ----+ Il nostro programma aprira' se stesso come file non tipato e record-size = 1 [ var f: file; ] assign ( f, ParamStr(0) ); reset ( f, 1 ); si posizionera' in fondo per leggere la dimensione dei dati [ var datasize: longint; ] seek ( f, filesize(f) - 4 ); blockread ( f, datasize, 4 ); e a questo punto sapra' dove si trova l'inizio dei dati (e cioe' la dimensione dell'eseguibile) [ var datastart: longint; ] datastart := filesize(f) - datasize - 4; Cosi' risulta anche possibile lasciare i dati `fuori' dall'eseguibile mentre si sta' lavorando su di esso semplicemente mettendo <nomefile> al posto di ParamStr(0). Soltanto al momento della `distribuzione' del programma lo si modifica in questo senso, poi si compila, si saldano i dati e automaticamente il progr. si `riadatta' alla nuova dimensione del file `ospite' senza ulteriori modifiche del codice. Mettendo un header piu' complesso in fondo al file (a.e. con una stringa di identificazione ) si potrebbe automatizzare la cosa: if <c'e' l'header in fondo all'exe> then <apri questo in lettura> else <apri un altro file esterno> Poi si puo' ancora estendere in altri cento modi: un finto filesystem dei dati, crittazione, compressione... beh, tutto quel che si vuole :) [ un paio d'anni fa' ho sviluppato una libreria in C per questo formato che ho chiamato backpak (da buon informatico malato, un gioco di parole fra zaino, back perche' parte dal fondo e i file pak di Quake da cui predevo spunto), se a qualcuno interessa non e' il miglior esempio di codice, pero' fa il suo dovere... ] { -------------------------------------------------------------------------- } Topic nell'help del TP7 - RegisterBGIFont(), RegisterBGIDriver(), InitGraph() - $L (Link Object File Directive) - $X (Extended Syntax Switch) Link & Search utili ( ovvero: "Google e' tuo amico" ) - "exe file format" (il `backpak' e il discorso del saldare dati in fondo agli eseguibili valgono sia per eseguibili MZ che per PE) - "Linkable Intel Object" (i files .OBJ) { -------------------------------------------------------------------------- } -eof- 02/06/02 - (c) 2002, Dreadnaut production Il contenuto di questo documento e' da considerarsi esclusivamente invenzione dell' autore, ogni riferimento a cose o persone realmente esistenti e' puramente casuale e non voluto.