Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Gilberto Polzoni Laureato in Ingegneria Elettronica presso ` lUniversita di Ancona, Master per Manager Aziendale, docente in corsi di specializzazione tecnica in MS-SQL Server, dal 2000 svolge mansioni di analista e programmatore presso il Gruppo Loccioni (AEA, General Impianti, Summa) per macchine di collaudo e calibrazione di componenti automobilistici.
Limpaginazione automatica di questa rivista e realizzata al ` 100% con strumenti Open Source usando OpenOffice, Emacs, BHL, LaTeX, Gimp, Inkscape e i linguaggi Lisp, Python e BASH
For copyright information about the contents of Computer Programming, please see the section Copyright at the end of each article if exists, otherwise ask authors. Infomedia contents is 2004 Infomedia and released as Creative Commons 2.5 BY-NC-ND. Turing Club content is 2004 Turing Club released as Creative Commons 2.5 BY-ND. Le informazioni di copyright sul contenuto di Computer Programming sono riportate nella sezione Copyright alla ne di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e 2004 Infome` dia e rilasciato con Licenza Creative Commons 2.5 BYNC-ND. Il contenuto Turing Club e 2004 Turing Club ` e rilasciato con Licenza Creative Commons 2.5 BY-ND. Si applicano tutte le norme di tutela dei marchi e dei segni distintivi. ` E in ogni caso ammessa la riproduzione parziale o totale dei testi e delle immagini per scopo didattico purch e vengano integralmente citati gli autori e la completa identicazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. Contenuto pubblicitario inferiore al 45%. La biograa dellautore riportata nellarticolo e sul sito www.infomedia.it e di norma quella disponibi` le nella stampa dellarticolo o aggiornata a cura dellautore stesso. Per aggiornarla scrivere a info@infomedia.it o farlo in autonomia allindirizzo http://mags.programmers.net/moduli/biograa
FOCUS
Office Automation
uando le finestre muovevano i primi passi in ambiente Microsoft, allinterno del vecchio MS Windows 3.1, esisteva un accessorio chiamato Microsoft Recorder. Tale applicativo permetteva di registrare una sequenza di azioni fatte con mouse e tastiera. Al termine della registrazione era poi possibile eseguire nuovamente le azioni e vedere il proprio PC animarsi quasi magicamente. Qualcosa di simile (anche se senza generazione di codice) della registrazione macro di MS Office. La differenza sostanziale che Recorder registrava le azioni dellutente per tutte le applicazioni, e non solo per quella in cui era in esecuzione. In pi eseguiva le azioni sul PC proprio come se qualcuno lo stesse utilizzando, e quindi, con i tempi e gli spostamenti caratteristici dellutente. Questapplicativo poi scomparso nella bufera delle innovazioni. Sono rimaste per le API (Application Program Interface) che permettono tale operazione. Daltronde bisogna ammettere che molte delle moderne applicazioni utilizzano queste API per svariati funzionamenti particolari (Debug, Computer-Based Training, Traduttori On-line, ...). Tali funzioni sono comunemente indicate come Windows Hook functions e rivestono un ruolo importantissimo soprattutto per tutti gli ambienti di programmazione. In questarticolo ne utilizzeremo solamente una minima parte al fine di creare il nostro applicativo. Sar in ogni modo mostrata al lettore una libreria che permette agevolmente di utilizzarne la maggior parte. I primi due paragrafi dellarticolo introducono ai messaggi di Windows e chiariscono il meccanismo degli Hook, tramite le callback. Successivamente sar descritta la libreria per il loro utilizzo e limplementazione del registratore di Macro.
te dagli eventi. Ci significa che il programmatore non deve effettuare chiamate esplicite per ottenere linput da parte di una periferica quale la tastiera o il mouse. Il sistema operativo passa tutti gli input per unapplicazione alle varie finestre che essa possiede. Ci significa che ogni finestra possiede una funzione, chiamata window procedure che Windows chiama ogni volta che esso ha un input per tale finestra. La window procedure processa linput e restituisce il controllo al sistema. Windows passa gli input a tale funzione in forma di messaggi. Essi possono essere generati sia dal sistema sia dallapplicazione. Windows genera un messaggio ad ogni evento dellutente. Per esempio alla pressione di un tasto, oppure quando il mouse si muove o, ancora, quando si preme il pulsante sinistro del mouse sopra una lista. Inoltre i messaggi possono essere utilizzati per far comunicare tra loro le finestre che compongono unapplicazione. Il sistema invia un messaggio ad una window procedure con un insieme di quattro parametri: un window handle, un identificativo del messaggio e due parametri: Il window handle identifica univocamente la finestra alla quale il messaggio diretto e permette al sistema di determinare quale window procedure dovrebbe ricevere il messaggio; Lidentificativo una costante che identifica lo scopo del messaggio. Quando una funzione window procedure riceve un messaggio, essa usa tale identificativo per determinare come processare la richiesta; I due parametri (lParam, wParam) specificano i dati (o la locazione di essi) che la window procedure usa per eseguire il codice relativo al messaggio. Il significato di essi dipende dallidentificativo del messaggio. Windows usa due metodi per inviare dei messaggi ad una window procedure. Il primo consiste nellinserire i messaggi su di una coda (message queue) che li mantiene temporaneamente e li spedisce in ordine (First-In First-Out) alla window procedure. Il secondo metodo prevede la spedizione immediata del messaggio alla window procedure di destinazione senza lutilizzo di nessuna coda. I messaggi generati dal mouse o dalla tastiera utilizzano normalmente le message queue. Il sistema operativo mantiene una coda di messaggi di sistema ed una coda di messaggi per ogni GUI thread. Quando lutente stimola il sistema con il mouse o la tastiera, il relativo driver converte lo stimolo in un mes41
Gilberto Polzoni
gpolzoni@infomedia.it
Laureato in Ingegneria Elettronica presso lUniversit di Ancona, Master per Manager Aziendale, docente in corsi di specializzazione tecnica in MS-SQL Server, dal 2000 svolge mansioni di analista e programmatore presso il Gruppo Loccioni (AEA, General Impianti, Summa) per macchine di collaudo e calibrazione di componenti automobilistici.
FOCUS
TABELLA 1
I tipi di hook e la relativa visibilit
Tipo di hook WH_CALLWNDPROC WH_CALLWNDPROCRET WH_CBT WH_DEBUG WH_FOREGROUNDIDLE WH_GETMESSAGE WH_JOURNALPLAYBACK WH_JOURNALRECORD WH_KEYBOARD WH_KEYBOARD_LL WH_MOUSE WH_MOUSE_LL WH_MSGFILTER WH_SHELL WH_SYSMSGFILTER
Visibilit Thread o Thread o Thread o Thread o Thread o Thread o Sistema Sistema Thread o Sistema Thread o Sistema Thread o Thread o Sistema
Quando la filter function non pi utilizzata va rimossa dalla catena tramite la funzione UnhookWindowsHookEx(). Un hook viene impostato quando il puntatore ad una filter function inserito nella catena di un hook. Quindi il programma, dopo aver impostato tale funzione, ricever le notifiche dei messaggi filtrati grazie alla chiamata di tale funzione da parte del sistema operativo. Un tale meccanismo, simile a quello che MS Windows utilizza per lesecuzione della window procedure detto in generale callback.
saggio e inserisce questultimo nella coda dei messaggi di sistema. Successivamente il sistema rimuove i messaggi uno per volta, li esamina, e li spedisce alla coda di messaggi del thread di competenza. Il thread rimuove i messaggi dalla propria coda e indica al sistema di spedirli ad una specifica window procedure. Ci significa che la pressione di un tasto o il movimento del mouse (prima di arrivare alla finestra della nostra applicazione generica) passa normalmente per due code distinte: una globale di sistema (system message queue) ed una specifica del thread (thread message queue) [1].
Hook e callback
In Windows gli hook sono un meccanismo tramite il quale una funzione pu intercettare gli eventi (leggi messaggi) prima che essi arrivino ad unapplicazione. La funzione pu in qualche caso anche modificare gli eventi, modificando cos il comportamento di una finestra [2]. Tale funzione detta filter function ed classificata secondo il tipo di evento che intercetta. Infatti esistono diversi tipi di hook. Ad ogni tipo di hook corrisponde un insieme di messaggi che la filter function in grado di catturare. Quando si lega una filter function ad un hook si dice che lhook impostato (SetWindowsHookEx()). Se su di un hook impostato si riceve un evento, la relativa filter function richiamata. Per ogni hook possono esistere una o pi filter function. Esse quindi formano una catena. Normalmente quando una funzione della catena viene richiamata essa dovrebbe richiamare la successiva, grazie ad una apposita API (CallNextHookEx()). Se ci non avviene, la parte della catena che segue la funzione non ricever i messaggi. Nei sistemi operativi Microsoft la maggior parte degli hook pu essere impostata con due tipi di visibilit (scope): sistema o thread (Tabella 1). Tale distinzione deriva dal meccanismo sopra descritto di gestione dei messaggi di Windows. Se la visibilit di sistema, la filter function in grado di catturare tutti i messaggi relativi allhook su cui impostata. Daltro canto, se la visibilit di thread, la funzione in grado di catturare solo i messaggi dellhook relativi alla finestra che il thread possiede. Ovviamente la maggior funzionalit degli hook di sistema si paga in termini di prestazioni. 42
Windows Hook Helper DLL una DLL COM sviluppata da Steve McMahon [3] che permette di utilizzare agevolmente un buon numero di hook. Non tutti gli hook sono stati implementati. Ci non impedisce che la libreria risulti facilmente espandibile come daltronde di seguito stato fatto. Una descrizione esauriente della struttura della DLL esula dallo scopo di questo articolo. Si daranno solamente le informazioni necessarie per un buon utilizzo. da sottolineare che la DLL si presenta come ununica classe GlobalMultiUse e quindi utilizzabile veramente come una libreria. Internamente, la struttura delle classi di questo componente si basa su di un concetto molto semplice. Ogni tipo di Hook fornisce informazioni sullazione attuata dallutente tramite dei dati registrati sui due parametri di un messaggio. Quindi ad ogni tipo di hook corrisponde almeno una tipologia di informazioni sullazione catturata. Nella DLL tale tipologia schematizzata con una classe che permette la gestione delle informazioni registrate sui parametri del messaggio. Al fine di semplificare il meccanismo di callback , inoltre, definita una interfaccia (IWindowsHook) che il client della DLL dovr implementare. Tale interfaccia definisce un solo metodo (HookProc) che permette alla libreria di conoscere la funzione da richiamare per la notifica del messaggio. Allinterno della libreria inoltre gestita anche la catena delle filter function sgravando il client dal richiamare la CallNextHookEx(). Quindi per utilizzare gli hook in unapplicazione Visual Basic, sufficiente inserire il riferimento a questa libreria e fare implementare ad una delle nostre classi linterfaccia di callback IWindowsHook. Per impostare un hook sufficiente chiamare il metodo InstallHook. Allarrivo di un messaggio da catturare la libreria richiamer il metodo dellinterfaccia di callback (HookProc). Tale metodo presenta i seguenti parametri: eType: permette di discriminare il tipo di hook; nCode: permette di discriminare lazione (messaggio) che ha generato lhook; bConsume: permette di non far propagare la chiamata alle altre filter function della catena; lParam e wParam sono i parametri del messaggio. La funzione ritorna un valore long. Normalmente tale valore dovrebbe essere uguale a zero. Vedremo in seguito che nel nostro caso esso riveste una parte interessante.
Journal Hook
Nel nostro applicativo utilizzeremo una particolare famiglia di hook: Journal Hook. Essi permettono di registrare e rieseguire una sequenza di messaggi. I Journal Hook possono essere solamente con visibilit di sistema. Esistono due tipi di Journal Hook:
Computer Programming n. 135 - Maggio 2004
Office Automation
WH_JOURNALRECORD WH_JOURNALPLAYBACK Il primo permette di catturare i messaggi ed eventualmente di registrarli. Il secondo riesegue i messaggi come se fossero eseguiti dallutente. WH_JOURNALRECORD il pi semplice. Esso utilizza una sola modalit: il parametro nCode, nella nostra funzione di callback, pu assumere unicamente il valore HC_ACTION. Ci indica che unazione stata effettuata dallutente e che quindi essa pu essere recuperata tramite il parametro lParam. Tale parametro contiene un puntatore ad una struttura (di tipo EVENTMSG) usata per mantenere tutte le informazioni riguardanti un messaggio, anche listante nel quale il messaggio stato spedito. WH_JOURNALPLAYBACK un po pi complesso. Esso utilizza due modalit. Se nCode uguale a HC_GETNEXT allora il sistema operativo si attende dalla funzione di callback un nuovo messaggio. Esso dovr essere caricato su di una struttura EVENTMSG puntata dal parametro lParam attuale. In questa modalit il sistema si
LISTATO 1
La filter function del nostro applicativo
FIGURA 1
Private Function IWindowsHook_HookProc( _ ByVal eType As EHTHookTypeConstants, _ ByVal nCode As Long, ByVal wParam As Long, _ ByVal lParam As Long, bConsume As Boolean _ ) As Long Dim delta As Long //Verifica del tipo di Hook Select Case eType Case WH_JOURNALPLAYBACK lblStatus.ForeColor = vbGreen lblStatus.Caption = Playing //Verifica del tipo di azione If nCode = HC_SKIP Then msgs.Remove 1 If msgs.Count = 0 Then RemoveHook Me, WH_JOURNALPLAYBACK lblStatus.Caption = End If End If If nCode = HC_GETNEXT Then Dim recordedMsg As cJournallParam Set recordedMsg = msgs(1) With JournalPlaybacklParam(lParam) .hwnd = recordedMsg.hwnd .lParamHigh = recordedMsg.lParamHigh .lParamLow = recordedMsg.lParamLow .Msg = recordedMsg.Msg .MsgTime = recordedMsg.MsgTime _ + StartPlayMsgTime _ - StartRecMsgTime delta = .MsgTime - GetTickCount If delta > 0 Then IWindowsHook_HookProc = delta Else IWindowsHook_HookProc = 0 End If End With End If Case WH_JOURNALRECORD lblStatus.ForeColor = vbRed lblStatus.Caption = Recording If nCode = HC_ACTION Then _ msgs.Add JournalRecordlParam(lParam) End Select End Function
attende inoltre che gli venga indicata, dal ritorno della funzione di callback, il tempo di attesa prima di passare al messaggio successivo. Dopo aver eseguito un messaggio, il sistema informa la filter function di passare al messaggio successivo. Esso avviene con la seconda modalit (nCode uguale a HC_SKIP). Questo codice quindi permette alla funzione di effettuare eventuali operazioni prima di passare al successivo messaggio registrato. Per quanto riguarda questa tipologia di hook, la libreria di Steve McMahon sicuramente un buon inizio. Ci non toglie che qualche piccola modifica a tale libreria sia opportuna. Infatti, essa presenta solamente il supporto per WH_JOURNALRECORD. stato quindi necessario introdurre una nuova funzione di callback, ed inoltre, garantire che il ritorno di questultima possa essere diverso da zero.
43
FOCUS
Ora implementiamo la nostra filter function ereditando il metodo HookProc dallinterfaccia IWindowsHook. Come descritto in precedenza, questo metodo presenta tutte le informazioni utili per la gestione degli hook. Ora tramite il parametro eType possiamo selezionare il tipo di Journal hook.
Office Automation
duzione dei messaggi avverrebbe ad alta velocit. necessario temporizzare gli eventi da spedire durante il Play al fine di avere leffetto desiderato. Ci possibile grazie al campo MsgTime della struttura EVENTMSG. Questo campo ha il valore della API di sistema GetTickCount() al momento dellinvio del messaggio durante la registrazione. Quindi si utilizza il ritardo tra i messaggi come valore di ritorno della funzione di callback nel caso di nCode uguale a HC_GETNEXT ed il gioco fatto.
Quando eType assume il valore WH_JOURNALPLAYBACK dovremmo distinguere i due casi. Per nCode uguale a HC_SKIP sufficiente rimuovere il primo messaggio della nostra collezione (che funziona in tutto e per tutto come una coda FIFO) e controllare che essa non sia vuota. Nel caso non ci siano pi messaggi da spedire si rimuove lhook WH_JOURNALPLAYBACK:
If nCode = HC_SKIP Then msgs.Remove 1 If msgs.Count = 0 Then RemoveHook Me, WH_JOURNALPLAYBACK lblStatus.Caption = End If End If
Conclusioni
Questo breve articolo presenta un semplice utilizzo di una grande risorsa che il meccanismo degli hook. Esso non completo ma presenta comunque un approccio che pu essere facilmente esteso e adattato alle esigenze. WinMacro un programma che si pone come un buon esempio a proposito. La libreria indicata nei vari paragrafi manca di una tipologia di Hook molto interessante: i CBT (Computer-Based Training). Inoltre non permette di decidere lo scope degli hook. in ogni modo pensabile unespansione in tal senso, magari nei prossimi articoli.
Per nCode uguale a HC_GETNEXT si dovr recuperare il primo messaggio della collezione, ed utilizzando la funzione di libreria JournalPlaybacklParam assegnarlo in tutti i suoi campi alla struttura puntata da lParam:
If nCode = HC_GETNEXT Then Dim recordedMsg As cJournallParam Set recordedMsg = msgs(1) With JournalPlaybacklParam(lParam) .hwnd = recordedMsg.hwnd .lParamHigh = recordedMsg.lParamHigh .lParamLow = recordedMsg.lParamLow .Msg = recordedMsg.Msg
Ora potremmo dire concluso il nostro applicativo. In effetti, se eseguissimo ci che abbiamo fin qui descritto, il PC registrerebbe i messaggi alla pressione del pulsante Rec ed avvierebbe la successiva esecuzione al Play. Tuttavia la pressione del tasto Play provocherebbe lesecuzione di tutti i messaggi senza i ritardi che sono intercorsi tra essi durante la fase di registrazione. Quindi la ripro44
CODICE ALLEGATO
ftp.infomedia.it
WinMacro