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

StackOverflow https://stackoverflow.com/questions/985646

  •  13-09-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top