Condividi tramite


Generazione di codice sorgente per ComWrappers

.NET 8 introduce un generatore di codice sorgente che crea automaticamente un'implementazione dell'API ComWrappers. Il generatore riconosce GeneratedComInterfaceAttribute.

Il sistema di interoperabilità COM integrato (non generato da codice sorgente) del runtime .NET, solo per Windows, genera uno stub IL, ovvero un flusso di istruzioni IL eseguito in JIT, in fase di esecuzione per facilitare la transizione dal codice gestito a COM e viceversa. Poiché questo stub IL viene generato in fase di esecuzione, non è compatibile con NativeAOT e il trimming IL. La generazione di stub in fase di esecuzione può anche rendere difficile la diagnosi dei problemi di marshalling.

L'interoperabilità integrata usa attributi come ComImport o DllImport, che si basano sulla generazione del codice in fase di esecuzione. Nel codice seguente ne viene illustrato un esempio:

[ComImport]
interface IFoo
{
    void Method(int i);
}

[DllImport("MyComObjectProvider.dll")]
static nint GetPointerToComInterface();

[DllImport("MyComObjectProvider.dll")]
static void GivePointerToComInterface(nint comObject);

// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);

L'API ComWrappers consente di interagire con COM in C# senza usare il sistema COM integrato, ma richiede una notevole quantità di codice boilerplate e non gestito scritto a mano. Il generatore di interfacce COM automatizza questo processo e rende ComWrappers semplice come il sistema COM integrato, ma lo fornisce in modo che sia compatibile con il trimming e AOT.

Utilizzo di base

Per usare il generatore di interfacce COM, aggiungere gli attributi GeneratedComInterfaceAttribute e GuidAttribute nella definizione di interfaccia che si vuole importare o esporre a COM. Il tipo deve essere contrassegnato come partial e avere visibilitàinternal o public per consentire al codice generato di accedervi.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
    void Method(int i);
}

Quindi, per esporre a COM una classe che implementa un'interfaccia, aggiungere GeneratedComClassAttribute alla classe di implementazione. Anche questa classe deve essere partial e internal o public.

[GeneratedComClass]
internal partial class Foo : IFoo
{
    public void Method(int i)
    {
        // Do things
    }
}

In fase di compilazione, il generatore crea un'implementazione dell'API ComWrappers ed è possibile usare il tipo StrategyBasedComWrappers o un tipo derivato personalizzato per utilizzare o esporre l'interfaccia COM.

[LibraryImport("MyComObjectProvider.dll")]
static nint GetPointerToComInterface();

[LibraryImport("MyComObjectProvider.dll")]
static void GivePointerToComInterface(nint comObject);

// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInterface(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo);
GivePointerToComInterface(ptr);

Personalizzare il marshalling

Il generatore di interfacce COM rispetta l'attributo MarshalUsingAttribute e alcuni utilizzi dell'attributo MarshalAsAttribute per personalizzare il marshalling dei parametri. Per altre informazioni, vedere come personalizzare il marshalling generato da codice sorgente con l'attributo MarshalUsing e come personalizzare il marshalling dei parametri con l'attributo MarshalAs. Le proprietà GeneratedComInterfaceAttribute.StringMarshalling e GeneratedComInterfaceAttribute.StringMarshallingCustomType si applicano a tutti i parametri e ai tipi restituiti di tipo string dell'interfaccia se non hanno altri attributi di marshalling.

HRESULT impliciti e PreserveSig

I metodi COM in C# hanno una firma diversa da quella dei metodi nativi. Il metodo COM standard ha un tipo restituito HRESULT, un tipo Integer a 4 byte che rappresenta gli stati di errore e di esito positivo. Questo valore restituito HRESULT è nascosto per impostazione predefinita nella firma C# e viene convertito in un'eccezione quando viene restituito un valore di errore. L'ultimo parametro "out" della firma COM nativa può essere convertito facoltativamente nel valore restituito nella firma C#.

Ad esempio, i frammenti di codice seguenti mostrano le firme del metodo C# e la firma nativa corrispondente dedotta dal generatore.

void Method1(int i);

int Method2(float i);
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);

Se si vuole gestire manualmente HRESULT, è possibile usare PreserveSigAttribute nel metodo per indicare al generatore di non eseguire questa trasformazione. I frammenti di codice seguenti illustrano la firma nativa prevista dal generatore quando viene applicato [PreserveSig]. I metodi COM devono restituire HRESULT, pertanto il valore restituito di qualsiasi metodo con PreserveSig deve essere int.

[PreserveSig]
int Method1(int i, out int j);

[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);

Per altre informazioni, vedere Conversioni implicite delle firme dei metodi nell'interoperabilità .NET

Incompatibilità e differenze con il COM integrato

Solo IUnknown

L'unica base di interfaccia supportata è IUnknown. Le interfacce con un oggetto InterfaceTypeAttribute con un valore diverso da InterfaceIsIUnknown non sono supportate nel COM generato da codice sorgente. Si presuppone che tutte le interfacce senza un oggetto InterfaceTypeAttribute derivino da IUnknown. Questo comportamento è diverso dal COM integrato, in cui il valore predefinito è InterfaceIsDual.

Impostazioni predefinite e supporto del marshalling

Il COM generato da codice sorgente presenta alcuni comportamenti di marshalling predefiniti diversi da quelli del COM integrato.

  • Nel sistema COM integrato, tutti i tipi hanno un attributo [In] implicito, ad eccezione delle matrici di elementi copiabili da BLT, che hanno attributi [In, Out] impliciti. Nel COM generato da codice sorgente, tutti i tipi, incluse le matrici di elementi copiabili da BLT, hanno la semantica [In].

  • Gli attributi [In] e [Out] sono consentiti solo nelle matrici. Se è richiesto il comportamento [Out] o [In, Out] in altri tipi, usare i modificatori di parametri in e out.

Interfacce derivate

Nel sistema COM integrato, se si dispone di interfacce che derivano da altre interfacce COM, è necessario dichiarare un metodo di shadowing per ogni metodo di base sulle interfacce di base con la parola chiave new. Per altre informazioni, vedere Ereditarietà delle interfacce COM e .NET.

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    new void Method1(int i);
    new void Method2(float f);
    void Method3(long l);
    void Method4(double d);
}

Il generatore di interfacce COM non prevede alcuno shadowing dei metodi di base. Per creare un metodo che eredita da un altro, è sufficiente indicare l'interfaccia di base come interfaccia di base C# e aggiungere i metodi dell'interfaccia derivata. Per altre informazioni, vedere il documento di progettazione.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    void Method3(long l);
    void Method4(double d);
}

Si noti che un'interfaccia con l'attributo GeneratedComInterface può ereditare solo da un'interfaccia di base con l'attributo GeneratedComInterface.

API Marshal

Alcune API in Marshal non sono compatibili con il COM generato da codice sorgente. Sostituire questi metodi con i metodi corrispondenti in un'implementazione ComWrappers.

Vedi anche