2016-03-17 2 views
14

Мне нужно объединить некоторые вложенные структуры в C# 4.0 в двоичные капли, чтобы перейти к платформе C++.C# interop: плохое взаимодействие между фиксированным и MarshalAs

Я до сих пор имел большой успех, используя unsafe/fixed для обработки массивов фиксированной длины примитивных типов. Теперь мне нужно обработать структуру, содержащую вложенные массивы фиксированной длины других структур.

Я использовал сложные обходные решения, сглаживающие структуры, но затем я натолкнулся на пример атрибута MarshalAs, который выглядел так, как будто это могло спасти мне массу проблем.

К сожалению, пока это дает мне правильный количество данных, кажется, также остановить fixed массивы из того ранжированы правильно, так как выход из этой программы демонстрирует. Вы можете подтвердить сбой, поставив точку останова на последней строке и исследуя память на каждом указателе.

using System; 
using System.Threading; 
using System.Runtime.InteropServices; 

namespace MarshalNested 
{ 
    public unsafe struct a_struct_test1 
    { 
    public fixed sbyte a_string[3]; 
    public fixed sbyte some_data[12]; 
    } 

    public struct a_struct_test2 
    { 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] 
    public sbyte[] a_string; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
    } 

    public unsafe struct a_struct_test3 
    { 
    public fixed sbyte a_string[3]; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
    } 


    public unsafe struct a_nested 
    { 
    public fixed sbyte a_notherstring[3]; 
    } 

    class Program 
    { 
    static unsafe void Main(string[] args) 
    { 
     a_struct_test1 lStruct1 = new a_struct_test1(); 
     lStruct1.a_string[0] = (sbyte)'a'; 
     lStruct1.a_string[1] = (sbyte)'b'; 
     lStruct1.a_string[2] = (sbyte)'c'; 

     a_struct_test2 lStruct2 = new a_struct_test2(); 
     lStruct2.a_string = new sbyte[3]; 
     lStruct2.a_string[0] = (sbyte)'a'; 
     lStruct2.a_string[1] = (sbyte)'b'; 
     lStruct2.a_string[2] = (sbyte)'c'; 

     a_struct_test3 lStruct3 = new a_struct_test3(); 
     lStruct3.a_string[0] = (sbyte)'a'; 
     lStruct3.a_string[1] = (sbyte)'b'; 
     lStruct3.a_string[2] = (sbyte)'c'; 

     IntPtr lPtr1 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct1, lPtr1, false); 

     IntPtr lPtr2 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct2, lPtr2, false); 

     IntPtr lPtr3 = Marshal.AllocHGlobal(15); 
     Marshal.StructureToPtr(lStruct3, lPtr3, false); 

     string s1 = ""; 
     string s2 = ""; 
     string s3 = ""; 
     for (int x = 0; x < 3; x++) 
     { 
     s1 += (char) Marshal.ReadByte(lPtr1+x); 
     s2 += (char) Marshal.ReadByte(lPtr2+x); 
     s3 += (char) Marshal.ReadByte(lPtr3+x); 
     } 

     Console.WriteLine("Ptr1 (size " + Marshal.SizeOf(lStruct1) + ") says " + s1); 
     Console.WriteLine("Ptr2 (size " + Marshal.SizeOf(lStruct2) + ") says " + s2); 
     Console.WriteLine("Ptr3 (size " + Marshal.SizeOf(lStruct3) + ") says " + s3); 

     Thread.Sleep(10000); 
    } 
    } 
} 

Выход:

Ptr1 (size 15) says abc 
Ptr2 (size 15) says abc 
Ptr3 (size 15) says a 

Так почему-то только сортировочных первый символ моих fixed строк ANSI. Есть ли способ обойти это, или я сделал что-то глупое, не связанное с сортировкой?

+0

Переполнение стека: получите репутацию для редактирования сообщений людей и плагиата документов MSDN. Задайте трудный вопрос? [Crickets] –

+1

Вы по крайней мере получаете значок переползания;) И теперь с щедростью вы получите внимание лучшего разработчика *, но не нужно отношения *. –

+0

Вы использовали этот сайт раньше? Он работает на плохое отношение. :-P –

ответ

14

Это случай отсутствия диагностики. Кто-то должен был высказаться и сказать, что ваша декларация не поддерживается. Там, где это кто-то является либо компилятором C#, создающим ошибку компиляции, либо маршаллером поля CLR, создающим исключение во время выполнения.

Не похоже, что вы не можете получить диагностику. Вы, конечно, получить один, когда вы на самом деле начать использовать по назначению-структуру: «Вы не можете использовать буферы фиксированного размера, содержащиеся в незакрепленных выражениях Попробуйте использовать фиксированное заявление»

a_struct_test3 lStruct3 = new a_struct_test3(); 
    lStruct3.some_data = new a_nested[4]; 
    lStruct3.some_data[0] = new a_nested(); 
    lStruct3.some_data[0].a_notherstring[0] = (sbyte)'a'; // Eek! 

Что вызывает CS1666,. Не так, что совет «попробуйте этот» - это полезно:

fixed (sbyte* p = &lStruct3.some_data[0].a_notherstring[0]) // Eek! 
    { 
     *p = (sbyte)'a'; 
    } 

Точная ошибка CS1666. Следующая вещь, которую вы хотите попробовать это поместить атрибут фиксированного буфера:

public unsafe struct a_struct_test3 { 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] 
    public fixed sbyte a_string[3]; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] 
    public a_nested[] some_data; 
} 
//... 

    a_struct_test3 lStruct3 = new a_struct_test3(); 
    lStruct3.some_data = new a_nested[4]; 
    IntPtr lPtr3 = Marshal.AllocHGlobal(15); 
    Marshal.StructureToPtr(lStruct3, lPtr3, false); // Eek! 

держит C# компилятор счастливым, но теперь CLR говорит, и вы получите TypeLoadException во время выполнения: «Дополнительная информация: не может фельдмаршал«a_string 'типа' MarshalNested.a_struct_test3 ': Недействительная комбинация управляемых/неуправляемых типов (этот тип значения должен быть сопряжен с Struct). "

Итак, вкратце, вы должны были получить либо CS1666, либо TypeLoadException в своей первоначальной попытке. Этого не произошло, потому что компилятор C# не был вынужден смотреть на плохую часть, он генерирует только CS1666 в инструкции, которая обращается к массиву. И это не произошло во время выполнения, потому что полевой маршаллер в CLR не пытался маршалировать массив, потому что он равен нулю. Вы можете подать отчет об обратной связи с ошибкой на сайте connect.microsoft.com, но я был бы очень удивлен, если они не будут закрывать его «по дизайну».


В общем, безвестном деталь имеет большое значение для поля ИАСА в CLR, фрагмент кода, который преобразует значение структуры и объекты класса их управляемого расположение их неуправляемый макет.Он плохо документирован, Microsoft не хочет прибивать детали конкретной реализации. В основном потому, что они слишком сильно зависят от целевой архитектуры.

Очень важно, является ли значение или объект blittable. Он горит, когда управляемый и неуправляемый макет идентичен. Это происходит только тогда, когда каждый член типа имеет точный размер и выравнивание в обоих макетах точно. Обычно это происходит, когда поля имеют очень простой тип значения (например, байт или int) или сама структура, которая является blittable. Не известно, когда это bool, слишком много противоречивых неуправляемых типов bool. Поле типа массива никогда не будет гореть, управляемые массивы не выглядят похожими на массивы C, так как они имеют заголовок объекта и элемент длины.

Желая иметь светлое значение или объект, он избегает создания маршаллера из-за необходимости создания копии. Нативный код получает простой указатель на управляемую память, и все, что необходимо, - это привязать память. Очень быстро. Также очень опасно, если декларация не соответствует, то собственный код может легко окрашиваться вне строк и повреждать кучу GC или стек кадров. Очень распространенная причина для программы, которая использует pinvoke для случайной бомбы с ExecutionEngineException, чрезвычайно трудно диагностировать. Такая декларация действительно заслуживает небезопасного ключевого слова, но компилятор C# не настаивает на этом. И это не так, компиляторам не разрешено делать какие-либо предположения о макете управляемого объекта. Вы сохраняете это безопасно, используя Debug.Assert() в возвращаемом значении Marshal.SizeOf<T>, это должно быть точное совпадение со значением sizeof(T) в программе на C.

Как отмечалось, массивы являются препятствием для получения светлого значения или объекта. Ключевое слово fixed предназначено в качестве обходного пути для этого. CLR рассматривает его как непрозрачный тип значения без каких-либо членов, просто капля байтов. Нет заголовка объекта и элемента Length, как можно ближе к массиву C. И используется в коде C#, как если бы вы использовали массив в программе на C, вы должны использовать указатель для обращения к элементам массива и три раза проверять, что вы не окрашиваете вне строк. Иногда вы должны использовать фиксированный массив, когда вы объявляете объединение (перекрывающиеся поля) и перекрываете массив со значением. Отравляя сборщик мусора, он больше не может понять, содержит ли поле корень объекта. Не обнаружен компилятором C#, но надежно отключает исключение TypeLoadException во время выполнения.


Короче говоря, использовать fixedтолько для blittable типа. Смешивание полей типа буфера фиксированного размера с полями, которые должно быть, не может быть обработано. И не полезно, объект или значение все равно копируются, поэтому вы можете использовать тип дружественного массива.

Смежные вопросы