Existe-t-il un moyen de créer une bibliothèque .Net pour Windows et Mac, avec des références dépendantes de la plate-forme?

12

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.

Plus clair
la source
Quel runtime .NET allez-vous utiliser?
Euphoric
2
Xamarin fait quelque chose de similaire. Peut-être avec des configurations de construction?
Ewan
2
Il vaut la peine de noter que Visual Studio pour mac n'est pas vraiment un studio visuel, c'est juste un studio Xamarin rebadgé. Peut-être y a-t-il quelque chose dans les documents Xamarin?
richzilla
3
La meilleure approche va être des assemblys différents ... Un pour les interfaces et le code commun et un pour la plate-forme cible. Cependant, vous pouvez cibler plusieurs cibles et utiliser des instructions #if pour échanger des implémentations. L'inconvénient est que tout ce qui n'est pas activé n'est pas évalué de sorte qu'il peut facilement être obsolète.
Berin Loritsch
Lorsque vous dites des références dépendantes de la plateforme, parlez-vous de code natif ou de tout code C #?
Berin Loritsch

Réponses:

4

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 #ifinstructions 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.

public class AudioProcessor
{
    private static readonly string AppName = "lame";
    private static readonly string FullAppPath;

    static AudioProcessor()
    {
        var platform = DetectPlatform();
        var architecture = Detect64or32Bits();

        FullAppPath = Path.combine(platform, architecture, AppName);
    }
}

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 #ifinstructions 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 .csprojfichier pour gérer les références dépendantes de la plateforme. Étant donné que votre .csprojfichier 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' #ifinstructions 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 #ifest 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' #ifinstruction, 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' #ifenfer et satisfaire un environnement sensiblement différent.

Berin Loritsch
la source
Je crains en C # (je n'ai pas utilisé C ++ pendant environ 18 ans et C # pendant environ une demi-journée, c'est normal). Cette nouvelle configuration api ... pourriez-vous fournir des liens vers cela. Je n'arrive pas à le trouver par moi-même.
Plus clair le
2
docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/… Il existe plusieurs packages Nuget pour ajouter des sources de configuration supplémentaires. Par exemple, à partir de variables d'environnement, de fichiers JSON, etc.
Berin Loritsch
1
C'est l' Microsoft.Extensions.Configurationensemble des API.
Berin Loritsch
1
Voici le projet racine github: github.com/aspnet/Configuration
Berin Loritsch