С#: преобразовать общий указатель в массив

Я хочу преобразовать byte* в byte[], но я также хочу иметь многоразовую функцию для этого:

public unsafe static T[] Create<T>(T* ptr, int length)
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
        array[i] = ptr[i];

    return array;
}

К сожалению, я получаю ошибку компилятора, потому что T может быть "управляемым типом .NET", и у нас не может быть указателей на те. Еще больше разочаровывает отсутствие ограничения универсального типа, которое могло бы ограничить T «неуправляемыми типами». Есть ли встроенная функция .NET для этого? Любые идеи?


person wj32    schedule 12.06.2009    source источник


Ответы (5)


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

Хотя там невозможно написать общий метод с общими ограничениями, которые могли бы описать, что возможно, не каждый тип можно копировать «небезопасным» способом. Есть некоторые исключения; классы являются одним из них.

Вот пример кода:

    public unsafe static T[] Create<T>(void* source, int length)
    {
        var type = typeof(T);
        var sizeInBytes =  Marshal.SizeOf(typeof(T));

        T[] output = new T[length];

        if (type.IsPrimitive)
        {
            // Make sure the array won't be moved around by the GC 
            var handle = GCHandle.Alloc(output, GCHandleType.Pinned);

            var destination = (byte*)handle.AddrOfPinnedObject().ToPointer();
            var byteLength = length * sizeInBytes;

            // There are faster ways to do this, particularly by using wider types or by 
            // handling special lengths.
            for (int i = 0; i < byteLength; i++)
                destination[i] = ((byte*)source)[i];

            handle.Free();
        }
        else if (type.IsValueType)
        {
            if (!type.IsLayoutSequential && !type.IsExplicitLayout)
            {
                throw new InvalidOperationException(string.Format("{0} does not define a StructLayout attribute", type));
            }

            IntPtr sourcePtr = new IntPtr(source);

            for (int i = 0; i < length; i++)
            {
                IntPtr p = new IntPtr((byte*)source + i * sizeInBytes);

                output[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
            }
        }
        else 
        {
            throw new InvalidOperationException(string.Format("{0} is not supported", type));
        }

        return output;
    }

    unsafe static void Main(string[] args)
    {
        var arrayDouble = Enumerable.Range(1, 1024)
                                    .Select(i => (double)i)
                                    .ToArray();

        fixed (double* p = arrayDouble)
        {
            var array2 = Create<double>(p, arrayDouble.Length);

            Assert.AreEqual(arrayDouble, array2);
        }

        var arrayPoint = Enumerable.Range(1, 1024)
                                   .Select(i => new Point(i, i * 2 + 1))
                                   .ToArray();

        fixed (Point* p = arrayPoint)
        {
            var array2 = Create<Point>(p, arrayPoint.Length);

            Assert.AreEqual(arrayPoint, array2);
        }
    }

Метод может быть универсальным, но не может принимать указатель универсального типа. Это не проблема, так как ковариация указателей помогает, но это имеет неприятный эффект, предотвращая неявное разрешение универсального типа аргумента. Затем вы должны явно указать MakeArray.

Я добавил особый случай для структур, где лучше всего иметь типы, указывающие атрибут макет структуры. Это может не быть проблемой в вашем случае, но если данные указателя поступают из собственного кода C или C++, важно указать тип макета (среда CLR может изменить порядок полей для лучшего выравнивания памяти).

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

Кроме того, если производительность является проблемой, существуют лучшие алгоритмы для копирования данных, чем копирование байт за байтом. (См. бесчисленные реализации memcpy для справки)

person Jérôme Laban    schedule 03.10.2009

Кажется, возникает вопрос: как указать универсальный тип как простой тип.

unsafe void Foo<T>() : where T : struct
{
   T* p;
}

Выдает ошибку:
Невозможно получить адрес, получить размер или объявить указатель на управляемый тип ('T')

person Henk Holterman    schedule 12.06.2009

Как насчет этого?

static unsafe T[] MakeArray<T>(void* t, int length, int tSizeInBytes) where T:struct
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    {
        IntPtr p = new IntPtr((byte*)t + (i * tSizeInBytes));
        result[i] = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
    }

    return result;
}

Здесь мы не можем использовать sizeof(T), но вызывающая сторона может сделать что-то вроде

byte[] b = MakeArray<byte>(pBytes, lenBytes, sizeof(byte));
person twon33    schedule 01.10.2009
comment
вы можете использовать Marshal.SizeOf(typeof(T)); чтобы получить размер. - person SoLaR; 02.05.2016

Я понятия не имею, будет ли работать следующее, но может (по крайней мере, оно компилируется :):

public unsafe static T[] Create<T>(void* ptr, int length) where T : struct
{
    T[] array = new T[length];

    for (int i = 0; i < length; i++)
    {
        array[i] = (T)Marshal.PtrToStructure(new IntPtr(ptr), typeof(T));
    }

    return array;
}

Ключ в том, чтобы использовать Marshal.PtrToStructure для преобразования в правильный тип.

person samjudson    schedule 12.06.2009
comment
Это не работает, потому что T может не быть структурой — byte, int, long и т. д. - person wj32; 12.06.2009
comment
Выглядит близко, но я не вижу увеличения указателя - он всегда будет использовать первый байт. Кроме того, есть ли причина для void* ? - person Henk Holterman; 12.06.2009
comment
'struct' означает тип значения, поэтому допустимы int, byte и т.д. И у вас не может быть указателя на тип значения none, поэтому struct — лучшее, что вы можете сделать. - person samjudson; 12.06.2009
comment
void* использовался, потому что вы не знаете, какого типа указатель. Честно говоря, я очень мало знаю об указателях, но, надеюсь, это может привести того, кто пытается это сделать, в правильном направлении. - person samjudson; 12.06.2009
comment
Причина в том, что структуры могут также содержать ссылки на классы, и тогда компилятор считает структуру управляемой .NET. - person wj32; 12.06.2009
comment
Вызов PtrToStructure в цикле может оказаться неэффективным при работе с большими массивами. Рассмотрите возможность одновременного копирования всех данных массива, используя, например, вызов kernel32.dll!RtlMoveMemory. - person Mattias S; 21.09.2009

Начиная с C# 7.3 это возможно следующим образом:

public unsafe static T[] Create<T>(T* ptr, int length) where T: unmanaged
{
    T[] array = new T[length];
    for (int i = 0; i < length; i++)
        array[i] = ptr[i];
    return array;
}
person cdiggins    schedule 05.06.2020