Guida al Visual Basic .NET
Capitolo 90° - Il Platform Invoke
Il platform invoke è una tecnica che permette all'applicazione di usare metodi definiti in librerie esterne. Ovviamente, queste
librerie non sono scritte in linguaggi .NET, altrimenti sarebbe bastato importarle come riferimento nel progetto. L'utilità più
diretta che consegue dall'uso del platform invoke è la possibilità di interagire con l'Application Programming Interface (API)
di Windows, ossia l'insieme delle librerie di sistema. Facendo questo è possibile intervenire a basso livello nel sistema operativo
e accedere e manipolare informazioni che non sarebbe normalmente consentito conoscere. Estrarre un metodo dalle librerie
Una cosa importante è il fatto che non si può utilizzare tutta la libreria nel suo insieme, ma si può solo estrarne
un metodo alla volta - e nella maggioranza dei casi basta quello. Con i termini "estrarre un metodo" voglio dire che nel nostro programma
dichiariamo un metodo normalmente (con nome, parametri, eccetera...) ma non ne specifichiamo il corpo: quando tale metodo verrà
richiamato, sarà invece usato il metodo scritto nella libreria. <System.Runtime.InteropServices.DllImport("NomeLibreria.dll")> _ Public Sub/Function [Nome]([Parametri]) End Sub/Function
L'attributo DllImport presenta anche moltissime altre proprietà, che per ora non ci servono. Se si usa l'attributo DllImport, il
nome del metodo deve coincidere esattamente con il nome del metodo che si sta importando dalla libreria (altrimenti il programma non saprebbe
quale scegliere). Declare Auto Sub [Nome] Lib "NomeLibreria.dll" Alias "VeroNome" ([Parametri])
In questo caso, non occorre che il nome del metodo coincida con quello della libreria, perché si può specificare il vero nome
nella clausola Alias. La keyword Auto, invece, è opzionale e indica quale set di caratterei usare per il passaggio di stringhe: in
questo caso, si usa quello predefinito. Le due alternative sono Ansi (ascii) e Unicode. Ma questi sono solo dettagli. Imports System.Runtime.InteropServices Module Module1 'Le funzioni che risiedono nelle librerie di sistema lavorano 'a basso livello, come già detto, perciò i tipi 'più frequentemente incontrati sono IntPtr (puntatore 'intero) e Int32 (intero a 32 bit). Nonostante ciò, esse 'possono anche richiedere tipi di dato molto più complessi, 'come in questo caso. La funzione che useremo necessita di 'un delegate come parametro. Public Delegate Function EnumCallback(ByVal Handle As IntPtr, _ ByVal lParam As Int32) As Boolean 'La funzione EnumDesktopWindows è definita nella libreria 'C:WINDOWSsystemsystem32user32.dll. Dato che si tratta di una 'libreria di sistema, possiamo omettere il percorso e scrivere solo 'il nome (provvisto di estensione). Come vedete, il nome con cui 'è dichiarata è lo stesso dl metodo definito 'in user32.dll. Per importarla correttamente, però, anche 'i parametri usati devono essere identici, almeno per tipo. 'Infatti, per identificare univocamente un metodo che potrebbe 'essere provvisto di overloads, è necessario sapere solo 'il nome del metodo e la quantità e il tipo di parametri. 'Anche cambiando il nome a un parametro, la signature non 'cambia, quindi mi sono preso la libertà di scrivere 'dei parametri più "amichevoli", poiché la 'dichiarazione originale - come è tipico del C - 'prevede dei nomi assurdi e difficili da ricordare. <DllImport("user32.dll")> _ Public Function EnumDesktopWindows(ByVal DesktopIndex As IntPtr, _ ByVal Callback As EnumCallback, ByVal lParam As Int32) As Boolean 'Notare che il corpo non viene definito End Function 'Questa funzione ha il compito di ottenere il titolo di 'una finestra provvisto in input il suo indirizzo (handle). 'Notare che non restituisce una stringa, ma un Int32. 'Infatti, la maggiore parte dei metodi definiti nelle librerie 'di sistema sono funzioni che restituiscono interi, ma questi 'interi sono inutili. Anche se sono funzioni, quindi, le si 'può trattare come banali procedure. In questo caso, 'tuttavia, l'intero restituito ha uno scopo, ed equivale 'alla lunghezza del titolo della finestra. 'GetWindowText, dopo aver identificato la finestra, 'ne deposita il titolo nello StringBuilder, che, essendo 'un tipo reference, viene sempre passato per indirizzo. 'Capacity indica invece il massimo numero di caratteri 'accettabili. <DllImport("user32.dll")> _ Public Function GetWindowText(ByVal Handle As IntPtr, _ ByVal Builder As StringBuilder, ByVal Capacity As Int32) As Int32 End Function Public Function GetWindowTitle(ByVal Handle As IntPtr) 'Crea un nuovo string builder, con una capacità 'di 255 caratteri Dim Builder As New System.Text.StringBuilder(255) 'Richiama la funzione di sistema per ottenere il 'titolo della finestra GetWindowText(Handle, Builder, Builder.Capacity) 'Dopo la chiamata a GetWindowText, Builder conterrà 'il titolo: lo restituisce alla funzione Return Builder.ToString End Function Public Function FoundWindow(ByVal Handle As IntPtr, _ ByVal lParam As Int32) As Boolean 'Ottiene il titolo della finestra Dim Title As String = GetWindowTitle(Handle) 'Scrive a schermo le informazioni sulla finestra Console.WriteLine("Handle {0:X8} - Titolo: {1}", _ Handle.ToInt32, Title) 'Restituisce sempre True: come già detto, i 'risultati delle funzioni di sistema non sono sempre 'utili, se non al sistema operativo stesso Return True End Function 'Enumera tutte le finestra Public Sub EnumerateWindows() 'Inizializza il metodo callback Dim Callback As New EnumCallback(AddressOf FoundWindow) Dim Success As Boolean 'Richiama la funzione di sistema. IntPtr.Zero come primo 'parametro indica che il desktop da considerare è 'quello correntemente aperto. Il secondo parametro 'fornisce l'indirizzo del metodo callback, che verrà 'chiamato ogni volta che sia stata trovata una nuova finestra Success = EnumDesktopWindows(IntPtr.Zero, Callback, 0) 'Se la funzione non ha successo, restituisce un errore If Success = False Then Console.WriteLine("Si è verificato un errore nell'applicazione!") End If End Sub Sub Main() Console.Clear() Console.WriteLine("Questo programma enumera tutte le finestre aperte.") Console.WriteLine("Premere un tasto qualsiasi per iniziare.") Console.ReadKey() EnumerateWindows() Console.ReadKey() End Sub End Module
Il meccanismo di fondo non è difficile. Quando si richiama la funzione EnumDesktopWindows, questa parte e cerca tutte le finestre
attive sul desktop: ogni volta che ne trova una richiama il delegate Callback passato come parametro, passandogli l'indirizzo della finestra
e un argomento aggiuntivo che non c'interessa. Il delegate, quindi, che nel nostro caso è FoundWindow, esegue le azioni necssarie, ossia
la stampa a video delle informazioni. Facendo correre il programma dovreste vedere una lista assai numerosa, molto di più di quanto
ci si sarebbe aspettato: questo accade perchè non vengono considerate finestre solo le windows forms, ma ci sono anche oggetti di
sistema nascosti (che poi non sono altro che processi) ed altri di natura ingannevole (ad esempio, la barra delle applicazioni è
essa stessa una finestra). Per ridurre un po' il numero di risultati, si potrebbe introdurre un controllo sul titolo, e imporre che non sia
Nothing. Tuttavia, l'importante è che abbiate capito come funziona il meccanismo del platform invoke (PInvoke). Documentazione
Il platform invoke è facile da usare, ma molto più difficile è sapere quali funzioni usare e dove trovarle. Se in un programma intuite di aver bisogno di funzioni di sistema, la prima cosa da fare è chiedere in un forum: ci sarà sicuramente qualcuno che conosce il metodo adatto da usare. Successivamente, potete fare una ricerca su MSDN, il sito documentativo ufficiale della Microsoft, e ottenere una spiegazione esatta di quello che la funzione fa e dei parametri richiesti. Alla fine, potete anche consultare PInvoke.NET, che espone una lista enorme di tutte le librerie di sistema e dei loro metodi, corredate di codice dichiarativo e talvolta anche di esempi. Quest'ultimo sito, inoltre, riporta sempre il numero e il tipo esatto di parametri da usare: vi ricordo, infatti, che in Visual Basic 6 i tipi numerici sono differenti da Visual Basic .NET e copiare un codice vecchio potrebbe causare un errore di sbilanciamento dello stack.
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|