Страницы

Поиск по вопросам

вторник, 31 декабря 2019 г.

Почему Marshal.SizeOf возвращает 1 байт для переменной типа char?

#c_sharp #net #unicode #char #sizeof


Приведу код:

char charVal = '૧'; // код 0AE7
Console.WriteLine(sizeof(char)); // 2

Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(charVal)); // 1?


Здесь объявляем переменную типа char и пытаемся выяснить ее размер. Как видно, для
размера типа результат будет 2, тогда как для переменной результат будет 1. Что-то
находил по поводу того, что результат Marshal.SizeOf(charVal) зависит от установленного
значения CharSet, но так и не понял, как управлять всем этим. 

По идее, я указал в коде символ юникода, поэтому символ должен занимать 2 байта,
но оказывается, что это не так. Почему?
    


Ответы

Ответ 1



Короткий ответ: Потому что по-умолчанию (если не указана кодировка) символы маршалируются в ANSI. При этом кодировку можно установить с помощью [StructLayout] и [DllImport] (Default Marshaling for Characters): When a managed Char type, which has Unicode formatting by default, is passed to unmanaged code, the interop marshaler converts the character set to ANSI. You can apply the DllImportAttribute attribute to platform invoke declarations and the StructLayoutAttribute attribute to a COM interop declaration to control which character set a marshaled Char type uses. Использовать Marshal.SizeOf имеет смысл только при взаимодействии с неуправляемым кодом. Результат для char мало что означает. Длинный ответ: Вроде выковырял описание этого поведения из документации. В документации Marshal.SizeOf говорится, что для символов результат определяется установленным CharSet: ... For character types, the size is affected by the CharSet value applied to that class. ... Что это означает? Это означает, что если тип char используется в структуре, то размер, который для символа вернет SizeOf определяется свойством CharSet атрибута StructLayout: [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct OneByte { public char ch; //1 } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct TwoByte { public char ch; //2 } Теперь осталось выяснить какая проставлена кодировка для структуры System.Char. В документации использованный атрибут явно не указан, но указывается, что по умолчанию используется ANSI: Char values and interop When a managed Char type, which is represented as a Unicode UTF-16 encoded code unit, is passed to unmanaged code, the interop marshaler converts the character set to ANSI by default. You can apply the DllImportAttribute attribute to platform invoke declarations and the StructLayoutAttribute attribute to a COM interop declaration to control which character set a marshaled Char type uses. Для верности проверяем исходный код Char: [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)] public struct Char Как видим CharSet не задан, соответственно, он принимает значение по умолчанию. В документации указывается LPSTR (однобайтный тип): Indicates whether string data fields within the class should be marshaled as LPWSTR or LPSTR by default. Но опять для верности проверяем код StructLayoutAttribute и выясняем что по умолчанию используется CharSet.None: CharSet charSet = CharSet.None; switch (type.Attributes & TypeAttributes.StringFormatMask) { ... Который, согласно документации, устарел и эквивалентен Ansi. Marshal.SizeOf скорее всего просто проверяет значение кодировки в атрибуте и возвращает на его основании 1. Этот результат имеет мало смысла т.к. Char не будет маршалится как отдельная структура В практических сценариях маршалинга будет использоваться кодировка, заданная для конкретной структуры. В процессе поиска нашел актуальную цитату из старой книги .NET and COM: The Complete Interoperability Guide Адама Натана: Caution Never use Marshal.SizeOf to determine the "real size" of a managed type, because the results can be misleading. Just think of Marshal.SizeOf as Marshal.UnmanagedSizeOf. An appropriate use of Marshal.SizeOf is to fill a struct's "size field" when passing it to unmanaged code. ... Furthermore, you should never use Marshal.SizeOf with the System.Char type to determine the current platform's character size — use Marshal.SystemDefaultCharSize instead. This is because Marshal.SizeOf always reports 1 for the size of the System.Char type regardless of platform due to an extremely subtle reason. It reports the size of the System.Char value type in the mscorlib assembly, which has a single character field. Because the definition is marked with CharSet.Ansi, the structure would have a size of 1 byte if marshalled to unmanaged code as a plain struct. ... But because the System.Char type is treated specially as a character primitive type, it is never passed as a plain struct to unmanaged code. By default, character parameters and return types are marshalled as 2-byte Unicode characters, and character fields in a structure are marshalled depending on the structure's character set. Note that doing sizeof(char) in C# unsafe code always returns 2 regardless of platform because .Net characters are always Unicode. Не уверен насчет маршалинга параметров и возвращаемых значений, но остальная информация вроде бы не устарела.

Комментариев нет:

Отправить комментарий