Microsoft MVP성태의 닷넷 이야기
.NET Framework: 774. C# - blittable 타입이란? [링크 복사], [링크+제목 복사],
조회: 21786
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  [128]  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1856정성태2/15/201521263.NET Framework: 493. TypeRef 메타테이블에 등록되는 타입의 조건파일 다운로드1
1855정성태2/10/201520802개발 환경 구성: 256. WebDAV Redirector - Sysinternals 폴더 연결 시 "The network path was not found" 오류 해결 방법
1854정성태2/10/201521799Windows: 104. 폴더는 삭제할 수 없지만, 그 하위 폴더/파일은 생성/삭제/변경하는 보안 설정
1853정성태2/6/201552059웹: 29. 여신금융협회 웹 사이트의 "Netscape 6.0은 지원하지 않습니다." 오류 메시지 [5]
1852정성태2/5/201522486.NET Framework: 492. .NET CLR Memory 성능 카운터의 의미파일 다운로드1
1851정성태2/5/201523401VC++: 88. 하룻밤의 꿈 - 인텔 하스웰의 TSX Instruction 지원 [2]
1850정성태2/4/201544255Windows: 103. 작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미 [4]
1849정성태2/4/201524189기타: 51. DropBox의 CPU 100% 현상 [1]파일 다운로드1
1848정성태2/4/201519432.NET Framework: 491. 닷넷 Generic 타입의 메타 데이터 토큰 값 알아내는 방법 [2]
1847정성태2/3/201522739기타: 50. C# - 윈도우에서 dropbox 동기화 폴더 경로 및 종료하는 방법
1846정성태2/2/201532016Windows: 102. 제어판의 프로그램 추가/삭제 항목을 수동으로 실행하고 싶다면? [1]
1845정성태1/26/201532899Windows: 101. 제어판의 "Windows 자격 증명 관리(Manage your credentials)"를 금지시키는 방법
1844정성태1/26/201530854오류 유형: 269. USB 메모리의 용량이 비정상적으로 보여진다면? [7]
1843정성태1/24/201521915VC++: 87. 무시할 수 없는 Visual C++ 런타임 함수 성능
1842정성태1/23/201544426개발 환경 구성: 255. 노트북 키보드에 없는 BREAK 키를 다른 키로 대체하는 방법
1841정성태1/21/201519399오류 유형: 268. Win32 핸들 관련 CLR4 보안 오류 사례
1840정성태1/8/201527615오류 유형: 267. Visual Studio - CodeLens 사용 시 CPU 100% 현상
1839정성태1/5/201520530디버깅 기술: 69. windbg 분석 사례 - cpu 100% 현상 (2)
1838정성태1/4/201540235기타: 49. 윈도우 내레이터(Narrator) 기능 끄는 방법(윈도우에 파란색의 굵은 테두리 선이 나타난다면?) [4]
1837정성태1/4/201526353디버깅 기술: 68. windbg 분석 사례 - 메모리 부족 [1]
1836정성태1/4/201526356디버깅 기술: 67. windbg - 덤프 파일과 handle 정보
1835정성태1/3/201526850개발 환경 구성: 254. SQL 서버 역시 SSL 3.0/TLS 1.0만을 지원하는 듯!
1834정성태1/3/201551481개발 환경 구성: 253. TLS 1.2를 적용한 IIS 웹 사이트 구성
1833정성태1/3/201527557.NET Framework: 490. System.Data.SqlClient는 SSL 3.0/TLS 1.0만 지원하는 듯! [3]
1832정성태1/2/201520647오류 유형: 266. Azure에 응용 프로그램 게시 중 로그인 오류
1831정성태1/1/201528527디버깅 기술: 66. windbg 분석 사례 - cpu 100% 현상 (1) [1]
... 121  122  123  124  125  126  127  [128]  129  130  131  132  133  134  135  ...