Dipendenze indesiderate in progetto per Compact Framework che utilizzi P/Invoke
Questa mattina vi voglio rendere partecipi di un problema che ho riscontrato durante la creazione di un setup project per smart device, soprattutto se il progetto richiede l’utilizzo di P/Invoke. Questo problema causa la comparsa di una dipendenza verso l’assembly mscorlib.dll del .NET Framework 2.0, e di conseguenza la creazione di un pacchetto di installazione (CAB) un po’ più grande del dovuto.
Andiamo con ordine. Supponiamo di avere una solution composta da due progetti separati:
- una normale Device Application per PPC/WM (che chiameremo TestForDeploy)
- un Smart Device CAB Project che si occupa di installare l’applicazione al punto 1 (che chiameremo Deploy)
Il template dell’applicazione generato da Visual Studio (sia 2005 che 2008) crea un’applicazione perfettamente funzionante. C’è solo una Windows Form che lui chiama Form1. Di conseguenza, le dipendenze richieste da entrambi i progetti sono puliti e sono il minimo indispensabile.
Ho volutamente espansi i nodi References e Detected Dependencies per mostrare l’elenco degli assembly richiesti dall’uno e dall’altro progetto.
Ora supponiamo che la nostra applicazione TestForDeploy debba utilizzare una COM Library scritta in C++. Per farlo dobbiamo far uso di una tecnologia chiamata P/Invoke: in pratica di tratta di creare una classe wrapper che mappa tutte le funzioni esposte dalla libreria natina. Questa classe wrapper viene creata on-demand da Visual Studio nel momento in cui la aggiungiamo ai riferimenti. Otteniamo lo stesso risultato utilizzando il tool da linea di comando tlbimp.exe, che crea un’assembly .NET partendo da una .dll in native code.
Ora, supponiamo di avere una libreria in D:Temp e che essa si chiami ComLibrary.dll.
Nel momento in cui la aggiungiamo alle References richieste da TestForDeploy, cambiano un bel po’ di cose, come si vede dallo screenshot qui sotto:
Ho tracciato delle linee rosse per nascondere la libreria, il cui nome rileva informazioni che preferisco non divulgare.
Osservate le Detected Dependencies rilevate dal progetto di setup: esso contiene l’assembly mscorlib due volte. Uno è l’mscorlib.dll del Compact Framework, mentre l’altro è l’mscorlib.DLL del .NET Framework per Desktop. Il resto dei files è corretto: viene inclusa la libreria in native code e viene incluso l’assembly che contiene la classe wrapper.
Se mantenessimo le cose così come sono, il file .CAB installerebbe su PPC/WM anche l’assembly mscorlib.DLL del .NET Framework. Onestamente, non so dire cosa accade: so solo che il .CAB raddoppia le sue dimensioni (da circa 3Mb passa a 6Mb – proprio a causa di quella dipendenza in più).
Di chi è la colpa? Googlando ho trovato diverse informazioni sui forum di Microsoft. Sui post si legge che la creazione della classe wrapper (e quindi l’uso più o meno esplicito di tlbimp.exe) prevede la dipendenza da mscorlib.DLL. Quindi sembra essere un problema di questo tool. Ho provato a lanciarlo da linea di comando per vederne i parametri (l’avevo studiato per qualche esame di certificazione, ma chi se lo ricordava più??). Esiste un parametro /reference che serve per indicare uno o più assembly che tlbimp.exe deve utilizzare per risolvere la definizione dei tipi, ma non è servito a nulla.
L’unico possibile workaround è quello di escludere quel mscorlib.DLL dal progetto di setup. Come? Semplicemente cliccandoci sopra col destro e selezionando Exclude dal menù contestuale, come si vede qua sotto:
Purtroppo l’informazione su quali files escludere non è persistita da nessuna parte da Visual Studio. Ciò significa che la volta successiva in cui aprirete l’IDE vi ritroverete nuovamente quel file incluso, e non c’è verso di fargli capire che non lo si vuole installare.
La feature di Detected Dependencies è propria dell’IDE di Visual Studio. Perchè dico questo? Perchè se avete rimosso il file come vi ho fatto vedere e utilizzare il tool msbuild.exe per rigenerare il file .CAB per la vostra installazione, quel file a tutti gli effetti sparisce dal vostro CAB.
Dovete solo ricordarvi di toglierlo ogni volta, prima di rigenerare il CAB. Ritengo comunque che se il problema fosse gestito a monte, all’interno della classe wrapper per intenderci, sarebbe molto meglio.