Composants Windows Runtime répartis pour l’application UWP à chargement indépendant

Cet article présente une fonctionnalité de la mise à jour Windows 10 et versions supérieures, destinée aux entreprises, qui permet aux applications .NET tactiles d’utiliser le code existant responsable des opérations clés critiques pour l'entreprise.

Introduction

Notez que l’exemple de code qui accompagne ce document peut être téléchargé pour Visual Studio 2015 &2017. Le modèle Microsoft Visual Studio pour générer des composants Windows Runtime répartis peut être téléchargé ici : le modèle Visual Studio 2015 ciblant les applications Windows universelles pour Windows 10

Windows inclut une nouvelle fonctionnalité appelée Composants Windows Runtime répartis pour les applications chargées de manière indépendante. Nous utilisons le terme IPC (communication entre processus) pour décrire la possibilité d’exécuter des ressources logicielles de bureau existantes dans un processus (composant de bureau) tout en interagissant avec ce code dans une application UWP. Il s’agit d’un modèle familier pour les développeurs d’entreprise en tant qu’applications et applications de base de données utilisant les services NT dans Windows partagent une architecture multiprocesseur similaire.

Le chargement indépendant de l’application est un composant essentiel de cette fonctionnalité. Les applications spécifiques à l’entreprise n’ont pas de place dans la Boutique Microsoft grande public et les entreprises ont des exigences très spécifiques concernant la sécurité, la confidentialité, la distribution, la configuration et la maintenance. Par conséquent, le modèle de chargement indépendant est à la fois une exigence de ceux qui utiliseraient cette fonctionnalité et un détail d’implémentation critique.

Les applications centrées sur les données constituent une cible clé pour cette architecture d’application. Il est prévu que les règles métier existantes installées, par exemple, dans SQL Server, constituent une partie commune du composant de bureau. Ce n’est certainement pas le seul type de fonctionnalité qui peut être profferé par le composant de bureau, mais une grande partie de la demande de cette fonctionnalité est liée à la logique métier et aux données existantes.

Enfin, compte tenu de la pénétration écrasante du runtime .NET et du langage C# dans le développement d’entreprise, cette fonctionnalité a été développée en mettant l’accent sur l’utilisation de .NET pour l’application UWP et les côtés du composant de bureau. Bien qu’il existe d’autres langages et runtimes possibles pour l’application UWP, l’exemple associé illustre uniquement C#, et est limité exclusivement au runtime .NET.

Composants d’application

Notez que cette fonctionnalité est exclusivement destinée à l’utilisation de .NET. L’application cliente et le composant de bureau doivent être créés à l’aide de .NET.

Modèle d'application

Cette fonctionnalité est conçue autour de l’architecture d’application générale appelée MVVM (Model View View-Model). Par conséquent, il est supposé que le « modèle » est entièrement hébergé dans le composant de bureau. Par conséquent, il doit être immédiatement évident que le composant de bureau sera « sans périphérique de contrôle » (c’est-à-dire qu’il ne contient pas d’IU). La vue sera entièrement contenue dans l’application d’entreprise chargée de manière indépendante. Bien qu’il n’y ait aucune exigence que cette application soit générée avec la construction « view-model », nous prévoyons que l’utilisation de ce modèle sera commune.

Composant de bureau

Le composant de bureau de cette fonctionnalité est un nouveau type d’application introduit dans le cadre de cette fonctionnalité. Ce composant de bureau peut uniquement être écrit en C# et doit cibler .NET 4.6 ou version ultérieure pour Windows 10. Le type de projet est un hybride entre le CLR ciblant UWP, car le format de communication interprocessus comprend des types et des classes UWP, tandis que le composant de bureau est autorisé à appeler toutes les parties de la bibliothèque de classes runtime .NET. L’impact sur le projet Visual Studio sera décrit plus en détail dans une section ultérieure. Cette configuration hybride permet d’effectuer le marshaling des types UWP entre l’application basée sur les composants de bureau tout en permettant au code CLR de bureau d’être appelé à l’intérieur de l’implémentation du composant de bureau.

Contrat

Le contrat entre l’application chargée de manière indépendante et le composant de bureau est décrit en termes de système de type UWP. Cela implique la déclaration d’une ou plusieurs classes C# qui peuvent représenter un UWP. Consultez la rubrique MSDN Création de composants Windows Runtime en C# et Visual Basic pour connaître les exigences spécifiques de création de la classe Windows Runtime à l’aide de C#.

Notez que les énumérations ne sont pas prises en charge dans le contrat des composants Windows Runtime entre le composant de bureau et l’application chargée de manière indépendante.

Application chargée de manière indépendante

L’application chargée de manière indépendante est une application UWP normale à tous les égards, à l’exception de celle-ci : elle est chargée de manière indépendante au lieu d’être installée via la Boutique Microsoft. La plupart des mécanismes d’installation sont identiques : le manifeste et la création du package d’application sont similaires (un ajout au manifeste est décrit en détail plus loin). Une fois le chargement indépendant activé, un script PowerShell simple peut installer les certificats nécessaires et l’application elle-même. La meilleure pratique normale est que l’application chargée de manière indépendante passe le test de certification WACK inclus dans le menu Project/Store dans Visual Studio

Notez que le chargement indépendant peut être activé dans Paramètres-> Mettre à jour et sécurité -> Pour les développeurs.

Un point important à noter est que le mécanisme App Broker fourni dans le cadre de Windows 10 est 32 bits uniquement. Le composant de bureau doit être 32 bits. Les applications chargées de manière indépendante peuvent être 64 bits (à condition qu’il existe à la fois des proxys 64 bits et 32 bits inscrits), mais cela sera atypique. La création de l’application chargée de manière indépendante en C# à l’aide de la configuration normale « neutre » et la valeur par défaut « préférer 32 bits » crée naturellement des applications 32 bits chargées de manière indépendante.

Instanciation de serveur et AppDomains

Chaque application chargée de manière indépendante reçoit sa propre instance d’un serveur App Broker (appelée « multistanciation »). Le code du serveur s’exécute à l’intérieur d’un seul AppDomain. Cela permet d’exécuter plusieurs versions de bibliothèques dans des instances distinctes. Par exemple, l’application A a besoin de V1.1 d’un composant et d’une application B a besoin de V2. Ces composants sont séparés de manière nette en ayant des composants V1.1 et V2 dans des répertoires de serveur distincts et en pointant l’application vers laquelle le serveur prend en charge la version souhaitée.

L’implémentation du code serveur peut être partagée entre plusieurs instances de serveur App Broker en pointant plusieurs applications vers le même annuaire de serveur. Il y aura toujours plusieurs instances du serveur App Broker, mais elles exécuteront du code identique. Tous les composants d’implémentation utilisés dans une seule application doivent être présents dans le même chemin d’accès.

Définition du contrat

La première étape de la création d’une application à l’aide de cette fonctionnalité consiste à créer le contrat entre l’application chargée de manière indépendante et le composant de bureau. Cette opération doit être effectuée exclusivement à l’aide de types Windows Runtime. Heureusement, ils sont faciles à déclarer à l’aide de classes C#. Toutefois, il existe des considérations importantes en matière de performances lors de la définition de ces conversations, qui sont abordées dans une section ultérieure.

La séquence de définition du contrat est introduite de la manière suivante :

Étape 1 : Créez une nouvelle bibliothèque de classes dans Visual Studio. Veillez à créer le projet à l’aide du modèle bibliothèque de classes, et non au modèle de composant Windows Runtime.

Une implémentation suit évidemment, mais cette section couvre uniquement la définition du contrat inter-processus. L’exemple associé inclut la classe suivante (EnterpriseServer.cs), la forme de début ressemblant à :

namespace Fabrikam
{
    public sealed class EnterpriseServer
    {

        public ILis<String> TestMethod(String input)
        {
            throw new NotImplementedException();
        }
        
        public IAsyncOperation<int> FindElementAsync(int input)
        {
            throw new NotImplementedException();
        }
        
        public string[] RetrieveData()
        {
            throw new NotImplementedException();
        }
        
        public event EventHandler<string> PeriodicEvent;
    }
}

Cela définit une classe « EnterpriseServer » qui peut être instanciée à partir de l’application chargée de manière indépendante. Cette classe fournit les fonctionnalités promises dans RuntimeClass. RuntimeClass peut être utilisé pour générer la référence winmd qui sera incluse dans l’application chargée de manière indépendante.

Étape 2 : Modifiez manuellement le fichier projet pour modifier le type de sortie du projet en composant Windows Runtime.

Pour ce faire dans Visual Studio, cliquez avec le bouton droit sur le projet nouvellement créé et sélectionnez « Décharger le projet », puis cliquez de nouveau avec le bouton droit sur « Modifier EnterpriseServer.csproj » pour ouvrir le fichier projet, un fichier XML, pour modification.

Dans le fichier ouvert, recherchez la balise <OutputType> et remplacez sa valeur par « winmdobj ».

Étape 3 : Créez une règle de build qui crée un fichier de métadonnées Windows « référence » (fichier .winmd). c’est-à-dire qu’il n’y a pas d’implémentation.

Étape 4 : Créez une règle de build qui crée un fichier de métadonnées Windows « implémentation », c’est-à-dire qui contient les mêmes informations de métadonnées, mais inclut également l’implémentation.

Ceci sera effectué par les scripts suivants. Ajoutez les scripts à la ligne de commande d’événement post-build, dans les événements build>propriétésdu projet.

Notez que le script est différent en fonction de la version de Windows que vous ciblez (Windows 10) et de la version de Visual Studio en cours d’utilisation.

Visual Studio 2015

    call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86 10.0.14393.0

    md "$(TargetDir)"\impl    md "$(TargetDir)"\reference

    erase "$(TargetDir)\impl\*.winmd"
    erase "$(TargetDir)\impl\*.pdb"
    rem erase "$(TargetDir)\reference\*.winmd"

    xcopy /y "$(TargetPath)" "$(TargetDir)impl"
    xcopy /y "$(TargetDir)*.pdb" "$(TargetDir)impl"

    winmdidl /nosystemdeclares /metadata_dir:C:\Windows\System32\Winmetadata "$(TargetPath)"

    midl /metadata_dir "%WindowsSdkDir%UnionMetadata" /iid "$(SolutionDir)BrokeredProxyStub\$(TargetName)_i.c" /env win32 /x86 /h   "$(SolutionDir)BrokeredProxyStub\$(TargetName).h" /winmd "$(TargetName).winmd" /W1 /char signed /nologo /winrt /dlldata "$(SolutionDir)BrokeredProxyStub\dlldata.c" /proxy "$(SolutionDir)BrokeredProxyStub\$(TargetName)_p.c"  "$(TargetName).idl"
    mdmerge -n 1 -i "$(ProjectDir)bin\$(ConfigurationName)" -o "$(TargetDir)reference" -metadata_dir "%WindowsSdkDir%UnionMetadata" -partial

    rem erase "$(TargetPath)"

Visual Studio 2017

    call "$(DevEnvDir)..\..\vc\auxiliary\build\vcvarsall.bat" x86 10.0.16299.0

    md "$(TargetDir)"\impl
    md "$(TargetDir)"\reference

    erase "$(TargetDir)\impl\*.winmd"
    erase "$(TargetDir)\impl\*.pdb"
    rem erase "$(TargetDir)\reference\*.winmd"

    xcopy /y "$(TargetPath)" "$(TargetDir)impl"
    xcopy /y "$(TargetDir)*.pdb" "$(TargetDir)impl"

    winmdidl /nosystemdeclares /metadata_dir:C:\Windows\System32\Winmetadata "$(TargetPath)"

    midl /metadata_dir "%WindowsSdkDir%UnionMetadata" /iid "$(SolutionDir)BrokeredProxyStub\$(TargetName)_i.c" /env win32 /x86 /h "$(SolutionDir)BrokeredProxyStub\$(TargetName).h" /winmd "$(TargetName).winmd" /W1 /char signed /nologo /winrt /dlldata "$(SolutionDir)BrokeredProxyStub\dlldata.c" /proxy "$(SolutionDir)BrokeredProxyStub\$(TargetName)_p.c"  "$(TargetName).idl"
    mdmerge -n 1 -i "$(ProjectDir)bin\$(ConfigurationName)" -o "$(TargetDir)reference" -metadata_dir "%WindowsSdkDir%UnionMetadata" -partial

    rem erase "$(TargetPath)"

Une fois que la référence winmd est créée (dans le dossier « référence » sous le dossier Cible du projet), elle est transmise manuellement (copiée) à chaque projet d’application chargé de manière indépendante consommateur et référencée. Ces opérations sont décrites dans la section suivante. La structure de projet incorporée dans les règles de build ci-dessus garantit que l’implémentation et la référence winmd sont dans des annuaires nettement séparés dans la hiérarchie de build pour éviter toute confusion.

Applications chargées de manière indépendante en détail

Comme indiqué précédemment, l’application chargée de manière indépendante est générée comme n’importe quelle autre application UWP, mais il existe un détail supplémentaire : déclaration de la disponibilité de RuntimeClass (es) dans le manifeste de l’application chargée de manière indépendante. Cela permet à l’application d’écrire simplement de nouvelles fonctionnalités pour accéder aux fonctionnalités du composant de bureau. Une nouvelle entrée de manifeste dans la section <Extension> décrit la classe Runtime Implémentée dans le composant de bureau et les informations sur l’emplacement où il se trouve. Ce contenu de déclaration dans le manifeste de l’application est le même pour les applications ciblant Windows 10. Par exemple :

<Extension Category="windows.activatableClass.inProcessServer">
    <InProcessServer>
        <Path>clrhost.dll</Path>
        <ActivatableClass ActivatableClassId="Fabrikam.EnterpriseServer" ThreadingModel="both">
            <ActivatableClassAttribute Name="DesktopApplicationPath" Type="string" Value="c:\test" />
        </ActivatableClass>
    </InProcessServer>
</Extension>

La catégorie est inProcessServer, car il existe plusieurs entrées dans la catégorie outOfProcessServer qui ne sont pas applicables à cette configuration d’application. Notez que le composant <Chemin d'accès> doit toujours contenir clrhost.dll (mais cela n’est pas appliqué et la spécification d’une valeur différente échoue de manière non définie).

La section <ActivateableClass> est la même qu’une véritable classe runtime in-process préférée par un composant Windows Runtime dans le package de l’application. <ActivateableClassAttribute> est un nouvel élément, et les attributs Name="DesktopApplicationPath" et Type="string" sont obligatoires et invariants. L’attribut Valeur pointe vers l’emplacement où réside l’implémentation du composant de bureau winmd (plus de détails sur ce point dans la section suivante). Chaque runtimeClass préféré par le composant de bureau doit avoir sa propre arborescence d’éléments <ActivateableClass>. ActivateableClassId doit correspondre au nom complet qualifié de l’espace de noms du RuntimeClass.

Comme mentionné dans la section « Définition du contrat », une référence de projet doit être faite à la référence winmd du composant de bureau. Le système de projet Visual Studio crée normalement une structure d’annuaires de deux niveaux portant le même nom. Dans l’exemple, il s’agit d’EnterpriseIPCApplication\EnterpriseIPCApplication. La référence winmd est copiée manuellement dans cet annuaire de deuxième niveau, puis la boîte de dialogue Références de projet est utilisée (cliquez sur le bouton Parcourir.. ) pour localiser et référencer ce winmd. Après cela, l’espace de noms de niveau supérieur du composant de bureau (par exemple, Fabrikam) doit apparaître en tant que nœud de niveau supérieur dans la partie Références du projet.

Notez qu’il est très important d’utiliser la référence winmd dans l’application chargée de manière indépendante. Si vous reportez accidentellement l’implémentation winmd vers le répertoire d’applications chargé de manière indépendante et que vous le référencez, vous recevrez probablement une erreur liée à « Impossible de trouver IStringable ». Il s’agit d’un signe sûr que le mauvais winmd a été référencé. Les règles post-build de l’application serveur IPC (détaillées dans la section suivante) séparent soigneusement ces deux winmd en répertoires distincts.

Les variables d’environnement (en particulier %ProgramFiles%) peuvent être utilisées dans <ActivateableClassAttribute Value=« path »>. Comme indiqué précédemment, App Broker prend uniquement en charge que les systèmes 32 bits, %ProgramFiles% pour que %ProgramFiles% soit donc résolu en C:\Program Files (x86) si l’application est exécutée sur un système d’exploitation 64 bits.

Détails du serveur IPC de bureau

Les deux sections précédentes décrivent la déclaration de la classe et la mécanique de transport duwinmd de référence vers le projet d’application chargé de manière indépendante. La majeure partie du travail restant dans le composant de bureau implique l’implémentation. Étant donné que le point entier du composant de bureau est de pouvoir appeler du code de bureau (généralement pour réutiliser les ressources de code existantes), le projet doit être configuré de manière spéciale. Normalement, un projet Visual Studio utilisant .NET utilise l’un des deux « profils ». L’un est pour le bureau (« .NetFramework ») et l’autre cible la partie de l’application UWP du CLR (« .NetCore »). Un composant de bureau dans cette fonctionnalité est un hybride entre ces deux. Par conséquent, la section références est très soigneusement construite pour fusionner ces deux profils.

Un projet d’application UWP normal ne contient aucune référence de projet explicite, car l’intégralité de la surface de l’API Windows Runtime est implicitement incluse. Normalement, seules d’autres références inter-projets sont effectuées. Toutefois, un projet de composant de bureau a un ensemble très spécial de références. Il s’agit d’abord d’un projet « Desktop\Class Library », et est donc un projet de bureau. Par conséquent, des références explicites à l’API Windows Runtime (via des références à des fichiers winmd) doivent être effectuées. Ajoutez des références appropriées, comme indiqué ci-dessous.

<ItemGroup>
    <!-- These reference are added by VS automatically when you create a Class Library project-->
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
    <!-- These reference should be added manually by editing .csproj file-->

    <Reference Include="System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
      <HintPath>$(MSBuildProgramFiles32)\Microsoft SDKs\NETCoreSDK\System.Runtime.WindowsRuntime\4.0.10\lib\netcore50\System.Runtime.WindowsRuntime.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows">
      <HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\Facade\Windows.WinMD</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Foundation.FoundationContract">
      <HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\Windows.Foundation.FoundationContract\1.0.0.0\Windows.Foundation.FoundationContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Foundation.UniversalApiContract">
      <HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\1.0.0.0\Windows.Foundation.UniversalApiContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Networking.Connectivity.WwanContract">
      <HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\Windows.Networking.Connectivity.WwanContract\1.0.0.0\Windows.Networking.Connectivity.WwanContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Activation.ActivatedEventsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Activation.ActivatedEventsContract\1.0.0.0\Windows.ApplicationModel.Activation.ActivatedEventsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Activation.ActivationCameraSettingsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Activation.ActivationCameraSettingsContract\1.0.0.0\Windows.ApplicationModel.Activation.ActivationCameraSettingsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Activation.ContactActivatedEventsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Activation.ContactActivatedEventsContract\1.0.0.0\Windows.ApplicationModel.Activation.ContactActivatedEventsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Activation.WebUISearchActivatedEventsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Activation.WebUISearchActivatedEventsContract\1.0.0.0\Windows.ApplicationModel.Activation.WebUISearchActivatedEventsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Background.BackgroundAlarmApplicationContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Background.BackgroundAlarmApplicationContract\1.0.0.0\Windows.ApplicationModel.Background.BackgroundAlarmApplicationContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Calls.LockScreenCallContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Calls.LockScreenCallContract\1.0.0.0\Windows.ApplicationModel.Calls.LockScreenCallContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Resources.Management.ResourceIndexerContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Resources.Management.ResourceIndexerContract\1.0.0.0\Windows.ApplicationModel.Resources.Management.ResourceIndexerContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Search.Core.SearchCoreContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Search.Core.SearchCoreContract\1.0.0.0\Windows.ApplicationModel.Search.Core.SearchCoreContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Search.SearchContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Search.SearchContract\1.0.0.0\Windows.ApplicationModel.Search.SearchContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.ApplicationModel.Wallet.WalletContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.ApplicationModel.Wallet.WalletContract\1.0.0.0\Windows.ApplicationModel.Wallet.WalletContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Custom.CustomDeviceContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Custom.CustomDeviceContract\1.0.0.0\Windows.Devices.Custom.CustomDeviceContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Portable.PortableDeviceContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Portable.PortableDeviceContract\1.0.0.0\Windows.Devices.Portable.PortableDeviceContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Printers.Extensions.ExtensionsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Printers.Extensions.ExtensionsContract\1.0.0.0\Windows.Devices.Printers.Extensions.ExtensionsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Printers.PrintersContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Printers.PrintersContract\1.0.0.0\Windows.Devices.Printers.PrintersContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Scanners.ScannerDeviceContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Scanners.ScannerDeviceContract\1.0.0.0\Windows.Devices.Scanners.ScannerDeviceContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Devices.Sms.LegacySmsApiContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Devices.Sms.LegacySmsApiContract\1.0.0.0\Windows.Devices.Sms.LegacySmsApiContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Gaming.Preview.GamesEnumerationContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Gaming.Preview.GamesEnumerationContract\1.0.0.0\Windows.Gaming.Preview.GamesEnumerationContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Globalization.GlobalizationJapanesePhoneticAnalyzerContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Globalization.GlobalizationJapanesePhoneticAnalyzerContract\1.0.0.0\Windows.Globalization.GlobalizationJapanesePhoneticAnalyzerContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Graphics.Printing3D.Printing3DContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Graphics.Printing3D.Printing3DContract\1.0.0.0\Windows.Graphics.Printing3D.Printing3DContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Management.Deployment.Preview.DeploymentPreviewContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Management.Deployment.Preview.DeploymentPreviewContract\1.0.0.0\Windows.Management.Deployment.Preview.DeploymentPreviewContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Management.Workplace.WorkplaceSettingsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Management.Workplace.WorkplaceSettingsContract\1.0.0.0\Windows.Management.Workplace.WorkplaceSettingsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.Capture.AppCaptureContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.Capture.AppCaptureContract\1.0.0.0\Windows.Media.Capture.AppCaptureContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.Capture.CameraCaptureUIContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.Capture.CameraCaptureUIContract\1.0.0.0\Windows.Media.Capture.CameraCaptureUIContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.Devices.CallControlContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.Devices.CallControlContract\1.0.0.0\Windows.Media.Devices.CallControlContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.MediaControlContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.MediaControlContract\1.0.0.0\Windows.Media.MediaControlContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.Playlists.PlaylistsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.Playlists.PlaylistsContract\1.0.0.0\Windows.Media.Playlists.PlaylistsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Media.Protection.ProtectionRenewalContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Media.Protection.ProtectionRenewalContract\1.0.0.0\Windows.Media.Protection.ProtectionRenewalContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Networking.NetworkOperators.LegacyNetworkOperatorsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Networking.NetworkOperators.LegacyNetworkOperatorsContract\1.0.0.0\Windows.Networking.NetworkOperators.LegacyNetworkOperatorsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Networking.Sockets.ControlChannelTriggerContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Networking.Sockets.ControlChannelTriggerContract\1.0.0.0\Windows.Networking.Sockets.ControlChannelTriggerContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Security.EnterpriseData.EnterpriseDataContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Security.EnterpriseData.EnterpriseDataContract\1.0.0.0\Windows.Security.EnterpriseData.EnterpriseDataContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Security.ExchangeActiveSyncProvisioning.EasContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Security.ExchangeActiveSyncProvisioning.EasContract\1.0.0.0\Windows.Security.ExchangeActiveSyncProvisioning.EasContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Services.Maps.GuidanceContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Services.Maps.GuidanceContract\1.0.0.0\Windows.Services.Maps.GuidanceContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Services.Maps.LocalSearchContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Services.Maps.LocalSearchContract\1.0.0.0\Windows.Services.Maps.LocalSearchContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.System.Profile.SystemManufacturers.SystemManufacturersContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.System.Profile.SystemManufacturers.SystemManufacturersContract\1.0.0.0\Windows.System.Profile.SystemManufacturers.SystemManufacturersContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.System.Profile.ProfileHardwareTokenContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.System.Profile.ProfileHardwareTokenContract\1.0.0.0\Windows.System.Profile.ProfileHardwareTokenContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.System.Profile.ProfileRetailInfoContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.System.Profile.ProfileRetailInfoContract\1.0.0.0\Windows.System.Profile.ProfileRetailInfoContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.System.UserProfile.UserProfileContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.System.UserProfile.UserProfileContract\1.0.0.0\Windows.System.UserProfile.UserProfileContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.System.UserProfile.UserProfileLockScreenContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.System.UserProfile.UserProfileLockScreenContract\1.0.0.0\Windows.System.UserProfile.UserProfileLockScreenContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.UI.ApplicationSettings.ApplicationsSettingsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.UI.ApplicationSettings.ApplicationsSettingsContract\1.0.0.0\Windows.UI.ApplicationSettings.ApplicationsSettingsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.UI.Core.AnimationMetrics.AnimationMetricsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.UI.Core.AnimationMetrics.AnimationMetricsContract\1.0.0.0\Windows.UI.Core.AnimationMetrics.AnimationMetricsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.UI.Core.CoreWindowDialogsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.UI.Core.CoreWindowDialogsContract\1.0.0.0\Windows.UI.Core.CoreWindowDialogsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.UI.Xaml.Hosting.HostingContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.UI.Xaml.Hosting.HostingContract\1.0.0.0\Windows.UI.Xaml.Hosting.HostingContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Windows.Web.Http.Diagnostics.HttpDiagnosticsContract">
      <HintPath>$(MsBuildProgramFiles32)\Windows Kits\10\References\Windows.Web.Http.Diagnostics.HttpDiagnosticsContract\1.0.0.0\Windows.Web.Http.Diagnostics.HttpDiagnosticsContract.winmd</HintPath>
      <Private>False</Private>
    </Reference>
</ItemGroup>

Les références ci-dessus sont un mélange prudent de références qui sont essentielles au bon fonctionnement de ce serveur hybride. Le protocole consiste à ouvrir le fichier .csproj (comme décrit dans la procédure de modification du projet OutputType) et à ajouter ces références si nécessaire.

Une fois les références correctement configurées, la tâche suivante consiste à implémenter la fonctionnalité du serveur. Consultez la rubrique Meilleures pratiques pour l’interopérabilité avec les composants Windows Runtime (applications UWP utilisant C#/VB/C++ et XAML). La tâche consiste à créer une dll de composant Windows Runtime capable d’appeler du code de bureau dans le cadre de son implémentation. L’exemple associé inclut les principaux modèles utilisés dans Windows Runtime :

  • Appels de méthode

  • Sources d’événements Windows Runtime par le composant de bureau

  • Opérations asynchrones Windows Runtime

  • Retour de tableaux de types de base

Installer

Pour installer l’application, copiez l’implémentation winmd dans le répertoire correct spécifié dans le manifeste de l’application chargée de manière indépendante associé : Value="path" de <ActivateableClassAttribute>. Copiez également les fichiers de support associés et la dll proxy/stub (ce dernier détail est abordé ci-dessous). L’échec de la copie de l’implémentation winmd vers l’emplacement de l’annuaire du serveur entraîne l’envoi d’une erreur « classe non inscrite » à l’ensemble des appels de l’application chargée de manière indépendante à nouveau sur RuntimeClass. L’échec de l’installation du proxy/stub (ou de l’échec de l’inscription) entraîne l’échec de tous les appels sans valeurs de retour. Cette dernière erreur n’est fréquemment pas associée aux exceptions visibles. Si des exceptions sont observées en raison de cette erreur de configuration, elles peuvent faire référence à « projection non valide ».

Considérations relatives à l’implémentation

Le serveur Windows Runtime de bureau peut être considéré comme « worker » ou « tâche ». Chaque appel au serveur fonctionne sur un thread hors interface utilisateur et tout le code doit être conscient et sécurisé. Quelle partie de l’application chargée de manière indépendante appelle la fonctionnalité du serveur est également importante. Il est essentiel d’éviter toujours d’appeler du code durable à partir de n’importe quel thread d’interface utilisateur dans l’application chargée de manière indépendante. Pour ce faire, deux méthodes s’offrent à vous :

  1. Si vous appelez des fonctionnalités de serveur à partir d’un thread d’interface utilisateur, utilisez toujours un modèle asynchrone dans la surface publique et l’implémentation du serveur.

  2. Appelez la fonctionnalité du serveur à partir d’un thread d’arrière-plan dans l’application chargée de manière indépendante.

Synchronisation Windows Runtime dans le serveur

Étant donné la nature interprocesseur du modèle d’application, les appels au serveur ont plus de surcharge que le code qui s’exécute exclusivement dans le processus. Il est normalement sûr d’appeler une propriété simple qui retourne une valeur en mémoire, car elle s’exécute assez rapidement pour bloquer le thread d’interface utilisateur n’est pas un problème. Toutefois, tout appel impliquant des E/S de n’importe quel type (cela inclut la gestion des fichiers et les récupérations de base de données) peut potentiellement bloquer le thread d’interface utilisateur appelant et provoquer l’arrêt de l’application en raison d’une absence de réponse. En outre, les appels de propriété sur les objets sont déconseillés dans cette architecture d’application pour des raisons de performances. Ceci est couvert plus en détail dans la section suivante.

Un serveur correctement implémenté implémente normalement les appels effectués directement à partir de threads d’interface utilisateur via le modèle asynchrone Windows Runtime. Cela peut être implémenté en suivant ce modèle. Tout d’abord, la déclaration (à nouveau, à partir de l’exemple qui l’accompagne) :

public IAsyncOperation<int> FindElementAsync(int input)

Cela déclare une opération asynchrone Windows Runtime qui retourne un entier. L’implémentation de l’opération asynchrone prend normalement la forme suivante :

return Task<int>.Run( () =>
{
    int retval = ...
    // execute some potentially long-running code here 
}).AsAsyncOperation<int>();

Notez qu’il est courant d’attendre d’autres opérations potentiellement durables lors de l’écriture de l’implémentation. Dans ce cas, le code Task.Run doit être déclaré :

return Task<int>.Run(async () =>
{
    int retval = ...
    // execute some potentially long-running code here 
    await ... // some other WinRT async operation or Task
}).AsAsyncOperation<int>();

Les clients de cette méthode asynchrone peuvent attendre cette opération comme toute autre opération asynchrone Windows Runtime.

Appeler la fonctionnalité de serveur à partir d’un thread d’arrière-plan d’application

Étant donné qu’il est courant que le client et le serveur soient écrits par la même organisation, une pratique de programmation peut être adoptée que tous les appels au serveur seront effectués par un thread d’arrière-plan dans l’application chargée de manière indépendante. Un appel direct qui collecte un ou plusieurs lots de données à partir du serveur peut être effectué à partir d’un thread d’arrière-plan. Lorsque les résultats sont intégralement récupérés, le lot de données en mémoire dans le processus d’application peut généralement être récupéré directement à partir du thread d’interface utilisateur. Les objets C# sont naturellement agiles entre les threads d’arrière-plan et les threads d’interface utilisateur. Ils sont donc particulièrement utiles pour ce type de modèle d’appel.

Création et déploiement du proxy Windows Runtime

Étant donné que l’approche IPC implique le marshaling des interfaces Windows Runtime entre deux processus, un proxy Windows Runtime et un stub enregistrés globalement doivent être utilisés.

Création le proxy dans Visual Studio

Le processus de création et d’inscription de proxys et de stubs à utiliser à l’intérieur d’un package d’application UWP standard est décrit dans la rubrique Déclenchement d’événements dans les composants Windows Runtime. Les étapes décrites dans cet article sont plus complexes que le processus décrit ci-dessous, car il implique l’inscription du proxy/stub à l’intérieur du package d’application (par opposition à l’inscription globale).

Étape 1 : Utilisation de la solution pour le projet de composant de bureau, créez un projet proxy/stub dans Visual Studio :

Solution > Add > Project > Visual C++ > Win32 Console Select DLL option.

Pour les étapes ci-dessous, nous partons du principe que le composant serveur est appelé MyWinRTComponent.

Étape 3 : Supprimez tous les fichiers CPP/H du projet.

Étape 4 : La section précédente « Définition du contrat » contient une commande post-build qui exécute winmdidl.exe, midl.exe, mdmerge.exe et ainsi de suite. L’une des sorties de l’étape midl de cette commande post-build génère quatre sorties importantes :

a) Dlldata.c

b) Un fichier d’en-tête (par exemple, MyWinRTComponent.h)

c) Un fichier *_i.c (par exemple, MyWinRTComponent_i.c)

d) Un fichier *_p.c (par exemple, MyWinRTComponent_p.c)

Étape 5 : Ajoutez ces quatre fichiers générés au projet « MyWinRTProxy ».

Étape 6 : Ajouter un fichier def au projet « MyWinRTProxy » (Projet > Ajouter un nouvel article > Code > Ficher de définition de module) et mettre à jour le contenu de la manière suivante :

LIBRARY MyWinRTComponent.Proxies.dll

EXPORTS

DllCanUnloadNow PRIVATE

DllGetClassObject PRIVATE

DllRegisterServer PRIVATE

DllUnregisterServer PRIVATE

Étape 7 : Ouvrez les propriétés du projet « MyWinRTProxy » :

Propriétés de configuration > Général > Nom cible :

MyWinRTComponent.Proxies

C/C++ > Définitions de préprocesseur > Ajouter

« WIN32 ;_WINDOWS ; REGISTER_PROXY_DLL »

En-tête précompilé C/C++ > : sélectionnez « Ne pas utiliser d’en-tête précompilé »

Éditeur de liens > Général > Ignorer la bibliothèque d’importation : sélectionnez « Oui »

Éditeur de liens > Entrée > Dépendances supplémentaires : Ajouter rpcrt4.lib;runtimeobject.lib

Éditeur de liens > Métadonnées Windows > Générer des métadonnées Windows : sélectionner « Non »

Étape 8 : Intégrez le projet « MyWinRTProxy ».

Déployer le proxy

Le proxy doit être enregistré globalement. La méthode la plus simple consiste à faire en sorte que votre processus d’installation appelle DllRegisterServer sur la dll proxy. Notez que, étant donné que la fonctionnalité prend uniquement en charge les serveurs créés pour x86 (c’est-à-dire sans prise en charge 64 bits), la configuration la plus simple consiste à utiliser un serveur 32 bits, un proxy 32 bits et une application chargée 32 bits de manière indépendante. Le proxy se trouve normalement en même temps que l’implémentation winmd pour le composant de bureau.

Une étape de configuration supplémentaire doit être effectuée. Pour charger et exécuter le proxy, le répertoire doit être marqué « read / execute » pour ALL_APPLICATION_PACKAGES. Pour ce faire, utilisez l’outil en ligne de commande icacls.exe. Cette commande doit s’exécuter dans le répertoire où réside la dll winmd et proxy/stub d’implémentation :

icacls . /T /grant *S-1-15-2-1:RX

Modèles et performances

Il est très important que les performances du transport inter-processus soient soigneusement surveillées. Un appel interprocesseur est au moins deux fois plus coûteux qu’un appel in-process. La création de conversations « chatty » entre processus ou exécution de transferts répétés d’objets volumineux tels que des images bitmap, peut entraîner des performances d’application inattendues et indésirables.

Voici une liste non exhaustive des éléments à prendre en compte :

  • Les appels de méthode synchrone du thread d’interface utilisateur de l’application au serveur doivent toujours être évités. Appelez la méthode à partir d’un thread d’arrière-plan dans l’application, puis utilisez CoreWindowDispatcher pour obtenir les résultats sur le thread d’interface utilisateur si nécessaire.

  • L’appel d’opérations asynchrones à partir d’un thread d’interface utilisateur d’application est sécurisé, mais tenez compte des problèmes de performances décrits ci-dessous.

  • Le transfert en bloc des résultats réduit les échanges excessifs interprocesseurs. Cela est normalement effectué à l’aide de la construction du tableau Windows Runtime.

  • Le renvoi List<T>T est un objet d’une opération asynchrone ou d’une extraction de propriété entraîne un grand nombre de chattines inter-processus. Par exemple, supposons que vous renvoyez un objet List<People>. Chaque passe d’itération sera un appel interprocesseur. Chaque objet People retourné est représenté par un proxy et chaque appel à une méthode ou une propriété sur cet objet individuel entraîne un appel inter-processus. Ainsi, un objet List<People> « innocent » où Count est volumineux entraîne un grand nombre d’appels lents. De meilleures performances résultent du transfert en bloc de structs du contenu dans un tableau. Par exemple :

struct PersonStruct
{
    String LastName;
    String FirstName;
    int Age;
   // etc.
}

Retournez ensuite PersonStruct[] au lieu de List<PersonObject>. Cela permet d’obtenir toutes les données dans un « tronçon » interprocessé

Comme pour toutes les considérations relatives aux performances, la mesure et le test sont essentiels. Dans l’idéal, la télémétrie doit être insérée dans les différentes opérations pour déterminer le temps nécessaire. Il est important de mesurer sur une plage : par exemple, combien de temps faut-il réellement pour consommer tous les objets People pour une requête particulière dans l’application chargée de manière indépendante ?

Une autre technique est le test de charge variable. Pour ce faire, vous pouvez placer des hooks de test de performances dans l’application qui introduisent des charges de retard variable dans le traitement du serveur. Cela peut simuler différents types de charge et la réaction de l’application à différentes performances du serveur. L’exemple montre comment mettre des retards de temps dans le code à l’aide de techniques asynchrones appropriées. La quantité exacte de retard à injecter et la plage de randomisation à placer dans cette charge artificielle varie en fonction de la conception de l’application et de l’environnement attendu dans lequel l’application s’exécutera.

Processus de développement

Lorsque vous apportez des modifications au serveur, il est nécessaire de s’assurer que toutes les instances en cours d’exécution ne sont plus en cours d’exécution. COM va finalement nettoyer le processus, mais le minuteur d’exécution prend plus de temps que ce qui est efficace pour le développement itératif. Ainsi, tuer une instance précédemment en cours d’exécution est une étape normale pendant le développement. Cela nécessite que le développeur effectue le suivi de l’instance dllhost qui héberge le serveur.

Le processus de serveur est trouvé et tué à l’aide du Gestionnaire des tâches ou d’autres applications tierces. L’outil de ligne de commande TaskList.exe est également inclus et a une syntaxe flexible, par exemple :

Interface Action
tasklist Répertorie tous les processus en cours d’exécution dans l’ordre approximatif de création, avec les processus les plus récemment créés près du bas.
tasklist /FI "IMAGENAME eq dllhost.exe" /M Répertorie les informations sur toutes les instances dllhost.exe. Le commutateur /M répertorie les modules qu’ils ont chargés.
tasklist /FI « PID eq 12564 » /M Vous pouvez utiliser cette option pour interroger dllhost.exe si vous connaissez son PID.

La liste des modules d’un serveur broker doit répertorier clrhost.dll dans sa liste de modules chargés.

Ressources