X-Windows in Assembly Language: Part I - THE.UNIX.WORLD
Pubblicato da mammon_ il 01/1999
Livello intermedio
Introduzione
/* Questo articolo e' stato tratto dall'Assembly Programming Journal numero 2 e tradotto da Little-John */
::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _ |\ \::::::::::. :::\_____\:::::::::::............................................THE.UNIX.WORLD
Iniziamo
La maniera assennata per scrivere programmi per X-Windows e' usare un toolkit come Xt o Gtk; la via facile sarebbe usare uno scripting package come Python o Tcl/Tk. I coders assembly moderni, comunque, sono rinomati per sacrificare tranquillito' e sensibilito' in nome della curiosito' e della velocito' di esecuzione; e' in questo spirito che la programmazione potenziale di X-Windows in assembly language saro' ora analizzata.
X-Windows Programming
---------------------
Come le altre GUIs, X-Windows usa uno stile di programmazione event-driven (dipendente dagli eventi) in cui una applicazione si registra con il sistema, mostra la sua interfaccia main user, e aspetta eventi di sistema che segnalino che l'utente abbia interagito con il programma. Ci sono quattro 'livelli' principali della X-Windows Programming: XProtocol, XLib, Xt o programmazione 'toolkit', e scripting.
XProtocol
X-Windows consiste di un X Server che gestisce l'output grafico, l'input della tastiera e del mouse, la segnalazione degli eventi, e i comandi mandati dal programma client (Window Managers, applicazioni). I client comunicano con l'X Server usando l'XProtocol, che consiste di flussi di byte scambiati tra il client e il server -- da un certo punto di vista come i pacchetti che una rete client scambia con una rete server. XProtocol e' virtualmente di nessun valore per la programmazione delle applicazioni, [for the coding overhead for each server request makes development impractical]. I dettagli delle richieste dell'XProtocol possono essere trovati in '/usr/include/X11/Xproto.h'.
XLib
L'equivalente delle Win32 API in X-Windows e' XLib. Anche se qualcuno usa i toolkits per la programmazione delle applicazioni, non c'e' modo di evitare l'XLib coding. XLib e' una interfaccia tra i programmi client e l'X Server; essenzialmente, e' una libreria di funzioni XProtocol esportate per essere usate dalle applicazioni.
Xt
La programmazione con un Toolkit e' come usare le class libraries (come MFC, OWL, o VCL) sulla piattaforma Win32. Ci sono diversi toolkit disponibili, come Qt, Gtk, Xt Intrinsics, Athena, e il toolkit Motif. Ogni toolkit consiste di widgets estendibili (come le risorse in Win32) che definiscono tipi di oggetti di base: bottoni, scrollbars, dialogs, edit windows, ecc.
Scripting
Una vasta varieto' di linguaggi di scripting sono disponibili per la piattaforma Unix, e molti di questi hanno anche windowing toolkits che li abilitano a creare applicazioni X-Windows. I piu' popolari sono Tcl/Tk, Python, e Java; inutile dire che questi metodi di programmazione potrebbero non essere implementati in assembly language.
The XLib Programming Model
--------------------------
Un'applicazione scritta per l'interfaccia XLib dimostra i principi principali della programmazione per X-Windows. Questi principi si sintetizzano in un metodo in 5-passi:
Passo I : Connessione al Display
Il primo passo di una applicazione X-Windows e' anche il piu' semplice: una call a XOpenDisplay; il risultato --restituito in eax naturalmente-- e' un pointer ad una struttura Display. Questo dovrebbe essere salvato, dato che ce ne saro' necessito' per quasi ogni altra call seguente:
p_disp = XOpenDisplay( NULL );
Nota: Sto fornendo il codice d'esempio in C per questa sezione; la ricostruzione in assembler saro' presentata piu' avanti.
mfontstruct = XLoadQueryFont( p_disp, "fixed");
WhitePix = WhitePixel( p_disp, DefaultScreen(p_disp));
BlackPix = BlackPixel( p_disp, DefaultScreen(p_disp));
Ancora una volta, i valori sono salvati per una utilizzazione successiva. Nota che un metodo piu' complesso per l'allocazione dei colori saro' utilizzato in seguito nell'assembly code; li', un handle alla colormap default di X Windowse' ottenuto con una call a XDefaultColormap, e XAllocNamedColor e' usato per allocare i valori del pixel bianco e nero pixel: cio' realizza lo stesso codice come sopra, ma senza usare le macro.
Main = XCreateSimpleWindow( \
p_disp, DefaultRootWindow( p_disp ), 100, 100, 100, 50, 1, BlackPix, WhitePix);
Creare un GC non e' strettamente necessario; comunque farne a meno rende imprevedibile l'aspetto dell'applicazione (ho visto che il background della mia finestra diventa trasparente). Un GC viene creato chiamando XCreateGC, a cui e' passato il display, l' handle della finestra, il valore della mask, e una struttura GraphicsContextValues:
theGC = XCreateGC(p_disp, Main,(GCFont | GCForeground | GCBackground), &gcv);
Gli eventi input sono selezionati usando la funzione XSelectInput, a cui e' passato il display, l'handle della finestra, i valori ORati delle mask dell'evento:
XSelectInput( p_disp, Main, ExposureMask );
Infine, la finestra e' mappata sul display (e quindi visualizzata) con la call XMapWindow, che e' relativamente autoesplicativa:
XMapWindow( p_disp, Main );
A questo punto la procedura deve essere creata per ogni child window (bottoni, scrollbars, ecc); cio' che segue espone la creazione di un bottone con il suo GC, e la selezione delle mask degli eventi Exposure e ButtonPress:
Exit = XCreateSimpleWindow(p_disp, Main, 15, 1, 60, 15, 1, WhitePix, BlackPix);
XSelectInput(p_disp, Exit, ExposureMask | ButtonPressMask );
XMapWindow(p_disp, Exit);
exitGC = XCreateGC(p_disp, Exit,(GCFont | GCForeground | GCBackground),&gcv);
Nota che non saro' necessario un altro GC per ogni finestra se condivide lo stesso background, foreground, e i colori dei font.
while( !Done ){
XNextEvent(p_disp, &theEvent);
if( theEvent.xany.window == Main){
if( theEvent.type == Expose && theEvent.xexpose.count == 0){
XDrawString(p_disp, Main, theGC, 1, 40, msgtext, strlen(msgtext));
}
}
if( theEvent.xany.window == Exit){
switch(theEvent.type){
case Expose:
if( theEvent.xexpose.count == 0){
XDrawString(p_disp, Exit, exitGC, 2, 11, extext, strlen(extext) );
}
break;
case ButtonPress:
Done = 1;
}
}
}
Passo V : Pulisci e chiudi il Display
XFreeGC(p_disp, theGC);
XFreeGC(p_disp, exitGC);
XUnloadFont(p_disp, mfontstruct->fid);
XDestroyWindow(p_disp, Main);
XCloseDisplay(p_disp);
exit(0);
Nota che tutte le funzioni, le strutture, e i messaggi usati sopra sono definiti in '/usr/include/X11/Xlib.h', './X11/Xutil.h' e './X11/X.h'.
asm( statements : output vars : input vars : modified registers);
Nota che gli ultimi tre parametri sono usualmente utilizzati solo se stai scrivendo una intera funzione in assembly language, o se stai modificando i registri che non salvi (e' meglio salvare tutti i registri che modificherai, se contengono valori che saranno necessari dopo).
asm( "
statement1 \n
statement2 \n
statement3 \n
statement4"
: "g" (outvar)
: "g" (invar)
: eax, ebx, ecx
);
o, come faccio qui:
asm( "statement1 \n\t"
"statement2 \n\t"
"statement3 \n\t"
"statement4 \n\t");
Piu' che questo non ci sono altre restrizioni. Le strutture non passano bane tra C e GAS; se hai bisogno di far riferimento a variabili di strutture specifiche dall'inline assembly, e' consigliabile mettere queste variabili in variabili C temporanee, che possono essere normalmente utilizzate dal blocco assembler.
fid = mfontstruct->fid;
asm( "
push fid\n
push mainGC\n
push p_disp\n
call XSetFont\n
add $12, %esp");
Piu' informazioni sull'assembler inline GCC possono essere reperite qui:
// xhell.c ============================================================
#include
#include
/* ==================== Global Variable Declarations =====================*/
char *msgtext = "You are in XHell",
*extext = "Exit XHell",
*m_font = "fixed",
*app_name = "xhello",
*window_title = "XHell",
*szWhite = "white",
*szBlack = "black";
XFontStruct *mfontstruct;
Display *p_disp;
Window Main, Exit;
GC mainGC, exitGC;
XEvent theEvent;
Font fid;
Colormap cmap;
int Done = 0;
unsigned long pxBlack, pxWhite;
XSetWindowAttributes xswa;
XColor pixBlack, pixWhite;
XGCValues gcv;
/* ================ Start della Main Function ==================== */
main()
{
/* ===== Connessione al Display ===== */
asm( "push $0\n\t"
"call XOpenDisplay\n\t"
"movl %eax, p_disp\n\t"
"add $4, %esp\n\t");
/* ===== Setup Colors e Fonts ===== */
asm( "push m_font\n\t"
"push p_disp\n\t"
"call XLoadQueryFont\n\t"
"add $8, %esp\n\t"
"movl %eax, mfontstruct");
/* ===== Prepara la Main Window ===== */
fid = mfontstruct->fid;
/* ===== Crea il Main Graphics Context ===== */
// Obtain Colormap Handle
asm( "push p_disp\n\t"
"call XDefaultScreen\n\t"
"add $4, %esp\n\t"
"push %eax\n\t"
"push p_disp\n\t"
"call XDefaultColormap\n\t"
"add $8, %esp\n\t"
"movl %eax, cmap");
// Alloca i colori White and Black
asm( "push $pixWhite\n\t"
"push $pixWhite\n\t"
"push szWhite\n\t"
"push cmap\n\t"
"push p_disp\n\t"
"call XAllocNamedColor\n\t"
"add $20, %esp");
asm( "push $pixBlack\n\t"
"push $pixBlack\n\t"
"push szBlack\n\t"
"push cmap\n\t"
"push p_disp\n\t"
"call XAllocNamedColor\n\t"
"add $20, %esp");
xswa.background_pixel = pixWhite.pixel;
asm( "push $xswa\n\t"
"movl $1, %ebx\n\t"
"shl $1, %ebx\n\t" //CWBackPixel = 1 << 1
"push %ebx\n\t"
"push $0\n\t" //CopyFromParent = 0 (X.h)
"push $1\n\t" //InputOutput = 1 (X.h)
"push $0\n\t" //CopyFromParent = 0 (X.h)
"push $1\n\t"
"push $50\n\t"
"push $100\n\t"
"push $100\n\t"
"push $100\n\t"
"push p_disp\n\t"
"call XDefaultRootWindow\n\t"
"add $4, %esp\n\t"
"push %eax\n\t"
"push p_disp\n\t"
"call XCreateWindow\n\t"
"add $48, %esp\n\t"
"movl %eax, Main");
gcv.font = fid;
asm( "push $gcv\n\t"
"movl $1, %ebx\n\t"
"shl $14, %ebx\n\t" //GCFont = 1 << 14
"push %ebx\n\t"
"push Main\n\t"
"push p_disp\n\t"
"call XCreateGC\n\t"
"add $16, %esp\n\t"
"movl %eax, mainGC");
pxBlack = pixBlack.pixel;
pxWhite = pixWhite.pixel;
asm( "push fid\n\t"
"push mainGC\n\t"
"push p_disp\n\t"
"call XSetFont\n\t"
"push pxBlack\n\t"
"push mainGC\n\t"
"push p_disp\n\t"
"call XSetForeground\n\t"
"push pxWhite\n\t"
"push mainGC\n\t"
"push p_disp\n\t"
"call XSetBackground\n\t"
"add $36, %esp");
asm( "movl $1, %ebx\n\t"
"shl $15, %ebx\n\t" //ExposureMask = 1 << 15
"push %ebx\n\t"
"push Main\n\t"
"push p_disp\n\t"
"call XSelectInput\n\t"
"add $12, %esp");
asm( "push Main\n\t"
"push p_disp\n\t"
"call XMapWindow\n\t"
"add $8, %esp");
/* ===== Crea le Child Windows ===== */
asm( "push pxWhite\n\t"
"push pxBlack\n\t"
"push $1\n\t"
"push $15\n\t"
"push $60\n\t"
"push $1\n\t"
"push $15\n\t"
"push Main\n\t"
"push p_disp\n\t"
"call XCreateSimpleWindow\n\t"
"movl %eax, Exit\n\t"
"add $36, %esp");
asm( "movl $1, %ebx\n\t"
"shl $15, %ebx\n\t" //ExposureMask = 1 << 15
"movl $1, %ecx\n\t"
"shl $2, %ecx\n\t" //ButtonPressMask = 1 << 2
"or %ecx, %ebx\n\t"
"push %ebx\n\t"
"push Exit\n\t"
"push p_disp\n\t"
"call XSelectInput\n\t"
"add $12, %esp");
gcv.foreground = pxBlack;
gcv.background = pxWhite;
asm( "push $gcv\n\t"
"movl $1, %ebx\n\t"
"shl $14, %ebx\n\t" //GCFont = 1 << 14
"movl $1, %ecx\n\t"
"shl $2, %ecx\n\t" //GCForeground = 1 << 2
"or %ecx, %ebx\n\t"
"movl $1, %ecx\n\t"
"shl $3, %ecx\n\t" //GCBackground = 1 << 3
"or %ecx, %ebx\n\t"
"push %ebx\n\t"
"push Exit\n\t"
"push p_disp\n\t"
"call XCreateGC\n\t"
"add $16, %esp\n\t"
"movl %eax, exitGC");
asm( "push Exit\n\t"
"push p_disp\n\t"
"call XMapWindow\n\t"
"add $8, %esp");
/* ===== Event Loop ===== */ while( !Done ){ //Implementeto in C per salvare spazio ;)
XNextEvent(p_disp, &theEvent);
if( theEvent.xany.window == Main){
if( theEvent.type == Expose && theEvent.xexpose.count == 0){
asm( "push $16\n\t"
"push msgtext\n\t"
"push $40\n\t"
"push $1\n\t"
"push mainGC\n\t"
"push Main\n\t"
"push p_disp\n\t"
"call XDrawString\n\t"
"add $28, %esp");
}
}
if( theEvent.xany.window == Exit){
switch(theEvent.type){
case Expose:
if( theEvent.xexpose.count == 0){
XDrawString(p_disp, Exit, exitGC, 2, 11, extext, strlen(extext) );
}
break;
case ButtonPress:
Done = 1;
}
}
}
/* ===== Chiude il Display ===== */
asm( "push mainGC\n\t"
"push p_disp\n\t"
"call XFreeGC\n\t"
"add $8, %esp\n\t"
"push exitGC\n\t"
"push p_disp\n\t"
"call XFreeGC\n\t"
"add $8, %esp\n\t"
"push fid\n\t"
"push p_disp\n\t"
"call XUnloadFont\n\t"
"add $8, %esp\n\t"
"push Main\n\t"
"push p_disp\n\t"
"call XDestroyWindow\n\t"
"call XCloseDisplay\n\t"
"add $8, %esp");
}
; EOF =================================================================
Come puoi vedere, creare un programma XLib in linguaggio assembly e' abbastanza 'unwieldly'. Il codice prodotto e' soprattutto manipolazione dei dati e chiamate C; non c'e' molto che l'assembly puo' offrire, neanche nell'event loop. Infatti l'unica vera ottimizzazione --apparte l'overhead aggiunto dal compiler, che nell'esempio sopra non bypassiamo-- e' nell'uso di chiamate dirette invece delle macro su cui l'originario "hello world" in C si basava.