Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[70-536, #31] P/Invoke e l'attributo DllImport

Introduzione
P/Invoke è l'abbreviazione di Platform Invoke  e consiste nella capacità da parte del nostro managed code di utilizzare DLL di sistema ed eseguire le funzioni contenute. Ho trovato di grande comodità il sito www.pinvoke.net, che ci aiuta con una grande raccolta di tutte le librerie di sistema e come poterle importare in una classe managed per rendere accessibile da C#, VB.NET e da tutti gli altri linguaggi della famiglia .NET. Anche in questo caso, come nel mio ultimo post, abbiamo a che fare con una classe wrapper: sarà questa che il codice managed eseguirà. Tale classe deve essere decorata dall'attributo DllImport, specificando il nome del file DLL che si vuole utilizzare in questa chiamata. Il namespace di riferimento per tutto quello che riguarda P/Invoke è System.Runtime.InteropServices.

Una chiamata semplice a GetTempPath
La funzione GetTempPath è funzione contenuta nel file kernel32.dll di Windows. Partendo da questo presupposto, in C# possiamo dichiarare una classe statica PInvoke ed inserire un metodo definito così:

static class PInvoke
{
    [DllImport("kernel32.dll")]
    
public static extern uint GetTempPath(uint nBufferLength,
       [Out] StringBuilder lpBuffer);
}

Il metodo è marcato come statico e come extern, che esplicita il fatto che il metodo è implementato esternamente rispetto all'assembly. Il secondo parametro è un oggetto StringBuilder che verrà restituito (notare la keyword out ) e conterrà il path per i files temporanei restituito dall'OS. Questo è un caso estremamente semplice. L'unica verrà difficoltà è alla fin fine mappare correttamente i tipi tra il mondo managed ed il mondo unmanaged. Per questo risorse on-line come www.pinvoke.net sono estremamente importanti ed utili, perchè hanno fatto gran parte del lavoro e ci basta copiare & incollare.

Torniamo a noi. Una volta dichiarata la classe ed il metodo, chiamarlo non è per nulla diverso a qualsiasi altro metodo statico scritto in C#.

StringBuilder bld = new StringBuilder();
PInvoke.GetTempPath(255, bld);
string tempPath = bld.ToString();

Quando ha senso usare P/Invoke? In primo luogo, quando una certa funzionalità non è esposta dal Framework . Ad esempio, la libreria kernel32.dll espone la funzione CopyFileEx, che però è direttamente utilizzabile da codice managed. P/Invoke è molto utile, ripeto, se vogliamo interagire con componenti COM che al contrario non sono disponibili in .NET. Questo articolo su CodeProject ad esempio dimostra come usare P/Invoke con Windows Media Player, cosa che può essere fatta tra l'altro con le tecniche viste ieri (ovvero aggiungere come riferimento il file WMP.dll ed accedere così all'object model).

Ovviamente P/Invoke non funziona solo con le librerie di sistema, ma con qualsiasi libreria COM scritta in C++, comprese quindi le nostre.

Usiamo P/Invoke, ma usiamolo pulito!
In alcune occasioni, P/Invoke è davvero necessario, per cui siamo obbligati per forza di cose a servirci delle sue potenzialità. Però dobbiamo pensare bene alle complicazioni ed agire di conseguenza.

Le Windows API ritornano le condizioni di errori come banali costanti. Questo è in netta contrapposizione con la logica delle Exception messa in campo da .NET e dal framework. Per ovviare a questo inconveniente, è opportuno costruire una classe wrapper che converta un HRESULT restituito da Windows nella Exception più opportuna. Questa classe wrapper può inoltre nascondere l'accesso al mondo unmanaged: in pratica, una best practice da applicare è rendere privata la dichiarazione del metodo extern e creare invece un metodo public di buffer che sarà quello che useremo realmente nel nostro codice.

Vediamo di applicare nel concreto queste best practices. Vogliamo sempre utilizzare la funzione API GetTempPath esportata dalla libreria kernel32.dll. Ho creato una classe PInvoke, il cui codice è:

sealed class PInvoke
{
    
public static string GetTemporaryPath()
    {
        StringBuilder bld = 
new StringBuilder();
        
uint ret =  GetTempPath(255, bld);
        
return(bld.ToString());
    }

    [DllImport("kernel32.dll")]
    
private static extern uint GetTempPath(uint nBufferLength,
       [Out] StringBuilder lpBuffer);
}

Ho riportato due metodi. Un metodo pubblico GetTemporaryPath, scritto completamente in codice managed. Un metodo privato GetTempPath che invece è extern, ed è in unmanaged code. Tale classe sarà accessibile dal chiamante solo attraverso il metodo pubblico, come è normale che sia. Questo modus operandi rende possibili alcune migliorie al codice che abbiamo scritto:

  1. quando il Framework renderà eventualmente disponibile la funzionalità alla quale adesso accediamo attraverso P/Invoke, ci basterà modificare l'implementazione del metodo pubblico GetTemporaryPath ed il gioco è fatto.
  2. così facendo, isoliamo tutto quello che riguarda P/Invoke. Questo ci permette di limitare le porzioni di codice interessate ad eventuali crash o a problemi dovuti ad Interop.
  3. come dicevo prima (ed è domanda di esame) possiamo mascherare gli errori ritornati da Win32 in opportune exception. Nel codice qui sopra, per esempio, memorizza nella variabile ret di tipo uint il valore ritornato dalla chiamata: mi basterebbe implementare un costrutto switch per intercettare eventuali errori ed agire di conseguenza.

Come usare DllImport
L'attributo DllImport ci permette di indicare in quale libreria viene esportata una certa funzione. Questo attributo ha diverse proprietà importanti. EntryPoint ci permette di specificare il nome della funzione contenuta nella libreria, nel caso in cui il nome sia diverso rispetto a quello che indichiamo noi nella dichiarazione del metodo. CharSet va impostato su CharSet.Auto, e ha importanza solo se la funzione esportata utilizza le stringhe; questo permette al CLR di utilizzare il set di caratteri corretto in base all'OS su cui si sta eseguendo il codice.

powered by IMHO 1.2

Print | posted on Wednesday, June 7, 2006 11:42 AM | Filed Under [ Esame 70-536 ]

Feedback

Gravatar

# [70-536, #32] L'attributo DllImport nei dettagli

6/12/2006 3:07 PM | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET