Esiste un modo per creare una libreria .Net per Windows e Mac, con riferimenti dipendenti dalla piattaforma?


12

Stiamo cercando di sviluppare un'app multipiattaforma per desktop (Windows e Mac) utilizzando C #. L'app contiene una quantità enorme di cose dipendenti dalla piattaforma e il nostro team leader vuole che scriviamo tutto quel codice in C #. Il modo più semplice per farlo sarebbe scrivere i wrapper in C ++ e fare semplicemente riferimento alle librerie nel codice C # e lasciare che le librerie C ++ gestiscano le cose della piattaforma ... ahimè, quello non dovrebbe essere.

Sto cercando di impostare una libreria utilizzando Visual Studio per Mac. Spero di essere in grado di fornire un'unica interfaccia in un'unica libreria per consentire l'accesso alla libreria di sintesi vocale nativa sulla piattaforma attuale, preferibilmente senza creare due assiemi. La libreria di sintesi vocale in C # per Windows non è disponibile su Mac, quindi non abbiamo davvero un'opzione alternativa rispetto a fornire noi stessi un ponte.

Sono contento che la libreria esegua la ricerca del sistema operativo per determinare su quale sistema operativo è in esecuzione e / o tentando di caricare un mucchio di librerie native e semplicemente usando quale libreria verrà caricata.

Se devo, avere un unico progetto che mi permetterà di scrivere due implementazioni dovrà fare. Non ho trovato un modo per creare un progetto di libreria in Visual Studio per Mac, che mi permetterà comunque di farlo. L'unico tipo di progetto che consentirà implementazioni multiple sembra richiedere che le implementazioni siano per iOS o per Android.


Quale runtime .NET hai intenzione di utilizzare?
Euforico

2
Xamarin fa qualcosa di simile. Forse con build config?
Ewan,

2
Vale la pena notare che Visual Studio per Mac non è davvero uno studio visivo, è solo uno studio Xamarin rinnovato. Forse c'è qualcosa nei documenti di Xamarin?
richzilla,

3
L'approccio migliore sarà l'assemblaggio diverso ... Uno per le interfacce e il codice comune e uno per la piattaforma di destinazione. Tuttavia, è possibile effettuare il multi-target e utilizzare le istruzioni #if per scambiare le implementazioni. Il rovescio della medaglia è che tutto ciò che non è abilitato non viene valutato in modo da poter essere facilmente aggiornato.
Berin Loritsch,

Quando dici riferimenti dipendenti dalla piattaforma, stai parlando di codice nativo o di tutto il codice C #?
Berin Loritsch,

Risposte:


4

Hai una buona domanda Probabilmente ci sono alcuni compromessi con la tua soluzione. La risposta definitiva dipende davvero da cosa intendi per piattaforma dipendente. Ad esempio, se stai avviando un processo per avviare applicazioni esterne e stai semplicemente passando da un'applicazione all'altra, probabilmente puoi gestirlo senza troppe complicazioni. Se stai parlando di P / Invoke con librerie native, c'è ancora qualcosa da fare. Tuttavia, se si esegue il collegamento con librerie che esistono solo su una piattaforma, probabilmente sarà necessario utilizzare più assembly.

App esterne

Probabilmente non avrai bisogno di usare le #ifdichiarazioni in questa situazione. Basta impostare alcune interfacce e avere un'implementazione per piattaforma. Utilizzare una fabbrica per rilevare la piattaforma e fornire l'istanza giusta.

In alcuni casi è solo un binario compilato per una piattaforma specifica, ma il nome dell'eseguibile e tutti i parametri sono definiti uguali. In tal caso si tratta di risolvere l'eseguibile giusto. Per un'app di conversione audio di massa che poteva essere eseguita su Windows e Linux, un inizializzatore statico risolveva il nome binario.

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);
    }
}

Niente di speciale qui. Solo buone vecchie lezioni di moda.

P / Invoke

P / Invoke è un po 'più complicato. La linea di fondo è che è necessario assicurarsi che sia caricata la versione corretta della libreria nativa. Su Windows dovresti P / Invocare SetDllDirectory(). Piattaforme diverse potrebbero non aver bisogno di questo passaggio. Quindi è qui che le cose possono diventare confuse. Potrebbe essere necessario utilizzare le #ifistruzioni per controllare quale chiamata viene utilizzata per controllare la risoluzione del percorso della libreria, in particolare se la si include nel pacchetto di distribuzione.

Collegamento a librerie dipendenti dalla piattaforma completamente diverse

L'approccio multi-targeting della vecchia scuola può essere utile qui. Tuttavia viene fornito con molta bruttezza. Nei giorni in cui alcuni progetti hanno tentato di avere lo stesso target Silverlight, WPF e potenzialmente UAP della DLL, è necessario compilare l'applicazione più volte con tag di compilazione diversi. La sfida con ciascuna delle piattaforme di cui sopra è che mentre condividono gli stessi concetti, le piattaforme sono sufficientemente diverse da dover aggirare quelle differenze. Qui è dove andiamo all'inferno #if.

Questo approccio richiede anche la modifica manuale del .csprojfile per gestire i riferimenti dipendenti dalla piattaforma. Poiché il tuo .csprojfile è un file MSBuild, è del tutto possibile farlo in un modo noto e prevedibile.

#se diavolo

È possibile attivare e disattivare sezioni di codice utilizzando le #ifistruzioni in modo che sia efficace nel gestire le differenze minori tra le applicazioni. In apparenza sembra una buona idea. L'ho persino usato come mezzo per attivare e disattivare la visualizzazione del riquadro di selezione per eseguire il debug del codice di disegno.

Il problema numero 1 #ifè che nessuno dei codici disattivati ​​viene valutato dal parser. È possibile che si verifichino errori di sintassi latenti o, peggio ancora, errori logici in attesa di ricompilare la libreria. Questo diventa ancora più problematico con il codice di refactoring. Qualcosa di semplice come rinominare un metodo o cambiare l'ordine dei parametri verrebbe normalmente gestito OK, ma poiché il parser non valuta mai nulla di spento #ifdall'istruzione improvvisamente hai un codice rotto che non vedrai fino a quando non ti ricompili.

Tutto il mio codice di debug che è stato scritto in quel modo ha dovuto essere riscritto dopo che una serie di refactoring lo hanno rotto. Durante la riscrittura, ho usato una classe di configurazione globale per attivare e disattivare quelle funzionalità. Ciò lo ha reso a prova di refactoring, ma tale soluzione non aiuta quando l'API è completamente diversa.

Il mio metodo preferito

Il mio metodo preferito, basato su molte lezioni dolorose apprese e persino basato sull'esempio di Microsoft, è di utilizzare più assiemi.

Un assembly NetStandard principale definirebbe tutte le interfacce e conterrebbe tutto il codice comune. Le implementazioni dipendenti dalla piattaforma si troverebbero in un assembly separato che aggiungerebbe funzionalità se incluse.

Questo approccio è esemplificato dalla nuova API di configurazione e dall'architettura di identità corrente. Poiché sono necessarie integrazioni più specifiche, è sufficiente aggiungere quei nuovi assiemi. Tali assiemi forniscono anche funzioni di estensione per incorporarsi nell'installazione. Se si utilizza un approccio di iniezione di dipendenza, questi metodi di estensione consentono alla libreria di registrare i propri servizi.

Questo è l'unico modo che conosco per evitare l' #ifinferno e soddisfare un ambiente sostanzialmente diverso.


Faccio schifo a C # (non ho usato C ++ per circa 18 anni e C # per circa mezza giornata, è prevedibile). Questa nuova configurazione api ... potresti fornirci dei collegamenti. Non riesco a trovarlo da solo.
Più chiaro il

2
docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/… Esistono più pacchetti Nuget per aggiungere ulteriori fonti di configurazione. Ad esempio, da variabili d'ambiente, file JSON, ecc.
Berin Loritsch,

1
È il Microsoft.Extensions.Configurationset di API.
Berin Loritsch,

1
Ecco il progetto root github: github.com/aspnet/Configuration
Berin Loritsch
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.