C #: конвертировать общий указатель в массив - PullRequest
2 голосов
/ 12 июня 2009

Я хочу преобразовать 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 ]

5 голосов
/ 03 октября 2009

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

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

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

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

1 голос
/ 12 июня 2009

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

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

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

1 голос
/ 01 октября 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));
0 голосов
/ 12 июня 2009

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

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

...