Microsoft MVP성태의 닷넷 이야기
.NET Framework: 774. C# - blittable 타입이란? [링크 복사], [링크+제목 복사],
조회: 21776
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)

C# - blittable 타입이란?

닷넷에서 blittable 타입에 대한 공식 문서는 다음에서 찾아볼 수 있습니다.

Blittable and Non-Blittable Types
; https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types

위의 문서에 따르면 기존 타입들 중 다음의 것들이 blittable 타입입니다.

System.Byte (byte)
System.SByte (sbyte)
System.Int16 (short)
System.UInt16 (ushort)
System.Int32 (int)
System.UInt32 (uint)
System.Int64 (long)
System.UInt64 (ulong)
System.IntPtr
System.UIntPtr
System.Single (float)
System.Double (double)

이와 함께 다음의 것들도 blittable 타입입니다.

  • One-dimensional arrays of blittable types, such as an array of integers. However, a type that contains a variable array of blittable types is not itself blittable.
  • Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). For more information about formatted value types, see Default Marshaling for Value Types.

첫 번째 조건을 풀어보면, blittable types의 1차원 배열도 blittable 타입이지만, 반면 배열을 포함한 타입은 blittable 타입이 아니라는 것입니다. 예를 들어 다음의 arr은 blittable 타입의 인스턴스이지만,

void Main()
{
    int [] arr = new int [100];
}

다음의 TestStruct 타입은 배열을 포함하고 있으므로 blittable 타입이 아닙니다.

struct TestStruct
{
    public int Age;       // int만 담고 있다면 TestStruct도 blittable 타입이겠지만.
    public int [] Scores; // 배열을 담고 있으므로 TestStruct는 blittable 타입이 아님.
}

두 번째 조건은 복합 타입(complex type)인 경우에 대해 blittable 조건을 명시하고 있습니다. 멤버로 blittable 타입, 또는 formatted 타입으로 마샬링 가능한 클래스만을 필드로 포함하는 "Formatted value types"라면 그 타입은 blittable이라는 것입니다. "Formatted" + "value types"이기 때문에 기본적으로 class 타입은 무조건 blittable이 아닙니다. 그리고 나머지 "Formatted 타입"이라는 것은 다음의 문서에서 설명하고 있습니다.

Default Marshaling for Value Types
; https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/0t2cwe11(v=vs.100)

A formatted type is a complex type that contains information that explicitly controls the layout of its members in memory.

결국 타입이 담고 있는 필드들에 대한 메모리 상의 정보를 가지고 있다면 "A formatted type"입니다. 그리고 그 정보라는 것은 StructLayout 특성과 연결됩니다.

눈으로 확인하는 LayoutKind 옵션 효과
; https://www.sysnet.pe.kr/2/0/1558

정리해 보면, "Formatted value types"는 값 형식 중에서 LayoutKind.Auto가 아닌 형식을 의미합니다. 모든 타입은 LayoutKind가 명시되어 있지 않으면 Auto가 되지만, 특별히 C# 컴파일러는 struct 타입에 한해 (사용자가 지정하지 않았다면) 자동으로 LayoutKind.Sequential을 지정해 줍니다. 따라서 사용자가 굳이 Auto로 지정하지 않은 struct 타입에 대해 오직 blittable 타입만을 필드로 포함하고 있다면 그 formatted value type도 blittable 타입이 됩니다.

따라서 struct인 경우 다음은 blittable이지만,

struct BlittableStructType
{
    public int Age;
}

다음의 struct들은 blittable이 아닙니다.

[StructLayout(LayoutKind.Auto)] // Auto Layout이므로.
struct AutoLayoutStructType
{
    public int Age;
}

struct NonBlittableStructType_HasString
{
    public int Age;
    public string Name; // 참조 타입인 string을 포함
}

struct NonBlittableStructType_HasArray
{
    public int Age;
    public int [] data; // 참조 타입인 배열을 포함
}




일단 문서는 그렇다 치고, 개인적으로 한 가지 혼란스러운 문구가 하나 있습니다.

Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). For more information about formatted value types, see Default Marshaling for Value Types.

위에서 말하는 "classes"에 속한 타입은 다음과 같은 것들을 의미합니다.

[StructLayout(LayoutKind.Explicit)]
class ExplicitClassType
{
    [FieldOffset(0)]
    public int Age;
}

그리고 문서에 의하면 저런 "classes"들을 포함하는 "Formatted value types"가 blittable하다고 합니다. 예를 들어 다음과 같은 struct가 blittable일 수 있습니다.

struct StructType
{
    public int Age;
    public ExplicitClassType data1;
}

blittable이 되기 위한 기본 조건은 문서의 처음에 언급한 대로,

Most data types have a common representation in both managed and unmanaged memory and do not require special handling by the interop marshaler. These types are called blittable types because they do not require conversion when they are passed between managed and unmanaged code.

해당 타입의 필드들이 managed/unmanaged 메모리에서 동일한 구성을 갖고 있어 "interop marshaler"에 의한 별도 처리가 필요하지 않은 것입니다. 좀 더 간단하게 표현하면, 그 인스턴스가 존재하는 메모리를 포인터로 가리킬 때 그 주소 영역에 모든 필드의 값들이 포함되어 있어야 합니다. 하지만 위의 StructType은 ExplicitClassType이 "marshaled as formatted types"에 속하고 따라서 그것의 메모리 크기까지 모두 계산이 되어 할당은 되지만 그래도 managed <-> unmanaged 간의 마샬링 절차가 필요합니다. 즉, 아래에서 설명할 interop marshaler를 고려해 보면 ExplicitClassType을 포함한 StructType 타입이 blittable 타입이라고 볼 수 없는 것입니다.




비록, 인스턴스의 필드들이 메모리의 연속된 공간에 펼쳐져 있지만 "do not require special handling by the interop marshaler" 조건을 만족시키지 못해 blittable이 아닌 타입도 있습니다. "Blittable and Non-Blittable Types" 문서에도 나오지만,

System.Array
System.Boolean
System.Char
System.Class
System.Object
System.Mdarray
System.String
System.Valuetype
System.Szarray

위의 것들은 Non-Blittable입니다. 이상한 것은 이 중에서 3개(System.Class, System.Mdarray, System.Szarray)는 원래 .NET BCL에 존재하지 않는 타입이고, 또 다른 3개(System.Array, System.Object, System.ValueType)는 딱히 자료형으로 쓰지 않는 타입입니다. 따라서 남은 자료형은 3개인데,

System.Boolean
System.Char
System.String

이게 왜 Non-Blittable이 아닌지 살짝 이해가 안 될 수도 있습니다. 이유는, 문서 상으로 보면 위의 것들은 interop marshaler가 그 중간에서 작업을 해줘야 하기 때문입니다.

우선 System.Boolean의 경우, 1바이트인데 unmanaged 공간에서는 BOOL 타입이 C/C++라면 1 바이트이거나 COM VARIANT에서는 short이기 때문에 중간에 interop marshaler가 변환 처리를 해야 합니다. (심지어 4바이트가 될 때도 있다고 합니다.)

System.Char는 managed 공간에서는 2바이트지만 unmanaged 공간으로 갈 때는 ANSI/UNICODE 2가지 버전으로 나뉠 수 있기 때문에 역시 중간에서 interop marshaler가 관여를 합니다.

마지막으로 System.String은 결국 System.Char의 연속 공간이기 때문에 마찬가지로 interop marshaler가 관여를 합니다. 재미있는 것은 System.String의 경우 설령 interop marshaler가 관여를 하지 않는다 해도 blittable 타입이 될 수 없습니다. 확인을 위해 .NET Reflector로 보면,

[Serializable, ComVisible(true), __DynamicallyInvokable]
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string>
{
    // Fields
    // ...[생략]...
    [NonSerialized]
    private char m_firstChar;
    [NonSerialized]
    private int m_stringLength;
    // ...[생략]...
}

System.String 타입이 포함하고 있는 필드는 char m_firstChar, int m_stringLength일 뿐 문자 배열 자체는 CLR 내부에 의해 숨겨져 있기 때문입니다.

그 이외의 타입들에 대해서는 interop marshaler는 대상 타입이 참조 객체를 포함하지 않고 LayoutKind.Auto만 아니라면 관여하지 않을 수 있습니다. 하지만, 그 이외의 경우라면 관여를 하게 됩니다.

우선 대상 타입이 Sequential이고, 그것이 ("classes if they are marshaled as formatted types" 조건에 해당하는) 참조 객체를 포함하고 있는 경우 managed와 unmanaged 간의 메모리 크기도 상이할뿐더러 필드 배치의 순서까지도 달라질 수 있으므로 당연히 interop marshaler가 중재할 수밖에 없습니다.

또한 대상 타입이 Explicit 일지라도 managed와 unmanaged 간의 메모리 구조가 동일하다는 것만 빼고는 여전히 managed일 때 참조 타입인 필드에 대해서는 힙 주소만을 포함하고 있다가 unmanaged로 마샬링이 되어야 할 때 interop marshaler의 중재로 메모리가 동일하게 바뀌게 됩니다. 이 때문에 "Formatted value types"가 blittable이 되려면 어찌 되었건 필드로 참조 타입을 가져서는 안됩니다.




그건 그렇고, 혹시 해당 타입이 blittable 타입인지 코드로 알 수 있을까요? 아쉽게도 닷넷 차원에서는 이것을 알 수 있는 직접적인 정보를 Type 클래스에서 제공하지 않습니다. 예전에도 ^^ 값 형식인지 참조 형식인지에 대한 구분도 메타데이터에서 제공하지 않는다고 했었는데요.

닷넷 메타데이터에 struct/class(값/참조 형식)의 구분이 있을까요?
; https://www.sysnet.pe.kr/2/0/10993

검색해 보면, 적당하게 다음의 코드가 눈에 띕니다.

The fastest way to check if a type is blittable?
; https://stackoverflow.com/questions/10574645/the-fastest-way-to-check-if-a-type-is-blittable

BlittableStructs/BlittableStructs/BlittableHelper.cs
; https://github.com/AndreyAkinshin/BlittableStructs/blob/master/BlittableStructs/BlittableHelper.cs

그런데, 제가 정리한 이 글에서의 요구를 만족시키진 않습니다. 왜냐하면 참조 형식(class)도 blittable이라고 반환하는 경우가 있기 때문입니다. 따라서 이 글에서 정리한 조건을 만족하려면 다음과 같이 if 문 하나를 더 추가해야 합니다.

public static class IsBlittableCache<T>
{
    public static bool IsBlittable()
    {
        return IsBlittableCache<T>.Value;
    }

    public static bool IsBlittable(Type type)
    {
        if (type.IsArray)
        {
            var elem = type.GetElementType();
            return elem.IsValueType && IsBlittable(elem);
        }

        if (type.IsValueType == false)
        {
            return false;
        }

        try
        {
            object instance = FormatterServices.GetUninitializedObject(type);
            GCHandle.Alloc(instance, GCHandleType.Pinned).Free();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static readonly bool Value = IsBlittable(typeof(T));
}

위의 코드로 몇몇 타입을 테스트해 보면 다음과 같은 결과를 얻을 수 있습니다. (아래의 출력에 사용된 타입의 구체적인 정의는 첨부 파일에서 확인할 수 있습니다.)

ClassType: False
SequentialClassType: False
SequentialClassType_HasString: False
ExplicitClassType: False
ExplicitClassType_HasString: False
AutoLayoutStructType: False
BlittableStructType: True
NonBlittableStructType_HasClassWithLayoutInfo: False
NonBlittableStructType_HasClassWithLayoutInfo2: False
NonBlittableStructType_HasClassWithLayoutInfo3: False
NonBlittableStructType_HasString: False
NonBlittableStructType_HasArray: False
System.Boolean: False
System.Char: False
System.Int32[]: True
System.Boolean[]: False
BlittableStructType[]: True
System.String: False

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/13/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1100정성태8/17/201128879.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점파일 다운로드1
1099정성태8/15/201128322오류 유형: 132. 어느 순간 갑자기 접속이 안 되는 TFS 서버
1098정성태8/15/201150356웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [2]
1097정성태8/15/201121650.NET Framework: 235. 메서드의 메타 데이터 토큰 값으로 클래스를 찾아내는 방법
1096정성태8/15/201125789디버깅 기술: 42. Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석 - (2)
1095정성태8/14/201126198디버깅 기술: 41. Windbg - 비정상 종료된 닷넷 프로그램의 StackTrace에서 보이는 offset 값 의미
1094정성태8/14/201130611오류 유형: 131. Fiddler가 강제 종료된 경우, 웹 사이트 방문이 안되는 현상
1093정성태7/27/201124221오류 유형: 130. Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor ... Access is denied.
1092정성태7/22/201126635Team Foundation Server: 46. 코드 이외의 파일에 대해 소스 제어에서 제외시키는 방법
1091정성태7/21/201125670개발 환경 구성: 128. WP7 Emulator 실행 시 audiodg.exe의 CPU 소모율 증가 [2]
1089정성태7/18/201131251.NET Framework: 234. 왜? Button 컨트롤에는 MouseDown/MouseUp 이벤트가 발생하지 않을까요?파일 다운로드1
1088정성태7/16/201124285.NET Framework: 233. Entity Framework 4.1 - 윈도우 폰 7에서의 CodeFirst 순환 참조 문제파일 다운로드1
1087정성태7/15/201127018.NET Framework: 232. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 - 두 번째 이야기파일 다운로드1
1086정성태7/14/201128428.NET Framework: 231. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 [1]파일 다운로드1
1085정성태7/14/201128891.NET Framework: 230. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류 - 두 번째 이야기파일 다운로드1
1084정성태7/11/201134173.NET Framework: 229. SQL 서버 - DB 테이블의 데이터 변경에 대한 알림 처리 [4]파일 다운로드1
1083정성태7/11/201128219.NET Framework: 228. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류
1082정성태7/10/201127780.NET Framework: 227. basicHttpBinding + 사용자 정의 인증 구현 [2]파일 다운로드1
1081정성태7/9/201127102VC++: 53. Windows 7에서 gcc.exe 실행 시 Access denied 오류 [2]
1080정성태7/8/201125605웹: 23. Sysnet 웹 사이트의 HTML5 변환 기록 - 두 번째 이야기파일 다운로드1
1079정성태7/6/201130022오류 유형: 129. Hyper-V + Realtek 랜카드가 설치된 시스템의 BSOD 현상 [2]
1078정성태7/5/201137516VC++: 52. Chromium 컴파일하는 방법 [2]
1077정성태6/24/201135154.NET Framework: 226. HttpWebRequest 타입의 HaveResponse 속성 이야기파일 다운로드1
1076정성태6/23/201129318오류 유형: 128. SQL Express - User Instance 옵션을 사용한 경우 발생하는 오류 메시지 유형 2가지
1075정성태6/21/201124908VS.NET IDE: 69. 윈폰 프로젝트에서 WCF 서비스 참조할 때 Reference.cs 파일이 비어있는 경우
1074정성태6/20/201125019.NET Framework: 225. 닷넷 네트워크 라이브러리의 트레이스 기능파일 다운로드1
... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...