Condividi tramite


Componenti Windows Runtime con C# e Visual Basic

Puoi usare codice gestito per creare tipi Windows Runtime personalizzati e includerli in un pacchetto di un componente Windows Runtime. È possibile usare il componente nelle app UWP (Universal Windows Platform) scritte in C++, JavaScript, Visual Basic o C#. Questo argomento descrive le regole per la creazione di un componente e illustra alcuni aspetti del supporto di .NET per Windows Runtime. In generale, questo supporto è progettato per essere trasparente per il programmatore .NET. Tuttavia, quando si crea un componente da usare con JavaScript o C++, si devono tenere presenti le differenze nella modalità di supporto di Windows Runtime in questi linguaggi.

Se si sta creando un componente per l'uso solo nelle app UWP scritte in Visual Basic o C# e il componente non contiene controlli UWP, valuta la possibilità di usare il modello Libreria di classi anziché il modello di progetto Componente Windows Runtime in Microsoft Visual Studio. Esistono meno limitazioni per una libreria di classi semplice.

Nota

Per gli sviluppatori C# che scrivono app desktop in .NET 6 o versioni successive, usare C#/WinRT per creare un componente Windows Runtime. Vedere Creare componenti Windows Runtime con C#/WinRT.

Dichiarazione dei tipi nei componenti Windows Runtime

Internamente, i tipi di Windows Runtime nel componente possono utilizzare qualsiasi funzionalità .NET consentita in un'app UWP. Per altre informazioni, vedere .NET per app UWP.

Esternamente, i membri dei tipi possono esporre solo tipi di per i relativi parametri e valori restituiti. L'elenco seguente descrive le limitazioni dei tipi .NET esposti da un componente Windows Runtime.

  • I campi, i parametri e i valori restituiti di tutti i tipi e i membri pubblici nel componente devono essere tipi di Windows Runtime. Questa limitazione include i tipi di Windows Runtime creati dall'utente, nonché i tipi forniti da Windows Runtime stesso. Include anche diversi tipi .NET. L'inclusione di questi tipi fa parte del supporto fornito da .NET per consentire l'uso naturale di Windows Runtime nel codice gestito. Il codice sembra usare tipi .NET familiari anziché i tipi Windows Runtime sottostanti. Ad esempio, è possibile usare tipi primitivi .NET come Int32 e Double, alcuni tipi fondamentali, ad esempio DateTimeOffset e Uri e alcuni tipi di interfaccia generici comunemente usati, ad esempio IEnumerable<T> (IEnumerable(Of T) in Visual Basic) e IDictionary<TKey,TValue>. Si noti che gli argomenti di tipo di questi tipi generici devono essere tipi di Windows Runtime. Questo argomento viene descritto nelle sezioni Passaggio dei tipi di Windows Runtime al codice gestito e Passaggio di tipi gestiti a Windows Runtime, più avanti in questo argomento.

  • Le classi e le interfacce pubbliche possono contenere metodi, proprietà ed eventi. È possibile dichiarare delegati per gli eventi o usare il delegato EventHandler<T>. Una classe o un'interfaccia pubblica non può:

    • Essere generici.
    • Implementare un'interfaccia che non è un'interfaccia di Windows Runtime( tuttavia, è possibile creare interfacce di Windows Runtime personalizzate e implementarle).
    • Derivare da tipi non inclusi in Windows Runtime, ad esempio System.Exception e System.EventArgs.
  • Tutti i tipi pubblici devono avere uno spazio dei nomi radice corrispondente al nome dell'assembly e il nome dell'assembly non deve iniziare con "Windows".

    Suggerimento. Per impostazione predefinita, i progetti di Visual Studio hanno nomi di spazio dei nomi corrispondenti al nome dell'assembly. In Visual Basic l'istruzione Namespace per questo spazio dei nomi predefinito non viene visualizzata nel codice.

  • Le strutture pubbliche non possono avere membri diversi dai campi pubblici e tali campi devono essere tipi di valore o stringhe.

  • Le classi pubbliche devono essere sealed (NotInheritable in Visual Basic). Se il modello di programmazione richiede polimorfismo, è possibile creare un'interfaccia pubblica e implementare tale interfaccia sulle classi che devono essere polimorfiche.

Debug del componente

Se l'app UWP e il componente vengono compilati con codice gestito, è possibile eseguirne il debug contemporaneamente.

Quando esegui il test del componente come parte di un'app UWP con C++, puoi eseguire il debug di codice gestito e nativo contemporaneamente. Il valore predefinito è solo codice nativo.

Per eseguire il debug del codice C++ nativo e del codice gestito

  1. Aprire il menu di scelta rapida per il progetto Visual C++ e selezionare Proprietà.
  2. Nelle pagine delle proprietà, in Proprietà di configurazione, selezionare Debugging.
  3. Selezionare Tipo di debugger e nella casella di riepilogo a discesa impostare Solo nativo su Misto (gestito e nativo). Scegliere OK.
  4. Impostare punti di interruzione nel codice nativo e gestito.

Quando si esegue il test del componente come parte di un'app UWP con JavaScript, per impostazione predefinita la soluzione è in modalità di debug JavaScript. In Visual Studio non è possibile eseguire il debug di JavaScript e del codice gestito contemporaneamente.

Per eseguire il debug di codice gestito invece di JavaScript

  1. Aprire il menu di scelta rapida per il progetto JavaScript e selezionare Proprietà.
  2. Nelle pagine delle proprietà, in Proprietà di configurazione, selezionare Debugging.
  3. Selezionare Tipo di debugger e nella casella di riepilogo a discesa modificare Solo script in Solo gestito. Scegliere OK.
  4. Impostare i punti di interruzione nel codice gestito ed eseguire il debug come di consueto.

Passaggio dei tipi di Windows Runtime al codice gestito

Come accennato in precedenza nella sezione Dichiarazione dei tipi nei componenti Windows Runtime, alcuni tipi .NET possono essere visualizzati nelle firme dei membri delle classi pubbliche. Questo fa parte del supporto fornito da .NET per abilitare l'uso naturale di Windows Runtime nel codice gestito. Include tipi primitivi e alcune classi e interfacce. Quando il componente viene usato da JavaScript o dal codice C++, è importante sapere come appaiono i tipi .NET al chiamante. Vedere Procedura dettagliata per la creazione di un componente Windows Runtime C# o Visual Basic e chiamata del componente da JavaScript per esempi in JavaScript. In questa sezione vengono illustrati i tipi di uso comune.

In .NET, tipi primitivi quali la Int32 struttura presentano numerosi metodi e proprietà utili, ad esempio il metodo TryParse. Al contrario, i tipi primitivi e le strutture di Windows Runtime dispongono solo di campi. Quando si utilizzano passano questi tipi nel codice gestito, sembrano essere tipi .NET e si possono utilizzare le proprietà e i metodi dei tipi .NET normalmente. L'elenco seguente riepiloga le sostituzioni eseguite automaticamente nell'IDE:

  • Per le primitive di Windows Runtime Int32, Int64, Single, Double, Boolean, String (una raccolta non modificabile di caratteri Unicode), Enum, UInt32, UInt64, and Guid, usa il tipo dello stesso nome nello spazio dei nomi System.
  • Per UInt8, usare System.Byte.
  • Per Char16, usare System.Char.
  • Per l'interfaccia IInspectable, usare System.Object.

Se C# o Visual Basic fornisce una parola chiave del linguaggio per uno di questi tipi, è possibile usare invece la parola chiave del linguaggio.

Oltre ai tipi primitivi, alcuni tipi Windows Runtime di uso comune vengono visualizzati nel codice gestito come equivalenti .NET. Si supponga, ad esempio, che il codice JavaScript usi la classe Windows.Foundation.Uri e che si desideri passarlo a un metodo C# o Visual Basic. Il tipo equivalente nel codice gestito è la classe .NET System.Uri ed è il tipo da usare per il parametro del metodo. È possibile stabilire quando un tipo Windows Runtime viene visualizzato come tipo .NET, perché IntelliSense in Visual Studio nasconde il tipo Windows Runtime quando si scrive codice gestito e presenta il tipo .NET equivalente. In genere i due tipi hanno lo stesso nome. Si noti tuttavia che la struttura Windows.Foundation.DateTime viene visualizzata nel codice gestito come System.DateTimeOffset e non come System.DateTime.

Per alcuni tipi di raccolta comunemente usati, il mapping è tra le interfacce implementate da un tipo Windows Runtime e le interfacce implementate dal tipo .NET corrispondente. Come per i tipi indicati in precedenza, si dichiarano i tipi di parametro usando il tipo .NET. Ciò nasconde alcune differenze tra i tipi e rende più naturale la scrittura di codice .NET.

La tabella seguente elenca i tipi di interfaccia generici più comuni, insieme ad altri mapping di interfaccia e classi comuni. Per un elenco completo dei tipi di Windows Runtime mappati da .NET, vedere Mapping .NET dei tipi di Windows Runtime.

Windows Runtime .NET
IIterable<T> IEnumerable<T>
IVector<T> IList<T>
IVectorView<T> IReadOnlyList<T>
IMap<K, V> IDictionary<TKey, TValue>
IMapView<K, V> IReadOnlyDictionary<TKey, TValue>
IKeyValuePair<K, V> KeyValuePair<TKey, TValue>
IBindableIterable IEnumerable
IBindableVector IList
Windows.UI.Xaml.Data.INotifyPropertyChanged System.ComponentModel.INotifyPropertyChanged
Windows.UI.Xaml.Data.PropertyChangedEventHandler System.ComponentModel.PropertyChangedEventHandler
Windows.UI.Xaml.Data.PropertyChangedEventArgs System.ComponentModel.PropertyChangedEventArgs

Quando un tipo implementa più interfacce, è possibile usare qualsiasi interfaccia implementata come tipo di parametro o tipo restituito di un membro. Ad esempio, è possibile passare o restituire un oggetto Dictionary<int, string> (Dictionary(Of Integer, String) in Visual Basic) come IDictionary<int, string>, IReadOnlyDictionary<int, string>, o IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>>.

Importante

JavaScript usa l'interfaccia visualizzata per prima nell'elenco di interfacce implementate da un tipo gestito. Ad esempio, se si restituisce Dictionary<int, string> al codice JavaScript, viene visualizzata come IDictionary<int, string> indipendentemente dall'interfaccia specificata come tipo restituito. Ciò significa che se la prima interfaccia non include un membro visualizzato nelle interfacce successive, tale membro non è visibile a JavaScript.

In Windows Runtime, IMap<K, V> e IMapView<K, V> vengono iterati usando IKeyValuePair. Quando vengono passati al codice gestito, vengono visualizzati come IDictionary<TKey, TValue> and IReadOnlyDictionary<TKey, TValue>, quindi naturalmente si usa System.Collections.Generic.KeyValuePair<TKey, TValue> per enumerarli.

Il modo in cui le interfacce vengono visualizzate nel codice gestito influisce sul modo in cui vengono visualizzati i tipi tramite cui vengono implementate queste interfacce. Ad esempio, la classe PropertySet implementa IMap<K, V>, che viene visualizzata nel codice gestito come IDictionary<TKey, TValue>. PropertySet compare come se avesse implementato IDictionary<TKey, TValue> invece di IMap<K, V>, dunque nel codice gestito risulta avere un metodo Add che si comporta come il metodo Add nei dizionari .NET. Non risulta avere un metodo Insert. È possibile visualizzare questo esempio nell'argomento Procedura dettagliata per la creazione di un componente Windows Runtime C# o Visual Basic e la chiamata da JavaScript.

Passaggio dei tipi gestiti a Windows Runtime

Come descritto nella sezione precedente, alcuni tipi di Windows Runtime possono essere visualizzati come tipi .NET nelle firme dei membri del componente o nelle firme dei membri di Windows Runtime quando vengono usati nell'IDE. Quando passi tipi .NET a questi membri o li usi come valori restituiti dei membri del componente, vengono visualizzati nel codice dall'altro lato come tipo di Windows Runtime corrispondente. Per esempi di effetti che possono avere quando il componente viene chiamato da JavaScript, vedere la sezione "Restituzione di tipi gestiti dal componente" in Procedura dettagliata per la creazione di un componente C# o Visual Basic Windows Runtime e chiamata da JavaScript.

Metodi di overload

In Windows Runtime è possibile eseguire l'overload dei metodi. Tuttavia, se dichiari più overload con lo stesso numero di parametri, devi applicare l'attributo Windows.Foundation.Metadata.DefaultOverloadAttribute solo a uno di questi overload. Questo overload è l'unico che è possibile richiamare da JavaScript. Ad esempio, nel codice seguente l'overload che accetta un valore int (Integer in Visual Basic) è l'overload predefinito.

public string OverloadExample(string s)
{
    return s;
}

[Windows.Foundation.Metadata.DefaultOverload()]
public int OverloadExample(int x)
{
    return x;
}
Public Function OverloadExample(ByVal s As String) As String
    Return s
End Function

<Windows.Foundation.Metadata.DefaultOverload> _
Public Function OverloadExample(ByVal x As Integer) As Integer
    Return x
End Function

[IMPORTANTE] JavaScript consente di passare qualsiasi valore a OverloadExample e di assegnare il valore al tipo richiesto dal parametro. È possibile chiamare OverloadExample con "forty-two", "42" o 42.3, ma tutti questi valori vengono passati all'overload predefinito. L'overload predefinito nell'esempio precedente restituisce rispettivamente 0, 42 e 42.

Non è possibile applicare l'attributo DefaultOverloadAttribute ai costruttori. Tutti i costruttori di una classe devono avere numeri diversi di parametri.

Implementazione di IStringable

A partire da Windows 8.1, Windows Runtime include un'interfaccia IStringable il cui singolo metodo, IStringable.ToString, fornisce un supporto di formattazione di base paragonabile a quello fornito da Object.ToString. Se si sceglie di implementare IStringable in un tipo gestito pubblico esportato in un componente Windows Runtime, si applicano le limitazioni seguenti:

  • È possibile definire l'interfaccia IStringable solo in una relazione "class implementa", ad esempio il codice seguente in C#:

    public class NewClass : IStringable
    

    O il codice Visual Basic indicato di seguito:

    Public Class NewClass : Implements IStringable
    
  • Non è possibile implementare IStringable in un'interfaccia.

  • Non è possibile dichiarare un parametro di tipo IStringable.

  • IStringable non può essere il tipo restituito di un metodo, di una proprietà o di un campo.

  • Non è possibile nascondere l'implementazione di IStringable dalle classi di base usando una definizione del metodo, ad esempio:

    public class NewClass : IStringable
    {
       public new string ToString()
       {
          return "New ToString in NewClass";
       }
    }
    

    L'implementazione di IStringable.ToString deve invece eseguire sempre l'override dell'implementazione della classe di base. È possibile nascondere un'implementazione di ToString solo richiamandola sull'istanza di una classe fortemente tipizzata.

Nota

In diverse condizioni, le chiamate dal codice nativo a un tipo gestito che implementa IStringable o nasconde l'implementazione ToString possono produrre un comportamento imprevisto.

Operazioni asincrone

Per implementare un metodo asincrono nel componente, aggiungere "Async" alla fine del nome del metodo e restituire una delle interfacce di Windows Runtime che rappresentano azioni o operazioni asincrone: IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress>.

Per implementare il metodo asincrono, è possibile usare le attività .NET (le Task class e generic Task<TResult>). È necessario restituire un'attività che rappresenta un'operazione in corso, ad esempio un'attività restituita da un metodo asincrono scritto in C# o Visual Basic o da un'attività restituita dal metodo Task.Run. Se si usa un costruttore per creare l'attività, è necessario chiamare il relativo metodo Task.Start prima di restituirlo.

Un metodo che usa await (Await in Visual Basic) richiede la async parola chiave (Async in Visual Basic). Se si espone un metodo di questo tipo da un componente Windows Runtime, applicare la parola chiave async al delegato passato al metodo Run.

Per operazioni e azioni asincrone che non supportano la creazione di report di annullamento o stato, è possibile utilizzare il metodo di estensione WindowsRuntimeSystemExtensions.AsAsyncAction o AsAsyncOperation<TResult> per eseguire il wrapping dell'attività nell'interfaccia appropriata. Ad esempio, il codice seguente implementa un metodo asincrono usando il metodo Task.Run<TResult> per avviare un'attività. Il metodo di estensione AsAsyncOperation<TResult> restituisce l'attività come operazione asincrona di Windows Runtime.

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return Task.Run<IList<string>>(async () =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    }).AsAsyncOperation();
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
     As IAsyncOperation(Of IList(Of String))

    Return Task.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function).AsAsyncOperation()
End Function

Il codice JavaScript seguente illustra come chiamare il metodo usando un oggetto WinJS.Promise. La funzione passata al metodo then viene eseguita al termine della chiamata asincrona. Il parametro stringList contiene l'elenco di stringhe restituite dal metodo DownloadAsStringAsync e la funzione esegue qualsiasi elaborazione necessaria.

function asyncExample(id) {

    var result = SampleComponent.Example.downloadAsStringAsync(id).then(
        function (stringList) {
            // Place code that uses the returned list of strings here.
        });
}

Per operazioni e azioni asincrone che supportano la creazione di report di annullamento o stato, usare la classe AsyncInfo per generare un'attività avviata e associare le funzionalità di annullamento e segnalazione dello stato dell'attività con le funzionalità di annullamento e segnalazione dello stato dell'interfaccia di Windows Runtime appropriata. Per un esempio che supporta la creazione di report di annullamento e stato, vedere Procedura dettagliata per la creazione di un componente Windows Runtime C# o Visual Basic e chiamata da JavaScript.

Si noti che è possibile usare i metodi della classe AsyncInfo anche se il metodo asincrono non supporta l'annullamento o la creazione di report sullo stato. Se si usa una funzione lambda di Visual Basic o un metodo anonimo C#, non fornire parametri per l'interfaccia token e IProgress<T>. Se si usa una funzione lambda C#, specificare un parametro token ma ignorarlo. L'esempio precedente, che usa il metodo AsAsyncOperation<TResult>, è simile al seguente quando si usa invece l'overload del metodo AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>).

public static IAsyncOperation<IList<string>> DownloadAsStringsAsync(string id)
{
    return AsyncInfo.Run<IList<string>>(async (token) =>
    {
        var data = await DownloadDataAsync(id);
        return ExtractStrings(data);
    });
}
Public Shared Function DownloadAsStringsAsync(ByVal id As String) _
    As IAsyncOperation(Of IList(Of String))

    Return AsyncInfo.Run(Of IList(Of String))(
        Async Function()
            Dim data = Await DownloadDataAsync(id)
            Return ExtractStrings(data)
        End Function)
End Function

Se si crea un metodo asincrono che supporta facoltativamente l'annullamento o la creazione di report sullo stato, è consigliabile aggiungere overload che non dispongono di parametri per un token di annullamento o l'interfaccia IProgress<T>.

Generazione di eccezioni

È possibile generare qualsiasi tipo di eccezione incluso in .NET per le app di Windows. Non è possibile dichiarare i propri tipi di eccezione pubblici in un componente Windows Runtime, ma è possibile dichiarare e generare tipi non pubblici.

Se il componente non gestisce l'eccezione, viene generata un'eccezione corrispondente nel codice che ha chiamato il componente. La modalità di visualizzazione dell'eccezione al chiamante dipende dal modo in cui il linguaggio chiamante supporta Windows Runtime.

  • In JavaScript l'eccezione viene visualizzata come oggetto in cui il messaggio di eccezione viene sostituito da un'analisi dello stack. Quando esegui il debug dell'app in Visual Studio, puoi visualizzare il testo originale del messaggio visualizzato nella finestra di dialogo eccezione del debugger, identificato come "Informazioni WinRT". Non è possibile accedere al testo del messaggio originale dal codice JavaScript.

    Suggerimento. Attualmente, l'analisi dello stack contiene il tipo di eccezione gestita, ma non è consigliabile analizzare la traccia per identificare il tipo di eccezione. Usare invece un valore HRESULT come descritto più avanti in questa sezione.

  • In C++, l'eccezione viene visualizzata come eccezione della piattaforma. Se la proprietà HResult dell'eccezione gestita può essere mappata al valore HRESULT di un'eccezione di piattaforma specifica, viene usata l'eccezione specifica; in caso contrario, viene generata un'eccezione Platform::COMException. Il testo del messaggio dell'eccezione gestita non è disponibile per il codice C++. Se è stata generata un'eccezione specifica della piattaforma, viene visualizzato il testo del messaggio predefinito per tale tipo di eccezione; in caso contrario, non viene visualizzato alcun testo del messaggio. Vedere Eccezioni (C++/CX).

  • In C# o Visual Basic l'eccezione è un'eccezione gestita normale.

Quando si genera un'eccezione dal componente, è possibile semplificare la gestione dell'eccezione da parte di un chiamante JavaScript o C++ generando un tipo di eccezione non pubblico il cui valore della proprietà HResult è specifico del componente. HRESULT è disponibile per un chiamante JavaScript tramite la proprietà number dell'oggetto eccezione e per un chiamante C++ tramite la proprietà COMException::HResult.

Nota

Usare un valore negativo per HRESULT. Un valore positivo viene interpretato come esito positivo e non viene generata alcuna eccezione nel chiamante JavaScript o C++.

Dichiarazione e generazione di eventi

Quando si dichiara un tipo per contenere i dati per l'evento, deriva da Object anziché da EventArgs, perché EventArgs non è un tipo Windows Runtime. Usare EventHandler<TEventArgs> come tipo dell'evento e usare il tipo di argomento dell'evento come argomento di tipo generico. Generare l'evento esattamente come si farebbe in un'applicazione .NET.

Quando il componente Windows Runtime viene usato da JavaScript o C++, l'evento segue il modello di evento di Windows Runtime previsto da tali linguaggi. Quando si usa il componente da C# o Visual Basic, l'evento viene visualizzato come un normale evento .NET. Un esempio è fornito in Procedura dettagliata per la creazione di un componente Windows Runtime C# o Visual Basic e chiamata del componente da JavaScript.

Se si implementano funzioni di accesso agli eventi personalizzate (dichiarare un evento con la parola chiave Custom, in Visual Basic), è necessario seguire il modello di evento di Windows Runtime nell'implementazione. Vedere Eventi personalizzati e funzioni di accesso agli eventi nei componenti di Windows Runtime. Si noti che quando si gestisce l'evento da codice C# o Visual Basic, sembra comunque essere un evento .NET ordinario.

Passaggi successivi

Dopo aver creato un componente Windows Runtime per il proprio uso, è possibile che la funzionalità incapsuli sia utile per altri sviluppatori. Sono disponibili due opzioni per la creazione di pacchetti di un componente per la distribuzione ad altri sviluppatori. Vedere Distribuzione di un componente Windows Runtime gestito.

Per altre informazioni sulle funzionalità del linguaggio Visual Basic e C# e sul supporto di .NET per Windows Runtime, vedere la documentazione di Visual Basic e C#.

Risoluzione dei problemi

Sintomo Rimedio
In un'app C++/WinRT, quando viene usato un componente Windows Runtime C# che usa XAML, il compilatore genera un errore "'MyNamespace_XamlTypeInfo': non è un membro di 'winrt::MyNamespace'"dove MyNamespace è il nome dello spazio dei nomi del componente Windows Runtime. Nel file pch.h dell'app C++/WinRT consumer aggiungi #include <winrt/MyNamespace.MyNamespace_XamlTypeInfo.h>sostituendo MyNamespace in base alle esigenze.