Il CLR in un processo è come Highlander: ce ne può essere soltanto uno

di Alessandro Catorcini, in CLR Hosting,

Una delle ragioni principali per le quali il .NET Framework è diventato popolare molto rapidamente è che fa molto per proteggere i programmatori da se stessiò errori come doppi free, access violations, leaks, sono stati ridotti e in certe ipotesi resi impossibili. Purtroppo, il costo di più protezioni si paga con un accesso più ostico ai livelli più bassi del sistema e con il fatto che capire fino in fondo le assunzioni di base che si fanno quando si scrive codice in .NET non è semplice.

Questo è il primo di una serie di posting che parleranno di modi non ovvi in cui le cose possono andare storte, e su come si può uscire da situazioni difficili.

Il primo argomento di cui voglio parlare è una limitazione poco conosciuta, ma dalle conseguenze imprevedibili nei casi in cui si violino i suoi invarianti: quando gira codice di .NET in un processo, può essere caricata solo una versione del CLR, e una volta caricata, non può essere scaricata o fatta ripartire da zero. Solo Silverlight fa eccezione a questa regola e può coesistere con un desktop CLR nello stesso processo.

Quando un'applicazione (per esempio Microsoft Office o Adobe Photoshop) espone un modello di estensibilità tramite COM, questo consente a codice di terze parti (l'add-in) di interagire con le strutture dell'applicazione, spesso all'interno dei confini del processo. E' del tutto possibile (e piuttosto banale) esporre tipi di .NET come oggetti COM. Oppure è piuttosto semplice seguire le ricette che anche alcuni articoli di MSDN presentano per scrivere piccole shim che usano le hosting API del CLR per fare partire codice di .NET nel processo (vedi l'articolo di Siew-Moi Khor e Andrew Whitechapel "Isolating Microsoft Office Extensions with the COM Shim Wizard Version 2.0") - questo consente di aggirare il fatto che senza una shim dedicata non è possibile firmare il codice con Authenticode.

Non c'è nulla di sbagliato nel farlo. Quello dal quale bisogna guardarsi sono le assunzioni che si fanno.

Questo non è un problema quando si ha a che fare con un processo host che è scritto in modo da prevedere che qualcuno degli add-in nel processo potrebbero richiedere di avere un arbitro se ci fossero delle richieste divergenti, ma può creare un problema quasi indebuggabile altrimenti.

Tempo fa mi trovai davanti ad un problema interessante. Un grosso cliente usava un add-in di Excel per fare dei complicati calcoli finanziari, e la potenza di .NET aveva fatto sì che avessero portato tutta l'applicazione da VBA e COM. Eravamo ancora agli albori di VSTO, quindi l'add-in era una semplice shim che chiamava CoreBindToRuntimeEx per caricare in memoria il CLR ed eseguire gli assembly dell'applicazione.

Le performance erano ottime e i risultati della sperimentazione iniziale altamente positivi. Nonappena iniziarono il deploy, però, circa il 40% delle workstation crashava Excel al boot. L'unico rimedio sembrava disinstallare in nuovo add-in. Il problema sembrava essere completamente casuale, presentandosi su macchine con le stesse applicazioni installate.

Dopo un paio di giorni arrivammo alla soluzione: quello che importava era non solo la presenza delle stesse applicazioni, ma l'ordine nel quale erano state installate. Questo perché Excel caricava gli add-in nell'ordine in cui erano stati installati, e uno degli add-in che erano installati da un'altra suite di utility era stato scritto usando .NET 1.1.

La sequenza delle operazioni quindi diventava

  1. Fai il boot di Excel
  2. carica il primo add-in scritto con .NET
  3. carica e fai partire il CLR specificato carica gli assembly richiesti

A questo punto se per caso il CLR caricato e fatto partire era la versione 1.1, l'add-in compilato per 2.0 falliva il load, e l'eccezione non gestita veniva propagata tramite COM fino alla soppressione del processo.

Successivi Service Pack di Office e l'avvento di VSTO hanno oggi risolto questo problema almeno per Office, ma ogni volta che si carica o si usa codice .NET in un processo che non è in grado di arbitrare le versioni del runtime, bisogna ricordarsi di gestire il caso in cui il proprio non sia l'unico add-in che cerca di caricare il CLR nel processo, e che molto probabilmente gli altri non l'hanno fatto ;-)

Per quelli di voi che scrivono applicazioni estensibili tramite COM, se volete supportare veramente add-in scritti usando il .NET Framework dovete scrivere un arbitro per il loader del CLR. Farlo è piuttosto semplice: basta registrare una callback tramite LockCLRVersion e dirottare tutte le chiamate che provano a caricare a fare partire un CLR a passare dal vostro codice di arbitraggio.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese