Gestione di Assembly e Componenti in .NET

Fino a qualche anno fa la creazione di applicazioni per componenti ridistribuibili avveniva attraverso l’uso di COM ( Component Object Model ) con tutte le problematiche che questo comportava: scrivere una grande quantità di codice per implentare le interfacce COM come IUnknown, IDispatch ed altre, registrare queste interfacce nel registro di sistema di Windows con tutti i possibili errori del caso. Oggi abbiamo il framework .NET, che per certi aspetti è un’evoluzione di COM e permette di superare le difficoltà del vecchio metodo, infatti è possibile usufruire di tutte le potenzialità di COM ed allo stesso tempo avere delle classi compatibili ed interoperabili con le vecchie versioni del componente, registrazione senza intervenire nel registro di sistema, scrivere componenti senza bisogno di implementare interfacce di infrastruttura, avere l’ereditarietà indipendentemente dal linguaggio utilizzato, avere dei componenti riutilizzabili su varie piattaforme. La base di funzionamento del riutilizzo binario del codice in .NET è costituita dal CLR ( Common Language Runtime ), un ambiente d’esecuzione comune a tutti i linguaggi di programmazione supportati. Il codice eseguito dal runtime parte dalla compilazione di un codice intermedio denominato IL ( Intermediate Language ).
In .NET l’unità fondamentale è l’Assembly che è un contenitore di tipi come strutture, classi ed altro, che può avere un entry point eseguibile o utilizzato per esporre i tipi che contiene. I tipi all’interno di un Assembly sono organizzate in modo gerarchico attraverso delle strutture chiamate Namespace. All’interno di un Namespace troviamo altri Namespace e classi organizzate gerarchicamente, ad esempio per poter utilizzare le funzioni per le finestre dobbiamo accedere ai tipi contenuti nel Namespace Forms che a sua volta si trova in Windows e che a sua volta risiede in System, il Namespace principale e base di tutti gli altri. Quindi in un’applicazione C# dovremmo scrivere:

using System.Windows.Form

mentre in Visual Basic .NET:

Imports System.Windows.Forms

Gli Assembly destinati a fornire codice condiviso si possono suddividere in: Privati ( Private ) o Condivisi ( Shared ), i primi sono destinati alla sola fornitura di servizi per una specifica applicazione, mentre i secondi soddisfano le esigenze di più applicazioni. Gli Assembly Shared possono essere inseriti all’interno di un contenitore centrale denominato Global Assembly Cache che è uno storage dove vengono inserite fisicamente le dll. Gli Assembly hanno la capacità di autodescriversi, da qui la semplificazione nella loro gestione, la parte descrittiva di un Assembly prende il nome di metadati. In essi abbiamo la descrizione della struttura del componente, dei tipi in esso contenuti, in particolare i riferimenti alle risorse, parte dell’Assembly che prende il nome di Manifest.
Gli Assembly in .NET vengono eseguiti all’interno di strutture denominate AppDomain ( Application Domain ) che possono a loro volta contenere gli Assembly in esecuzione su uno o più Thread. Riferimenti diretti tra i diversi Assembly possono avvenire solo in quelli facenti parte della stessa AppDomain, altrimenti, anche se fanno parte dello stesso processo, devono utilizzare l’infrastruttura di Remoting di .NET.
Alla base dell’identità di un Assembly abbiamo il concetto di Assembly Reference (AsmRef ) che serve a rendere distinguibile un componente dagli altri. L’Assembly Reference è costituito dal nome univoco del componente, dalla versione numerica e dalla cosiddetta Culture o nazionalità della versione. Se utiliziamo Visual Studio ad ogni progetto viene aggiunto uno specifico file contenente le informazioni relative all’Assembly ( AssemblyInfo ). Gli attributi possono anche essere aggiunti direttamente nel file sorgente, occorre solo il riferimento a System.Reflection e System.Runtime.CompilerServices.

// File : prova.cs

using System;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyDescription("Componente di prova")]
[assembly:AssemblyCompany("RGPSoft")]
[assembly:AssemblyCopyright("2011 - Giampaolo Rossi")]

namespace Prova
{
   public class ProvaSaluto
   {
      public void Saluto()
     {
        MessageBox.Show("Ciao a tutti!");
     }
   }
}

In questo semplice esempio abbiamo messo in pratica quello che abbiamo detto finora, per compilare il componente basta dare questo comando:

csc /t:library /r:system.windows.forms.dll prova.cs

Noterete che non abbiamo fatto riferimento a mscore.dll che contiene il Namespace System, perché viene automaticamente referenziata dal compilatore. Ora passiamo ad analizzare l’Assembly creato tramite lo strumento fornitoci dal framework ILDASM ( ildasm.exe ). Basta dare il comando:

ildasm prova.dll

e compare la finestra del programma che mostra le caratteristiche del nostro Assembly, se facciamo clic sul Manifesto abbiamo le informazioni di dipendenza e di definizione del nostro Assembly.

ildasm

Informazioni con ILDASM dell'Assembly prova.dll

Ora creiamo il client che utilizza il nostro componente precedentemente creato:

// File: client.cs

using System;
using Prova;

public class Client
{
   public static void Main()
   {
       ProvaSaluto obj = new ProvaSaluto();
       obj.Saluto();
   }
}

Per compilare la nostra applicazione che fa uso del componente “Prova” basta dare il comando:

csc /r:prova.dll client.cs

Ultima informazione che voglio darvi è il concetto di PrivatePath che è la directory in cui l’applicazione viene eseguita. Nel momento in cui il codice client va in esecuzione, il runtime legge il manifest nell’Assembly e trova i riferimenti al nostro private Assembly “Prova”, a questo punto viene controllato il PrivatePath tramite un file di configurazione che deve avere lo stesso nome dell’eseguibile con estensione .config e posizionato nella stessa cartella del file eseguibile. Il file config deve avere la seguente struttura XML che consente di modificare il PrivatePath:

<configuration>
   <runtime>
	<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
		<probing privatePath="..." />
	<assemblyBinding>
   </runtime>
</configuration>

In questo articolo abbiamo introdotto le caratteristiche fondamentali del riutilizzo binario del codice nel framework .NET descrivendo il concetto di Assembly, quindi abbiamo creato un Private Assembly e lo abbiamo utilizzato. In futuro potremmo parlare degli Shared Assembly e vedere come il .NET gestisce e risolve il problema del versioning degli Assembly tra più applicazioni e vedere anche come utilizzare i vecchi oggetti COM all’interno del .NET framework.

Informazioni su Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.
Questa voce è stata pubblicata in Programmazione e contrassegnata con . Contrassegna il permalink.