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) usando C #. L'app contiene una quantità massiccia di materiale dipendente dalla piattaforma e il nostro team leader vuole che scriviamo tutto il codice in C #. Il modo più semplice per farlo sarebbe scrivere wrappers in C ++ e fare semplicemente riferimento alle librerie nel codice C # e lasciare che le librerie C ++ si occupino della piattaforma ... ahimè, non è così.

Sto provando a configurare una libreria usando Visual Studio per Mac. Spero di essere in grado di fornire una singola interfaccia in una singola libreria per garantire l'accesso al testo nativo alla libreria vocale sulla piattaforma corrente, preferibilmente senza creare due assembly. La libreria di sintesi vocale in C # per Windows non è disponibile su Mac, quindi non disponiamo di un'opzione alternativa piuttosto che fornire un bridge.

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

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

    
posta Clearer 10.04.2018 - 13:49
fonte

1 risposta

4

Hai una bella domanda. Ci sono probabilmente dei compromessi con la tua soluzione. La risposta definitiva dipende davvero da cosa intendi per piattaforma dipendente. Ad esempio, se si avvia un processo per avviare applicazioni esterne e si passa semplicemente da un'applicazione all'altra, è possibile gestirlo senza troppe complicazioni. Se stai parlando di P / Invoke con le librerie native, c'è ancora un po 'di più da fare. Tuttavia, se si collegano librerie che esistono solo su una piattaforma, probabilmente sarà necessario utilizzare più assiemi.

App esterne

Probabilmente non avrai bisogno di usare #if dichiarazioni in questa situazione. Basta configurare alcune interfacce e disporre di un'implementazione per piattaforma. Utilizzare una fabbrica per rilevare la piattaforma e fornire l'istanza corretta.

In alcuni casi è solo un binario compilato per una piattaforma specifica ma il nome dell'eseguibile e tutti i parametri sono definiti allo stesso modo. In tal caso si tratta di risolvere il file eseguibile corretto. Per un'app di conversione audio di massa che può essere eseguita su Windows e Linux, ho avuto un inizializzatore statico per risolvere 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 eccezionale qui. Solo buone classi di moda.

P / Invoke

P / Invoke è un po 'più complicato. La linea di fondo è che è necessario assicurarsi che venga caricata la versione corretta della libreria nativa. Su Windows potresti P / Invoke SetDllDirectory() . Diverse piattaforme potrebbero non aver bisogno di quel passaggio. Quindi questo è dove le cose possono diventare disordinate. Potrebbe essere necessario utilizzare #if istruzioni per controllare quale chiamata è 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. Comunque viene con molta bruttezza. Nei giorni in cui alcuni progetti tentavano di avere la stessa destinazione della DLL Silverlight, WPF e potenzialmente UAP, dovresti 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 lavorare su quelle differenze. È qui che entriamo nel diavolo di #if .

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

#if hell

Puoi attivare e disattivare sezioni di codice usando le istruzioni #if , quindi è 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 bounding box per eseguire il debug del codice di disegno.

Il problema numero 1 con #if è che nessuno del codice disattivato viene valutato dal parser. Potresti avere errori di sintassi latenti o, peggio, errori di logica che ti aspettano per ricompilare la libreria. Questo diventa ancora più problematico con il codice di refactoring. Qualcosa di semplice come rinominare un metodo o modificare l'ordine dei parametri normalmente viene gestito correttamente, ma poiché il parser non valuta mai nulla disattivato dall'istruzione #if , hai improvvisamente rotto il codice che non vedrai finché non ricompilerai.

Tutto il mio codice di debug che è stato scritto in quel modo ha dovuto essere riscritto dopo una serie di refactoring lo ha rotto. Durante la riscrittura, ho utilizzato una classe di configurazione globale per attivare e disattivare tali funzioni. Ciò ha reso la prova degli strumenti 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 anche basato sull'esempio di Microsoft, consiste nell'utilizzare più assembly.

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

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

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

    
risposta data 10.04.2018 - 14:54
fonte

Leggi altre domande sui tag