Generación de origen para ComWrappers
.NET 8 presenta un generador de origen que crea una implementación de API de ComWrappers automáticamente. El generador reconoce GeneratedComInterfaceAttribute.
El sistema de interoperabilidad COM integrado en el tiempo de ejecución de .NET (no generado en origen) solo para Windows genera un código auxiliar (un flujo de instrucciones de IL que se entrega cuando es necesario) en tiempo de ejecución para facilitar la transición del código administrado a COM y viceversa. Dado que este código auxiliar de IL se genera en tiempo de ejecución, no es compatible con el recorte nativeAOT y IL. La generación de código auxiliar en tiempo de ejecución también puede dificultar el diagnóstico de problemas de serialización.
La interoperabilidad integrada usa atributos como ComImport
o DllImport
, que dependen de la generación de código en tiempo de ejecución. El código siguiente muestra un ejemplo de esto:
[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);
La API ComWrappers
permite interactuar con COM en C# sin usar el sistema COM integrado, pero requiere escribir a mano una gran cantidad de código no seguro y genérico. El generador de interfaz COM automatiza este proceso y hace que ComWrappers
sea tan sencillo como el COM integrado, pero lo entrega de forma recortable y muy compatible con AOT.
Uso básico
Para usar el generador de interfaz COM, agregue los atributos GeneratedComInterfaceAttribute y GuidAttribute en la definición de interfaz desde la que desea importar o exponer a COM. El tipo debe marcarse como partial
y tener visibilidad internal
o public
para que el código generado pueda acceder a él.
[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
void Method(int i);
}
A continuación, para exponer una clase que implementa una interfaz en COM, agregue GeneratedComClassAttribute a la clase de implementación. Esta clase también debe ser partial
y internal
o public
.
[GeneratedComClass]
internal partial class Foo : IFoo
{
public void Method(int i)
{
// Do things
}
}
En tiempo de compilación, el generador crea una implementación de la API de ComWrappers y puede usar el tipo StrategyBasedComWrappers o un tipo derivado personalizado para consumir o exponer la interfaz 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);
Personalización de la serialización
El generador de interfaz COM respeta el atributo MarshalUsingAttribute y algunos usos del atributo MarshalAsAttribute para personalizar la serialización de parámetros. Para obtener más información, consulte cómo personalizar la serialización generada por el origen con el atributo MarshalUsing
y personalizar la serialización de parámetros con el atributo MarshalAs
. Las propiedades GeneratedComInterfaceAttribute.StringMarshalling y GeneratedComInterfaceAttribute.StringMarshallingCustomType se aplican a todos los parámetros y tipos devueltos de tipo string
en la interfaz si no tienen otros atributos de serialización.
PreserveSig y HRESUL implícitos
Los métodos COM de C# tienen una firma diferente a los métodos nativos. COM estándar tiene un tipo de valor devuelto de HRESULT
, un tipo entero de 4 bytes que representa los estados de error y correcto. Este valor devuelto HRESULT
está oculto de forma predeterminada en la firma de C# y se convierte en una excepción cuando se devuelve un valor de error. El último parámetro "out" de la firma COM nativa puede convertirse opcionalmente en la devolución en la firma de C#.
Por ejemplo, los fragmentos de código siguientes muestran firmas de método de C# y la firma nativa correspondiente que el generador deduce.
void Method1(int i);
int Method2(float i);
HRESULT Method1(int i);
HRESULT Method2(float i, _Out_ int* returnValue);
Si desea controlar HRESULT
usted, puede usar PreserveSigAttribute en el método para indicar que el generador no debe realizar esta transformación. Los fragmentos de código siguientes muestran qué firma nativa espera el generador cuando se aplica [PreserveSig]
. Los métodos COM deben devolver HRESULT
, por lo que el valor devuelto de cualquier método con PreserveSig
debe ser int
.
[PreserveSig]
int Method1(int i, out int j);
[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);
HRESULT Method2(float i);
Para obtener más información, consulte Traducciones implícitas de firmas de método en interoperabilidad de .NET.
Incompatibilidades y diferencias con COM integrada
Solo IUnknown
La única base de interfaz admitida es IUnknown
. Las interfaces con un InterfaceTypeAttribute que tiene un valor distinto de InterfaceIsIUnknown no se admiten en COM generado por el origen. Se supone que cualquier interfaz sin un elemento InterfaceTypeAttribute
se deriva de IUnknown
. Esto difiere de COM integrado donde el valor predeterminado es InterfaceIsDual.
Serialización de valores predeterminados y compatibilidad
El COM generado por el origen tiene algunos comportamientos de serialización predeterminados diferentes de COM integrados.
En el sistema COM integrado, todos los tipos tienen un atributo implícito
[In]
, excepto las matrices de elementos que pueden transferirse en bloque de bits, que tienen atributos implícitos[In, Out]
. En COM generado por el origen, todos los tipos, incluidas las matrices de elementos que pueden transferirse en bloque de bits, tienen semánticas[In]
.Los atributos
[In]
y[Out]
solo se permiten en matrices. Si se requiere el comportamiento[Out]
o[In, Out]
en otros tipos, use los modificadores de parámetroin
yout
.
Interfaces derivadas
En el sistema COM integrado, si tiene interfaces que derivan de otras interfaces COM, debe declarar un método de "shadowing" para cada método base en las interfaces base con la palabra clave new
. Para obtener más información, vea herencia de interfaz de COM y .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);
}
El generador de interfaz COM no espera "shadowing" de métodos base. Para crear un método que herede de otro, simplemente indique la interfaz base como una interfaz base de C# y agregue los métodos de la interfaz derivada. Para más información, consulte la documentación de diseño.
[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);
}
Tenga en cuenta que una interfaz con el atributo GeneratedComInterface
solo puede heredar de una interfaz base que tenga el atributo GeneratedComInterface
.
Interfaces derivadas a través de límites de ensamblado
En .NET 8, no se admite definir una interfaz con el atributo GeneratedComInterfaceAttribute que deriva de una interfaz con atributos de GeneratedComInterface
definida en otro ensamblado.
En .NET 9 y versiones posteriores, este escenario se admite con las restricciones siguientes:
- El tipo de interfaz base debe compilarse con el mismo marco de trabajo de destino que el tipo derivado.
- El tipo de interfaz base no debe sombrear ningún miembro de su interfaz base, si tiene uno.
Además, los cambios en los desplazamientos de cualquier método virtual generado en la cadena de interfaz base definida en otro ensamblado no se tendrán en cuenta en las interfaces derivadas hasta que se vuelva a generar el proyecto.
Nota:
En .NET 9 y versiones posteriores, se emite una advertencia al heredar interfaces COM generadas a través de los límites de ensamblado para informarle sobre las restricciones y problemas de uso de esta característica. Puede deshabilitar esta advertencia para reconocer las limitaciones y heredar de entre los límites del ensamblado.
API de serialización
Algunas API de Marshal no son compatibles con COM generado por el origen. Reemplace estos métodos por sus métodos correspondientes en una implementación ComWrappers
.