다음을 통해 공유


형식 마샬링

마샬링(Marshalling )은 관리 코드와 네이티브 코드를 교차해야 하는 경우 형식을 변환하는 프로세스입니다.

관리 코드와 관리되지 않는 코드의 형식이 다르기 때문에 마샬링이 필요합니다. 관리 코드에서는 예를 들어 string가 사용되며, 관리되지 않는 문자열은 .NET string 인코딩(UTF-16), ANSI 코드 페이지 인코딩, UTF-8, null로 종료된 문자열, ASCII 등이 될 수 있습니다. 기본적으로 P/Invoke 하위 시스템은 이 문서에 설명된 기본 동작을 기반으로 올바른 작업을 수행하려고 합니다. 그러나 추가 제어가 필요한 경우 MarshalAs 특성을 사용하여 관리되지 않는 쪽에서 예상되는 형식을 지정할 수 있습니다. 예를 들어 문자열을 null로 종료된 UTF-8 문자열로 보내려면 다음과 같이 할 수 있습니다.

[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPUTF8Str)] string parameter);

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);

어셈블리에 System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute 특성을 적용하는 경우 다음 섹션의 규칙이 적용되지 않습니다. 이 특성이 적용될 때 .NET 값이 네이티브 코드에 노출되는 방법에 대한 자세한 내용은 사용하지 않도록 설정된 런타임 마샬링을 참조하세요.

일반 형식 마샬링에 대한 기본 규칙

일반적으로 런타임은 마샬링할 때 최소한의 작업이 필요하도록 "올바른 작업"을 수행하려고 합니다. 다음 표에서는 매개 변수 또는 필드에서 사용할 때 각 형식이 기본적으로 마샬링되는 방법을 설명합니다. C99/C++11 고정 너비 정수 및 문자 형식은 모든 플랫폼에 대해 다음 테이블이 올바른지 확인하는 데 사용됩니다. 이러한 형식과 맞춤 및 크기 요구 사항이 동일한 네이티브 형식을 사용할 수 있습니다.

이 첫 번째 표에서는 P/Invoke 및 필드 마샬링 모두에 대해 마샬링이 동일한 형식에 대한 매핑을 설명합니다.

C# 키워드 .NET 형식 네이티브 타입
byte System.Byte uint8_t
sbyte System.SByte int8_t
short System.Int16 int16_t
ushort System.UInt16 uint16_t
int System.Int32 int32_t
uint System.UInt32 uint32_t
long System.Int64 int64_t
ulong System.UInt64 uint64_t
char System.Char char 또는 P/Invoke 또는 char16_t 구조체의 인코딩에 따라 달라집니다. 문자 집합 설명서를 참조하세요.
System.Char char* 또는 P/Invoke 또는 char16_t* 구조체의 인코딩에 따라 달라집니다. 문자 집합 설명서를 참조하세요.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
.NET 포인터 형식(예 void*: ) void*
System.Runtime.InteropServices.SafeHandle에서 유래된 유형 void*
System.Runtime.InteropServices.CriticalHandle에서 유래된 유형 void*
bool System.Boolean Win32 BOOL 형식
decimal System.Decimal COM DECIMAL 구조체
.NET 대리자 네이티브 함수 포인터
System.DateTime Win32 DATE 형식
System.Guid Win32 GUID 형식

매개 변수 또는 구조체로 마샬링하는 경우 마샬링의 몇 가지 범주에는 다른 기본값이 있습니다.

.NET 형식 네이티브 형식(매개 변수) 네이티브 형식(필드)
.NET 배열 배열 요소의 네이티브 표현 배열 시작에 대한 포인터입니다. 특성 없이 [MarshalAs] 허용되지 않음
LayoutKind 또는 Sequential 또는 Explicit를 사용하는 클래스 클래스의 네이티브 표현에 대한 포인터 클래스의 기본 표현

다음 표에는 Windows 전용인 기본 마샬링 규칙이 포함되어 있습니다. 비 Windows 플랫폼에서는 이러한 형식을 마샬링할 수 없습니다.

.NET 형식 네이티브 형식(매개 변수) 네이티브 형식(필드)
System.Object VARIANT IUnknown*
System.Array COM 인터페이스 특성 없이 [MarshalAs] 허용되지 않음
System.ArgIterator va_list 허용되지 않음
System.Collections.IEnumerator IEnumVARIANT* 허용되지 않음
System.Collections.IEnumerable IDispatch* 허용되지 않음
System.DateTimeOffset int64_t은 1601년 1월 1일 자정 이후의 시간을 틱 단위로 나타냅니다. int64_t은 1601년 1월 1일 자정 이후의 시간을 틱 단위로 나타냅니다.

일부 형식은 필드가 아닌 매개 변수로만 마샬링할 수 있습니다. 이러한 형식은 다음 표에 나와 있습니다.

.NET 형식 네이티브 형식(매개 변수만 해당)
System.Text.StringBuilder char*에 따라 char16_t* 또는 CharSet이 P/Invoke 중에 선택됩니다. 문자 집합 설명서를 참조하세요.
System.ArgIterator va_list (Windows x86/x64/arm64에만 해당)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

이러한 기본값이 원하는 대로 정확하게 수행되지 않는 경우 매개 변수를 마샬링하는 방법을 사용자 지정할 수 있습니다. 매개 변수 마샬링 문서에서는 다양한 매개 변수 형식을 마샬링하는 방법을 사용자 지정하는 방법을 안내합니다.

COM 시나리오의 기본 마샬링

.NET의 COM 개체에서 메서드를 호출하는 경우 .NET 런타임은 일반적인 COM 의미 체계와 일치하도록 기본 마샬링 규칙을 변경합니다. 다음 표에서는 .NET 런타임이 COM 시나리오에서 사용하는 규칙을 나열합니다.

.NET 형식 네이티브 형식(COM 메서드 호출)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
대리자 형식 _Delegate* .NET Framework에서 .NET Core 및 .NET 5 이상에서는 허용되지 않습니다.
System.Drawing.Color OLECOLOR
.NET 배열 SAFEARRAY
System.String[] SAFEARRAYBSTR

클래스 및 구조체 마샬링

형식 마샬링의 또 다른 측면은 구조체를 관리되지 않는 메서드에 전달하는 방법입니다. 예를 들어 관리되지 않는 메서드 중 일부에는 매개 변수로 구조체가 필요합니다. 이러한 경우 매개 변수로 사용하기 위해 해당 구조체 또는 클래스를 세계의 관리되는 부분에 만들어야 합니다. 그러나 클래스를 정의하는 것만으로는 충분하지 않습니다. 또한 마샬러에게 클래스의 필드를 관리되지 않는 구조체에 매핑하는 방법을 지시해야 합니다. 여기서 특성이 StructLayout 유용해집니다.

using System;
using System.Runtime.InteropServices;

Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);

Console.WriteLine(systemTime.Year);

internal static partial class Win32Interop
{
    [LibraryImport("kernel32.dll")]
    internal static partial void GetSystemTime(out SystemTime systemTime);

    [StructLayout(LayoutKind.Sequential)]
    internal ref struct SystemTime
    {
        public ushort Year;
        public ushort Month;
        public ushort DayOfWeek;
        public ushort Day;
        public ushort Hour;
        public ushort Minute;
        public ushort Second;
        public ushort Millisecond;
    }
}

이전 코드는 GetSystemTime() 함수를 호출하는 간단한 예제를 보여줍니다. 흥미로운 비트는 줄 13에 있습니다. 특성은 클래스의 필드를 다른(관리되지 않는) 쪽의 구조체에 순차적으로 매핑하도록 지정합니다. 즉, 필드의 이름은 중요하지 않으며 다음 예제와 같이 관리되지 않는 구조체에 해당해야 하므로 순서만 중요합니다.

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

경우에 따라 구조체에 대한 기본 마샬링이 필요한 작업을 수행하지 않는 경우가 있습니다. 구조 마샬링 사용자 지정 문서에서는 구조 마샬링을 어떻게 사용자 지정하는지 설명합니다.