C#: преобразование универсального указателя в массив


Я хочу преобразовать 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 для этого? Есть идеи?

4 2

4 ответа:

Метод, который может соответствовать тому, что вы пытаетесь сделать, - это Маршал.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 для справки)

Похоже, что встает вопрос: Как определить универсальный тип, чтобы он был простым типом.

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

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

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

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));

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

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 для преобразования в правильный тип.