Sei sulla pagina 1di 6

CP pensareprogettareprogrammare n.

135 maggio 2004

Un registratore di macro per Windows


di Gilberto Polzoni
Realizzare un registratore di eventi di sistema in VB con pochissimo codice ed una libreria COM disponibile sul web

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.

pubblicato su WWW.INFOMEDIA.IT stampa digitale da Lulu Enterprises Inc. stores.lulu.com/infomedia


Infomedia
` Infomedia e limpresa editoriale che da quasi venti anni ha raccolto la voce dei programmatori, dei sistemisti, dei professionisti, degli studenti, dei ricercatori e dei professori dinformatica italiani. Sono pi` di 800 gli autori che hanno realizzato per le teu state Computer Programming, Dev, Login, Visual Basic Journal e Java Journal, molte migliaia di articoli tecnici, presentazioni di prodotti, tecnologie, protocolli, strumenti di lavoro, tecniche di sviluppo e semplici trucchi e stratagemmi. Oltre 6 milioni di copie distribuite, trentamila pagine stampate, fanno di questa impresa la pi` grande ed u inuente realt` delleditoria specializzata nel campo della a programmazione e della sistemistica. In tutti questi anni le riviste Infomedia hanno vissuto della passione di quanti vedono nella programmazione non solo la propria professione ma unattivit` vitale e un vero a divertimento. ` Nel 2009, Infomedia e cambiata radicalmente adottando ` un nuovo modello aziendale ed editoriale e si e organizzata attorno ad una idea di Impresa Sociale di Comunit` , a partecipata da programmatori e sistemisti, separando le attivit` di gestione dellinformazione gestite da un board a comunitario professionale e quelle di produzione gesti` te da una impresa strumentale. Questo assetto e in linea con le migliori esperienze internazionali e rende Infomedia ancora di pi` parte della Comunit` nazionale degli u a sviluppatori di software. ` Infomedia e media-partner di manifestazioni ed eventi in ambito informatico, collabora con molti dei pi` imporu tanti editori informatici italiani come partner editoriale e fornitore di servizi di localizzazione in italiano di testi in lingua inglese.

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

Un registratore di macro per Windows


Realizzare un registratore di eventi di sistema in VB con pochissimo codice ed una libreria COM disponibile sul web
di Gilberto Polzoni

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

Windows e gli input dellutente


Le applicazioni che funzionano in ambiente MS Windows sono da tempo event-driven, vale a dire guida-

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.

Computer Programming n. 135 - Maggio 2004

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

Sistema Sistema Sistema Sistema Sistema 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.

Windows Hook Helper DLL


Sistema Sistema Sistema Sistema

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

WinMacro in azione su MS Paint

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.

Il registratore di macro WinMacro


Ora possiamo dilettarci con il codice. Vedremo che grazie alla libreria e ai piccoli accorgimenti inseriti sar veramente semplice sviluppare il cuore della nostra applicazione. Creiamo un nuovo progetto Standard EXE di VB6. Inseriamo il riferimento alla libreria ed alla nostra form facciamo implementare linterfaccia IWindowsHook. Successivamente inseriamo i tre pulsanti classici (Rec, Stop, Play) di qualsiasi registratore che si rispetti. Sullevento click del pulsante Rec va linstallazione dellhook WH_JOURNALRECORD:
Call InstallHook(Me, WH_JOURNALRECORD)

Sullevento click del pulsante Stop va la disinstallazione dellhook WH_JOURNALRECORD:


Call RemoveHook(Me, WH_JOURNALRECORD)

Sullevento click del pulsante Play va linstallazione dellhook WH_JOURNALPLAYBACK:


Call InstallHook(Me, WH_JOURNALPLAYBACK)

Computer Programming n. 135 - Maggio 2004

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

Windows Hook Helper


DLL permette di utilizzare agevolmente un buon numero di hook
Quando tale parametro uguale a WH_JOURNALRECORD decidiamo di aggiungere il messaggio ad una collezione. Per far ci la nostra libreria fornisce delle funzioni che permettono di maneggiare con estrema facilit le informazioni registrate sui parametri lParam e wParam. Nel nostro caso solo lParam ci interessa e quindi una scrittura come la seguente raggiunge da sola lo scopo:
msgs.Add JournalRecordlParam(lParam)

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.

Utilizzi e miglioramenti per WinMacro


Ora potremo fare dei test interessanti, come registrare le nostre azioni durante il disegno di una qualsiasi forma utilizzando MS Paint e successivamente premere il tasto Play della nostra applicazione e verificare che il computer ridisegna la forma originale seguendo fedelmente i nostri movimenti (Figura 1). Oppure potremo aprire MS Excel e registrare una macro che riduce ad uno i fogli del WorkBook di default, e magari eseguirla ogni qualvolta apriamo Excel. Attenzione. da rilevare che il nostro applicativo si basa sugli input dellutente e quindi eventuali spostamenti casuali o errori che determinano una visualizzazione delle finestre non identica alla fase di registrazione causa normalmente il fallimento della sequenza di play. comunque facile trovare applicazioni pratiche al nostro lavoro. Potremo ad esempio aggiungere funzionalit di salvataggio di sequenze di messaggi per magari permetterne lassegnazione ad un tasto specifico della tastiera (questo sempre grazie ad una ulteriore tipologia di Hook implementata nella libreria). Successivamente potremo ottimizzare tali sequenze eliminando messaggi del mouse adiacenti o permetterne lesecuzione senza temporizzazione. Potremo monitorare il comportamento di un utente (privacy permettendo...) o introdurre funzionalit macro nel nostro software.

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

BIBLIOGRAFIA & RIFERIMENTI


[1] http://msdn.microsoft.com/library/default.asp?url= /library/en-us/winui/winui/windowsuserinterface/windowing/messagesandmessagequeues/aboutmessagesandmessagequeues.asp About Messages and Message Queues [2] Kyle Marsh, Win32 Hooks, Microsoft Developer Network Technology Group [3] http://vbaccelerator.com - sito originario della vbAccelerator Windows Hook Library di Steve McMahon

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

Computer Programming n. 135 - Maggio 2004