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")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();
[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);
// 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")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();
[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);
// 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.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
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, CreateComInterfaceFlags.None);
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 parametriin
eout
.
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
.
Interfacce derivate attraverso i limiti dell'assembly
In .NET 8 non è supportato definire un'interfaccia con l'attributo GeneratedComInterfaceAttribute che deriva da un'interfaccia GeneratedComInterface
con attributi definita in un altro assembly.
In .NET 9 e versioni successive questo scenario è supportato con le restrizioni seguenti:
- Il tipo di interfaccia di base deve essere compilato con lo stesso framework di destinazione del tipo derivato.
- Il tipo di interfaccia di base non deve nascondere alcun membro dell'interfaccia di base, se presente.
Inoltre, tutte le modifiche apportate a eventuali offset dei metodi virtuali generati nella catena di interfaccia di base definita in un altro assembly non verranno considerati nelle interfacce derivate fino a quando il progetto non viene ricompilato.
Nota
In .NET 9 e versioni successive viene generato un avviso quando ereditano interfacce COM generate attraverso i limiti dell'assembly per informare le restrizioni e i problemi di utilizzo di questa funzionalità. È possibile disabilitare questo avviso per confermare le limitazioni ed ereditare oltre i limiti dell'assembly.
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
.