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, #32] L'attributo DllImport nei dettagli

I parametri dell'attributo DllImport
Nell'ultimo post riguardante l'esame 70-536, abbiamo visto come utilizzare l'attributo DllImport per poter utilizzare una funzione esportata da una libreria unmanaged. Nella sua forma base, DllImport richiede un solo parametro: una stringa che contiene il nome del file DLL che esporta la funzione che ci interessa utilizzare nella nostra applicazione managed. L'ultima volta avevamo fatto un piccolo esempio, considerando la funzione GetTempPath, esportata da kernel32.dll. la dichiarazione nella nostra classe managedera:

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

L'attributo DllImport può avere qualche parametro aggiuntivo. Ad esempio, nel codice qui sopra il nome della funzione GetTempPath corrisponde al nome della funzione contenuta in kernel32.dll. Se per qualche motivo il nome della funzione managed contenuta nel prototipo differisce dal nome della funzione contenuta nella libreria unmanaged, dobbiamo fornire al framework l'entrypoint corretto. Ad esempio:

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

Il parametro EntryPoint di DllImport nella dichiarazione qui sopra dice semplicemente che il nome della funzione in managed code è GetTemporaryPath, che punta all'entrypoint GetTempPath della libreria kernel32.dll. Un secondo possibile parametro è CharSet, che riveste un'importanza fondamentale nel caso in cui dobbiamo lavorare con le stringhe: impostando questo parametro su CharSet.Auto, ci assicuriamo che le stringhe vengono passate correttamente dal mondo managed al mondo unmanaged, e viceversa. Per il marshaling, il CLR si basa sul sistema operativo su cui sta girando l'assembly.

Il default marshaling e l'attributo MarshalAsAttribute
Il default marshaling è il comportamento di default che .NET assume ogni volta che deve passare oggetti tra il mondo managed ed il mondo unmanaged. Tecnicamente, è espresso da una serie di tabelle che esprimono come un tipo managed viene convertito nel tipo unmanaged. Ad esempio, questa tabella considera i value-typed di .NET. Tener presente l'attributo MarshalAs, grazie al quale possiamo modificare questo comportamento, comunicando esplicitamente al CLR come effettuare il marshaling in un particolare frangente. Caso tipico sono le stringhe, che in base al tipo unmanaged (LPStr, BStr, LPWStr, etc.) possono subire il marshaling verso il contesto managed secondo diverse tipi di destinazione.

Fare il marshaling di strutture
Esiste una pagina su MSDN che descrive accuratamente questo scenario. Fino ad adesso, abbiamo trattato con tipi di dato semplici, ad esclusione delle stringhe dove abbiamo a che fare innanzitutto con il CharSet, in secondo luogo con stringhe a lunghezza fissa o variabile (dove quindi dobbiamo usare il tipo string oppure la classe StringBuilder).

Cosa succede se dobbiamo invece trasferire intere strutture dati? Ne dobbiamo fare ovviamente il marshaling, ma come? Dobbiamo capire essenzialmente che il codice del mondo managed ed il codice del mondo unmanaged formattano i dati in memoria in modo diverso. Dobbiamo trovare quindi una strada per dire al CLR di non mappare i dati in memoria come vorrebbe lui, ma di rispettare determinate logiche. Questa strada è disponibile, ancora una volta, con un attributo .NET dedicato: StructLayout. Questo attributo prende come parametro un valore dell'enum LayoutKind, che esprime la modalità con cui i campi della nostra struttura vengono mappati in memoria. I valori di questo enum più interessanti sono principalmente due:

  1. Explicit: siamo noi che indichiamo al CLR come mappare (uno ad uno) i campi della struttura. Attraverso l'attributo FieldOffset, esprimiamo in bytes la posizione fisica di un campo all'interno di una classe o di una struttura. Questo vale ovviamente sia nel mondo managed, che in unmanaged.
  2. Sequential: i campi vengono mappati in memoria sequenzialmente, nello stesso ordine con cui questi servono dal lato unmanaged della nostra applicazione. I campi possono essere non contigui

Funzioni di callback
In altri scenari serve una funzione di callback, ovvero una funzione scritta in managed code che viene eseguita al termine dell'esecuzione di una funzione unmanaged. In altre parole, dal nostro codice .NET eseguiamo una funzione nativa attraverso P/Invoke, la quale deve sapere cosa eseguire quando la sua esecuzione termina. In realtà, come vedremo fra poco, non è detto che la callback serva solamente al termine dell'esecuzione: la funzione unmanaged potrebbe utilizzare la callback quando necessario, magari per avvisare il mondo .NET. Anche in questo caso, possiamo fare riferimento a questa pagina su MSDN per maggiori dettagli. La tecnica consiste nell'utilizzare un delegate, che non è nient'altro che un puntatore a funzione in ambiente .NET. L'esempio su MSDN tratta un caso molto particolare: attraverso P/Invoke, utilizziamo la funzione EnumWindows, la quale richiede una callback, che viene eseguita ogni volta che la funzione unmanaged lo richiede. Come facciamo a capire quando una funzione unmanaged richiede una callback? Bisogna leggere con attenzione il prototipo della funzione stessa, perchè dal tipo e dal nome dei parametri possiamo arrivare a capirlo. Il prototipo C++ della funzione EnumWindows è il seguente:

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)

Ciò significa che se vogliamo usare questa funzione unmanaged dobbiamo semplicemente convertire il primo parametro in un delegate:

public delegate bool CallBack(int hwnd, int lParam);

e conseguente utilizzare questo delegate come funzione di callback.

powered by IMHO 1.2

Print | posted on Monday, June 12, 2006 3:08 PM | Filed Under [ Esame 70-536 ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET