エクスポート時の型の変換
更新 : 2007 年 11 月
このトピックでは、次に示す型がエクスポート プロセスでどのように変換されるかを示します。
クラス
インターフェイス
値型
列挙体
一般に、エクスポートされた型は、アセンブリ内で持っていた名前と同じ名前を保持します。ただし、マネージ名に関連付けられている名前空間は保持されません。たとえば、次のコード例の A.B.IList 型は、エクスポートされたタイプ ライブラリでは、IList に変換されます。COM のクライアントは、この型を A.B.IList の代わりに IList として参照できます。
Namespace A
Namespace B
Interface IList
…
End Interface
End Namespace
End Namespace
namespace A {
namespace B {
interface IList {
…
}
}
}
異なる名前空間の型は同じ名前を持つことができるため、この手法では、アセンブリ内の型名が衝突する可能性があります。エクスポート プロセスは衝突を検出すると、名前空間を保持して、名前のあいまいさを解決します。同じ型名が含まれている 2 つの名前空間のコード例を次に示します。
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 {…};
};
また、エクスポート プロセスは、名前空間と型名を結合してプログラム ID (ProgId) を自動的に生成します。たとえば、前の例で示したマネージ クラス LinkedList に対して生成される ProgId は、A.B.LinkedList です。
名前空間と型名を結合すると、無効な ProgId が生成される場合があります。ProgId は 39 文字までに制限されており、ピリオド以外の区切り記号を含めることはできません。この制限を回避するには、エクスポート プロセスで ID を自動生成するのではなく、ProgIdAttribute を適用することにより、ソース コード内に ProgId を指定します。
クラス
エクスポートのプロセスでは、アセンブリの (ComVisible (false) 属性を省略する) 各パブリック クラスをタイプ ライブラリのコクラスに変換します。エクスポートされたコクラスは、メソッドもプロパティも持っていません。しかし、マネージ クラスの名前を保持し、マネージ クラスで明示的に実装されているすべてのインターフェイスを実装します。
IShape インターフェイスと Circle クラスを定義して IShape を実装するコード例を次に示します。コード例の後に、変換後のタイプ ライブラリ表現を示します。
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);
}
タイプ ライブラリ表現
[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
HRESULT Draw();
HRESULT Move(int x, int y);
}
[ uuid(…) ]
coclass Circle {
interface IShape;
}
それぞれのコクラスは、クラス インターフェイスと呼ばれるもう 1 つのインターフェイスを実装できます。このインターフェイスは、エクスポート プロセスによって自動的に生成されます。クラス インターフェイスは、元のマネージ クラスで利用可能なすべてのメソッドおよびプロパティを公開します。COM のクライアントは、クラス インターフェイスを使って呼び出すことによって、メソッドやプロパティにアクセスできます。
マネージ クラス定義のすぐ上で GuidAttribute を適用することにより、そのクラスに固有の汎用一意識別子 (UUID) を割り当てることができます。変換中、エクスポート プロセスは、GuidAttribute に提供された値をタイプ ライブラリ内の UUID に転送します。それ以外の場合は、エクスポート プロセスは、クラスの完全名 (名前空間を含む) が含まれているハッシュから UUID を取得します。完全名を使用することにより、特定の名前空間の特定の名前のクラスからは常に同じ UUID が生成され、名前の異なる 2 つのクラスからは同じ UUID が生成されないことが保証されます。
抽象クラスおよびパブリックの既定のコンストラクタを持たないクラスには、noncreatable タイプ ライブラリ属性が設定されます。licensed、hidden、restricted、control など、コクラスに適用されるその他のタイプ ライブラリ属性は設定されません。
インターフェイス
エクスポート プロセスは、マネージ インターフェイスをマネージ インターフェイスと同じメソッドとプロパティを持つ COM のインターフェイスに変換しますが、メソッド シグネチャは大きく異なります。
インターフェイス ID
COM のインターフェイスには、それぞれのインターフェイスを区別するためのインターフェイス ID (IID) が含まれています。GuidAttribute 属性を適用することにより、マネージ インターフェイスに固定 IID を割り当てることができます。この属性が適用されておらず、固定 IID が割り当てられていない場合は、変換中、エクスポート プロセスが自動的に固定 IID を割り当てます。ランタイムによって割り当てられる IID は、インターフェイス名 (名前空間を含む) とそのインターフェイス内に定義されているすべてのメソッドの完全シグネチャで構成されます。マネージ インターフェイスのメソッドの順序を変更するか、またはメソッドの引数や戻り値の型を変更することにより、インターフェイスに割り当てられた IID を変更します。メソッド名を変更しても、IID には影響しません。
COM のクライアントは、ランタイムによって実装された QueryInterface メソッドを使用して、固定 IID またはランタイムによって割り当てられた IID を持つインターフェイスを取得できます。ランタイムによって生成された IID は、その型のメタデータ内では永続化されません。
インターフェイスの型
エクスポート プロセスは、他の指定がない場合、タイプ ライブラリ内ですべてのマネージ インターフェイスをデュアル インターフェイスに変換します。デュアル インターフェイスにより、COM のクライアントは事前バインディングと遅延バインディングのどちらかを選択できます。
インターフェイスに InterfaceTypeAttribute 属性を適用することにより、インターフェイスをデュアル インターフェイスとしてエクスポートするか、IUnknown から派生したインターフェイスとしてエクスポートするか、またはディスパッチ専用インターフェイス (Dispinterface) としてエクスポートするかを選択できます。エクスポートされたすべてのインターフェイスは、マネージ コードでのインターフェイスの継承階層に関係なく、すべて IUnknown または IDispatch から直接拡張されます。
インターフェイスの型を制御する省略可能な値を示すコード例を次に示します。タイプ ライブラリにエクスポートされた後、これらのオプションは、コードの後のタイプ ライブラリ表現で示す結果を生成します。
' 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();
}
タイプ ライブラリ表現
[ 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 スロットも公開します。
クラス インターフェイス
クラス インターフェイスの詳細と、使用に関する推奨事項については、「クラス インターフェイスの概要」を参照してください。エクスポート プロセスは、マネージ コードに明示的に定義されているインターフェイスを使用せずに、マネージ クラスの代わりとして自動的にこのインターフェイスを生成できます。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;}
}
タイプ ライブラリ表現
[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 です。
エクスポート プロセスは、常に新しいインターフェイス ID (IID) を生成します。クラス インターフェイスの IID を明示的に設定することはできません。
既定では、どちらのインターフェイスも、IDispatch インターフェイスから派生します。
インターフェイスは、ODL、dual、hidden、nonextensible、oleautomation の各属性があります。
どちらのインターフェイスも、その基本クラス (System Object) のすべてのパブリック メンバを持ちます。
基本クラスのプライベート メンバまたは内部メンバは、どちらのインターフェイスにも含まれていません。
各メンバには、自動的に一意の DispId が割り当てられています。DispId は、クラスのメンバに DispIdAttribute を割り当てることによって明示的に設定できます。
メソッド シグネチャは、HRESULT を返すように変換され、[out, retval] パラメータを含みます。
プロパティおよびフィールドは、[propget]、[propput]、および [propputref] に変換されます。
既定インターフェイス
COM には既定インターフェイスの概念があります。既定インターフェイスのメンバは、Visual Basic のような遅延バインディング言語では、クラスのメンバとして扱われます。.NET Framework では、クラス自体がメンバを持つことができるため、既定インターフェイスは不要です。ただし、COM にクラスを公開するときは、クラスが既定インターフェイスを持っていると、クラスははるかに使いやすくなります。
マネージ クラスをタイプ ライブラリにコクラスとしてエクスポートすると、通常、1 つのインターフェイスがそのクラスの既定インターフェイスとして識別されます。タイプ ライブラリでどのインターフェイスも既定インターフェイスとして識別されない場合、ほとんどの COM アプリケーションは、最初に実装されたインターフェイスをそのコクラスの既定インターフェイスと見なします。
次のコード例に示すように、エクスポート プロセスは、クラス インターフェイスを持たないマネージ クラスを変換し、最初に実装されたインターフェイスをエクスポート タイプ ライブラリ内の既定インターフェイスと見なします。コード例の後に、変換後のクラスのタイプ ライブラリ表現を示します。
<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassWithNoClassInterface
Implements IExplicit
Implements IAnother
Sub M()
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class ClassWithNoClassInterface : IExplicit, IAnother {
void M();
}
タイプ ライブラリ表現
coclass ClassWithNoClassInterface {
[default] IExplicit;
IAnother;
}
エクスポート プロセスは、そのクラスが明示的に実装しているそれ以外のインターフェイスとは関係なく、常にクラス インターフェイスをそのクラスの既定インターフェイスと見なします。2 つのクラスを次の例に示します。
<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();
}
タイプ ライブラリ表現
// 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;
…
};
タイプ ライブラリ表現
enum DaysOfWeek {
DaysOfWeek_Sunday = 0;
DaysOfWeek_Monday;
DaysOfWeek_Tuesday;
…
};
ランタイムは、マネージ列挙型のメンバをそれらのメンバが属する列挙型にスコープを設定します。たとえば、前の例で示した DaysOfWeek 列挙型の Sunday へのすべての参照は、DaysOfWeek 列挙型で修飾される必要があります。DaysOfWeek.Sunday の代わりに Sunday を参照することはできません。メンバ名が一意であることは、COM 列挙体の必須条件です。