Generazione dell'origine per le operazioni platform invoke

.NET 7 introduce un generatore di origini per operazioni PInvoke che riconosce la classe LibraryImportAttribute nel codice C#.

Quando non usa la generazione dell'origine, il sistema di interoperabilità predefinito nel runtime .NET genera in fase di esecuzione uno stub IL, ovvero un flusso di istruzioni IL eseguito in JIT, per facilitare la transizione da codice gestito a codice non gestito. Il codice seguente illustra la definizione e la chiamata di un PInvoke che usa questo meccanismo:

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

Lo stub IL gestisce il marshalling dei parametri e dei valori restituiti e la chiamata del codice non gestito, rispettando al tempo stesso le impostazioni in DllImportAttribute che influiscono sulla modalità di richiamata del codice non gestito, ad esempio SetLastError. Dal momento che questo stub IL viene generato in fase di esecuzione, non è disponibile per gli scenari del compilatore AOT (Ahead-Of-Time) o di trimming IL. La generazione del linguaggio intermedio (IL) rappresenta un costo importante da considerare per il marshalling. Questo costo può essere misurato in termini di prestazioni dell'applicazione e supporto per le potenziali piattaforme di destinazione che potrebbero non consentire la generazione dinamica del codice. Il modello applicativo AOT nativo risolve i problemi correlati alla generazione dinamica del codice precompilando tutto il codice direttamente nel codice nativo. L'uso di DllImport non è consigliato per le piattaforme che richiedono scenari completi di AOT nativo, pertanto è consigliabile considerare approcci diversi, ad esempio la generazione dell'origine. Anche il debug della logica di marshalling negli scenari DllImport è un esercizio non banale.

Il generatore di origini PInvoke, incluso con .NET 7 SDK e abilitato per impostazione predefinita, cerca la classe LibraryImportAttribute in un metodo static e partial per attivare la generazione di codice di marshalling in fase di compilazione, evitando in tal modo di dover generare uno stub IL in fase di esecuzione e consentendo l'inlining di PInvoke. Sono inclusi anche analizzatori e correzioni del codice per facilitare la migrazione dal sistema predefinito al generatore di origini e l'utilizzo in generale.

Utilizzo di base

L'uso di LibraryImportAttribute è appositamente studiato per essere simile a quello di DllImportAttribute. È possibile convertire l'esempio precedente per usare la generazione dell'origine PInvoke usando LibraryImportAttribute e contrassegnando il metodo come partial invece di extern:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

Durante la compilazione verrà attivato il generatore di origini per generare un'implementazione del metodo ToLower che gestisce il marshalling del parametro string e del valore restituito in formato UTF-16. Poiché il marshalling è ora codice sorgente generato, è possibile visualizzarne ed esaminarne la logica in un debugger.

MarshalAs

Il generatore di origini rispetta anche la classe MarshalAsAttribute. Il codice precedente può anche essere scritto nel modo seguente:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

Alcune impostazioni per MarshalAsAttribute non sono supportate. Il generatore di origini genererà un errore se si prova a usare impostazioni non supportate. Per altre informazioni, vedere Differenze rispetto a DllImport.

Convenzione di chiamata

Per specificare la convenzione di chiamata, usare UnmanagedCallConvAttribute, ad esempio:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

Differenze rispetto a DllImport

Il campo LibraryImportAttribute è progettato per essere una conversione immediata da DllImportAttribute nella maggior parte dei casi, ma sono previste alcune modifiche intenzionali:

  • Non esiste alcun equivalente di CallingConvention in LibraryImportAttribute. In alternativa, è necessario usare UnmanagedCallConvAttribute.
  • L'enumerazione CharSet (per CharSet) è stata sostituita con StringMarshalling (per StringMarshalling). La codifica ANSI è stata rimossa e la principale sostituzione disponibile è ora UTF-8.
  • Non esiste alcun equivalente per BestFitMapping e ThrowOnUnmappableChar. Questi campi erano pertinenti solo durante il marshalling di una stringa ANSI in Windows. Il comportamento del codice generato per il marshalling di una stringa ANSI sarà equivalente a quello di BestFitMapping=false e ThrowOnUnmappableChar=false.
  • Non esiste alcun equivalente di ExactSpelling. Questo campo è un'impostazione basata su Windows e non ha alcun effetto sui sistemi operativi non Windows. Il nome del metodo o EntryPoint deve riprodurre l'ortografia esatta del nome del punto di ingresso. Questo campo include usi cronologici correlati ai suffissi A e W usati nella programmazione Win32.
  • Non esiste alcun equivalente di PreserveSig. Questo campo era un'impostazione basata su Windows. Il codice generato converte sempre direttamente la firma.
  • Il progetto deve essere contrassegnato come unsafe con AllowUnsafeBlocks.

Esistono anche differenze in termini di supporto per alcune impostazioni in MarshalAsAttribute, il marshalling predefinito di determinati tipi e altri attributi correlati all'interoperabilità. Per altre informazioni, vedere la documentazione sulle differenze in termini di compatibilità.

Vedi anche