- presentazione del sito
- Registrazione
- Eventi, mostre, convegni ed iniziative segnalate dalle aziende
- Recensioni ed articoli
- Le Mailing Lists
- La rivista Pc Ciechi
- Wiki
- Chi siamo
- Donazioni
- Un progetto degno di nota: Wintalbra
- Come navigare in questo sito
- rss
- Bancomat Accessibili sul territorio nazionale
- Contattaci
- I sostenitori di SpazioAusili
Come eseguire una qualsiasi funzione in una dll
di Donato Taddei
su nvda, 12\04\2008, h. 16.24.
Questo testo mostra come utilizzare una qualsiasi dll e le funzioni in essa contenute dall'interno di nvda
Ed ora, un po' di pubblicità
:o tramite script python.
Come si sa una libreria dinamica dll è un file eseguibile contenente una collezione di funzioni e variabili che possono essere utilizzate anche nello stesso
tempo da programmi diversi, si dice perciò che possono essere rilocati in qualsiasi pagina di mmoria. Tutti gli attuali sisemi operativi utilizzano librerie
dinamiche o rilocabili.
é questo il caso delle dll utilizzate dal sistema operativo windows, dette perciò di sistema,
o di dll condivise da più programmi come quelle contenenti le funzioni per accedere a internet.
nvda accede a molte dll: alcune di sistema, come ad esempio l'accesso facilitato, altre sue proprie, in pratica tutto il compilatore python e alcune sue
estensioni.
Alcuni anni fa passava su uictech un messaggio che informava dell'esistenza di uno script per jaws capace appunto di utilizzare dll che scaricai anche.
Non so se questa funzionalità sia ora implementata nel "core" di jaws.
Se non lo fosse sarebbe un indiscutiile immenso punto a favore di nvda.
Se anche questa funzionalità fosse attualmente implementata in jaws comunque dal punto di vista utente nvda offrirebbe su questo di più:
In entrambi i casi l'accesso al sistema è mediato da una interfaccia a script: nel caso di jaws tale mediazione avviene solo nei limiti e con le modalità
previste dal centinaio di funzioni base (built-in) su cui si regge l'engine di scripting dello screen-reader;
Per contro Nel caso di nvda gli script sono codificati in un linguaggio di programmazione di portata globale;
con uno script di nvda si può ad esempio gestire una connessione internet, maneggiare oggetti ole come fogli di calcolo, accedere e processare documenti
di word senza aprire lo stesso, e in una parola fare tutto quello che si fa
con un linguaggio di programmazione di alto livello.
Il controllo che l'utente può acquisire sulla sua macchinaè dunque infinitamente maggiore
Per inciso un utente che imparasse a bazzicare col serpente in maniera significativa avrebbe anche una opportunità teorica di poter sfruttare professionalmente
le conoscenze acquisite al di fuori dell'angusto specifico di cecolandia e di screen-reader per ciechi.
Di seguito viene riportato un file di esempio
python che ho chiamato "usare.py" concisamente ma sufficientemente commentato, contenente codice tratto in stragran parte dalla documentazione di python,
e reso attuale da una applicazione concreta.
Il file compila corettamente e non contiene errori come ho potuto verificare passo passo perchè è una tipica caratteristica del python quella di fermarsi
al primo errore nel file.
In questo file si mostra:
- come sapere quali funzioni (metodi) sono esprtati da un modulo python e gli attributi associati a ciascuno di essi;
- come chiamare un oggetto interno a python (contenuto in qualcuna delle sue dll)
- come controllare l'esistenza di una funzione in una qualsiasi dll
- come associare un handle alla propria dll attraverso kernel32.dll
- come passare argomenti, detti anche parametri, a una funzione di una dll
_ come ricevere dati restituiti da una funzione di una dll
- come passare argomenti tramite indirizzo (by reference) a una funzione di una dll
- come usare puntatori a funzioni callback di una dll
- come accedere a variabili esportate da una dll,
per esempio a una tabella interna all'interprete python utilizzata dalla dll python25.dll di nvda,
che ne è il cuore;
- come abbordare il problema principale: sapere cioè precisamente il formato dei dati richiesto e restituito da una funzione di una dll.
Esistono anche altri modi di utilizzo delle dll, attraverso moduli di estensione win32 ma gli esempi mostrati si riferiscono al modo standard con cui da
python si accede a tali librerie, il modulo ctypes contenuto nel "core dell'interprete.
Nvda fa uso massiccio di questo modulo, di gran lunga il più importato nei suoi sorgenti, non solo in ben 12 tra quelli compilati, ma anche in alcuni sorgenti
nelle directories appmodules e synthdrivers.
Una buona ragione per imparare a conoscerlo per chi avesse velleità di scriptare e personalizzare nvda.
Buona dormita!
Donato Taddei
-----------------------------------
import ctypes
print dir(ctypes)
# risultato una lista contenente i metodi implementati in ctypes
#['ARRAY', 'ArgumentError', 'Array', 'BigEndianStructure', 'CDLL', 'CFUNCTYPE', 'DEFAULT_MODE', 'DllCanUnloadNow', 'DllGetClassObject', 'FormatError',
'GetLastError', 'HRESULT', 'LibraryLoader', 'LittleEndianStructure', 'OleDLL', 'POINTER', 'PYFUNCTYPE', 'PyDLL', 'RTLD_GLOBAL', 'RTLD_LOCAL', 'SetPointerType',
'Structure', 'Union', 'WINFUNCTYPE', 'WinDLL', 'WinError', '_CFuncPtr', '_FUNCFLAG_CDECL', '_FUNCFLAG_PYTHONAPI', '_FUNCFLAG_STDCALL', '_Pointer', '_SimpleCData',
'__builtins__', '__doc__', '__file__', '__name__', '__path__', '__version__', '_c_functype_cache', '_calcsize', '_cast', '_cast_addr', '_check_HRESULT',
'_check_size', '_ctypes_version', '_dlopen', '_endian', '_memmove_addr', '_memset_addr', '_os', '_pointer_type_cache', '_string_at', '_string_at_addr',
'_sys', '_win_functype_cache', '_wstring_at', '_wstring_at_addr', 'addressof', 'alignment', 'byref', 'c_buffer', 'c_byte', 'c_char', 'c_char_p', 'c_double',
'c_float', 'c_int', 'c_int16', 'c_int32', 'c_int64', 'c_int8', 'c_long', 'c_longlong', 'c_short', 'c_size_t', 'c_ubyte', 'c_uint', 'c_uint16', 'c_uint32',
'c_uint64', 'c_uint8', 'c_ulong', 'c_ulonglong', 'c_ushort', 'c_void_p', 'c_voidp', 'c_wchar', 'c_wchar_p', 'cast', 'cdll', 'create_string_buffer', 'create_unicode_buffer',
'memmove', 'memset', 'oledll', 'pointer', 'py_object', 'pydll', 'pythonapi', 'resize', 'set_conversion_mode', 'sizeof', 'string_at', 'windll', 'wstring_at']
# per ciascun metodo vedere gli attributi ad esso associati
for ciascuna in dir(ctypes):
print ciascuna, dir(ciascuna)
# risultato: tutti i metodi e relativi attributi: di cui alcuni esempi
# per dare una idea del modo in cui python associa a ciascun nome
# una serie di attributi
# ARRAY ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__',
'__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum',
'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust',
'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
# CDLL ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__',
'__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum',
'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust',
'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
# CFUNCTYPE ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index',
'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex',
'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
# importa tutte le funzioni (methods) del modulo ctypes (typi dato c)
# in tal modo possono essere chiamate direttamente, senza premettere "ctypes.".
from ctypes import *
# il modulo ctypes e' il modo standard di Python per chiamare dll.
# esso viene importato in circa un terzo dei moduli di nvda e precisamente:
# nella directory appmodules contenente gli script per personalizzare le applicazioni in 4 file
# appModules\miranda32.py appModules\mplayerc.py
# appModules\winamp.py appModules\_default.py
# nella directory synthdrivers dei driver di sintesi
# synthDrivers\_espeak.py synthDrivers\_sapi4serotekHelper.py
# e nei seguenti moduli compilati nella directory principale
# appModuleHandler charHook IAccessibleHandler JABHandler keyboardHandler languageHandler
# mouseHandler nvwave oleTypes virtualBuffer winKernel winUser
# poiche' esistono due differenti convenzioni di chiamata delle fujnzioni,
# python, che e' pignolo, offre tre differenti metodi:
# cdll, windll, oledll, il primo per le dll che usano la convenzione cdecl,
# come lla c run-time library classica msvcrt e famiglia,
# windll per quelle che usano la convenzione syscall, tipica di windows e del pascal.
# oledll ritorna sempre un Hresult
# la print provoca il riscontro al prompt del risultato, con l'indirizzo dell'oggetto
# o il relativo messaggio di errore
# Dunque va tolta nell'uso normale
print windll.kernel32
print cdll.msvcrt
libc = cdll.msvcrt
# chiamata di un oggetto interno a python (contenuto in qualcuna delle sue dll)
libc.printf
print windll.kernel32.GetModuleHandleA
# Per vedere se una data funzione e' presente nella dll
funzione = getattr(windll.perl561, "RunPerl")
# in alcune dll si puo' accedere alle funzioni anche mediante un numero di ordine
cdll.kernel32[1]
# usare la funzione time interna a python
print libc.time(None)
# stampa in esadecimale l'handle ricevuto da GetModuleHandlea in kernel32
print hex(windll.kernel32.GetModuleHandleA(None))
# L'handle assegnato alla mia dll
Miohandle= hex(windll.kernel32.GetModuleHandleA("perl561.dll"))
# Passare argomenti a una funzione
# python di default usa interi di 4 bytes, floating-point a doppia precisione e concepisce le stringhe come puntatori, indirizzi a partire dal quale e fino
a incontrare
# uno zero binario che ne indichi la fine per rappresentare
#le sue variabili.
# pertanto e' necessario convertire le variabili python nel formato richiesto
# dalla funzione che si vuole chiamare nella dll
#il modulo ctype fa anche questo mestiere offrendo una serie di funzioni
# per convertire precisamente nei formati degli argomenti, tipici del c
# offrendo un apposito modulo anche per i formati tipici delle api win32 (wintypes).
# alcuni esempi
c_int() #un c int a zero
c_long(0)
c_char_p("ciao, bella gente!") # puntatore a carattere in c
c_char_p('stringa tra apici')
c_ushort(65355)
# per passare alla funzione un buffer di lungheza determinata:
p = create_string_buffer(3)
p = create_string_buffer("Ciao", 10) # passa una stringa di 10 bytes con "ciao"
# per stringhe unicode usare create_unicode_buffer
# dati restituiti dalla funzione chiamata
# di default python assume che le funzioni restituiscano un intero.
# e' possibile modificare tale default utilizzando l'attributo restype dell'oggetto
# come in questoesempio in cui la funzion interna e standard del c strcr
# aspetta un puntatore a stringa e un carattere, e ritorna un puntatore a stringa:
strchr = libc.strchr
strchr("abcdef", ord("d"))
# risultato: 8059983
strchr.restype = c_char_p
strchr("abcdef", ord("d"))
# risultato: 'def'
strchr.restype = c_char_p
strchr.argtypes = [c_char_p, c_char]
strchr("abcdef", "d")
# risultato: 'def'
# scrivendo invece: strchr("abcdef", "def")
#errore lui dice: Traceback (most recent call last):
# ArgumentError: argument 2: exceptions.TypeError: one character string expected
# giustamente aspettava un carattere, non una stringa
strchr("abcdef", "d")
# risultato: 'def'
# passaggio di argomenti by reference
# Le funzioni byref e pointer permettono il passaggio di argomenti via indirizzo
# (by reference)) come nel caso, per dire la prima che mi viene, di RegisterClass
# vedere la documentazione python per ctypes per quanto riguarda il passaggio
# alle funzioni di struct e union
# e' possibile raggrupparle in classi che dovranno avere un attributo __fields__
# la funzione pointer, oltre a passare un indirizzo, costruisce
# uno specifico oggetto per quel tipo dato. cosi':
i = c_int(42) # un intero inizializzato a 42
pi = pointer(i) #puntatore all'intero i
pi.contents # conterra' come intero 42
# poiche' pointer come detto restituisce un oggetto sara' possibile assegnargli
# nuovi tipi dato o nuovi valori ed anche indicizzarli sicche':
pi[0] # come in c, il primo valore assgnato a pi
#pi[n] # l'ennesimo valore assenato a pi
# callback
# ctypes permette altresi' di creare puntatori a funzioni, a volte dette callback, # mediante la creazione di una classe che ne rappresenti il tipo dato
# restituito all'uscita e il numero e tipo di argomenti in entrata,
# oltre alla convenzione di chiamata (c cdecl o syscall):
# CFUNCTYPE per il teo c standard, WINFUNCTYPE per le syscall come le api win32.
# una funzione che ritorna un intero e accetta due puntatori a intero
# e segue la convenzione c
miacallback = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
# utilizzo di variabili esportate da una dll
# questo esempio mostra come accedere a una tabella esportada da python contenente
# l'insieme dei moduli "frozen", letteralmente congelati, ovvero dei moduli Compilati, # e collegati, pronti all'uso.
# accederemo a questa tabella chiamata PyImport_FrozenModuels esportata dalla
# Python25.dll, il cuore di nvda, l'interprete python.
# L'esempio mostra altresi' l'uso della funzione pointer
# e dell'uso di classi per passare o ricevere strutture complesse di dati.
# La tabella e' referenziata da un puntatore che punta
# a un insieme omogeneo (array) di records costituiti a loro volta
# da due puntatori a carattere che puntano al nome e al codice e da un intero
# con la dimensione del modulo:
# definizione di classe
class struct_frozen(Structure):
_fields_ = [("name", c_char_p), ("code", POINTER(c_ubyte)), ("size", c_int)]
# instanziazione della suddetta classe
FrozenTable = POINTER(struct_frozen)
miadll=windll.python25
table = FrozenTable.in_dll(miadll, "PyImport_FrozenModules")
# stampa il risultato
for item in table:
print item.name, item.size
if item.name is None:
break
# come sapere precisamente il tipo dati richiesto dagli argomenti di una funzione
# per quanto riguarda le funzioni contenuti nelle dll di sistema
# tutti i compilatori c hanno una directory include contenente file con estensione .h
# dove sono definiti i prototipi di un grandissimo numero di funzioni
# basta procurarsene uno, es. il visual c sdk sul sitodella Microsoft
# o il c++ builder della Borland, o l'ottimo lcc-win32,
# o il gcc per windows detto MINGW, soprattutto il file windows.h.
# per dll proprietarie
# ricorrere, ove presente, alla documentazione,
# ricorrere, ove presente al file header *.h
# Chiusura del cerchio
# Devo eseguire lo script di Mbrolita utilizzando la mia perl561.dll
# nel file win32.h dei miei sorgenti trovo:
# DllExport int RunPerl(int argc, char **argv, char **env);
# Questa funzione restituisce un intero, e richiede un intero col numero di argomenti
# e due puntatori ad array contenente il primo i parametri della riga di comando
# cioe' il nome ello script e la/le frasi da sintetizzare),
#mentre il secondo punta all'array delle variabili di ambiente
dll = windll.kernel32.LoadLibraryA("perl561.dll")
print dll
dll= windll.perl561
mia = getattr(windll.perl561, "RunPerl")
a = int(len(environ))
argc=int(2)
print a, argc
class parms(Structure):
_fields_ = [("argc", c_int),("argv", c_char_p * argc),("env", c_char_p * a)]
parametri = parms
parametri.argc = 2
parametri.argv = ["sintargv.pl" "Ciao bella gente!"]
parametri.env = environ
print parametri.argc, parametri.argv, parametri.env
mia.argtypes = [c_int, c_char_p * 2, c_char_p *30]
#mia(2,"sintargv.pl""ciao", None)
mia(parametri.argc,parametri.argv,parametri.env)
in
Se qualcuno fosse particolarmente curioso posso fornire anche un prospetto che mostra l'utilizzo dei metodi e degli attributi ctypes nei sorgenti di nvda
con indicazione dei file e del numero di riga in cui ciascuno viene usato.
***
Questo testo completa il discorso di come chiamare ed usare dll dall'interno di nvda o da python,
proponendo semplicemente una serie di esempi tratti esclusivamente dai sorgenti di nvda.
Ancorchè in inglese i nomi utillizzati sono quasi sempre esplicativi, onde mi ritengo sollevato dal perdere tempo a fare traduzioni banali.
In generale Python preferisce le minuscole.
Viceversa windows usa solitamente nomi di funzioni con in maiuscola la prima lettera di ciascuna parola, solitamente attaccate, laddove il pitone preferisce
il trattino basso o sottolineatura.
Solitamente l'estensione dll viene omessa nei nomi delle librerie usate
e i loro nomi possono anche essere in minuscola.
Abbiamo detto che il modulo ctypes esporta tre oggetti base:
windll per le librerie che usano convenzioni di chiamata standard di windows,
cdll per quelle che usano la convenzione c
e oledll
Vediamone in dettaglio il loro uso nel codice di nvda.
L'estrazione è stata fatta senza alcun criterio di ordine, ovviamente in automatico:
basta e avanza comunque per fornire un po di esempi di uso reale e serve pure a prendere confidenza
con la scrittura del Python.
Windll
# esempio di chiamata by reference
Questo è il modulo di inerfaccia con l'accesso facilitato
res=windll.oleacc.AccessibleObjectFromWindow(window,objectID,byref(IAccessible._iid_),byref(ptr))
res=windll.oleacc.AccessibleObjectFromEvent(window,objectID,childID,byref(pacc),byref(varChild))
res=windll.oleacc.AccessibleObjectFromPoint(point,byref(pacc),byref(varChild))
res=windll.oleacc.WindowFromAccessibleObject(ia,byref(hwnd))
windll.oleacc.AccessibleChildren(ia,startIndex,numChildren,children,byref(realCount))
textLen=windll.oleacc.GetRoleTextW(role,0,0)
windll.oleacc.GetRoleTextW(role,buf,textLen+1)
textLen=windll.oleacc.GetStateTextW(state,0,0)
windll.oleacc.GetStateTextW(state,buf,textLen+1)
# questo invece è nel modulo di tastiera
ctypes.windll.shlwapi.SHLoadIndirectString(s,buf,256,None) ch=ctypes.windll.user32.MapVirtualKeyW(vkCode,winUser.MAPVK_VK_TO_CHAR)
nel language handler questo interroga kernel32 sulle impostazioni locali
windowsLCID=ctypes.windll.kernel32.GetThreadLocale()
questo invece dal'handler per la gestione del mouse interroga la winuser32.dll
hdc=ctypes.windll.user32.GetDC(0)
p=ctypes.windll.gdi32.GetPixel(hdc,i,j)
windowAtPoint=ctypes.windll.user32.WindowFromPoint(x,y)
Vediamo ora cdll
questo ha a che vedere con la java bridge (habhandler)
bridgeDll=cdll.WINDOWSACCESSBRIDGE
questo ha sempre a che vedere con la testiera:
rileva gli eventi di pressione e rilascio di un tasto
ctypes.cdll.keyHook.initialize(internal_keyDownEvent,internal_keyUpEvent)
ctypes.cdll.keyHook.terminate()
Questo invece aggancia e sgancia il controllo delmouse
ctypes.cdll.mouseHook.initialize(internal_mouseEvent)
ctypes.cdll.mouseHook.terminate()