値型に対する既定のマーシャリング

整数や浮動小数点数などの多くの値型は blittable 型なのでマーシャリングは必要ありません。 その他の非 blittable 型の場合は、マネージ メモリ内とアンマネージ メモリ内でその表現が異なるため、マーシャリングが必要です。 さらに他の型については、相互運用の境界を越えた明示的な書式指定が必要です。

ここでは、書式指定された値型に関する次の情報を示します。

  • プラットフォーム呼び出しで使用される値型

  • COM 相互運用機能で使用される値型

このトピックでは、書式指定した型について説明するだけでなく、マーシャリングの動作が通常とは異なるシステム値型についても確認します。

書式指定された型は複合型であり、メモリ内で自身のメンバーのレイアウトを明示的に制御する情報を含んでいます。 メンバーのレイアウト情報は、StructLayoutAttribute 属性によって設定されます。 レイアウトは、次の LayoutKind 列挙値のいずれかになります。

  • LayoutKind.Automatic

    効率化を図るために、共通言語ランタイムが型のメンバーの順序を自由に変更できることを示します。 ただし、値型をアンマネージ コードに渡すとき、メンバーのレイアウトは予測可能です。 このような構造体のマーシャリングを試みると、自動的に例外が発生します。

  • LayoutKind.Sequential

    型のメンバーがマネージ型定義に示されているのと同じ順序で、アンマネージ メモリ内にレイアウトされることを示します。

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

アンマネージ コードへのマーシャリングの場合、上の書式指定された型は C スタイルの構造体としてマーシャリングされます。 そのため、構造体引数を持つアンマネージ 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);
}

アンマネージ API では、関数に RECT へのポインターを渡すことを必要とするため、Rect 値型を参照渡しする必要があります。 アンマネージ API では、POINT をスタック上に渡す必要があるため、Point 値型は値渡しされます。 この微妙な違いがたいへん重要です。 参照は、アンマネージ コードにポインターとして渡されます。 値はアンマネージ コードのスタック上に渡されます。

メモメモ

書式指定された型を構造体としてマーシャリングする場合は、その型に含まれるフィールドにだけアクセスできます。型がメソッド、プロパティまたはイベントを持つ場合、それらのメンバーにはアンマネージ コードからアクセスできません。

クラスが固定のメンバー レイアウトを持つ場合は、そのクラスを C スタイルの構造体としてアンマネージ コードにマーシャリングすることもできます。 クラスのメンバー レイアウト情報は、StructLayoutAttribute 属性でも提供されます。 固定レイアウトを持つ値型と固定レイアウトを持つクラスの主な違いは、アンマネージ コードへのマーシャリング方法です。 値型はスタック上に値渡しされるため、この型のメンバーに対して呼び出し先が変更を行ったとしても、その変更内容は呼び出し元にはわかりません。 参照型は参照渡しされるため (型への参照はスタック上に渡される)、この型の blittable 型メンバーに対して呼び出し先が行ったすべての変更の内容は、呼び出し元にも反映されます。

メモメモ

参照型が非 blittable 型のメンバーを持つ場合には、2 回の変換が必要です。1 回目は引数をアンマネージ側に渡すときで、2 回目は呼び出しから制御が返されるときです。このようにオーバーヘッドが増加するため、呼び出し先による変更の内容を呼び出し元が確認する必要がある場合には、引数に対して明示的に 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 引数は参照引数としては型指定されません。 値型とは異なり、クラスは常に参照渡しされます。

SetXY という名前のメソッドを持つ別の Point クラスを次のコード例に示します。 この型はシーケンシャル レイアウトを持つため、アンマネージ コードに渡して構造体としてマーシャリングできます。 ただし、オブジェクトを参照渡しする場合でも、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 相互運用機能で使用される値型

書式指定された型を COM 相互運用機能のメソッド呼び出しに渡すこともできます。 実際、値型をタイプ ライブラリにエクスポートする場合、その値型は自動的に構造体に変換されます。 次の例に示すように、Point 値型は Point という名前を持つ型定義 (typedef) になります。 タイプ ライブラリ内の他の場所にある、この Point 値型に対するすべての参照は、Point typedef と置換されます。

タイプ ライブラリ表現

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 へのポインターがスタック上に渡されます。 相互運用マーシャラーは、どちらの方向についても高水準の間接参照 (Point **) はサポートしません。

メモメモ

エクスポートされたタイプ ライブラリでは明示的なレイアウトを表現できないため、LayoutKind 列挙値が Explicit と設定されている構造体は COM 相互運用機能では使用できません。

システム値型

System 名前空間は、ボックス化された形式のランタイム プリミティブ型を表現するいくつかの値型を含んでいます。 たとえば、値型 System.Int32 構造体はボックス化した形式の ELEMENT_TYPE_I4 を表します。 このような値型は、書式指定された他の型と同じように構造体としてマーシャリングする代わりに、それらのプリミティブ型をボックス化したのと同じ方法でマーシャリングします。 そのため、System.Int32 は、long 型のメンバーを 1 つ含む構造体としてではなく、ELEMENT_TYPE_I4 としてマーシャリングされます。 プリミティブ型表現がボックス化された System 名前空間内の値型の一覧を次の表に示します。

システム値型

要素の型

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 名前空間には、別の方法で処理される値型も含まれます。 アンマネージ コードには、これらの型に合った適切な書式が既に用意されているため、マーシャラーはこれらの型をマーシャリングするときに特別な規則を適用します。 System 名前空間に属する特別な値型、およびそれらのマーシャリング後のアンマネージ型を次の表に列挙します。

システム値型

IDL 型

System.DateTime

DATE

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

Stdole2 タイプ ライブラリに含まれるアンマネージ型の DATEGUIDDECIMAL、および OLE_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;

マネージ 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 型

方向属性

コピーと固定

その他の技術情報

既定のマーシャリングの動作