Meccanismi di thunking nelle piattaforme Windows 3.x, 9x, NT


Nelle piattaforme Microsoft a 32 bit esiste un meccanismo che permette alle applicazioni a 16 bit di chiamare il codice a 32 bit, e in alcune (Win32s e Win32c) di eseguire del codice a 16 bit da applicazioni a 32 bit. Questo meccanismo si chiama thunking, cioè uno 'smanettamento' in grado di integrare due mondi completamente diversi. Anche se in apparenza il thunking sembra essere di poca utilità per il programmatore, in realtà esso viene eseguito dal sistema operativo più spesso di quanto si possa immaginare. Inoltre vi sono alcune attività, ad esempio l'accesso alle strutture del disco fisso, che non possono essere effettuate direttamente da applicazioni Win32. Mi pare quindi giusto esplorare in dettaglio anche questa funzionalità del sistema operativo. Nella piattaforma Win32 sono disponibili tre diversi tipi di thunking, uno per ogni ambiente di Windows, ciò assicura (a detta di Microsoft) la piena portabilità delle applicazioni Win32, ma c'è anche chi sostiene che il detto Microsoft "running anywhere" per le applicazioni WIN32 non è poi così vero. La figura 1 riassume appunto tutti i tipi di thunking disponibili nelle tre diverse piattaforme WIN32.

[Figura 1]

Dalla figura 1 appare evidente che il meccanismo di thunking è strettamente correlato con il sistema operativo che lo possiede, e ciò è in linea con la definizione che ho dato precedentemente al thunking, appunto un rude ma complicato smanettamento interno al sistema operativo. In Windows 95 è stato aggiunto il supporto al Generic Thunk, il quale permette la compatibilità con Windows NT, mentre per ovvi motivi la piattaforma Win32s (la s finale sta per subset, poiché Win32s è a tutti gli effetti un subset della Win32) è stata abbandonata al proprio destino. In figura 1 le tre piattaforme WIN32 sono elencate (da destra a sinistra) nell'ordine cronologico della loro apparizione nel mercato dell'informatica, cioè prima le Win32 di NT, poi il subset Win32s di Windows 3.x e per ultima la Win32c di Windows 95 (la c sta per Chicago, il nome in codice di Windows 95, essa venne poi rimossa per sottolineare l'integrazione tra NT e Windows 95). La figura 2 evidenzia invece il tipo di thunking supportato e come viene fornito in ogni ambiente Win32.

[Figura 2]

Passiamo velocemente in rassegna tutti i tre tipi di thunking. Il Generic Thunking permette ad una applicazione a 16 bit di caricare ed eseguire del codice contenuto in DLL a 32 bit. Questo tipo di thunking è implementato da un set di API esportate dalla libreria WOW32. Nell'architettura Win32 di Windows NT le applicazioni Windows a 16 bit sono eseguite in un sottosistema chiamato Windows On Win32 oppure Windows On Windows (WOW). Invece l'Universal Thunking permette alle applicazioni a 32 bit (che ovviamente girano in Windows 3.x grazie a Win32s) di caricare e eseguire il codice contenuto in DLL a 16 bit. E' anche possibile eseguire il codice contenuto in DLL a 32 bit da applicazioni a 16 bit sebbene ciò non sia ufficialmente supportato. Il codice che costituisce il meccanismo di thunking è contenuto nelle DLL che costituiscono la Win32s, in particolare in nella libreria KERNEL32. Data la particolare architettura dell'ambiente Win32s è da notare che sia le applicazioni a 16 bit che quelle a 32 bit girano in un unico spazio d'indirizzamento in memoria. Il Flat Thunking invece permette sia al codice a 16 bit di chiamare quello a 32 bit e sia il contrario, cioè il codice a 32 bit può chiamare quello a 16 bit. Questo meccanismo è implementato tramite un particolare compilatore chiamato appunto Thunk Compiler il cui utilizzo analizzeremo più avanti in dettaglio. Durate il run time il thunking è gestito da delle API contenute nella libreria KERNEL32.DLL, (FT_xxxxx(), QT_Thunk(), ThunkConnect32(), SMapLSxxxxx(), SUnMapLSxxxxx(), C16ThkSL01 ed altre non documentate). In questo articolo approfondiremo il flat thunking di Windows 9x con un esempio di thunking da un'applicazione WIN32 a del codice a 16 bit contenuto in una apposita DLL.

FLAT THUNKING

Essenzialmente il flat thunking (nel verso 32->16) è composto da un'applicazione WIN32, una DLL WIN32 e una DLL WIN16 che lavorano insieme. Nel nostro esempio di flat thunking questi tre moduli saranno: APP32.EXE, APPTK32.DLL, APPTK16.DLL. Come un programmatore può facilmente immaginare, questi 3 eseguibili sono ottenuti da altrettanti file sorgenti APP32.C, APPTK32.C, APPTK16.C, opportunamente compilati. Ciò è giusto, ma il thunking (e da qui in poi con il termine thunking ci riferiremo esclusivamente al flat thunking nel verso 32->16), necessita di un altro sorgente, uno script che opportunamente compilato produce il codice che invoca il thunking, cioè il passaggio dalla modalità protetta a 16 bit a quella a 32 bit. La figura 3 evidenzia le fasi di creazione dei tre file eseguibili partendo dai sorgenti, come dicevo precedentemente in aggiunta ai tre sorgenti vi è anche il file SCRIPT.THK.

[Figura 3]

Come si può vedere da figura 3 il processo di costruzione di una applicazione che esegue il thunking è abbastanza lungo e laborioso. Inoltre richiede la presenza di diversi strumenti di sviluppo quali il compilatore C a 16 bit, il compilatore C a 32 bit, del thunk compiler contenuto nell'SDK, del compilatore ASSEMBLER e infine di un'utility che sia in grado di marcare la DLL a 16 bit come versione 4.0 (o maggiore) altrimenti il thunking non funzionerà. Proprio per questa complessità la creazione di applicazioni che eseguono il thunking è uno dei pochi progetti per il quale è meglio abbandonare l'ambiente di sviluppo visuale per una più agevole gestione con file batch, o in alternativa lavorare su due progetti distinti nell'ambiente visuale e gestire manualmente (tramite file batch) il thunk compiler. C'è inoltre da sottolineare che ci sono delle limitazioni (imposte dall'architettura del sistema operativo) su ciò che una DLL può eseguire durante un thunk, nel nostro caso (ripeto flat thunk da 32 a 16 bit) l'applicazione a 32 bit non deve passare puntatori a dati che risiedono nello stack (ad esempio variabili locali nel linguaggio C) come parametri del thunk. Altra cosa da evitare è chiamare durante il thunk delle DLL a 16 bit che cambiano lo stack.

IL THUNK COMPILER

Il thunk compiler consiste essenzialmente in un programma THUNK.EXE che prende come input uno script contenente delle definizioni e dei prototipi di funzioni che saranno chiamate durante il thunking in uno stile molto simile a quello del linguaggio C. In aggiunta nel thunk compiler sono disponibili alcuni switch che permettono di controllare in modo più stretto le modalità con cui avverrà il thunk; essi sono:

enablemapdirect3216 per creare un thunk dal codice a 32 bit a quello a 16
enablemapdirect1632 per creare un thunk dal codice a 16 bit a quello a 32
preload16 per caricare la DLL a 16 bit contemporaneamente all'applicazione a 32 bit
preload32 per caricare la DLL a 32 bit contemporaneamente all'applicazione a 16 bit
win31compat per sincronizzare lo scarico della DLL a 32 bit con la fine del processo a 16 bit
faulterrorcode per cambiare il codice d'errore restituito, da usare nel prototipo di funzione

Per creare quindi un thunk da 32 -> 16 bit, si inserirà come prima linea dello script la seguente assegnazione:

enablemapdirect3216=true


Seguono poi le definizioni dei tipi, il thunk compiler supporta la maggior parte dei tipi di dati integrali supportati in genere dai compilatori C/C++; in aggiunta il thunk compiler presenta anche il tipo di dato bool. Ecco in dettaglio i tipi di dati integrali supportati dal thunk compiler: short, long, unsigned short, unsigned long, int, unsigned int, void, char, unsigned char, string, nulltype, hinst, hinst16, hinst32, bool, bool16, bool32. Il compilatore non supporta gli arry di puntatori, array di strutture, unioni, e in genere tutti i tipi di dati in virgola mobile (float e double ad esempio). Il prototipo della funzione è simile a quello che si usa nel C/C++ , solo che in questo caso occorre specificare all'interno delle parentesi {} l'eventuale utilizzo dei puntatori, cioè input | output | inout). Si prenda come riferimento l'esempio seguente per la funzione C/C++ Test():

int Test(LPSTR StringaSoloLettura,LPSTR StringaSoloScrittura,LPSTR StringaLetturaScrittura, int Bytes);

ad essa corrisponderà il seguente prototipo nel thunk script:

typedef char *LPSTR;

int Test(LPSTR StringaSoloLettura,LPSTR StringaSoloScrittura,LPSTR StringaLetturaScrittura, int Bytes)
{

StringaSoloLettura = input; // questo è il valore di default (input)
StringaSoloScrittura = output;
StringaLetturaScrittura = inout;

}

Due cose da notare: la prima è che per il tipo di dato integrale int non deve essere specificato il tipo di utilizzo; seconda cosa che il thunk accetta i commenti dopo la doppia barra // come il C/C++. Il compilatore presenta alcune opzioni d'avvio che sono ben documentate direttamente dal compilatore, ecco infatti l'output dell'help on line fornito da THUNK.EXE:

C:\Programmi\DevStudio\bin>thunk /?
Microsoft (R) Thunk Compiler Version 1.8 for Windows 95. May 11 1995 13:16:19
Copyright (c) Microsoft Corp 1988-1998. All rights reserved.

Thunk compiler usage
thunk [{-|/}options] infile[.ext]

where options include:

? Display this help screen
h Display this help screen
o Override default output filename
p Change 16-bit structure alignment (default = 2)
P Change 32-bit structure alignment (default = 4)
t Override default stem name
Nx Name segment or class where x is

C32 32-bit code segment name
C16 16-bit code segment name

Il sorgente in assembler generato dal thunk compiler deve essere compilato due volte, la prima volta specificando le opzioni /DIS_16 (si definisce la macro IS_16) /FoSCR16.OBJ (assegna il nome SCR16 al file oggetto creato) per generare il file oggetto SCR16.OBJ e la seconda volta con le opzioni /DIS_32 (si definisce la macro IS_32) /FoSCR32.OBJ (assegna il nome SCR32 al file oggetto creato) per generare il file oggetto SCR32.OBJ. Come evidenziato in figura 3 questi due oggetti verranno linkati con gli altri oggetti costituenti rispettivamente la DDL a 16 bit e quella a 32 bit. Prima di linkare gli oggetti e ottenere le DLL bisogna pero creare i file di definizione (.DEF) contenenti i riferimenti alle funzioni che effettivamente implementano il thunking. Il file di definizione della DLL a 16 bit dovrà perciò contenere le seguenti definizioni (APPTK16.DEF nel nostro esempio):

IMPORTS
C16ThkSL01 = KERNEL.631
ThunkConnect16 = KERNEL.651

EXPORTS
Script_ThunkData16 @1 RESIDENTNAME
DllEntryPoint @2 RESIDENTNAME
;Più tutte le funzioni da chiamare

Il file di definizione della DLL a 32 bit (APPTK32.DEF) dovrà invece contenere:

EXPORTS
Script_ThunkData32 @1
;Più tutte le funzioni da chiamare

SVILUPPO DEL PROGRAMMA D'ESEMPIO

Eccoci quindi alla costruzione del nostro esempio di flat thunk da 32 bit a 16 bit. In questo esempio costruiremo una semplice applicazione a 32 bit basata su una finestra con due controlli, una casella di testo e uno spin che eseguirà il thunk ad una DLL a 16 bit. Il programma sul lato a 32 bit deve solo acquisire i dati dai 2 controlli e passarli eseguendo il thunk ad una funzione sul lato a 16 bit che ne visualizzerà il contenuto tramite la funzione MessageBox().Il codice a 16 bit restituirà al codice a 32 bit il numero di caratteri complessivamente visualizzati da MessageBox().Il programma è molto semplice e di facile comprensione, quindi non annoierò nessuno con i sui dettagli. Tutti i sorgenti, makefile, batch, e tutto quanto serve per la costruzione del programma sono contenuti nel file THKEXP.ZIP. Visto che la costruzione (intesa come compilazione e linkaggio) dei tre moduli eseguibili richiede parecchi tools di sviluppo, il file THKEXP.ZIP contiene anche i moduli già compilati.


Copyright © 1998 by Roberto Bianchi