У меня есть общий метод для сериализации массива любого типа struct
в массив из byte
с использованием Marshal.StructureToPtr
и Marshal.Copy
. Полный код:Сериализация массива структур в байт [] - Что не так с моим кодом?
internal static byte[] SerializeArray<T>(T[] array) where T : struct
{
if (array == null)
return null;
if (array.Length == 0)
return null;
int position = 0;
int structSize = Marshal.SizeOf(typeof(T));
byte[] rawData = new byte[structSize * array.Length];
IntPtr buffer = Marshal.AllocHGlobal(structSize);
foreach (T item in array)
{
Marshal.StructureToPtr(item, buffer, false);
Marshal.Copy(buffer, rawData, position, structSize);
position += structSize;
}
Marshal.FreeHGlobal(buffer);
return rawData;
}
Он работает безупречно 99,99% времени. Однако для одного из моих пользователей Windows 7 с определенными входными данными этот код будет предсказуемо вызывать следующее исключение не.NET:
Площадь данных, переданных системному вызову, слишком мала. (Исключение из HRESULT: 0x8007007A).
К сожалению, у меня нет доступа к машине пользователя, чтобы прикрепить отладчик, и я не смог реплицировать проблему даже при работе с теми же входными данными, что и мой пользователь. Это происходит только на машине одного пользователя и только с определенными входными данными, но на ее машине это происходит каждый раз с теми же входными данными, поэтому это определенно не случайно.
Приложение предназначено для .NET 4.5.
Может ли кто-нибудь увидеть что-то не так с этим кодом? Мое единственное предположение - существует некоторая несоответствие между тем, что сообщается Marshal.SizeOf
, и фактическим размером структуры данных, что приводит к недостаточной памяти, выделяемой для структуры.
Если это имеет значение, здесь структура сериализации при возникновении ошибки (это представление позиций символов в результате OCR):
public struct CharBox
{
internal char Character;
internal float Left;
internal float Top;
internal float Right;
internal float Bottom;
}
Как вы можете видеть, все поля должны быть постоянным размером всех время, поэтому мое первоначальное распределение одного сегмента фиксированной длины неуправляемой памяти, в которое нужно сериализовать каждый struct
, не должно быть проблемой (должно быть?).
Хотя я бы приветствовал альтернативные или улучшенные методы сериализации, я гораздо больше заинтересован в прибивании этой конкретной ошибки. Благодаря!
Update Благодаря TnTnMn, указывая мне, что char
не blittable типа, я посмотрел юникод символов ввода, чтобы увидеть, если они были сортировочной правильно. Оказывается, они НЕ.
Для CharBox { 0x2022, .15782328, .266239136, .164901689, .271627158 },
сериализации (в шестнадцатеричном) должно быть:
22 20 00 00 (Символ *)
6D 9C 21 3E (слева)
7F 50 88 3E (Top)
FD DB 28 3E (справа)
B7 12 8B 3E (Bottom)
(* Поскольку я не использовал явный макет, он дополнялся четырьмя байтами; Я сейчас разочарован собой за ненужное увеличение размера данных на 11% ...)
Вместо этого, он сериализации как:
95 00 00 00 (символов)
6D 9C 21 3E (слева)
7F 50 88 3E (Верх)
FD DB 28 3E (справа)
B7 12 8B 3E (внизу)
Так что это маршалинг char
0x2022 вместо 0x95. Как это бывает, 0x2022 Unicode и 0x95 ANSI оба являются символами пули. Таким образом, это не случайное, а скорее марширование всего на ANSI, которое, как я теперь помню, является стандартной процедурой, если вы не указали CharSet
.
Итак, это, по крайней мере, подтверждает, что происходит какое-то непреднамеренное поведение, и дает нам хорошую рабочую теорию относительно того, какие условия (а именно, символ Юникода в структуре) могут привести к ошибке.
То, что он не объясняет, - это то, почему это привело бы к возникновению исключения вообще, не говоря уже о том, почему он не поднят ни на одной машине, кроме этого пользователя. Что касается первого, то несоответствие в размере unciode против ANSI, я полагаю, должно соответствовать сообщению об ошибке («Область данных, переданная системному вызову, слишком мала»), но неуправляемый буфер - который размером 4 байта для char
, будет больше, чем необходимо, а не меньше. Почему CLR или ОС расстраиваются при написании только 1 байт в область, предназначенную для 2 и достаточно большую для 4?
Что касается последнего, я подумал, что, возможно, пользователь может быть на более низкой версии .NET, чем все остальные, что может быть, если она не получит все обновления для Windows 7. Но я просто попробовал это на виртуальной машине с новой установкой Windows 7 и .NET 4.5 (самая низкая версия, которую поддерживает приложение) и по-прежнему не может воспроизвести ошибку. Я пытаюсь выяснить, какая именно версия .NET у нее есть, если она равна 4.5.1 или что-то в этом роде. Тем не менее, это кажется длинным выстрелом.
Кажется, что единственный способ, чтобы знать наверняка будет изменить Character
члена int
(чтобы отступы то же самое для существующих данных) и только привести его к char
, когда это необходимо, а затем посмотреть, что изменяет результат на машине пользователя. Это также будет хорошей возможностью обернуть каждый отдельный вызов Marshal
в обработчике исключений, поскольку Джон предложил посмотреть, что именно вызывает ошибку.
Хорошая новость заключается в том, что это довольно низкоприоритетная функция, поэтому я могу позволить ей выйти из строя безопасно, даже если она будет продолжаться.
Отчитается. Спасибо всем.
Является ли 'CharBox' в той же DLL или EXE, где вызывается функция SerializeArray()? Если нет, то «внутренняя» - это твоя проблема, я бы подумал. – John
Это в том же модуле. –
ОК, следующим шагом будет определить, какой из системных вызовов выбрасывает это исключение. Когда мы используем маршалл, мы религиозно используем утверждения try..catch вокруг каждого вызова. Тогда вы будете знать, где искать дальше. Могут быть всевозможные причины этой общей ошибки. – John