Agosto 2015

Volume 30 numero 8

Il presente articolo è stato tradotto automaticamente.

Windows con C++ - componenti di Runtime di finestra con MIDL

Da Kenny Kerr | Agosto 2015

Kenny KerrNel mio articolo di luglio 2015 (msdn.microsoft.com/magazine/mt238401), ho introdotto il concetto di componenti Windows Runtime (WinRT) come l'evoluzione del paradigma di programmazione COM. Considerando che Win32 mettere COM sul lato, il Runtime di Windows mette COM davanti e centro. Il Runtime di Windows è il successore di Win32, il quest'ultimo essendo un termine generico per l'API di Windows come essa comprende molte diverse tecnologie e modelli di programmazione. Il Runtime di Windows fornisce un modello di programmazione coerenza e unificato, ma in ordine per farlo riuscire gli sviluppatori sia all'interno che all'esterno di Microsoft hanno bisogno di strumenti migliori per sviluppare componenti WinRT e utilizzare tali componenti all'interno di applicazioni.

Lo strumento principale fornito da Windows SDK per servire questa esigenza è il compilatore MIDL. Nella colonna di luglio, ho mostrato come il compilatore MIDL potrebbe produrre il file di metadati del Runtime di Windows (WINMD) lingua più proiezioni richiedono di utilizzare componenti WinRT. Naturalmente, qualsiasi sviluppatore di lunga data sulla piattaforma Windows saranno consapevoli del fatto che il compilatore MIDL produce anche codice che un compilatore C o C++ può consumare direttamente. Infatti, MIDL stesso non sa nulla circa il formato di file WINMD. Si tratta principalmente di analisi file IDL e produce codice per i compilatori C e C++ per supportare COM e lo sviluppo di chiamata (RPC) di procedura remota e la produzione di DLL proxy. Il compilatore MIDL è un pezzo storicamente critico delle macchine che gli ingegneri che hanno sviluppato il Runtime di Windows ha scelto di non correre il rischio di romperlo e invece sviluppato un "compilatore di sub" responsabile solo per il Runtime di Windows. Gli sviluppatori non sono generalmente consapevoli di questo sotterfugio — e non è necessario — ma aiuta a spiegare il modo il compilatore MIDL funziona in pratica.

Diamo un'occhiata a qualche codice sorgente IDL e vedere che cosa sta realmente accadendo con il compilatore MIDL. Qui è un file di origine IDL che definisce un'interfaccia COM classica:

C:\Sample>type Sample.idl
import "unknwn.idl";
[uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
interface IHen : IUnknown
{
  HRESULT Cluck();
}

COM classico non ha una nozione forte di spazi dei nomi, quindi l'interfaccia IHen è semplicemente definito in ambito file. La definizione di IUnknown deve anche essere importata prima dell'uso. Quindi posso solo passare questo file con il compilatore MIDL per produrre una serie di manufatti:

C:\Sample>midl Sample.idl
C:\Sample>dir /b
dlldata.c
Sample.h
Sample.idl
Sample_i.c
Sample_p.c

Il file di origine dlldata contiene alcune macro che implementano le esportazioni necessarie per una DLL del proxy. Il Sample_i.c contiene il GUID dell'interfaccia IHen, dovrebbe voi usare un compilatore 25-year-old che manca il supporto per declspec uuid che attribuisce i GUID per tipi. Poi c'è Sample_p.c, che contiene le istruzioni di marshalling per la DLL proxy. Io glissare su questi per il momento e invece concentrarsi sulla Sample.h, che contiene qualcosa di piuttosto comodo. Se si guardano tutte le macro orribile destinate per aiutare gli sviluppatori C utilizzare COM (orrore!) troverete questo:

MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
IHen : public IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
};

Non è elegante C++ ma, dopo la pre-elaborazione, esso ammonta a una classe C++ che eredita da IUnknown e aggiunge una funzione virtuale pura dei relativi propri. Questo è utile perché significa che non devi scrivere a mano, questo eventuale introduzione di una mancata corrispondenza tra la definizione C++ dell'interfaccia e l'originale definizione di IDL che potrebbero consumare altri linguaggi e strumenti. Così che è l'essenza di ciò che il compilatore MIDL fornisce agli sviluppatori C++, produrre una traduzione del codice sorgente IDL in modo tale che un compilatore C++ può consumare quelli tipi direttamente.

Ora torniamo a Windows Runtime. I'll aggiornare il codice sorgente IDL solo leggermente per soddisfare i requisiti più rigorosi per i tipi di WinRT:

C:\Sample>type Sample.idl
import "inspectable.idl";
namespace Sample
{
  [uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
  [version(1)]
  interface IHen : IInspectable
  {
    HRESULT Cluck();
  }
}

Le interfacce WinRT devono ereditare direttamente da IInspectable, e uno spazio dei nomi viene utilizzato in parte per associare i tipi con il componente di attuazione. Se si tenta di compilarlo come prima, mi imbatto in un problema:

.\Sample.idl(3) : error MIDL2025 : syntax error : expecting an interface name or DispatchInterfaceName or CoclassName or ModuleName or LibraryName or ContractName or a type specification near "namespace"

Il compilatore MIDL non riconosce la parola chiave namespace e si arrende. Ecco a che cosa l'opzione della riga di comando /winrt. Dice al compilatore MIDL di passare la riga di comando direttamente al compilatore MIDLRT pre-elaborare il file di origine IDL. È questo compilatore secondo — MIDLRT — che prevede l'opzione della riga di comando di /metadata_dir ho accennato nella colonna di luglio:

C:\Sample>midl /winrt Sample.idl /metadata_dir
  "C:\Program Files (x86)\Windows Kits ..."

Come ulteriore prova di questo, dare un'occhiata più da vicino l'output del compilatore MIDL e vedrete cosa intendo:

C:\Sample>midl /winrt Sample.idl /metadata_dir "..."
Microsoft (R) 32b/64b MIDLRT Compiler Engine Version 8.00.0168
Copyright (c) Microsoft Corporation. All rights reserved.
MIDLRT Processing .\Sample.idl
.
.
.
Microsoft (R) 32b/64b MIDL Compiler Version 8.00.0603
Copyright (c) Microsoft Corporation. All rights reserved.
Processing C:\Users\Kenny\AppData\Local\Temp\Sample.idl-34587aaa
.
.
.

Ho rimosso parte dell'elaborazione delle dipendenze al fine di evidenziare i punti chiave. Chiamando il MIDL eseguibile con le opzioni /winrt ciecamente passa la linea di comando per l'eseguibile MIDLRT prima di uscire. MIDLRT analizza l'IDL prima di generare il file WINMD, ma produce anche un altro file IDL temporaneo. Questo file temporaneo di IDL è una traduzione dell'originale con tutte le parole chiave specifiche per WinRT, come gli spazi dei nomi, sostituito in modo tale che il compilatore MIDL originale lo accetterà. MIDLRT, quindi chiama l'eseguibile MIDL nuovamente ma senza l'opzione di /winrt e con la posizione del file IDL temporaneo così può produrre l'originale insieme di intestazioni di C e C++ e file di origine come prima.

Lo spazio dei nomi nel file IDL originale viene rimosso e il nome dell'interfaccia IHen è decorato nel file IDL temporaneo come segue:

interface __x_Sample_CIHen : IInspectable
.
.
.

Questo è in realtà una forma codificata del nome del tipo che viene interpretato dal compilatore MIDL dato l'opzione della riga di comando di /gen_namespace che MIDLRT utilizza quando si chiama MIDL con l'output pre-elaborato. Il compilatore MIDL originale può quindi elaborare questo direttamente senza conoscenze specifiche di Windows Runtime. Questo è solo un esempio, ma ti dà un'idea di come la nuova utensileria rende la maggior parte della tecnologia esistente per ottenere il lavoro fatto. Se siete curiosi di vedere come funziona, potrebbe frugare nella cartella temporanea, il compilatore MIDL tracce fuori, solo per scoprire che quelle files—Sample.idl-34587aaa nell'esempio precedente — sono mancanti. L'eseguibile MIDLRT è attenzione a pulire dopo se stesso, ma se si include l'opzione della riga di comando /savePP, MIDL non eliminare questi file temporanei per il preprocessore. In ogni caso, un po' più di pre-elaborazione viene generata in e la risultante Sample.h ora ha qualcosa che anche un compilatore C++ riconoscerà come uno spazio dei nomi:

namespace Sample {
  MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
  IHen : public IInspectable
  {
  public:
    virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
  };
}

Quindi posso implementare questa interfaccia come prima, fiduciosa che il compilatore preleverà eventuali discrepanze tra l'implementazione e le definizioni originali che ho codificato in IDL. D'altra parte, se avete solo bisogno di MIDL per produrre il file WINMD e non hanno bisogno di tutti i file di origine per un compilatore C o C++, è possibile evitare tutti gli elementi di costruzione supplementare con l'opzione della riga di comando /nomidl. Questa opzione viene passata, insieme a tutto il resto, dal MIDL eseguibile per l'eseguibile MIDLRT. MIDLRT ignora quindi l'ultimo passaggio della chiamata MIDL nuovo termine produce il file WINMD. È consuetudine, anche quando usando un Windows Runtime ABI prodotto da MIDL, includere l'opzione della riga di comando di /ns_prefix, in modo che gli spazi dei nomi e tipi risultanti sono racchiusi all'interno dello spazio dei nomi "ABI" come segue:

namespace ABI {
  namespace Sample {
    MIDL_INTERFACE("e21df825-937d-4b0b-862e-e411b57e280e")
    IHen : public IInspectable
    {
    public:
      virtual HRESULT STDMETHODCALLTYPE Cluck( void) = 0;
    };
  }
}

Infine, devo dire che MIDL né MIDLRT è sufficiente per produrre un file WINMD autonomo che sufficientemente descrive tipi di un componente. Se vi capita di fare riferimento a tipi esterni, solitamente altri tipi definiti dal sistema operativo, il file WINMD, prodotto dal processo descritto finora, devono ancora essere Uniti con il file di metadati principali per la versione di Windows che stai targeting. Lasciatemi illustrare il problema.

Comincerò con uno spazio dei nomi IDL che descrive un'interfaccia IHen sia una classe di gallina attivabile che implementa questa interfaccia, come mostrato Figura 1.

Figura 1 la classe di gallina in IDL

namespace Sample
{
  [uuid(e21df825-937d-4b0b-862e-e411b57e280e)]
  [version(1)]
  interface IHen : IInspectable
  {
    HRESULT Cluck();
  }
  [version(1)]
  [activatable(1)]
  runtimeclass Hen
  {
    [default] interface IHen;
  }
}

Avrai quindi implementarla utilizzando la stessa tecnica che ho descritto nella colonna di luglio, tranne per il fatto che ora posso contare sulla definizione di IHen come previsto dal compilatore MIDL. Ora, all'interno di un app di WinRT, posso semplicemente creare un oggetto di gallina e chiamare il metodo coccodè. Io uso c# per illustrare il lato di app dell'equazione:

public void SetWindow(CoreWindow window)   
{
  Sample.IHen hen = new Sample.Hen();
  hen.Cluck();
}

Il metodo SetWindow è parte dell'implementazione di IFrameworkView fornito dall'applicazione c#. (Ho descritto IFrameworkView nel mio articolo di agosto 2013, che potete leggere a msdn.microsoft.com/magazine/jj883951.) E, naturalmente, questo funziona. C# è assolutamente dipendente WINMD metadati che descrivono il componente. D'altra parte, si assicura di codice C++ nativo di condivisione con i client c# una brezza. La maggior parte del tempo, comunque. Un problema che si pone è se si fa riferimento a tipi di esterno, come ho accennato poco fa. Aggiorniamo il metodo Cluck per richiedere un CoreWindow come argomento. CoreWindow è definito dal sistema operativo in modo che non posso definire semplicemente all'interno del mio file di origine IDL.

In primo luogo, I'll aggiornare IDL per dipendere dall'ICore­finestra di interfaccia. Semplicemente importeranno la definizione come segue:

import "windows.ui.core.idl";

E poi io aggiungere un parametro di ICoreWindow al metodo coccodè:

HRESULT Cluck([in] Windows.UI.Core.ICoreWindow * window);

Il compilatore MIDL trasformerà che "importare" in un #include di windows.ui.core.h all'interno dell'intestazione che esso genera quindi tutto quello che devo fare è aggiornare la mia implementazione di classe di gallina:

virtual HRESULT __stdcall Cluck(ABI::Windows::UI::Core::ICoreWindow *) 
  noexcept override
{
  return S_OK;
}

Ora posso compilare il componente come prima e la nave per lo sviluppatore dell'app. Lo sviluppatore dell'app c# doverosamente aggiorna la chiamata al metodo Cluck con un riferimento a CoreWindow dell'app come segue:

public void SetWindow(CoreWindow window)
{
  Sample.IHen hen = new Sample.Hen();
  hen.Cluck(window);
}

Purtroppo, il compilatore c# si lamenta ora:

error CS0012: The type 'ICoreWindow' is defined in an assembly
  that is not referenced.

Vedete, il compilatore c# non riconosce le interfacce siano gli stessi. Il compilatore c# non è soddisfatto con solo un nome di tipo corrispondente e non è possibile effettuare la connessione con il tipo di Windows dello stesso nome. A differenza di C++, c# è molto dipendente dalle informazioni di tipo binario a collegare i puntini. Per risolvere questo problema, io posso impiegare un altro strumento fornito con il Windows SDK che compongono o unire i metadati dal sistema operativo Windows insieme ai metadati del mio componente, risolvendo correttamente il ICoreWindow al file dei metadati principali per il sistema operativo. Questo strumento è chiamato MDMERGE:

c:\Sample>mdmerge /i . /o output /partial /metadata_dir "..."

Gli eseguibili MIDLRT e MDMERGE sono abbastanza particolari su loro argomenti della riga di comando. È necessario arrivare giusto in ordine per farlo funzionare. In questo caso, io non posso aggiornare semplicemente Sample.winmd in luogo scegliendo il /i (input) e le opzioni /o (output) nella stessa cartella perché MDMERGE effettivamente Elimina il file di WINMD input al completamento. La/opzione partial racconta MDMERGE a cercare l'ICoreWindowInterfaccia irrisolto nei metadati forniti dall'opzione di /metadata_dir. Questo è chiamato i metadati di riferimento. MDMERGE quindi può essere utilizzato per unire più file WINMD ma in questo caso, sto usandolo solo per risolvere i riferimenti per i tipi di OS.

A questo punto, il Sample.winmd risultante correttamente punti ai metadati dal sistema operativo Windows quando si fa riferimento all'Interfaccia ICoreWindow e il compilatore c# è soddisfatto e compilerà l'app come scritto. Unisciti a me il mese prossimo come continuare a esplorare Windows Runtime da C++.


Kenny Kerr è un programmatore di computer basato nel Canada, nonché un autore per Pluralsight e MVP Microsoft. Ha Blog a kennykerr.ca ed è possibile seguirlo su Twitter a twitter.com/kennykerr.

Grazie all'esperto tecnico Microsoft seguente per la revisione di questo articolo: Larry Osterman