次の方法で共有


クラス、構造体、および共用体のマーシャリング

.NET Framework では、クラスと構造体は似ています。 両方とも、フィールド、プロパティ、およびイベントを持つことができます。 静的メソッドと非静的メソッドを使用することもできます。 注目すべき違いの 1 つは、構造体が値型であり、クラスが参照型である点です。

次の表に、クラス、構造体、および共用体のマーシャリング オプションを示します。は、その使用法を説明します。対応するプラットフォーム呼び出しサンプルへのリンクを提供します。

タイプ 説明 サンプル
値によるクラス。 マネージド ケースのように、整数メンバーを持つクラスを In/Out パラメーターとして渡します。 SysTime サンプル
値による構造。 構造体を In パラメーターとして渡します。 構造体のサンプル
参照による構造。 構造体を In/Out パラメーターとして渡します。 OSInfo サンプル
入れ子構造を持つ構造体 (フラット化)。 アンマネージ関数で入れ子になった構造体を持つ構造体を表すクラスを渡します。 構造体は、マネージド プロトタイプの 1 つの大きな構造にフラット化されます。 FindFile サンプル
別の構造体へのポインターを持つ構造体。 2 番目の構造体へのポインターを含む構造体をメンバーとして渡します。 構造体のサンプル
値による整数を持つ構造体の配列。 整数のみを含む構造体の配列を In/Out パラメーターとして渡します。 配列のメンバーは変更できます。 配列のサンプル
参照による整数と文字列を含む構造体の配列。 整数と文字列を含む構造体の配列を Out パラメーターとして渡します。 呼び出された関数は、配列のメモリを割り当てます。 OutArrayOfStructs サンプル
値型を持つ共用体。 値型 (整数と double) を持つ共用体を渡します。 共用体のサンプル
型が混在する共用体。 型 (整数と文字列) が混在する共用体を渡します。 共用体のサンプル
プラットフォーム固有のレイアウトを持つ構造体。 ネイティブ パッキング定義を持つ型を渡します。 プラットフォームのサンプル
構造体内の NULL 値。 値型への参照ではなく、null 参照 (Visual Basic では Nothing ) を渡します。 HandleRef サンプル

構造体のサンプル

このサンプルでは、2 番目の構造体を指す構造体を渡し、埋め込み構造体を持つ構造体を渡し、配列を埋め込んだ構造体を渡す方法を示します。

Structs サンプルでは、元の関数宣言と共に示されている次のアンマネージ関数を使用します。

  • PinvokeLib.dllからエクスポートされた TestStructInStruct

    int TestStructInStruct(MYPERSON2* pPerson2);
    
  • PinvokeLib.dllからエクスポートされた TestStructInStruct3

    void TestStructInStruct3(MYPERSON3 person3);
    
  • PinvokeLib.dllからエクスポートされた TestArrayInStruct

    void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
    

PinvokeLib.dll は、前述の関数の実装と、MYPERSON、MYPERSON2MYPERSON3および MYARRAYSTRUCT の 4 つの構造体を含むカスタムアンマネージ ライブラリです。 これらの構造体には、次の要素が含まれています。

typedef struct _MYPERSON
{
   char* first;
   char* last;
} MYPERSON, *LP_MYPERSON;

typedef struct _MYPERSON2
{
   MYPERSON* person;
   int age;
} MYPERSON2, *LP_MYPERSON2;

typedef struct _MYPERSON3
{
   MYPERSON person;
   int age;
} MYPERSON3;

typedef struct _MYARRAYSTRUCT
{
   bool flag;
   int vals[ 3 ];
} MYARRAYSTRUCT;

マネージド MyPersonMyPerson2MyPerson3、および MyArrayStruct 構造体には、次の特性があります。

  • MyPerson には文字列メンバーのみが含まれます。 CharSet フィールドは、アンマネージ関数に渡されるときに文字列を ANSI 形式に設定します。

  • MyPerson2には、構造体への MyPerson が含まれています。 コードが安全でないとマークされない限り、.NET Framework アプリケーションではポインターが使用されないため、IntPtr 型はアンマネージ構造体への元のポインターを置き換えます。

  • MyPerson3 には、埋め込み構造体として MyPerson が含まれています。 別の構造体内に埋め込まれた構造体は、埋め込み構造の要素をメイン構造に直接配置することでフラット化できます。または、このサンプルのように埋め込み構造として残すことができます。

  • MyArrayStruct には整数の配列が含まれています。 MarshalAsAttribute属性は、配列内の要素の数を示すために使用される、UnmanagedType列挙値を ByValArray に設定します。

このサンプルのすべての構造体に対して、 StructLayoutAttribute 属性が適用され、メンバーがメモリ内で順番に順番に配置されます。

NativeMethods クラスには、TestStructInStruct クラスによって呼び出されるTestStructInStruct3TestArrayInStruct、およびAppメソッドのマネージド プロトタイプが含まれています。 各プロトタイプは、次のように 1 つのパラメーターを宣言します。

  • TestStructInStruct は、 MyPerson2 型への参照をパラメーターとして宣言します。

  • TestStructInStruct3 は、パラメーターとして型 MyPerson3 を宣言し、パラメーターを値で渡します。

  • TestArrayInStruct は、 MyArrayStruct 型への参照をパラメーターとして宣言します。

メソッドの引数としての構造体は、パラメーターに ref (Visual Basic の ByRef ) キーワードが含まれている場合を除き、値によって渡されます。 たとえば、 TestStructInStruct メソッドは、アンマネージ コードに MyPerson2 型のオブジェクトへの参照 (アドレスの値) を渡します。 MyPerson2が指す構造体を操作するために、サンプルは指定したサイズのバッファーを作成し、Marshal.AllocCoTaskMemメソッドとMarshal.SizeOfメソッドを組み合わせてアドレスを返します。 次に、マネージド構造体の内容をアンマネージド バッファーにコピーします。 最後に、このサンプルでは、 Marshal.PtrToStructure メソッドを使用してアンマネージ バッファーからマネージド オブジェクトにデータをマーシャリングし、 Marshal.FreeCoTaskMem メソッドを使用してアンマネージ メモリ ブロックを解放します。

プロトタイプの宣言

// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public value struct MyPerson
{
public:
    String^ first;
    String^ last;
};

[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson2
{
public:
    IntPtr person;
    int age;
};

[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson3
{
public:
    MyPerson person;
    int age;
};

[StructLayout(LayoutKind::Sequential)]
public value struct MyArrayStruct
{
public:
    bool flag;
    [MarshalAs(UnmanagedType::ByValArray, SizeConst = 3)]
    array<int>^ vals;
};

private ref class NativeMethods
{
public:
    // Declares a managed prototype for unmanaged function.
    [DllImport("..\\LIB\\PinvokeLib.dll")]
    static int TestStructInStruct(MyPerson2% person2);

    [DllImport("..\\LIB\\PinvokeLib.dll")]
    static int TestStructInStruct3(MyPerson3 person3);

    [DllImport("..\\LIB\\PinvokeLib.dll")]
    static int TestArrayInStruct(MyArrayStruct% myStruct);
};
// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyPerson
{
    public string first;
    public string last;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyPerson2
{
    public IntPtr person;
    public int age;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyPerson3
{
    public MyPerson person;
    public int age;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyArrayStruct
{
    public bool flag;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public int[] vals;
}

internal static class NativeMethods
{
    // Declares a managed prototype for unmanaged function.
    [DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
    internal static extern int TestStructInStruct(ref MyPerson2 person2);

    [DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
    internal static extern int TestStructInStruct3(MyPerson3 person3);

    [DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
    internal static extern int TestArrayInStruct(ref MyArrayStruct myStruct);
}
' Declares a managed structure for each unmanaged structure.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure MyPerson
    Public first As String
    Public last As String
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson2
    Public person As IntPtr
    Public age As Integer
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson3
    Public person As MyPerson
    Public age As Integer
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure MyArrayStruct
    Public flag As Boolean
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)>
    Public vals As Integer()
End Structure

Friend Class NativeMethods
    ' Declares managed prototypes for unmanaged functions.
    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Shared Function TestStructInStruct(
        ByRef person2 As MyPerson2) As Integer
    End Function

    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Shared Function TestStructInStruct3(
        ByVal person3 As MyPerson3) As Integer
    End Function

    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Shared Function TestArrayInStruct(
        ByRef myStruct As MyArrayStruct) As Integer
    End Function
End Class

関数の呼び出し

public ref class App
{
public:
    static void Main()
    {
        // Structure with a pointer to another structure.
        MyPerson personName;
        personName.first = "Mark";
        personName.last = "Lee";

        MyPerson2 personAll;
        personAll.age = 30;

        IntPtr buffer = Marshal::AllocCoTaskMem(Marshal::SizeOf(personName));
        Marshal::StructureToPtr(personName, buffer, false);

        personAll.person = buffer;

        Console::WriteLine("\nPerson before call:");
        Console::WriteLine("first = {0}, last = {1}, age = {2}",
            personName.first, personName.last, personAll.age);

        int res = NativeMethods::TestStructInStruct(personAll);

        MyPerson personRes =
            (MyPerson)Marshal::PtrToStructure(personAll.person,
                MyPerson::typeid);

        Marshal::FreeCoTaskMem(buffer);

        Console::WriteLine("Person after call:");
        Console::WriteLine("first = {0}, last = {1}, age = {2}",
            personRes.first, personRes.last, personAll.age);

        // Structure with an embedded structure.
        MyPerson3 person3;// = gcnew MyPerson3();
        person3.person.first = "John";
        person3.person.last = "Evans";
        person3.age = 27;
        NativeMethods::TestStructInStruct3(person3);

        // Structure with an embedded array.
        MyArrayStruct myStruct;// = new MyArrayStruct();

        myStruct.flag = false;
        myStruct.vals = gcnew array<int>(3);
        myStruct.vals[0] = 1;
        myStruct.vals[1] = 4;
        myStruct.vals[2] = 9;

        Console::WriteLine("\nStructure with array before call:");
        Console::WriteLine(myStruct.flag);
        Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
            myStruct.vals[1], myStruct.vals[2]);

        NativeMethods::TestArrayInStruct(myStruct);
        Console::WriteLine("\nStructure with array after call:");
        Console::WriteLine(myStruct.flag);
        Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
            myStruct.vals[1], myStruct.vals[2]);
    }
};
public class App
{
    public static void Main()
    {
        // Structure with a pointer to another structure.
        MyPerson personName;
        personName.first = "Mark";
        personName.last = "Lee";

        MyPerson2 personAll;
        personAll.age = 30;

        IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(personName));
        Marshal.StructureToPtr(personName, buffer, false);

        personAll.person = buffer;

        Console.WriteLine("\nPerson before call:");
        Console.WriteLine("first = {0}, last = {1}, age = {2}",
            personName.first, personName.last, personAll.age);

        int res = NativeMethods.TestStructInStruct(ref personAll);

        MyPerson personRes =
            (MyPerson)Marshal.PtrToStructure(personAll.person,
            typeof(MyPerson));

        Marshal.FreeCoTaskMem(buffer);

        Console.WriteLine("Person after call:");
        Console.WriteLine("first = {0}, last = {1}, age = {2}",
            personRes.first, personRes.last, personAll.age);

        // Structure with an embedded structure.
        MyPerson3 person3 = new MyPerson3();
        person3.person.first = "John";
        person3.person.last = "Evans";
        person3.age = 27;
        NativeMethods.TestStructInStruct3(person3);

        // Structure with an embedded array.
        MyArrayStruct myStruct = new MyArrayStruct();

        myStruct.flag = false;
        myStruct.vals = new int[3];
        myStruct.vals[0] = 1;
        myStruct.vals[1] = 4;
        myStruct.vals[2] = 9;

        Console.WriteLine("\nStructure with array before call:");
        Console.WriteLine(myStruct.flag);
        Console.WriteLine($"{myStruct.vals[0]} {myStruct.vals[1]} {myStruct.vals[2]}");

        NativeMethods.TestArrayInStruct(ref myStruct);
        Console.WriteLine("\nStructure with array after call:");
        Console.WriteLine(myStruct.flag);
        Console.WriteLine($"{myStruct.vals[0]} {myStruct.vals[1]} {myStruct.vals[2]}");
    }
}
Public Class App
    Public Shared Sub Main()
        ' Structure with a pointer to another structure.
        Dim personName As MyPerson
        personName.first = "Mark"
        personName.last = "Lee"

        Dim personAll As MyPerson2
        personAll.age = 30

        Dim buffer As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(
            personName))
        Marshal.StructureToPtr(personName, buffer, False)

        personAll.person = buffer

        Console.WriteLine(ControlChars.CrLf & "Person before call:")
        Console.WriteLine("first = {0}, last = {1}, age = {2}",
            personName.first, personName.last, personAll.age)

        Dim res As Integer = NativeMethods.TestStructInStruct(personAll)

        Dim personRes As MyPerson =
            CType(Marshal.PtrToStructure(personAll.person,
            GetType(MyPerson)), MyPerson)

        Marshal.FreeCoTaskMem(buffer)

        Console.WriteLine("Person after call:")
        Console.WriteLine("first = {0}, last = {1}, age = {2}",
        personRes.first,
            personRes.last, personAll.age)

        ' Structure with an embedded structure.
        Dim person3 As New MyPerson3()
        person3.person.first = "John"
        person3.person.last = "Evans"
        person3.age = 27
        NativeMethods.TestStructInStruct3(person3)

        ' Structure with an embedded array.
        Dim myStruct As New MyArrayStruct()

        myStruct.flag = False
        Dim array(2) As Integer
        myStruct.vals = array
        myStruct.vals(0) = 1
        myStruct.vals(1) = 4
        myStruct.vals(2) = 9

        Console.WriteLine(vbNewLine + "Structure with array before call:")
        Console.WriteLine(myStruct.flag)
        Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
            myStruct.vals(1), myStruct.vals(2))

        NativeMethods.TestArrayInStruct(myStruct)
        Console.WriteLine(vbNewLine + "Structure with array after call:")
        Console.WriteLine(myStruct.flag)
        Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
            myStruct.vals(1), myStruct.vals(2))
    End Sub
End Class

FindFile サンプル

このサンプルでは、2 つ目の埋め込み構造体を含む構造体をアンマネージ関数に渡す方法を示します。 また、 MarshalAsAttribute 属性を使用して、構造体内で固定長配列を宣言する方法も示します。 このサンプルでは、埋め込み構造体要素が親構造体に追加されます。 フラット化されていない埋め込み構造体のサンプルについては、「 構造体のサンプル」を参照してください。

FindFile サンプルでは、元の関数宣言と共に示されている次のアンマネージ関数を使用します。

  • Kernel32.dllからエクスポートされた FindFirstFile

    HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
    

関数に渡される元の構造体には、次の要素が含まれています。

typedef struct _WIN32_FIND_DATA
{
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  TCHAR    cFileName[ MAX_PATH ];
  TCHAR    cAlternateFileName[ 14 ];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;

このサンプルでは、 FindData クラスには、元の構造体と埋め込み構造体の各要素に対応するデータ メンバーが含まれています。 2 つの元の文字バッファーの代わりに、クラスは文字列を置き換えます。 MarshalAsAttribute は、 UnmanagedType 列挙体を ByValTStr に設定します。これは、アンマネージ構造体内に出現する固定長のインライン文字配列を識別するために使用されます。

NativeMethods クラスには、FindFirstFile クラスをパラメーターとして渡す、FindData メソッドのマネージド プロトタイプが含まれています。 参照型であるクラスは既定で In パラメーターとして渡されるため、パラメーターは InAttribute 属性と OutAttribute 属性で宣言する必要があります。

プロトタイプの宣言

// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Auto)]
public ref class FindData
{
public:
    int  fileAttributes;
    // creationTime was an embedded FILETIME structure.
    int  creationTime_lowDateTime;
    int  creationTime_highDateTime;
    // lastAccessTime was an embedded FILETIME structure.
    int  lastAccessTime_lowDateTime;
    int  lastAccessTime_highDateTime;
    // lastWriteTime was an embedded FILETIME structure.
    int  lastWriteTime_lowDateTime;
    int  lastWriteTime_highDateTime;
    int  nFileSizeHigh;
    int  nFileSizeLow;
    int  dwReserved0;
    int  dwReserved1;
    [MarshalAs(UnmanagedType::ByValTStr, SizeConst = 260)]
    String^  fileName;
    [MarshalAs(UnmanagedType::ByValTStr, SizeConst = 14)]
    String^  alternateFileName;
};

private ref class NativeMethods
{
public:
    // Declares a managed prototype for the unmanaged function.
    [DllImport("Kernel32.dll", CharSet = CharSet::Auto)]
    static IntPtr FindFirstFile(String^ fileName, [In, Out]
        FindData^ findFileData);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FindData
{
    public int fileAttributes = 0;
    // creationTime was an embedded FILETIME structure.
    public int creationTime_lowDateTime = 0;
    public int creationTime_highDateTime = 0;
    // lastAccessTime was an embedded FILETIME structure.
    public int lastAccessTime_lowDateTime = 0;
    public int lastAccessTime_highDateTime = 0;
    // lastWriteTime was an embedded FILETIME structure.
    public int lastWriteTime_lowDateTime = 0;
    public int lastWriteTime_highDateTime = 0;
    public int nFileSizeHigh = 0;
    public int nFileSizeLow = 0;
    public int dwReserved0 = 0;
    public int dwReserved1 = 0;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string fileName = null;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    public string alternateFileName = null;
}

internal static class NativeMethods
{
    // Declares a managed prototype for the unmanaged function.
    [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
    internal static extern IntPtr FindFirstFile(
        string fileName, [In, Out] FindData findFileData);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Class FindData
    Public fileAttributes As Integer = 0
    ' creationTime was a by-value FILETIME structure.
    Public creationTime_lowDateTime As Integer = 0
    Public creationTime_highDateTime As Integer = 0
    ' lastAccessTime was a by-value FILETIME structure.
    Public lastAccessTime_lowDateTime As Integer = 0
    Public lastAccessTime_highDateTime As Integer = 0
    ' lastWriteTime was a by-value FILETIME structure.
    Public lastWriteTime_lowDateTime As Integer = 0
    Public lastWriteTime_highDateTime As Integer = 0
    Public nFileSizeHigh As Integer = 0
    Public nFileSizeLow As Integer = 0
    Public dwReserved0 As Integer = 0
    Public dwReserved1 As Integer = 0
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
    Public fileName As String = Nothing
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)>
    Public alternateFileName As String = Nothing
End Class

Friend Class NativeMethods
    ' Declares a managed prototype for the unmanaged function.
    Friend Declare Auto Function FindFirstFile Lib "Kernel32.dll" (
        ByVal fileName As String, <[In], Out> ByVal findFileData As _
        FindData) As IntPtr
End Class

関数の呼び出し

public ref class App
{
public:
    static void Main()
    {
        FindData^ fd = gcnew FindData();
        IntPtr handle = NativeMethods::FindFirstFile("C:\\*.*", fd);
        Console::WriteLine("The first file: {0}", fd->fileName);
    }
};
public class App
{
    public static void Main()
    {
        FindData fd = new FindData();
        IntPtr handle = NativeMethods.FindFirstFile("C:\\*.*", fd);
        Console.WriteLine($"The first file: {fd.fileName}");
    }
}
Public Class App
    Public Shared Sub Main()
        Dim fd As New FindData()
        Dim handle As IntPtr = NativeMethods.FindFirstFile("C:\*.*", fd)
        Console.WriteLine($"The first file: {fd.fileName}")
    End Sub
End Class

共用体のサンプル

このサンプルでは、値型のみを含む構造体と、値型と文字列をパラメーターとして含む構造体を、共用体を想定するアンマネージ関数に渡す方法を示します。 共用体は、2 つ以上の変数で共有できるメモリの場所を表します。

Unions サンプルでは、元の関数宣言と共に示されている次のアンマネージ関数を使用します。

  • PinvokeLib.dllからエクスポートされた TestUnion

    void TestUnion(MYUNION u, int type);
    

PinvokeLib.dll は、前に示した関数と、 MYUNION と MYUNION2 の 2 つの共用体の実装を含むカスタムアンマネージド ライブラリ です。 共用体には、次の要素が含まれています。

union MYUNION
{
    int number;
    double d;
}

union MYUNION2
{
    int i;
    char str[128];
};

マネージド コードでは、共用体は構造体として定義されます。 MyUnion構造体には、メンバーとして整数と double の 2 つの値型が含まれています。 StructLayoutAttribute属性は、各データ メンバーの正確な位置を制御するように設定されます。 FieldOffsetAttribute属性は、共用体のアンマネージ表現内でのフィールドの物理的位置を提供します。 両方のメンバーが同じオフセット値を持つことに注意してください。そのため、メンバーは同じメモリ部分を定義できます。

MyUnion2_1MyUnion2_2 には、それぞれ値型 (整数) と文字列が含まれています。 マネージド コードでは、値型と参照型の重複は許可されません。 このサンプルでは、メソッドのオーバーロードを使用して、呼び出し元が同じアンマネージ関数を呼び出すときに両方の型を使用できるようにします。 MyUnion2_1のレイアウトは明示的であり、正確なオフセット値を持っています。 これに対し、 MyUnion2_2 にはシーケンシャル レイアウトがあります。明示的なレイアウトは参照型では許可されないためです。 MarshalAsAttribute属性は、UnmanagedType列挙体を ByValTStr に設定します。これは、共用体のアンマネージド表現内に表示されるインラインの固定長文字配列を識別するために使用されます。

NativeMethods クラスには、TestUnionメソッドと TestUnion2 メソッドのプロトタイプが含まれています。 TestUnion2 は、パラメーターとして MyUnion2_1 または MyUnion2_2 を宣言するためにオーバーロードされます。

プロトタイプの宣言

// Declares managed structures instead of unions.
[StructLayout(LayoutKind::Explicit)]
public value struct MyUnion
{
public:
    [FieldOffset(0)]
    int i;
    [FieldOffset(0)]
    double d;
};

[StructLayout(LayoutKind::Explicit, Size = 128)]
public value struct MyUnion2_1
{
public:
    [FieldOffset(0)]
    int i;
};

[StructLayout(LayoutKind::Sequential)]
public value struct MyUnion2_2
{
public:
    [MarshalAs(UnmanagedType::ByValTStr, SizeConst = 128)]
    String^ str;
};

private ref class NativeMethods
{
public:
    // Declares managed prototypes for unmanaged function.
    [DllImport("..\\LIB\\PInvokeLib.dll")]
    static void TestUnion(MyUnion u, int type);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    static void TestUnion2(MyUnion2_1 u, int type);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    static void TestUnion2(MyUnion2_2 u, int type);
};
// Declares managed structures instead of unions.
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
    [FieldOffset(0)]
    public int i;
    [FieldOffset(0)]
    public double d;
}

[StructLayout(LayoutKind.Explicit, Size = 128)]
public struct MyUnion2_1
{
    [FieldOffset(0)]
    public int i;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyUnion2_2
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string str;
}

internal static class NativeMethods
{
    // Declares managed prototypes for unmanaged function.
    [DllImport("..\\LIB\\PInvokeLib.dll")]
    internal static extern void TestUnion(MyUnion u, int type);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    internal static extern void TestUnion2(MyUnion2_1 u, int type);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    internal static extern void TestUnion2(MyUnion2_2 u, int type);
}
' Declares managed structures instead of unions.
<StructLayout(LayoutKind.Explicit)>
Public Structure MyUnion
    <FieldOffset(0)> Public i As Integer
    <FieldOffset(0)> Public d As Double
End Structure

<StructLayout(LayoutKind.Explicit, Size:=128)>
Public Structure MyUnion2_1
    <FieldOffset(0)> Public i As Integer
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure MyUnion2_2
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
    Public str As String
End Structure

Friend Class NativeMethods
    ' Declares managed prototypes for unmanaged function.
    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Shared Sub TestUnion(
        ByVal u As MyUnion, ByVal type As Integer)
    End Sub

    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Overloads Shared Sub TestUnion2(
        ByVal u As MyUnion2_1, ByVal type As Integer)
    End Sub

    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Overloads Shared Sub TestUnion2(
        ByVal u As MyUnion2_2, ByVal type As Integer)
    End Sub
End Class

関数の呼び出し

public ref class App
{
public:
    static void Main()
    {
        MyUnion mu;// = new MyUnion();
        mu.i = 99;
        NativeMethods::TestUnion(mu, 1);

        mu.d = 99.99;
        NativeMethods::TestUnion(mu, 2);

        MyUnion2_1 mu2_1;// = new MyUnion2_1();
        mu2_1.i = 99;
        NativeMethods::TestUnion2(mu2_1, 1);

        MyUnion2_2 mu2_2;// = new MyUnion2_2();
        mu2_2.str = "*** string ***";
        NativeMethods::TestUnion2(mu2_2, 2);
    }
};
public class App
{
    public static void Main()
    {
        MyUnion mu = new MyUnion();
        mu.i = 99;
        NativeMethods.TestUnion(mu, 1);

        mu.d = 99.99;
        NativeMethods.TestUnion(mu, 2);

        MyUnion2_1 mu2_1 = new MyUnion2_1();
        mu2_1.i = 99;
        NativeMethods.TestUnion2(mu2_1, 1);

        MyUnion2_2 mu2_2 = new MyUnion2_2();
        mu2_2.str = "*** string ***";
        NativeMethods.TestUnion2(mu2_2, 2);
    }
}
Public Class App
    Public Shared Sub Main()
        Dim mu As New MyUnion()
        mu.i = 99
        NativeMethods.TestUnion(mu, 1)

        mu.d = 99.99
        NativeMethods.TestUnion(mu, 2)

        Dim mu2_1 As New MyUnion2_1()
        mu2_1.i = 99
        NativeMethods.TestUnion2(mu2_1, 1)

        Dim mu2_2 As New MyUnion2_2()
        mu2_2.str = "*** string ***"
        NativeMethods.TestUnion2(mu2_2, 2)
    End Sub
End Class

プラットフォームのサンプル

シナリオによっては、ターゲット プラットフォームによって structunion のレイアウトが異なる場合があります。 たとえば、COM シナリオで定義されている STRRET 型について考えてみます。

#include <pshpack8.h> /* Defines the packing of the struct */
typedef struct _STRRET
    {
    UINT uType;
    /* [switch_is][switch_type] */ union
        {
        /* [case()][string] */ LPWSTR pOleStr;
        /* [case()] */ UINT uOffset;
        /* [case()] */ char cStr[ 260 ];
        }  DUMMYUNIONNAME;
    }  STRRET;
#include <poppack.h>

上記の struct は、型のメモリ レイアウトに影響を与える Windows のヘッダーで宣言されています。 マネージド環境で定義されている場合、ネイティブ コードと適切に相互運用するには、これらのレイアウトの詳細が必要です。

32 ビット プロセスでのこの型の適切なマネージド定義は次のとおりです。

[StructLayout(LayoutKind.Explicit, Size = 264)]
public struct STRRET_32
{
    [FieldOffset(0)]
    public uint uType;

    [FieldOffset(4)]
    public IntPtr pOleStr;

    [FieldOffset(4)]
    public uint uOffset;

    [FieldOffset(4)]
    public IntPtr cStr;
}

64 ビット プロセスでは、サイズ フィールドオフセットが異なります。 正しいレイアウトは次のとおりです。

[StructLayout(LayoutKind.Explicit, Size = 272)]
public struct STRRET_64
{
    [FieldOffset(0)]
    public uint uType;

    [FieldOffset(8)]
    public IntPtr pOleStr;

    [FieldOffset(8)]
    public uint uOffset;

    [FieldOffset(8)]
    public IntPtr cStr;
}

相互運用シナリオでネイティブ レイアウトを適切に考慮しないと、ランダムなクラッシュや、より悪い計算が発生する可能性があります。

既定では、.NET アセンブリは.NET ランタイムの 32 ビット バージョンと 64 ビット バージョンの両方で実行できます。 アプリは、実行時間まで待機して、使用する前の定義を決定する必要があります。

次のコード スニペットは、実行時に 32 ビットと 64 ビットの定義を選択する方法の例を示しています。

if (IntPtr.Size == 8)
{
    // Use the STRRET_64 definition
}
else
{
    Debug.Assert(IntPtr.Size == 4);
    // Use the STRRET_32 definition
}

SysTime サンプル

このサンプルでは、構造体へのポインターを必要とするアンマネージ関数にクラスへのポインターを渡す方法を示します。

SysTime サンプルでは、元の関数宣言と共に示されている次のアンマネージ関数を使用します。

  • Kernel32.dllからエクスポートされた GetSystemTime

    VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
    

関数に渡される元の構造体には、次の要素が含まれています。

typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

このサンプルでは、 SystemTime クラスには、クラス メンバーとして表される元の構造体の要素が含まれています。 StructLayoutAttribute属性は、メンバーが表示される順序でメモリ内に順番に配置されるように設定されます。

NativeMethods クラスには、GetSystemTime メソッドのマネージド プロトタイプが含まれています。SystemTime クラスは既定で In/Out パラメーターとして渡されます。 参照型であるクラスは既定で In パラメーターとして渡されるため、パラメーターは InAttribute 属性と OutAttribute 属性で宣言する必要があります。 呼び出し元が結果を受け取るには、これらの 方向属性を 明示的に適用する必要があります。 App クラスは、SystemTime クラスの新しいインスタンスを作成し、そのデータ フィールドにアクセスします。

コード サンプル

using namespace System;
using namespace System::Runtime::InteropServices;     // For StructLayout, DllImport

[StructLayout(LayoutKind::Sequential)]
public ref class SystemTime
{
public:
    unsigned short year;
    unsigned short month;
    unsigned short weekday;
    unsigned short day;
    unsigned short hour;
    unsigned short minute;
    unsigned short second;
    unsigned short millisecond;
};

public class NativeMethods
{
public:
    // Declares a managed prototype for the unmanaged function using Platform Invoke.
    [DllImport("Kernel32.dll")]
    static void GetSystemTime([In, Out] SystemTime^ st);
};

public class App
{
public:
    static void Main()
    {
        Console::WriteLine("C++/CLI SysTime Sample using Platform Invoke");
        SystemTime^ st = gcnew SystemTime();
        NativeMethods::GetSystemTime(st);
        Console::Write("The Date is: ");
        Console::Write("{0} {1} {2}", st->month, st->day, st->year);
    }
};

int main()
{
    App::Main();
}
// The program produces output similar to the following:
//
// C++/CLI SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class SystemTime
{
    public ushort year;
    public ushort month;
    public ushort weekday;
    public ushort day;
    public ushort hour;
    public ushort minute;
    public ushort second;
    public ushort millisecond;
}

internal static class NativeMethods
{
    // Declares a managed prototype for the unmanaged function using Platform Invoke.
    [DllImport("Kernel32.dll")]
    internal static extern void GetSystemTime([In, Out] SystemTime st);
}

public class App
{
    public static void Main()
    {
        Console.WriteLine("C# SysTime Sample using Platform Invoke");
        SystemTime st = new SystemTime();
        NativeMethods.GetSystemTime(st);
        Console.Write("The Date is: ");
        Console.Write($"{st.month} {st.day} {st.year}");
    }
}

// The program produces output similar to the following:
//
// C# SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
Imports System.Runtime.InteropServices

' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential)>
Public Class SystemTime
    Public year As Short
    Public month As Short
    Public weekday As Short
    Public day As Short
    Public hour As Short
    Public minute As Short
    Public second As Short
    Public millisecond As Short
End Class

Friend Class NativeMethods
    ' Declares a managed prototype for the unmanaged function.
    Friend Declare Sub GetSystemTime Lib "Kernel32.dll" (
        <[In](), Out()> ByVal st As SystemTime)
End Class

Public Class App
    Public Shared Sub Main()
        Console.WriteLine("VB .NET SysTime Sample using Platform Invoke")
        Dim st As New SystemTime()
        NativeMethods.GetSystemTime(st)
        Console.Write($"The Date is: {st.month} {st.day} {st.year}")
    End Sub
End Class
' The program produces output similar to the following:
'
' VB .NET SysTime Sample using Platform Invoke
' The Date is: 3 21 2010

OutArrayOfStructs サンプル

このサンプルでは、整数と文字列を含む構造体の配列を Out パラメーターとしてアンマネージ関数に渡す方法を示します。

このサンプルでは、 Marshal クラスを使用し、安全でないコードを使用してネイティブ関数を呼び出す方法を示します。

このサンプルでは、 PinvokeLib.dllで定義 されているラッパー関数とプラットフォーム呼び出しを使用します。ソース ファイルにも用意されています。 TestOutArrayOfStructs関数とMYSTRSTRUCT2構造体を使用します。 構造体には、次の要素が含まれています。

typedef struct _MYSTRSTRUCT2
{
   char* buffer;
   UINT size;
} MYSTRSTRUCT2;

MyStruct クラスには、ANSI 文字の文字列オブジェクトが含まれています。 CharSet フィールドは ANSI 形式を指定します。 MyUnsafeStructは、文字列ではなく IntPtr 型を含む構造体です。

NativeMethods クラスには、オーバーロードされたTestOutArrayOfStructsプロトタイプ メソッドが含まれています。 メソッドがポインターをパラメーターとして宣言する場合、クラスは unsafe キーワードでマークする必要があります。 Visual Basic では安全でないコードを使用できないため、オーバーロードされたメソッド、アンセーフ修飾子、および MyUnsafeStruct 構造体は不要です。

App クラスは、配列を渡すために必要なすべてのタスクを実行するUsingMarshaling メソッドを実装します。 配列は、データが呼び出し先から呼び出し元に渡されることを示す out (Visual Basic でByRef ) キーワードでマークされます。 実装では、次の Marshal クラス メソッドを使用します。

  • PtrToStructure アンマネージド バッファーからマネージド オブジェクトにデータをマーシャリングします。

  • DestroyStructure 構造体内の文字列用に予約されているメモリを解放します。

  • FreeCoTaskMem 配列用に予約されているメモリを解放します。

前述のように、C# では安全でないコードが許可され、Visual Basic では許可されません。 C# サンプルでは、 UsingUnsafePointer は、 Marshal クラスの代わりにポインターを使用して、 MyUnsafeStruct 構造体を含む配列を返す別のメソッド実装です。

プロトタイプの宣言

// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public ref class MyStruct
{
public:
    String^ buffer;
    int size;
};

// Declares a structure with a pointer.
[StructLayout(LayoutKind::Sequential)]
public value struct MyUnsafeStruct
{
public:
    IntPtr buffer;
    int size;
};

private ref class NativeMethods
{
public:
    // Declares managed prototypes for the unmanaged function.
    [DllImport("..\\LIB\\PInvokeLib.dll")]
    static void TestOutArrayOfStructs(int% size, IntPtr% outArray);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    static void TestOutArrayOfStructs(int% size, MyUnsafeStruct** outArray);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MyStruct
{
    public string buffer;
    public int size;
}

// Declares a structure with a pointer.
[StructLayout(LayoutKind.Sequential)]
public struct MyUnsafeStruct
{
    public IntPtr buffer;
    public int size;
}

internal static unsafe class NativeMethods
{
    // Declares managed prototypes for the unmanaged function.
    [DllImport("..\\LIB\\PInvokeLib.dll")]
    internal static extern void TestOutArrayOfStructs(
        out int size, out IntPtr outArray);

    [DllImport("..\\LIB\\PInvokeLib.dll")]
    internal static extern void TestOutArrayOfStructs(
        out int size, MyUnsafeStruct** outArray);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Class MyStruct
    Public buffer As String
    Public someSize As Integer
End Class

Friend Class NativeMethods
    ' Declares a managed prototype for the unmanaged function.
    <DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
    Friend Shared Sub TestOutArrayOfStructs(
        ByRef arrSize As Integer, ByRef outArray As IntPtr)
    End Sub
End Class

関数の呼び出し

public ref class App
{
public:
    static void Main()
    {
        Console::WriteLine("\nUsing marshal class\n");
        UsingMarshaling();
        Console::WriteLine("\nUsing unsafe code\n");
        UsingUnsafePointer();
    }

    static void UsingMarshaling()
    {
        int size;
        IntPtr outArray;

        NativeMethods::TestOutArrayOfStructs(size, outArray);
        array<MyStruct^>^ manArray = gcnew array<MyStruct^>(size);
        IntPtr current = outArray;
        for (int i = 0; i < size; i++)
        {
            manArray[i] = gcnew MyStruct();
            Marshal::PtrToStructure(current, manArray[i]);

            Marshal::DestroyStructure(current, MyStruct::typeid);
            //current = (IntPtr)((long)current + Marshal::SizeOf(manArray[i]));
            current = current + Marshal::SizeOf(manArray[i]);

            Console::WriteLine("Element {0}: {1} {2}", i, manArray[i]->buffer,
                manArray[i]->size);
        }
        Marshal::FreeCoTaskMem(outArray);
    }

    static void UsingUnsafePointer()
    {
        int size;
        MyUnsafeStruct* pResult;

        NativeMethods::TestOutArrayOfStructs(size, &pResult);
        MyUnsafeStruct* pCurrent = pResult;
        for (int i = 0; i < size; i++, pCurrent++)
        {
            Console::WriteLine("Element {0}: {1} {2}", i,
                Marshal::PtrToStringAnsi(pCurrent->buffer), pCurrent->size);
            Marshal::FreeCoTaskMem(pCurrent->buffer);
        }
        Marshal::FreeCoTaskMem((IntPtr)pResult);
    }
};
public class App
{
    public static void Main()
    {
        Console.WriteLine("\nUsing marshal class\n");
        UsingMarshaling();
        Console.WriteLine("\nUsing unsafe code\n");
        UsingUnsafePointer();
    }

    public static void UsingMarshaling()
    {
        int size;
        IntPtr outArray;

        NativeMethods.TestOutArrayOfStructs(out size, out outArray);
        MyStruct[] manArray = new MyStruct[size];
        IntPtr current = outArray;
        for (int i = 0; i < size; i++)
        {
            manArray[i] = new MyStruct();
            Marshal.PtrToStructure(current, manArray[i]);

            //Marshal.FreeCoTaskMem((IntPtr)Marshal.ReadInt32(current));
            Marshal.DestroyStructure(current, typeof(MyStruct));
            current = (IntPtr)((long)current + Marshal.SizeOf(manArray[i]));

            Console.WriteLine($"Element {i}: {manArray[i].buffer} {manArray[i].size}");
        }

        Marshal.FreeCoTaskMem(outArray);
    }

    public static unsafe void UsingUnsafePointer()
    {
        int size;
        MyUnsafeStruct* pResult;

        NativeMethods.TestOutArrayOfStructs(out size, &pResult);
        MyUnsafeStruct* pCurrent = pResult;
        for (int i = 0; i < size; i++, pCurrent++)
        {
            Console.WriteLine($"Element {i}: {Marshal.PtrToStringAnsi(pCurrent->buffer)} {pCurrent->size}");
            Marshal.FreeCoTaskMem(pCurrent->buffer);
        }

        Marshal.FreeCoTaskMem((IntPtr)pResult);
    }
}
Public Class App
    Public Shared Sub Main()
        Console.WriteLine(vbNewLine + "Using marshal class" + vbNewLine)
        UsingMarshaling()
        'Visual Basic 2005 cannot use unsafe code.
    End Sub

    Public Shared Sub UsingMarshaling()
        Dim arrSize As Integer
        Dim outArray As IntPtr

        NativeMethods.TestOutArrayOfStructs(arrSize, outArray)
        Dim manArray(arrSize - 1) As MyStruct
        Dim current As IntPtr = outArray
        Dim i As Integer

        For i = 0 To arrSize - 1
            manArray(i) = New MyStruct()
            Marshal.PtrToStructure(current, manArray(i))

            Marshal.DestroyStructure(current, GetType(MyStruct))
            current = IntPtr.op_Explicit(current.ToInt64() _
                + Marshal.SizeOf(manArray(i)))

            Console.WriteLine("Element {0}: {1} {2}", i, manArray(i).
                buffer, manArray(i).someSize)
        Next i
        Marshal.FreeCoTaskMem(outArray)
    End Sub
End Class

こちらも参照ください