导出类型转换

本主题描述导出过程如何转换以下类型:

  • 接口

  • 值类型

  • 枚举

总的说来,导出的类型会保留它们在程序集中具有的相同名称,但与托管名称相关联的命名空间除外。 例如,下面的代码示例中的 A.B.IList 类型转换为导出类型库中的 IList。 COM 客户端可以将类型当作 IList(而不是 A.B.IList)来引用。

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

当采用这种方法时,由于不同命名空间中的类型可以具有相同的名称,一个程序集中的类型名称可能会发生冲突。 当导出过程检测到冲突时,它将保留命名空间,以避免不明确的命名。 下面的代码示例显示两个具有同一类型名称的命名空间。

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 {…}
}

以下类型库表示形式显示了每个类型名称的解析过程。 此外,由于句点在类型库名称中无效,导出过程会将每个句点替换为下划线。

类型库表示形式

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

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

导出过程还会自动通过组合命名空间和类型名称来生成编程标识符 (ProgId)。 例如,为以上示例所示的托管 LinkedList 类生成的 ProgId 是 A.B.LinkedList。

组合命名空间和类型名称后,可能会生成无效的 ProgId。 一个 ProgId 仅限于 39 个字符,并且不能包含句点之外的任何标点字符。 若要避免这些限制,可以通过应用 ProgIdAttribute 在源代码中指定 ProgId,而不是让导出过程为您生成标识符。

导出进程会将程序集中的每个公共类(省略 ComVisible (false) 特性)转化为类型库中的 coclass。 导出的 coclass 既不具有方法,也不具有属性;但是,它将保留托管类的名称并实现所有由托管类显式实现的接口。

下面的代码示例显示 IShape 接口以及实现 IShape 的 Circle 类的定义。 该代码示例之后是转换的类型库表示形式。

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);
}

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

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

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

每个 coclass 都可以实现一个其他的接口,该接口称作类接口,可以由导出过程自动生成。 类接口将公开原托管类中的所有可用方法和属性,从而使 COM 客户端能够通过类接口调用并访问这些方法和属性。

通过紧接在托管类定义之上应用 GuidAttribute,可以给托管类分配特定的通用唯一标识符 (UUID)。 在转换过程中,导出过程会将给 GuidAttribute 提供的值传递给类型库中的 UUID。 否则,导出过程将从包括完整类名(包括命名空间)的哈希中获取 UUID。 通过使用完整名称,可以确保给定命名空间中具有给定名称的类始终会生成同一个 UUID,而名称不同的两个类决不会生成同一个 UUID。

抽象类和没有公共、默认构造函数的类用 noncreatable 类型库特性来标记。 将不设置其他适用于 coclass 的类型库特性,如 licensedhiddenrestrictedcontrol

接口

导出过程会将托管接口转换为与托管接口具有相同方法和属性的 COM 接口,但它们的方法签名却存在很大的差异。

46f8ac6z.collapse_all(zh-cn,VS.110).gif接口标识

COM 接口包含接口标识符 (IID),用以互相区别。 通过应用 GuidAttribute 特性,可以将固定的 IID 分配给任何托管接口。 如果省略此特性并且未分配固定的 IID,导出过程将在转换时自动分配一个 IID。 由运行时分配的 IID 包括接口名称(包括命名空间)和在该接口内定义的所有方法的完整签名。 通过对托管接口的方法重新排序或更改方法参数和返回类型,可以更改分配给该接口的 IID。 更改方法名称不会影响 IID。

利用运行时所实现的 QueryInterface 方法,COM 客户端可以获得具有固定 IID 或由运行时分配的 IID 的接口。 由运行时分配的 IID 不会在元数据中为该类型一直存在。

46f8ac6z.collapse_all(zh-cn,VS.110).gif接口类型

除非另作指定,导出过程会将所有托管接口转换为类型库中的双绑定接口。 双绑定接口使 COM 客户端能够在早期绑定和后期绑定之间进行选择。

您可以选择将 InterfaceTypeAttribute 特性应用于接口,以指示应将该接口导出为双绑定接口、IUnknown 导出接口或仅调度接口 (dispinterface)。 所有导出的接口都将直接从 IUnknownIDispatch 扩展,无论它们在托管代码中处于何种继承层次结构。

下面的代码示例显示用于控制接口类型的可选值。 当导出到类型库之后,这些选项将产生类型库表示形式(在代码示例之后)所示的结果。

' 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();
}

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

[ 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();
};

在导出进程中,大多数接口都用 odl 以及 oleautomation 类型库特性来标记。 (调度接口属于例外)。 双绑定接口用 dual 类型库特性来标记。 双重接口从 IDispatch 接口导出,但它也对其方法公开 vtable 槽。

46f8ac6z.collapse_all(zh-cn,VS.110).gif类接口

如需有关类接口的完整说明以及用法建议,请参见类接口简介。 导出过程可以代表托管类自动生成此接口,而无须在托管代码中显式定义接口。 COM 客户端无法直接访问类方法。

下面的代码示例显示一个基类和一个派生类。 这两个类都不实现显式接口。 导出过程将为这两个托管类提供类接口。

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;}
}

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

[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();
}

导出的类接口具有下列特性:

  • 每个类接口都保留托管类的名称,但带有下划线前缀。 当某个接口名称与先前定义的接口名称发生冲突时,新的名称将附加一个下划线和一个递增的数字。 例如,_ClassWithClassInterface 后的下一个可用名称为 _ClassWithClassInterface_2。

  • 导出过程始终会生成新的接口标识符 (IID)。 您不能显式地设置类接口的 IID。

  • 默认情况下,两种类接口都从 IDispatch 接口导出。

  • 这些接口具有 ODL、dual、hidden、nonextensible 和 oleautomation 特性。

  • 两种接口都具有其基类 (System.Object) 的所有公共成员。

  • 它们不包含类的私有或内部成员。

  • 将自动为每个成员分配一个唯一的 DispId。 可以通过将 DispIdAttribute 应用于类的成员来显式设置 DispId。

  • 方法签名将经过转换,返回 HRESULT 并具有 [out, retval] 参数。

  • 属性和字段将转换为 [propget]、[propput] 和 [propputref]。

46f8ac6z.collapse_all(zh-cn,VS.110).gif默认接口

COM 具有默认接口的概念。 默认接口的成员将由类似于 Visual Basic 的后期绑定语言当作类的成员来进行处理。 在 .NET Framework 中,由于类本身就可以具有成员,因此无须使用默认接口。 但是,当向 COM 公开类时,如果类具有默认接口,使用起来会容易得多。

当托管类作为 coclass 导出到类型库时,通常会将一个接口标识为该类的默认接口。 如果没有将任何接口标识为类型库中的默认接口,大多数 COM 应用程序将假定所实现第一个接口为该 coclass 的默认接口。

如下面的代码示例所示,导出过程将转换没有类接口的托管类,并将所实现的第一个接口标识为导出类型库中的默认接口。 该代码示例之后是所转换类的类型库表示形式。

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

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

coclass ClassWithNoClassInterface {
    [default] IExplicit; 
    IAnother;
}

无论类显式地实现了其他哪些接口,导出进程始终会将类接口标记为类的默认接口。 下面的示例显示了两个类。

<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();   
}

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

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

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

值类型

值类型(扩展 System.Value 的类型)将当作带有类型定义的 C 样式结构导出到类型库中。 结构成员的布局通过应用于该类型的 StructLayoutAttribute 特性来控制。 仅导出值类型的字段。 如果值类型具有方法,则无法从 COM 访问这些方法。

例如:

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

Point 值类型当作点 typedef 导出到 COM 中,如下例所示:

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

请注意,此转换过程从 typedef 中移除了 SetXY 方法。

枚举

导出过程会将托管枚举当作已更改成员名称的枚举添加到类型库中,成员名称在经过更改后可确保成员命名的唯一性。 为了确保每个成员名称都是唯一的,Tlbexp.exe 将在导出时在每个成员的枚举名称前加上下划线前缀。 例如,以下简单枚举会生成一组类型库表示形式。

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

46f8ac6z.collapse_all(zh-cn,VS.110).gif类型库表示形式

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

运行时将托管枚举成员的范围限制在它们所属的枚举。 例如,对 DaysOfWeek 枚举中 Sunday 的所有引用(如上例所示)必须用 DaysOfWeek 来限定。 您不能引用 Sunday 来替代 DaysOfWeek.Sunday。 成员命名的唯一性是 COM 枚举的一项要求。

请参见

概念

导出程序集转换

导出模块转换

导出成员转换

导出参数转换

其他资源

有关从程序集转换到类型库的摘要