값 형식에 대한 기본 마샬링

정수 및 부동 소수점 숫자 같은 대부분의 값 형식은 Blittable이며 마샬링을 필요로 하지 않습니다. 그 이외의 비 Blittable 형식은 관리되는 메모리와 관리되지 않는 메모리에서 서로 다른 형식을 가지며 마샬링을 필요로 합니다. 그 밖의 다른 형식도 상호 운용 경계를 넘을 때는 명시적인 형식 지정이 필요합니다.

이 항목에서는 formatted 값 형식에 대한 다음 내용을 다룹니다.

  • 플랫폼 호출에 사용되는 값 형식

  • COM Interop에 사용되는 값 형식

이 항목에서는 formatted 형식에 대해 설명할 뿐만 아니라 특별한 마샬링 동작이 수행되는 시스템 값 형식에 대해서도 설명합니다.

formatted 형식은 메모리에 있는 멤버의 레이아웃을 명시적으로 제어하는 정보가 포함된 복잡한 형식입니다. 멤버 레이아웃 정보는 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;
}

비관리 코드로 마샬링될 때 이러한 formatted 형식은 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 값 형식은 값으로 전달됩니다. 이러한 차이는 미세하지만 매우 중요합니다. 참조는 비관리 코드에 포인터로 전달됩니다. 값은 비관리 코드에서 스택에 전달됩니다.

참고참고

formatted 형식이 구조체로 마샬링될 때에는 형식 내에 있는 필드에만 액세스할 수 있습니다.형식에 메서드, 속성 또는 이벤트가 있는 경우 이 항목들은 비관리 코드에서 액세스할 수 없습니다.

또한 클래스는 멤버 레이아웃이 고정된 경우 비관리 코드에 C 스타일 구조체로 마샬링될 수도 있습니다. 클래스에 대한 멤버 레이아웃 정보는 StructLayoutAttribute 특성과 함께 제공되기도 합니다. 레이아웃이 고정된 값 형식과 레이아웃이 고정된 클래스 간의 주요 차이는 비관리 코드로 마샬링되는 방식에 있습니다. 값 형식은 값으로 스택에 전달되므로, 호출 수신자가 형식 멤버에 대해 변경한 내용을 호출자는 볼 수 없습니다. 참조 형식은 참조로 전달되고 형식에 대한 참조는 스택에 전달되므로, 호출 수신자가 한 형식의 blittable 형식 멤버에 대해 변경한 모든 내용을 호출자가 볼 수 있습니다.

참고참고

참조 형식에 비 blittable 형식의 멤버가 있으면 변환이 두 번 필요합니다. 첫 번째는 인수가 관리되지 않는 쪽에 전달될 때이고 두 번째는 호출에서 반환될 때입니다.이러한 추가 오버헤드로 인해 호출자가 호출 수신자에 의해 변경된 내용을 보려면 인수에 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 클래스를 보여 줍니다. 형식의 레이아웃이 sequential 레이아웃이므로 이 형식은 비관리 코드에 전달되어 구조체로 마샬링될 수 있습니다. 그러나 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에 사용되는 값 형식

Formatted 형식은 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 인터페이스를 통해 마샬링할 때는 P/Invoke 호출에 대해 값 및 참조를 마샬링하는 데 사용되는 것과 동일한 규칙이 사용됩니다. 예를 들어, Point 값 형식의 인스턴스를 .NET Framework에서 COM으로 전달할 때 Point는 값으로 전달됩니다. Point 값 형식을 참조로 전달하는 경우에는 Point에 대한 포인터가 스택에 전달됩니다. interop 마샬러에서는 방향에 상관없이 상위 수준의 간접 참조(Point **)를 지원하지 않습니다.

참고참고

내보낸 형식 라이브러리는 명시적 레이아웃을 표현할 수 없으므로 LayoutKind 열거형 값이 Explicit로 설정된 구조체는 COM interop에 사용될 수 없습니다.

시스템 값 형식

System 네임스페이스에는 런타임 기본 형식의 boxed 형식을 나타내는 여러 개의 값 형식이 있습니다. 예를 들어 값 형식 System.Int32 구조체는 ELEMENT_TYPE_I4의 boxed 형식을 나타냅니다. 이러한 형식은 다른 formatted 형식처럼 구조체로 마샬링하는 대신 해당 형식이 boxing하는 기본 형식과 같은 방식으로 마샬링합니다. 따라서 System.Int32long 형식의 단일 멤버가 포함된 구조체가 아니라 ELEMENT_TYPE_I4로 마샬링됩니다. 다음 표에서는 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 네임스페이스에 있는 일부 다른 값 형식은 이와 다르게 처리됩니다. 비관리 코드에는 이러한 형식을 위해 적절하게 만들어진 형식이 있으므로 마샬러에는 이러한 형식을 마샬링하는 특별한 규칙이 있습니다. 다음 표에서는 System 네임스페이스에 있는 특수한 값 형식과 해당 형식이 마샬링되는 관리되지 않는 형식을 보여 줍니다.

시스템 값 형식

IDL 형식

System.DateTime

DATE

System.Decimal

DECIMAL

System.Guid

GUID

System.Drawing.Color

OLE_COLOR

다음 코드에서는 Stdole2 형식 라이브러리에 있는 관리되지 않는 형식 DATE, GUID, DECIMALOLE_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 형식

방향 특성

복사 및 고정

기타 리소스

기본 마샬링 동작