實值型別的預設封送處理

大部分的實值型別,例如整數和浮點數值 (Floating-Point Number),都是 Blittable,並且不需要封送處理。 其他非 Blittable 型別在 Managed 和 Unmanaged 記憶體中有不同的表示,並且需要封送處理。 但其他型別需要跨互通界限進行明確格式化。

這個主題提供以下有關格式化實值型別的資訊:

  • 在平台叫用中使用的實值型別

  • 在 COM Interop 中使用的實值型別

本主題除了描述格式化型別外,也會說明具有獨特封送處理行為的系統實值型別。

格式化的型別是一種複雜型別,包含了在記憶體中明確控制其成員配置的資訊。 使用 StructLayoutAttribute 屬性可提供成員配置資訊。 配置可以是下列其中一個 LayoutKind 列舉型別值:

  • LayoutKind.Automatic

    指示 Common Language Runtime 可以為了更高的效率隨意重新排列型別的成員。 不過,當實值型別傳遞至 Unmanaged 程式碼時,成員的配置是可以預料的。 試圖封送處理這類結構會自動造成例外狀況。

  • LayoutKind.Sequential

    指示型別的成員可以透過它們出現在 Managed 型別定義中的相同順序來配置於 Unmanaged 記憶體中。

  • LayoutKind.Explicit

    指示根據每個欄位提供的 FieldOffsetAttribute 來配置成員。

在平台叫用中使用的實值型別

在下列範例中,Point 和 Rect 型別會使用 StructLayoutAttribute 提供成員配置資訊。

Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
   Public x As Integer
   Public y As Integer
End Structure
<StructLayout(LayoutKind.Explicit)> Public Structure Rect
   <FieldOffset(0)> Public left As Integer
   <FieldOffset(4)> Public top As Integer
   <FieldOffset(8)> Public right As Integer
   <FieldOffset(12)> Public bottom As Integer
End Structure
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}   

[StructLayout(LayoutKind.Explicit)]
public struct Rect {
   [FieldOffset(0)] public int left;
   [FieldOffset(4)] public int top;
   [FieldOffset(8)] public int right;
   [FieldOffset(12)] public int bottom;
}

當封送處理至 Unmanaged 程式碼時,這些格式化的型別會封送處理為 C-Style 結構。 這便提供了簡單的方式來呼叫具有結構引數的 Unmanaged API。 例如,POINT 和 RECT 結構可以傳遞至 Microsoft Win32 API PtInRect 函式,如下所示:

BOOL PtInRect(const RECT *lprc, POINT pt);

您可以使用以下的平台叫用定義來傳遞結構:

Class Win32API    
   Declare Auto Function PtInRect Lib "User32.dll" _
    (ByRef r As Rect, p As Point) As Boolean
End Class
class Win32API {
   [DllImport("User32.dll")]
   public static extern Bool PtInRect(ref Rect r, Point p);
}

因為 Unmanaged API 預期 RECT 的指標傳遞至函式中,因此 Rect 實值型別必須以傳址方式傳遞。 而因為 Unmanaged API 預期 POINT 傳遞到堆疊上,因此 Point 值必須以傳值方式傳遞。 這種微妙的差異是非常重要的。 參考是當成指標傳遞至 Unmanaged 程式碼。 數值是在堆疊上傳遞至 Unmanaged 程式碼。

注意事項注意事項

當格式化型別經封送處理成結構時,只有型別內的欄位可以存取。如果型別有方法、屬性或事件的話,則無法從 Unmanaged 程式碼中存取它們。

只要類別有固定成員配置,也可以當成 C-Style 結構封送處理至 Unmanaged 程式碼。 使用 StructLayoutAttribute 屬性可提供類別的成員配置資訊。 具有固定配置的數值成員和具有固定配置的類別之間的主要差異在於,將其封送處理至 Unmanaged 程式碼的方式不同。 實值型別是以實值方式 (在堆疊上) 傳遞,因此呼叫端不會看到被呼叫端對型別的成員所做的任何變更。 參考型別是以傳址方式 (型別的參考是傳遞到堆疊上) 傳遞,因此呼叫端會看到被呼叫端對型別的 Blittable 型別成員所做的任何變更。

注意事項注意事項

如果參考型別 (Reference Type) 有非 Blittable 型別的成員,則需要轉換兩次:第一次是在引數傳遞至 Unmanaged 端時,而第二次是在引數從呼叫返回時。由於這個增加的固定成本因素,如果呼叫端想要查看由被呼叫端所做的變更,In/Out 參數就必須明確地套用至引數中。

在下列範例中,SystemTime 類別有循序成員配置,可以傳遞至 Win32 API GetSystemTime 函式。

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime
   Public wYear As System.UInt16
   Public wMonth As System.UInt16
   Public wDayOfWeek As System.UInt16
   Public wDay As System.UInt16
   Public wHour As System.UInt16
   Public wMinute As System.UInt16
   Public wSecond As System.UInt16
   Public wMilliseconds As System.UInt16
End Class
[StructLayout(LayoutKind.Sequential)]
   public class SystemTime {
   public ushort wYear; 
   public ushort wMonth;
   public ushort wDayOfWeek; 
   public ushort wDay; 
   public ushort wHour; 
   public ushort wMinute; 
   public ushort wSecond; 
   public ushort wMilliseconds; 
}

GetSystemTime 函式定義如下:

void GetSystemTime(SYSTEMTIME* SystemTime);

GetSystemTime 的相等平台叫用定義顯示如下:

Public Class Win32
   Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (ByVal sysTime _
   As SystemTime)
End Class
class Win32API {
   [DllImport("Kernel32.dll", CharSet=CharSet.Auto)]
   public static extern void GetSystemTime(SystemTime st);
}

請注意,因為 SystemTime 是類別而不是實值型別,因此 SystemTime 引數並不是輸入為參考引數。 與實值型別不同之處在於,類別永遠是以傳址方式傳遞。

下列程式碼範例會示範不同的 Point 類別,其具有一個名稱為 SetXY 的方法。 因為型別有循序的配置,它可以傳遞至 Unmanaged 程式碼,並封送處理為結構。 不過,即使物件是以傳址方式傳遞,還是無法從 Unmanaged 程式碼中呼叫 SetXY 成員。

<StructLayout(LayoutKind.Sequential)> Public Class Point
   Private x, y As Integer
   Public Sub SetXY(x As Integer, y As Integer)
      Me.x = x
      Me.y = y
   End Sub
End Class
[StructLayout(LayoutKind.Sequential)]
public class Point {
   int x, y;
   public void SetXY(int x, int y){ 
      this.x = x;
      this.y = y;
   }
}

在 COM Interop 中使用的實值型別

格式化的型別也可以傳遞到 COM Interop 方法呼叫中。 事實上,當匯出至型別程式庫時,實值型別會自動轉換成結構。 如下列範例所示,Point 實值型別會變成名為 Point 的型別定義 (typedef)。 Point 會取代在型別程式庫中的所有 Point 實值型別參考。

型別程式庫表示

typedef struct tagPoint {
   int x;
   int y;
} Point;
interface _Graphics {
   …
   HRESULT SetPoint ([in] Point p)
   HRESULT SetPointRef ([in,out] Point *p)
   HRESULT GetPoint ([out,retval] Point *p)
}

當透過 COM 介面進行封送處理時,會使用與封送處理數值和參考至平台叫用呼叫所用的相同規則。 例如,將 Point 實值型別的執行個體從 .NET Framework 傳遞至 COM 時,會以傳值方式傳遞 Point。 如果以傳址方式傳遞 Point 實值型別,則 Point 的指標會傳遞到堆疊上。 Interop 封送處理器不支援其他方向中較高層的間接取值 (Indirection) (Point **)。

注意事項注意事項

因為匯出的型別程式庫無法表示明確配置,因此 LayoutKind 列舉值設為 Explicit 的結構無法用於 COM Interop。

系統實值型別

System 命名空間有多個實值型別,代表 Boxed 的執行階段基本型別。 例如,實值型別 System.Int32 結構代表 Boxed 的 ELEMENT_TYPE_I4。 如同其他格式化的型別一樣,您是以和它們 Box 的基本型別一樣的方式將其封送處理,而不是將這些型別封送處理為結構。 因此會將 System.Int32 當做 ELEMENT_TYPE_I4 進行封送處理,而非當做內含型別 long 單一成員的結構。 下表含有 System 命名空間的實值型別單,這些實值型別為基本型別的 Boxed 表示。

系統實值型別

項目型別

System.Boolean

ELEMENT_TYPE_BOOLEAN

System.SByte

ELEMENT_TYPE_I1

System.Byte

ELEMENT_TYPE_UI1

System.Char

ELEMENT_TYPE_CHAR

System.Int16

ELEMENT_TYPE_I2

System.UInt16

ELEMENT_TYPE_U2

System.Int32

ELEMENT_TYPE_I4

System.UInt32

ELEMENT_TYPE_U4

System.Int64

ELEMENT_TYPE_I8

System.UInt64

ELEMENT_TYPE_U8

System.Single

ELEMENT_TYPE_R4

System.Double

ELEMENT_TYPE_R8

System.String

ELEMENT_TYPE_STRING

System.IntPtr

ELEMENT_TYPE_I

System.UIntPtr

ELEMENT_TYPE_U

System 命名空間中的其他實值型別會以不同方式處理。 因為 Unmanaged 程式碼已經具有這些型別的確立格式,所以封送處理器便具有將其封送處理的特殊規則。 下表列出 System 命名空間中的特殊實值型別,以及將其封送處理至的 Unmanaged 型別。

系統實值型別

IDL 型別

System.DateTime

DATE

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

下列程式碼顯示 Stdole2 型別程式庫中 Unmanaged 型別 DATEGUIDDECIMALOLE_COLOR 的定義。

型別程式庫表示

typedef double DATE;
typedef DWORD OLE_COLOR;

typedef struct tagDEC {
    USHORT    wReserved;
    BYTE      scale;
    BYTE      sign;
    ULONG     Hi32;
    ULONGLONG Lo64;
} DECIMAL;

typedef struct tagGUID {
    DWORD Data1;
    WORD  Data2;
    WORD  Data3;
    BYTE  Data4[ 8 ];
} GUID;

下列程式碼顯示 Managed IValueTypes 介面中對應的定義。

Public Interface IValueTypes
   Sub M1(d As System.DateTime)
   Sub M2(d As System.Guid)
   Sub M3(d As System.Decimal)
   Sub M4(d As System.Drawing.Color)
End Interface
public interface IValueTypes {
   void M1(System.DateTime d);
   void M2(System.Guid d);
   void M3(System.Decimal d);
   void M4(System.Drawing.Color d);
}

型別程式庫表示

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};

請參閱

概念

Blittable 和非 Blittable 型別

方向屬性

複製和 Pin

其他資源

預設的封送處理行為