WinRT mette a disposizione la possibilità di creare componenti che possono essere usati direttamente tramite uno qualunque dei linguaggi supportati, senza le complicazioni derivanti dal marshalling dei dati.
Una libreria Windows Runtime è infatti un componente scritto in C#, VB o C++ (ma non in JavaScript) che può essere usato da qualunque applicazione Windows Store, indipendentemente dal linguaggio utilizzato per svilupparla (incluse, in questo caso, applicazioni HTML/JavaScript).
Nell'articolo dedicato ai background task, abbiamo già avuto modo di lavorare con un componente Windows Runtime. In questo articolo approfondiremo i concetti e l'architettura che stanno dietro a un Windows Runtime Component e vedremo quali sono le regole da rispettare per poter creare un nostro componente Windows Runtime.
WinRT e WinMD
Per comprendere meglio come funziona un componente WinMD, è necessario partire dall'architettura di WinRT. Sin dalle sue prime versioni, infatti, Microsoft Windows ha messo a disposizione degli sviluppatori librerie e API per interagire con il sistema operativo. Tuttavia, prima di Windows 8 queste API e librerie erano spesso complesse e non facili da usare. Inoltre, anche lavorando con il framework .NET non era raro dover interagire con Component Object Model (COM) Interop e Win32 Interoperability via P/Invoke (Platform Invoke) per poter sfruttare le funzionalità del sistema operativo.
Il seguente snippet in C# mostra un esempio dei passaggi necessari, prima di WinRT, per importare due DLL native Win32 all'interno del mondo .NET allo scopo di catturare un'immagine dalla webcam del proprio PC. Come si può vedere, la sintassi non brilla certo per chiarezza ed è facile commettere errori.
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindow")]
static extern int capCreateCaptureWindow(
string lpszWindowName,
int dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
int hwndParent,
int nID);
[DllImport("avicap32.dll")]
static extern bool capGetDriverDescription(
int wDriverIndex,
[MarshalAs(UnmanagedType.LPTStr)] ref string lpszName,
int cbName,
[MarshalAs(UnmanagedType.LPTStr)] ref string lpszVer,
int cbVer);
Consapevole di questa complessità, con Windows 8 e WinRT Microsoft ha semplificato notevolmente l'interazione con il sistema operativo. In effetti, WinRT mette a disposizione un insieme di API completamente ridisegnate con l'obiettivo di offrire agli sviluppatori un meccanismo di interazione con il sistema operativo molto più semplice, senza le complicazioni proprie di P/Invoke e Interop.
Il prossimo snippet mostra come, grazie a WinRT, la sintassi utilizzata per raggiungere lo stesso obiettivo visto prima (scattare una foto dalla webcam) risulti estremamente più semplice e pulita, e conseguentemente più facile da manutenere.
var camera = new CameraCaptureUI();
var file = await camera.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (file != null)
{
var bitmap = new BitmapImage();
bitmap.SetSource(await file.OpenAsync(FileAccessMode.Read));
Photo.Source = bitmap;
}
Se, invece di XAML/C#, volessimo usare HTML5 e JavaScript, il codice risulterebbe estremamente simile a quello della versione in C#, come illustrato nel seguente estratto:
var camera = new capture.CameraCaptureUI();
camera.captureFileAsync(capture.CameraCaptureUIMode.photo)
.then(function (file) {
if (file != null) {
media.shareFile = file;
}
});
L'obiettivo principale di WinRT è quello di unificare l'esperienza di sviluppo di un'applicazione Windows Store, indipendentemente dal linguaggio utilizzato. Dal punto di vista architetturale, WinRT poggia sul Windows Runtime Core, ossia su un insieme di librerie C++ che fungono da ponte tra WinRT e il sottostante sistema operativo. La prossima immagine, presa dalla documentazione ufficiale di MSDN, mostra l'architettura complessiva di WinRT.
Il Windows Runtime Core sfrutta internamente un insieme proprietario di tipi di dati. Ad esempio, HSTRING è il tipo che rappresenta un valore testuale in WinRT, ma esistono anche tipi numerici come INT32 e UINT64, collezioni come IVector<T>
, enum, strutture, classi del runtime, ecc.
Subito sopra il Windows Runtime Core troviamo una serie di librerie e tipi specifici a disposizione di qualunque applicazione Windows 8. Ad esempio, esistono librerie per effettuare richieste in rete e per accedere allo storage (locale o remoto); picker per selezionare file, immagini, date, ecc.; nonché un insieme di classi in grado di sfruttare i media service, e così via.
Tutti questi tipi e librerie sono definiti in un insieme strutturato di namespace e sono descritti da metadati che prendono il nome di Windows Metadata (WinMD) e utilizzano lo stesso standard utilizzato dal framework .NET per la definizione dei metadati, ossia il Common Language Interface (ECMA-335).
Per poter consumare questi tipi da qualunque linguaggio di programmazione supportato, WinRT sfrutta un apposito projection layer che, grazie ai Windows Metadata, provvede a "tradurre" i tipi interni di WinRT in quelli del linguaggio di destinazione. Ad esempio, il tipo HSTRING
sopra menzionato viene tradotto nel System.String
di .NET, nel caso di un'applicazione basata sul Common Language Runtime (CLR, VB/C#), ovvero in un Platform::String
nel caso di un'applicazione sviluppata in C++.
In questo modo, il projection layer permette di esporre i tipi di WinRT tramite classi e oggetti propri del linguaggio prescelto (per una mappatura completa dei diversi tipi WinRT in .NET si rinvia alla documentazione ufficiale MSDN).
Possiamo facilmente ispezionare i file contenenti i metadati che definiscono i tipi di WinRT. Per default, questi file sono memorizzati sotto C:\Windows\System32\WinMetadata
. Il contenuto di default della directory contenente i file WinMD è il seguente:
Windows.ApplicationModel.winmd
Windows.Data.winmd
Windows.Devices.winmd
Windows.Foundation.winmd
Windows.Globalization.winmd
Windows.Graphics.winmd
Windows.Management.winmd
Windows.Media.winmd
Windows.Networking.winmd
Windows.Security.winmd
Windows.Storage.winmd
Windows.System.winmd
Windows.UI.winmd
Windows.UI.Xaml.winmd
Windows.Web.winmd
Come si può vedere, nella directory compare un file denominato Windows.Media.winmd, il quale contiene la definizione della classe CameraCaptureUI
usata in apertura di questo articolo. È possibile ispezionare i vari file WinMD tramite l'Intermediate Language Disassembler (ILDASM), un semplice tool disponibile nel Microsoft .NET SDK sin dal 2002, a sua volta distribuito assieme a Microsoft Visual Studio o scaricabile come parte del Microsoft .NET Framework SDK.
La prossima immagine mostra la finestra dell'ILDASM con la struttura del file Windows.Media.winmd con evidenziata la definizione del tipo CameraCaptureUI
già menzionato.
Con un doppio click sul nome del metodo è possibile vedere la sua definizione, che non è rappresentata dal codice sorgente del metodo stesso (il componente è infatti scritto in linguaggio nativo), quando piuttosto dai metadati che consentono di "mapparlo" con la libreria nativa utilizzata dietro le quinte. Nel seguente estratto è possibile osservare la definizione dei metadati del metodo CaptureFileAsync
esposto dalla classe CameraCaptureUI
.
.method public hidebysig newslot virtual final
instance class [Windows.Foundation]Windows.Foundation.IAsyncOperation`1
CaptureFileAsync([in] valuetype Windows.Media.Capture.CameraCaptureUIMode mode) runtime managed
{
.override Windows.Media.Capture.ICameraCaptureUI::CaptureFileAsync
} // end of method CameraCaptureUI::CaptureFileAsync
.method public hidebysig newslot virtual final
Spetterà poi all'infrastruttura del language projection il compito di tradurre questa definizione neutrale nel tipo equivalente nel linguaggio prescelto.
Creare una libreria WinMD
Ora che abbiamo chiarito meglio il ruolo giocato dai componenti Windows Runtime all'interno della complessiva architettura di WinRT, è giunto il momento di creare una nostra libreria di API per renderla disponibile all'interno di qualunque applicazione Windows Store, sfruttando il meccanismo di language projection visto in precedenza.
Internamente, i tipi WinRT del componente possono usare qualunque funzionalità del framework .NET accessibile da un'applicazione Windows 8. Esternamente, tuttavia, i tuoi tipi devono essere conformi a una serie rigorosa di requisiti (per l'approfondimento di queste regole si rinvia alla documentazione ufficiale di MSDN). Le principali regole da rispettare possono essere sintetizzate come segue:
- I campi, i parametri e i valori restituiti da tutti i tipi e membri pubblici del componente WinMD devono essere tipi supportati da WinRT, in modo da consentire l'opera di mappatura da parte del projection layer.
- Le classi (e interfacce) pubbliche non possono essere generiche, non possono derivare da tipi (o implementare interfacce) che non sono propri di WinRT (come ad esempio
System.Exception
). - Le strutture (
struct
) pubbliche possono non avere altri membri se non campi pubblici, e questi campi devono essere di tipovalue
o stringhe. - Le classi pubbliche devono essere
sealed
, il che significa che non possono essere derivate in sotto-classi. Se il modello di programmazione richiede l'uso del polimorfismo, possiamo creare un'interfaccia pubblica e implementarla sulle classi che devono essere polimorfiche. Le uniche eccezioni sono costituite dai controlli XAML. - Tutti i tipi pubblici devono avere la radice del namespace che coincide con il nome dell'assembly, e quest'ultimo non deve iniziare con "Windows.
- Per implementare metodi asincroni nel componente WinMD, è necessario aggiungere il suffisso "Async" al nome del metodo e restituire una delle interfacce di Windows Runtime che rappresentano operazioni asincrone, come:
IAsyncAction
,IAsyncActionWithProgress<TProgress>
,IAsyncOperation<TResult>
,o IAsyncOperationWithProgress<TResult, TProgress>
.
Chiarite le regole che devono essere rispettate per creare un componente WinMD, proviamo adesso a creare un componente in C# per poi utilizzarlo all'interno di applicazioni Windows Store sviluppate in altri linguaggi.
Come primo passo, in Visual Studio creiamo un progetto di tipo Windows Runtime Component in C#, come illustrato nella prossima immagine:
È interessante notare come in Visual Studio la classe aggiunta di default al progetto sia già marcata come sealed
, in ossequio alle regole sopra elencate. Rinominiamo la classe in SampleTool
e aggiungiamo il seguente metodo:
namespace Demo.Html.it.MyWinMDLibrary.CS
{
public sealed class SampleTool
{
publicBoolean IsMailAddress(String email)
{
var regexMail = new System.Text.RegularExpressions.Regex(@"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b");
return (regexMail.IsMatch(email));
}
}
}
Se ora compiliamo il progetto e navighiamo fino alla sotto-directory bin\Debug
, troveremo il file Demo.Html.it.MyFirstWinMDComponent.CS.winmd
con la definizione del nuovo tipo e del relativo metodo IsMailAddress
.
La nostra libreria di API è pronta, nella prossima puntata vedremo come utilizzarla da altre applicazioni Windows Store.
Per ulteriori approfondimenti sull'argomento, si rinvia al volume Creare applicazioni per Windows 8. Passo per passo, di L. Regnicoli, P. Pialorsi e R. Brunetti, edito per Microsoft Press, 2012 (in particolare il Cap. 5). La documentazione ufficiale Microsoft, in alcuni contesti riporta ancora la dicitura WinMD che utilizzeremo come sinonimo nell'articolo.