다음을 통해 공유


클래스, 구조체 및 유니언 마샬링

클래스 및 구조는 .NET Framework에서 유사합니다. 둘 다 필드, 속성 및 이벤트를 가질 수 있습니다. 정적 메서드와 비정적 메서드를 사용할 수도 있습니다. 한 가지 주목할 만한 차이점은 구조체가 값 형식이고 클래스가 참조 형식이라는 점입니다.

다음 표에서는 클래스, 구조체 및 공용 구조체에 대한 마샬링 옵션을 나열하고, 그 사용법을 설명하며, 해당 플랫폼 호출 샘플에 대한 링크를 제공합니다.

유형 설명 예시
값별 클래스입니다. 정수 멤버가 있는 클래스를 관리되는 사례와 같은 In/Out 매개 변수로 전달합니다. SysTime 샘플
값별 구조입니다. 구조를 In 매개 변수로 전달합니다. 구조체 샘플
참조를 기준으로 구조화합니다. 구조를 In/Out 매개 변수로 전달합니다. OSInfo 샘플
중첩된 구조체가 있는 구조체(평면화). 관리되지 않는 함수에 중첩된 구조체가 있는 구조를 나타내는 클래스를 전달합니다. 구조체는 관리되는 프로토타입에서 하나의 큰 구조로 평면화됩니다. FindFile 샘플
다른 구조체에 대한 포인터가 있는 구조체입니다. 두 번째 구조체에 대한 포인터를 포함하는 구조체를 멤버로 전달합니다. 구조체 샘플
값별 정수가 있는 구조체의 배열입니다. 정수만 포함된 구조체 배열을 In/Out 매개 변수로 전달합니다. 배열의 멤버를 변경할 수 있습니다. 배열 예제
참조별 정수 및 문자열이 있는 구조체의 배열입니다. 정수 및 문자열을 포함하는 구조체 배열을 Out 매개 변수로 전달합니다. 호출된 함수는 배열에 대한 메모리를 할당합니다. OutArrayOfStructs 샘플
값 형식과의 결합 구조체입니다. 값 유형(정수 및 double)을 사용하여 유니온을 전달합니다. 공용 구조체 샘플
혼합된 유형을 가진 공용체. 혼합 형식(정수 및 문자열)을 사용하여 유니언을 전달합니다. 공용 구조체 샘플
플랫폼별 레이아웃이 있는 구조체입니다. 네이티브 패킹 정의가 있는 타입을 전달합니다. 플랫폼 샘플
구조체의 값이 Null입니다. 값 형식에 대한 참조 대신 null 참조(Visual Basic에서는 Nothing )를 전달합니다. HandleRef 샘플

구조체 샘플

이 샘플에서는 두 번째 구조를 가리키는 구조를 전달하고, 포함된 구조체를 사용하여 구조를 전달하고, 포함된 배열을 사용하여 구조를 전달하는 방법을 보여 줍니다.

구조체 샘플은 원래 함수 선언과 함께 표시된 다음과 같은 관리되지 않는 함수를 사용합니다.

  • PinvokeLib.dll에서 내보낸 TestStructInStruct.

    int TestStructInStruct(MYPERSON2* pPerson2);
    
  • TestStructInStruct3를 PinvokeLib.dll에서 내보냈다.

    void TestStructInStruct3(MYPERSON3 person3);
    
  • PinvokeLib.dll에서 내보낸 TestArrayInStruct.

    void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
    

PinvokeLib.dll 이전에 나열된 함수에 대한 구현과 MYPERSON, MYPERSON2, MYPERSON3MYARRAYSTRUCT의 네 가지 구조를 포함하는 사용자 지정 관리되지 않는 라이브러리입니다. 이러한 구조에는 다음 요소가 포함됩니다.

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;

관리되는 MyPerson, MyPerson2, MyPerson3, 및 MyArrayStruct 구조체의 특징은 다음과 같습니다.

  • MyPerson 에는 문자열 멤버만 포함됩니다. CharSet 필드는 관리되지 않는 함수에 전달될 때 문자열을 ANSI 형식으로 설정합니다.

  • MyPerson2 구조체에 대한 IntPtr이 포함되어 있습니다MyPerson. 코드가 안전하지 않은 것으로 표시되지 않는 한 .NET Framework 애플리케이션은 포인터를 사용하지 않으므로 IntPtr 형식은 관리되지 않는 구조에 대한 원래 포인터를 대체합니다.

  • MyPerson3에는 MyPerson가 내장된 구조체가 포함되어 있습니다. 다른 구조체 내에 포함된 구조체는 포함된 구조체의 요소를 주 구조체에 직접 배치하여 평면화하거나 이 샘플에서와 같이 포함된 구조체로 남겨둘 수 있습니다.

  • MyArrayStruct 에는 정수 배열이 포함되어 있습니다. 이 특성은 MarshalAsAttribute 열거형 값을 배열의 요소 수를 나타내는 데 사용되는 UnmanagedType로 설정합니다.

이 샘플 StructLayoutAttribute 의 모든 구조체에 대해 멤버가 나타나는 순서대로 순차적으로 메모리에 정렬되도록 특성이 적용됩니다.

NativeMethods 클래스에는 TestStructInStruct, TestStructInStruct3, 및 TestArrayInStruct 메서드의 관리되는 프로토타입이 포함되어 있으며, 이러한 메서드는 App 클래스에 의해 호출됩니다. 각 프로토타입은 다음과 같이 단일 매개 변수를 선언합니다.

  • TestStructInStruct 는 형식 MyPerson2 에 대한 참조를 해당 매개 변수로 선언합니다.

  • TestStructInStruct3 는 형식 MyPerson3 을 매개 변수로 선언하고 매개 변수를 값으로 전달합니다.

  • TestArrayInStruct 는 형식 MyArrayStruct 에 대한 참조를 해당 매개 변수로 선언합니다.

매개 변수에 ref (Visual Basic의 ByRef ) 키워드가 포함되지 않는 한 메서드에 대한 인수로서의 구조체는 값으로 전달됩니다. 예를 들어 메서드는 TestStructInStruct 형식의 개체 MyPerson2 에 대한 참조(주소 값)를 관리되지 않는 코드에 전달합니다. 구조체를 조작하기 위해 MyPerson2가 가리키는 대상을 샘플이 지정된 크기의 버퍼를 생성한 후, Marshal.AllocCoTaskMemMarshal.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 샘플

이 샘플에서는 두 번째 포함된 구조체가 포함된 구조를 관리되지 않는 함수에 전달하는 방법을 보여 줍니다. 또한 특성을 사용하여 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 에서 클래스에는 원래 구조의 각 요소 및 포함된 구조체에 대한 해당 데이터 멤버가 포함됩니다. 두 개의 원래 문자 버퍼 대신 클래스는 문자열을 대체합니다. MarshalAsAttribute는 비관리 구조 내에 나타나는 인라인 고정 길이 문자 배열을 식별하는 데 사용되는 UnmanagedType로 열거형을 설정합니다.

NativeMethods 클래스는 FindFirstFile 클래스를 매개 변수로 전달하는 FindData 메서드의 관리되는 프로토타입을 포함합니다. 참조 형식인 클래스는 기본적으로 In 매개 변수로 전달되기 때문에, 매개 변수를 InAttributeOutAttribute 특성을 사용하여 선언해야 합니다.

프로토타입 선언

// 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

유니언 샘플

이 샘플에서는 값 형식만 포함하는 구조체와 값 형식과 문자열을 포함하는 구조를 공용 구조체를 예상하는 관리되지 않는 함수에 매개 변수로 전달하는 방법을 보여 줍니다. 공용 구조체는 둘 이상의 변수에서 공유할 수 있는 메모리 위치를 나타냅니다.

Unions 샘플은 원래 함수 선언과 함께 표시된 다음과 같은 관리되지 않는 함수를 사용합니다.

  • TestUnion은 PinvokeLib.dll에서 내보낸 것입니다.

    void TestUnion(MYUNION u, int type);
    

PinvokeLib.dll 이전에 나열된 함수와 두 개의 공용 구조체( MYUNIONMYUNION2)에 대한 구현을 포함하는 사용자 지정 관리되지 않는 라이브러리입니다. 공용체에는 다음 요소가 포함됩니다.

union MYUNION
{
    int number;
    double d;
}

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

관리 코드에서 유니언은 구조체로 정의됩니다. 구조체에는 MyUnion 정수와 double의 두 가지 값 형식이 해당 멤버로 포함됩니다. 특성은 StructLayoutAttribute 각 데이터 멤버의 정확한 위치를 제어하도록 설정됩니다. 이 FieldOffsetAttribute 속성은 공용체의 비관리 표현 내에서 필드의 물리 위치를 제공합니다. 두 멤버 모두 오프셋 값이 같으므로 멤버가 동일한 메모리 부분을 정의할 수 있습니다.

MyUnion2_1에는 값 유형(정수)이 포함되어 있고, MyUnion2_2에는 문자열이 포함되어 있습니다. 관리 코드에서는 값 형식 및 참조 형식이 겹칠 수 없습니다. 이 샘플에서는 메서드 오버로드를 사용하여 호출자가 동일한 관리되지 않는 함수를 호출할 때 두 형식을 모두 사용할 수 있도록 합니다. 레이아웃 MyUnion2_1 은 명시적이며 정확한 오프셋 값을 가집니다. 반면에 MyUnion2_2 명시적 레이아웃은 참조 형식에서 허용되지 않으므로 순차적 레이아웃이 있습니다. 이 특성은 MarshalAsAttribute 열거형을 UnmanagedType로 설정합니다. 이 열거형은 공용 구조체의 관리되지 않는 표현 내에 나타나는 인라인 고정 길이 문자 배열을 식별하는 데 사용됩니다.

NativeMethods 클래스에는 TestUnionTestUnion2 메서드의 프로토타입이 포함됩니다. TestUnion2MyUnion2_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;
}

interop 시나리오에서 네이티브 레이아웃을 제대로 고려하지 못하면 임의 크래시 또는 더 나쁜 잘못된 계산이 발생할 수 있습니다.

기본적으로 .NET 어셈블리는 32비트 및 64비트 버전의 .NET 런타임 모두에서 실행할 수 있습니다. 앱은 런타임까지 기다렸다가 사용할 이전 정의 중 어느 것을 결정해야 합니다.

다음 코드 조각은 런타임에 32비트 정의와 64비트 정의 중에서 선택하는 방법의 예제를 보여줍니다.

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

SysTime 샘플

이 샘플에서는 구조체에 대한 포인터를 예상하는 관리되지 않는 함수에 클래스에 대한 포인터를 전달하는 방법을 보여 줍니다.

SysTime 샘플은 원래 함수 선언과 함께 표시된 다음과 같은 관리되지 않는 함수를 사용합니다.

  • GetSystemTime가 Kernel32.dll에서 내보내집니다.

    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 클래스를 In/Out 매개변수로 전달하는 관리되는 SystemTime 메서드 프로토타입을 포함하고 있습니다. 참조 형식인 클래스는 기본적으로 In 매개 변수로 전달되기 때문에, 매개 변수를 InAttributeOutAttribute 특성을 사용하여 선언해야 합니다. 호출자가 결과를 받으려면 이러한 방향 특성을 명시적으로 적용해야 합니다. 클래스는 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 키워드로 표시되어 데이터가 호출자에서 호출 수신자로 전달됨을 나타냅니다 (ByRef는 Visual Basic에서 사용). 구현에서는 다음 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

참고하십시오