Nous essayons de développer une application multiplateforme pour les ordinateurs de bureau (Windows et Mac) en utilisant C #. L'application contient une quantité énorme de choses dépendantes de la plate-forme et notre chef d'équipe veut que nous écrivions tout ce code en C #. Le moyen le plus simple serait d'écrire des wrappers en C ++ et de simplement référencer les bibliothèques dans le code C # et de laisser les bibliothèques C ++ gérer les trucs de la plate-forme ... hélas, ce n'est pas le cas.
J'essaie de configurer une bibliothèque à l'aide de Visual Studio pour Mac. J'espère pouvoir fournir une interface unique dans une seule bibliothèque pour accorder l'accès à la bibliothèque native de synthèse vocale sur la plate-forme actuelle - de préférence sans faire deux assemblages. La bibliothèque de synthèse vocale en C # pour Windows n'est pas disponible sur Mac, nous n'avons donc pas vraiment d'autre option que de fournir nous-mêmes un pont.
Je suis heureux que la bibliothèque effectue une recherche de système d'exploitation pour déterminer sur quel système d'exploitation elle s'exécute et / ou tente de charger un tas de bibliothèques natives et utilise simplement la bibliothèque qui se charge.
Si je dois, avoir un seul projet qui me permettra d'écrire deux implémentations devra faire. Je n'ai pas trouvé de moyen de créer un projet de bibliothèque dans Visual Studio pour Mac, cela me permettra cependant de le faire. Le seul type de projet qui permettra plusieurs implémentations semble exiger que les implémentations soient pour iOS ou pour Android.
la source
Réponses:
Vous avez une bonne question. Il y a probablement des compromis avec votre solution. La réponse ultime dépend vraiment de ce que vous entendez par dépendant de la plate-forme. Par exemple, si vous démarrez un processus pour démarrer des applications externes et que vous passez simplement d'une application à une autre, vous pouvez probablement gérer cela sans trop de complications. Si vous parlez de P / Invoke avec des bibliothèques natives, il y a un peu plus à faire. Cependant, si vous établissez une liaison avec des bibliothèques qui n'existent que sur une seule plate-forme, vous devrez probablement utiliser plusieurs assemblys.
Applications externes
Vous n'aurez probablement pas besoin d'utiliser des
#if
instructions dans cette situation. Il vous suffit de configurer certaines interfaces et d'avoir une implémentation par plate-forme. Utilisez une usine pour détecter la plate-forme et fournir la bonne instance.Dans certains cas, il s'agit simplement d'un binaire compilé pour une plate-forme spécifique, mais le nom de l'exécutable et tous les paramètres sont définis de la même manière. Dans ce cas, il s'agit de résoudre le bon exécutable. Pour une application de conversion audio en vrac qui pouvait fonctionner sur Windows et Linux, j'ai eu un initialiseur statique pour résoudre le nom binaire.
Rien d'extraordinaire ici. Juste de bons cours à l'ancienne.
P / Invoke
P / Invoke est un peu plus délicat. L'essentiel est que vous devez vous assurer que la bonne version de la bibliothèque native est chargée. Sur Windows, vous feriez P / Invoke
SetDllDirectory()
. Différentes plates-formes peuvent ne pas avoir besoin de cette étape. C'est donc là que les choses peuvent devenir désordonnées. Vous devrez peut-être utiliser des#if
instructions pour contrôler quel appel est utilisé pour contrôler la résolution de votre chemin d'accès à la bibliothèque, en particulier si vous l'incluez dans votre package de distribution.Lien vers des bibliothèques dépendantes de la plateforme complètement différentes
L'approche multi-ciblage de la vieille école peut être utile ici. Cependant, cela vient avec beaucoup de laideur. À l'époque où certains projets tentaient d'avoir la même DLL cible Silverlight, WPF et potentiellement UAP, vous deviez compiler l'application plusieurs fois avec différentes balises de compilation. Le défi avec chacune des plates-formes ci-dessus est que, bien qu'elles partagent les mêmes concepts, les plates-formes sont suffisamment différentes pour que vous deviez contourner ces différences. C'est là que nous entrons dans l'enfer de
#if
.Cette approche nécessite également l'édition manuelle du
.csproj
fichier pour gérer les références dépendantes de la plateforme. Étant donné que votre.csproj
fichier est un fichier MSBuild, il est tout à fait possible de le faire de manière connue et prévisible.#si l'enfer
Vous pouvez activer et désactiver des sections de code à l'aide d'
#if
instructions afin de gérer efficacement les différences mineures entre les applications. En surface, cela semble être une bonne idée. Je l'ai même utilisé comme moyen d'activer et de désactiver la visualisation de la boîte englobante pour déboguer le code de dessin.Le problème numéro 1
#if
est qu'aucun des codes désactivés n'est évalué par l'analyseur. Vous pouvez avoir des erreurs de syntaxe latentes, ou pire, des erreurs de logique qui vous attendent pour recompiler la bibliothèque. Cela devient encore plus problématique avec le refactoring de code. Quelque chose d'aussi simple que de renommer une méthode ou de changer l'ordre des paramètres serait normalement géré correctement, mais parce que l'analyseur n'évalue jamais quoi que ce soit désactivé par l'#if
instruction, vous avez soudainement cassé du code que vous ne verrez pas avant de recompiler.Tout mon code de débogage qui a été écrit de cette manière a dû être réécrit après qu'une série de refactorisations l'ait interrompu. Pendant la réécriture, j'ai utilisé une classe de configuration globale pour activer et désactiver ces fonctionnalités. Cela l'a rendu refactorisé, mais une telle solution n'aide pas lorsque l'API est complètement différente.
Ma méthode préférée
Ma méthode préférée, basée sur de nombreuses leçons douloureuses apprises, et même basée sur le propre exemple de Microsoft, consiste à utiliser plusieurs assemblys.
Un seul assemblage NetStandard définira toutes les interfaces et contiendra tout le code commun. Les implémentations dépendantes de la plate-forme seraient dans un assemblage séparé qui ajouterait des fonctionnalités lorsqu'elles sont incluses.
Cette approche est illustrée par la nouvelle API de configuration et l'architecture d'identité actuelle. Comme vous avez besoin d'intégrations plus spécifiques, vous ajoutez simplement ces nouveaux assemblages. Ces assemblages fournissent également des fonctions d'extension pour s'intégrer dans votre configuration. Si vous utilisez une approche d'injection de dépendances, ces méthodes d'extension permettent à la bibliothèque d'enregistrer ses services.
C'est à peu près le seul moyen que je connaisse pour éviter l'
#if
enfer et satisfaire un environnement sensiblement différent.la source
Microsoft.Extensions.Configuration
ensemble des API.