Conversión de tipos exportados

En este tema se describe cómo el proceso de exportación convierte los tipos siguientes:

  • Clases

  • Interfaces

  • Tipos de valor

  • Enumeraciones

En general, los tipos exportados mantienen el mismo nombre que tenían en el ensamblado, excepto el espacio de nombres asociado al nombre administrado. Por ejemplo, el tipo A.B.IList del siguiente ejemplo de código se convierte en IList en la biblioteca de tipos exportada. Un cliente COM puede hacer referencia al tipo como IList en lugar de A.B.IList.

Namespace A
    Namespace B
        Interface IList
               …       
        End Interface
    End Namespace
End Namespace
namespace A {
    namespace B {
        interface IList {
               …
        }
    }
}

Con este planteamiento, los nombres de los tipos de un ensamblado pueden entrar en conflicto porque los tipos de espacios de nombres diferentes pueden tener el mismo nombre. Si el proceso de exportación detecta un conflicto, conserva el espacio de nombres para eliminar ambigüedades en los nombres. En el siguiente ejemplo de código se muestran dos espacios de nombres con el mismo nombre de tipo.

Namespace A
    Namespace B
        Public Class LinkedList
            Implements IList
        End Class
      
        Public Interface IList
        End Interface 
    End Namespace
End Namespace

Namespace C
    Public Interface IList
    End Interface
End Namespace
namespace A {
    namespace B {
        public class LinkedList : IList {…}
        public interface IList {…}
    }
}
   namespace C {
       public interface IList {…}
}

En la siguiente representación de biblioteca de tipos se muestra la resolución de cada nombre de tipo. Además, como los puntos no se admiten en los nombres de bibliotecas de tipos, el proceso de exportación reemplaza cada punto por un carácter de subrayado.

Representación de biblioteca de tipos

library Widgets 
{
    […]
    coclass LinkedList 
    {
        interface A_B_IList
    };

    […]
    interface A_B_IList {…};
    […]
    interface C_IList {…};
};

El proceso de exportación también genera automáticamente un identificador de programación (ProgId) combinando el espacio de nombres y el nombre del tipo. Por ejemplo, el identificador ProgId generado para la clase administrada LinkedList que se muestra en los ejemplos anteriores es A.B.LinkedList.

La combinación del espacio de nombres y el nombre del tipo puede producir un identificador ProgId no válido. Los identificadores ProgId tienen una limitación de 39 caracteres y no pueden contener caracteres de puntuación que no sean puntos. Para evitar estas limitaciones, se puede especificar un identificador ProgId en el código fuente aplicando ProgIdAttribute, en lugar de dejar que el proceso de exportación genere el identificador.

Clases

El proceso de exportación convierte cada clase pública de un ensamblado (que omite el atributo ComVisible (false)) en una coclase de una biblioteca de tipos. Una coclase exportada no tiene métodos ni propiedades, pero mantiene el nombre de la clase administrada e implementa todas las interfaces implementadas explícitamente por dicha clase.

En el siguiente ejemplo de código se muestra la definición de la interfaz IShape y de la clase Circle, que implementa IShape. La representación de biblioteca de tipos convertida se encuentra a continuación del ejemplo de código.

Public Interface IShape
    Sub Draw()
    Sub Move(x As Integer, y As Integer)
End Interface

Class Circle
    Implements IShape
    Sub Draw Implements IShape.Draw
    …    
    Sub Move(x As Integer, y As Integer) Implements IShape.Move
    …
    Sub Enlarge(x As Integer)
    …
End Class
public interface IShape {
    void Draw();
    void Move(int x, int y);
}
class Circle : IShape  {
    void Draw();
    void Move(int x, int y);
    void Enlarge(int x);
}

Representación de biblioteca de tipos

[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
    HRESULT Draw();
    HRESULT Move(int x, int y);
}

[ uuid(…) ]
coclass Circle {
    interface IShape;
}

Cada coclase puede implementar otra interfaz, denominada interfaz de clase, que el proceso de exportación puede generar automáticamente. La interfaz de clase expone todos los métodos y propiedades disponibles en la clase administrada original, permitiendo que los clientes COM tengan acceso a ellos llamando mediante la interfaz de clase.

Se puede asignar un identificador único universal (UUID) concreto a la clase aplicando GuidAttribute inmediatamente sobre la definición de la clase administrada. Durante el proceso de conversión, el proceso de exportación transfiere el valor proporcionado a GuidAttribute al identificador UUID de la biblioteca de tipos. De no ser así, el proceso de exportación obtiene los identificadores UUID de un código hash con el nombre completo de la clase, incluido el espacio de nombres. El uso del nombre completo garantiza que una clase con un nombre concreto en un espacio de nombres concreto genera siempre el mismo identificador UUID, y que dos clases con nombres distintos nunca generan el mismo identificador UUID.

Las clases abstractas y las clases sin constructores públicos predeterminados se marcan con el atributo de biblioteca de tipos noncreatable. Otros atributos de biblioteca de tipos que se aplican a las coclases, como licensed, hidden, restricted y control, no se establecen.

Interfaces

El proceso de exportación convierte las interfaces administradas en interfaces COM con los mismos métodos y propiedades que la interfaz administrada, pero los prototipos de método son bastante diferentes.

Identidades de interfaz

Las interfaces COM contienen un identificador de interfaz (IID) para diferenciar las interfaces. Se puede asignar un identificador IID fijo a cualquier interfaz administrada aplicando el atributo GuidAttribute. Si se omite este atributo y no se asigna un identificador IID fijo, el proceso de exportación asigna automáticamente uno durante la conversión. Un identificador IID asignado en tiempo de ejecución está formado por el nombre de la interfaz (incluido el espacio de nombres) y el prototipo completo de todos los métodos definidos en la interfaz. Si se cambia el orden de los métodos en la interfaz administrada o se modifican los tipos de argumento de método y de valor devuelto, se cambia el identificador IID asignado a la interfaz. El cambio de nombre de un método no afecta al identificador IID.

Empleando el método QueryInterface implementado por el motor en tiempo de ejecución, los clientes COM pueden obtener una interfaz que tenga un identificador IID fijo o un identificador IID asignado en tiempo de ejecución. Los identificadores IID generados por el motor en tiempo de ejecución no persisten en los metadatos del tipo.

Tipos de interfaz

A menos que se especifique lo contrario, el proceso de exportación convierte todas las interfaces administradas en interfaces duales en las bibliotecas de tipos. Las interfaces duales permiten que los clientes COM elijan enlaces en tiempo de diseño o en tiempo de ejecución.

Se puede aplicar el atributo InterfaceTypeAttribute a una interfaz para indicar de forma selectiva que se debe exportar como interfaz dual, como interfaz derivada de IUnknown o como interfaz de sólo envío (dispinterface). Todas las interfaces exportadas se extienden directamente de IUnknown o de IDispatch, independientemente de su jerarquía de herencia en el código administrado.

En el siguiente ejemplo de código se muestran los valores opcionales para controlar el tipo de interfaz. Una vez exportadas a una biblioteca de tipos, estas opciones producen los resultados que se muestran en la representación de biblioteca de tipos que sigue al código de ejemplo.

' Creates a Dual interface by default.
Public Interface InterfaceWithNoInterfaceType
    Sub test()
End Interface

' Creates a Dual interface explicitly.
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface InterfaceWithInterfaceIsDual
    Sub test()
End Interface

' Creates an IUnknown interface (not dispatch).
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Public Interface InterfaceWithInterfaceIsIUnknown
    Sub test()
End Interface

' Creates a Dispatch-only interface (dispinterface).
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface InterfaceWithInterfaceIsIDispatch
    Sub test()
End Interface
// Creates a Dual interface by default.
public interface InterfaceWithNoInterfaceType {
    void test();
}
// Creates a Dual interface explicitly.
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface InterfaceWithInterfaceIsDual {
    void test();
}
// Creates an IUnknown interface (not dispatch).
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface InterfaceWithInterfaceIsIUnknown {
    void test();
}
// Creates a Dispatch-only interface(dispinterface).
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface InterfaceWithInterfaceIsIDispatch {
    void test();
}

Representación de biblioteca de tipos

[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithNoInterfaceType : IDispatch {
    HRESULT test();
};
[ odl, uuid(…), dual, oleautomation ]
interface InterfaceWithInterfaceIsDual : IDispatch {
     HRESULT test();
};
[ odl, uuid(…), oleautomation ]
interface InterfaceWithInterfaceIsIUnknown : IUnknown {
     HRESULT test();
};
[ uuid(…) ]
dispinterface InterfaceWithInterfaceIsIDispatch {
     properties:
     methods:
         void test();
};

La mayoría de las interfaces se marcan con los atributos de biblioteca de tipos odl y oleautomation durante el proceso de exportación. Las interfaces de sólo envío constituyen la excepción. Las interfaces duales se marcan con el atributo de biblioteca de tipos dual. Una interfaz dual se deriva de la interfaz IDispatch, pero también expone ranuras vtable para sus métodos.

Interfaces de clase

Para obtener una descripción completa de la interfaz de clase e instrucciones de uso, vea Presentar la interfaz de clase. El proceso de exportación puede generar esta interfaz automáticamente en nombre de una clase administrada sin una interfaz definida explícitamente en el código administrado. Los clientes COM no pueden tener acceso directo a los métodos de clase.

En el ejemplo de código siguiente se muestran una clase base y una clase derivada. Ninguna de las dos implementa una interfaz explícita. El proceso de exportación proporciona una interfaz de clase para las dos clases administradas.

Public Class BaseClassWithClassInterface
    Private Shared StaticPrivateField As Integer
    Private PrivateFld As Integer
   
    Private Property PrivateProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Private Sub PrivateMeth()
        Return
    End Sub 
    Friend Shared StaticInternalField As Integer
    Friend InternalFld As Integer
   
    Friend Property InternalProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Friend Sub InternalMeth()
        Return
    End Sub 
    Public Shared StaticPublicField As Integer
    Public PublicFld As Integer
   
    Public Property PublicProp() As Integer
        Get
            Return 0
        End Get
        Set
        End Set
    End Property
   
    Public Sub PublicMeth()
        Return
    End Sub
End Class
 
Public Class DerivedClassWithClassInterface
    Inherits BaseClassWithClassInterface
   
    Public Sub Test()
        Return
    End Sub
End Class
public class BaseClassWithClassInterface {
    private  static int  StaticPrivateField;
    private  int  PrivateFld;
    private  int  PrivateProp{get{return 0;} set{;}}
    private  void PrivateMeth() {return;}

    internal static int  StaticInternalField;
    internal int  InternalFld;
    internal int  InternalProp{get{return 0;} set{;}}
    internal void InternalMeth() {return;}

    public   static int  StaticPublicField;
    public   int  PublicFld;
    public   int  PublicProp{get{return 0;} set{;}}
    public   void PublicMeth() {return;}
}

public class DerivedClassWithClassInterface : BaseClassWithClassInterface {
    public void Test() {return;}
}

Representación de biblioteca de tipos

[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _BaseClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
};
[odl,uuid(…), hidden, dual, nonextensible, oleautomation]
interface _DerivedClassWithClassInterface : IDispatch {
     [id(00000000),propget]   HRESULT ToString([out, retval] BSTR* p);
     [id(0x60020001)]         HRESULT Equals([in] VARIANT obj, 
                                [out, retval] VARIANT_BOOL* p);
     [id(0x60020002)]         HRESULT GetHashCode([out,retval] long* p);
     [id(0x60020003)]         HRESULT GetType([out, retval] _Type** p);
     [id(0x60020004),propget] HRESULT PublicProp([out,retval] long* p);
     [id(0x60020004),propput] HRESULT PublicProp([in] long p);
     [id(0x60020006)]         HRESULT PublicMeth();
     [id(0x60020007),propget] HRESULT PublicFld([out, retval]long* p);
     [id(0x60020007),propput] HRESULT PublicFld([in] long p);
     [id(0x60020008)]         HRESULT Test();
}

Las interfaces de clase exportadas tienen las siguientes características:

  • Cada interfaz de clase mantiene el nombre de la clase administrada, pero precedido por un carácter de subrayado. Si un nombre de interfaz entra en conflicto con el de una interfaz definida con anterioridad, el nuevo nombre recibe como sufijo un carácter de subrayado y un número incremental. Por ejemplo, el siguiente nombre disponible para _ClassWithClassInterface es _ClassWithClassInterface_2.

  • El proceso de exportación genera siempre nuevos identificadores de interfaz (IID). Los identificadores IID de las interfaces de clase no se pueden establecer explícitamente.

  • De manera predeterminada, las dos interfaces de clase se derivan de interfaces IDispatch.

  • Las interfaces tienen los siguientes atributos: ODL, dual, hidden, nonextensible y oleautomation.

  • Las dos interfaces tienen todos los miembros públicos de su clase base (System.Object).

  • No contienen los miembros privados o internos de la clase.

  • Cada miembro recibe automáticamente un identificador DispId único. Los identificadores DispId se pueden establecer explícitamente aplicando DispIdAttribute al miembro de la clase.

  • Los prototipos de método se transforman para devolver resultados HRESULT y tienen los parámetros [out, retval].

  • Las propiedades y campos se transforman en [propget], [propput] y [propputref].

Interfaz predeterminada

En COM existe el concepto de interfaz predeterminada. Los miembros de la interfaz predeterminada son tratados como miembros de la clase por los lenguajes de enlace en tiempo de ejecución como Visual Basic. En .NET Framework no existe la necesidad de una interfaz predeterminada porque las propias clases pueden tener miembros. Sin embargo, cuando se exponen clases a COM, las clases se usan con mayor facilidad si tienen una interfaz predeterminada.

Cuando una clase administrada se exporta a una biblioteca de tipos como coclase, normalmente se define una interfaz como predeterminada para la clase. Si no se define ninguna interfaz como predeterminada en la biblioteca de tipos, la mayoría de las aplicaciones COM suponen que la primera interfaz implementada es la interfaz predeterminada de esa coclase.

Como se muestra en el siguiente ejemplo de código, el proceso de exportación convierte una clase administrada que no tiene interfaz de clase y marca la primera interfaz implementada como interfaz predeterminada de la biblioteca de tipos exportada. La representación de biblioteca de tipos de la clase convertida se encuentra a continuación del ejemplo de código.

<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassWithNoClassInterface
    Implements IExplicit
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.None)]
public class ClassWithNoClassInterface : IExplicit, IAnother {
    void M();   
}

Representación de biblioteca de tipos

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

El proceso de exportación marca siempre la interfaz de clases como interfaz predeterminada de la clase, independientemente de cualquier otra interfaz que la clase implemente explícitamente. En el ejemplo siguiente se muestran dos clases.

<ClassInterface(ClassInterfaceType.AutoDispatch)> _
Public Class ClassWithAutoDispatch
    Implements IAnother
    Sub M()
    …
End Class 
 
<ClassInterface(ClassInterfaceType.AutoDual)> _
Public Class ClassWithAutoDual
    Implements IAnother
    Sub M()
    …
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class ClassWithAutoDispatch : IExplicit, IAnother {
    void M();   
}

[ClassInterface(ClassInterfaceType.AutoDual)]
public class ClassWithAutoDual : IExplicit, IAnother {
    void M();   
}

Representación de biblioteca de tipos

// ClassWithAutoDispatch: IDispatch
coclass ClassWithAutoDispatch {
    [default] _ClassWithAutoDispatch;
    interface _Object;
    IExplicit;
    IAnother;
}
interface _ClassWithAutoDual {…}

coclass ClassWithAutoDual {
    [default] _ClassWithAutoDual; 
    IExplicit;
    IAnother;
}

Tipos de valor

Los tipos de valor (tipos que extienden System.Value) se exportan a las bibliotecas de tipos como estructuras de estilo C con la definición del tipo. El atributo StructLayoutAttribute, que se aplica al tipo, controla el diseño de los miembros de la estructura. Sólo se exportan los campos del tipo de valor. Si un tipo de valor tiene métodos, dichos métodos no están accesibles desde COM.

Por ejemplo:

[StructLayout(LayoutKind.Sequential)]
public struct Point {
    int x;
    int y;
    public void SetXY(int x, int y){ 
        this.x = x;
        this.y = y;
    }
};

El tipo de valor Point se exporta a COM como una definición de tipo (typedef) del tipo Point, como se muestra en el siguiente ejemplo:

typedef 
[uuid(…)]
struct tagPoint {
    short x;
    short y;
} Point;

Tenga en cuenta que el proceso de conversión quita el método SetXY de la definición de tipo (typedef).

Enumeraciones

El proceso de exportación agrega una enumeración administrada a las bibliotecas de tipos como enumeración modificando los nombres de los miembros para garantizar una nomenclatura exclusiva. Para garantizar que cada miembro tiene un nombre único, Tlbexp.exe agrega como prefijo un carácter de subrayado al nombre de la enumeración de cada miembro durante el proceso de exportación. Por ejemplo, la siguiente enumeración sencilla produce un conjunto de representaciones de bibliotecas de tipos.

Enum DaysOfWeek {
    Sunday = 0;
    Monday;
    Tuesday;
    …
};

Representación de biblioteca de tipos

enum DaysOfWeek {
    DaysOfWeek_Sunday = 0;
    DaysOfWeek_Monday;
    DaysOfWeek_Tuesday;
    …
};

El motor en tiempo de ejecución incluye los miembros de la enumeración administrada en la enumeración a la que pertenecen. Por ejemplo, todas las referencias a Sunday en la enumeración DaysOfWeek que se muestra en el ejemplo anterior, se deben calificar con DaysOfWeek. No se puede hacer referencia a Sunday en lugar de DaysOfWeek.Sunday. Las enumeraciones COM requieren nombres de miembro exclusivos.

Vea también

Conceptos

Conversión de ensamblados exportados

Conversión de módulos exportados

Conversión de miembros exportados

Conversión de parámetros exportados

Otros recursos

Resumen de la conversión de ensamblados en bibliotecas de tipos