Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - Il Platform Invoke

Guida al Visual Basic .NET

Capitolo 90° - Il Platform Invoke

<< Precedente Prossimo >>

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.
Per importare un metodo da una dll di sistema si possono usare due differenti modalità. La prima - quella migliore per .NET - consiste nell'utilizzare l'attributo DllImport:

<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).
Il secondo modo è esattamente ripreso tale e quale dal Visual Basic 6:

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.
Nell'esempio che segue userò l'attributo DllImport, perchè è la scelta più corretto in ambito .NET. Questo semplice programma trova tutte le finestre aperte sullo schermo e ne comunica all'utente titolo e indirizzo di memoria (handle).

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.

<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...