匯出的類型轉換
這個主題將描述匯出處理序如何轉換下列型別:
類別
介面
實值型別
列舉
一般而言,匯出的型別會保留它們在組件中所擁有的同樣名稱 (除了與 Managed 名稱關聯的命名空間以外)。 例如,下列程式碼範例中的 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)。 例如,為前面範例中所示 Managed LinkedList 類別產生的 ProgId 為 A.B.LinkedList。
結合命名空間和型別名稱也可能會導致無效的 ProgId。 ProgId 限制為 39 個字元而且不能含有句號以外的標點符號字元。 若要避開這些限制,您可以在原始程式碼中套用 ProgIdAttribute 來指定 ProgId,而不要讓匯出處理序為您產生識別項。
類別
匯出處理序會將組件中的每一個公用類別 (省略 ComVisible (false) 屬性的類別) 轉換為型別程式庫中的 Coclass。 匯出的 Coclass 既沒有方法也沒有屬性;不過,它保留了 Managed 類別的名稱並且會實作 Managed 類別所明確實作的所有介面。
以下程式碼範例顯示了實作 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);
}
型別程式庫表示
[ uuid(…), dual, odl, oleautomation ]
interface IShape : IDispatch {
HRESULT Draw();
HRESULT Move(int x, int y);
}
[ uuid(…) ]
coclass Circle {
interface IShape;
}
每一個 Coclass 都可以實作另一個其他介面,稱為類別介面且可由匯出處理序自動產出。 這個類別介面會公開原來 Managed 類別中可用的所有方法和屬性,讓 COM 用戶端能夠藉由在類別介面之中呼叫來存取它們。
您可以直接將 GuidAttribute 套用在 Managed 類別定義之上,來指派特定的通用唯一識別項 (UUID) 給類別。 在轉換處理期間,匯出處理序會將提供給 GuidAttribute 的值傳輸給型別程式庫中的 UUID。 要不然,匯出處理序會從包括此類別之完整名稱 (包括命名空間) 的雜湊取得 UUID。 使用完整名稱可以確保,在特定命名空間內具有某個指定名稱的類別,必定會產生同樣的 UUID,而兩個具有不同名稱的類別,則絕不會產生相同的 UUID。
抽象類別和沒有公用、預設建構函式的類別,都是以 noncreatable 型別程式庫屬性標記。 其他套用至 Coclass 的型別程式庫屬性 (例如 licensed、hidden、restricted 及 control) 都不會設定。
介面
匯出處理序會將 Managed 介面轉換為具有與 Managed 介面同樣方法和屬性的 COM 介面,但是方法簽章卻明顯不同。
介面識別
COM 介面包括一個介面識別項 (IID) 用來與其他介面彼此區別。 您可以套用 GuidAttribute 屬性,來指派固定的 IID 給任何 Managed 介面。 如果您省略這項屬性而且不指派固定的 IID,匯出處理序便會在轉換期間自動指派。 Runtime 指派的 IID 是由介面名稱 (包括命名空間) 和介面內定義之所有方法的完整簽章所構成。 您可以藉由重新排列 Managed 介面上的方法,或變更方法引數和傳回型別,來變更指派給介面的 IID。 變更方法的名稱不會影響 IID。
使用由執行階段實作的 QueryInterface 方法,COM 用戶端可以取得具有固定 IID 或由執行階段指派之 IID 的介面。 由執行階段產生的 IID 不會在型別的中繼資料中被保存 (Persist)。
介面型別
除非您另外指定,否則匯出處理序會將所有 Managed 介面,轉換為型別程式庫中的雙重介面。 雙重介面可以讓 COM 用戶端選擇早期或是晚期繫結。
您可以將 InterfaceTypeAttribute 屬性套用至介面,選擇性地指示該介面應匯出為雙重介面、IUnknown 衍生介面或唯分派介面 (分配程式介面)。 所有匯出的介面會直接從 IUnknown 或 IDispatch 擴充,而不論它們在 Managed 程式碼中的繼承階層架構如何。
以下程式碼範例顯示了控制介面型別的選擇性值。 一旦匯出至型別程式庫後,這些選項會產生顯示於這個程式碼之後的型別程式庫表示中的結果。
' 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 介面衍生而來,但是它也會公開 (Expose) 其方法的 vtable 位置。
類別介面
如需類別介面的完整說明和使用建議,請參閱類別介面簡介。 匯出處理序可以代表 Managed 類別自動產生這個介面,而不需要在 Managed 程式碼中明確地定義介面。 COM 用戶端不能直接存取類別方法。
以下程式碼範例所示為基底類別和衍生類別。 這兩個類別都沒有實作明確的介面。 匯出處理序為這兩個 Managed 類別都提供了類別介面。
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();
}
匯出的類別介面具有下列特性:
每個類別介面都保留了 Managed 類別的名稱,但是會在前面加上底線。 當某個介面名稱與先前定義的介面名稱抵觸時,新名稱的後面會附加一個底線和一個增量號碼。 例如,_ClassWithClassInterface 的下一個可用名稱為 _ClassWithClassInterface_2。
匯出處理序永遠會產生新的介面識別項 (IID)。 您不能明確地設定類別介面的 IID。
根據預設,這兩個類別介面都是從 IDispatch 介面衍生。
這些介面具有 [ODL]、[dual]、[hidden]、[nonextensible] 和 [oleautomation] 屬性。
這兩個介面都具有其基底類別 (System.Object) 的所有公用成員。
它們不包含類別的私用或內部成員。
每個成員都會自動指派一個唯一的 DispId。 藉由將 DispIdAttribute 套用至類別的成員,可以明確地設定這些 DispId。
方法簽章會被轉換以傳回 HRESULT 並且具有 [out, retval] 參數。
屬性和欄位會被轉換成為 [propget]、[propput] 和 [propputref]。
預設介面
COM 具有預設介面的概念。 預設介面的成員會被晚期繫結語言 (例如 Visual Basic) 視為類別的成員。 在 .NET Framework 中並不需要預設介面,因為類別本身就可以擁有成員。 但是,在向 COM 公開類別時,如果這些類別具有預設介面將會更方便使用。
當 Managed 類別被匯出至型別程式庫做為 Coclass 時,通常會有一個介面被識別為該類別的預設介面。 如果沒有介面被識別為型別程式庫中的預設介面,大部分 COM 應用程式會假設第一個實作的介面就是那個 Coclass 的預設介面。
如以下程式碼範例所示,匯出處理序會轉換沒有類別介面的 Managed 類別,並且將第一個實作的介面標記為匯出之型別程式庫中的預設介面。 轉換後之類別的型別程式庫表示會顯示於程式碼範例之後。
<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;
}
匯出處理序永遠會將類別介面標記為類別的預設介面,而不管類別明確實作的其他任何介面。 以下範例顯示兩個類別。
<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 實值型別會被匯出到 COM 做為 Point Typedef,如以下範例中所示:
typedef
[uuid(…)]
struct tagPoint {
short x;
short y;
} Point;
請注意,轉換處理序會從 Typedef 移除 SetXY 方法。
列舉
匯出處理序會以改變過的成員名稱將 Managed 列舉型別加入至型別程式庫做為列舉型別,以確保唯一的成員命名方式。 為了確保每個成員名稱都是唯一的,Tlbexp.exe 會在匯出處理過程中在每個成員的列舉名稱前面加上底線。 例如,下列簡單的列舉型別會產生一組型別程式庫表示。
Enum DaysOfWeek {
Sunday = 0;
Monday;
Tuesday;
…
};
型別程式庫表示
enum DaysOfWeek {
DaysOfWeek_Sunday = 0;
DaysOfWeek_Monday;
DaysOfWeek_Tuesday;
…
};
Runtime 會將 Managed 列舉型別的成員限定在它們所屬的列舉型別中。 例如,在先前範例中所示的 DaysOfWeek 列舉型別中,所有對 Sunday 的參考必須用 DaysOfWeek 來限定。 您不能用參考 Sunday 來取代 DaysOfWeek.Sunday。 唯一的成員命名方式是 COM 列舉型別的必要條件。