C#: 将泛型指针转换为数组

3

我想将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函数可以做到这一点?有任何想法吗?

5个回答

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的参考资料)


1

这个怎么样?

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

你可以使用Marshal.SizeOf(typeof(T));来获取大小。 - SoLaR

1
似乎问题变成了:如何将通用类型指定为简单类型。
unsafe void Foo<T>() : where T : struct
{
   T* p;
}

出现错误:
无法获取托管类型('T')的地址、大小或声明指向其的指针。

1

从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;
}

这将生成一个完整的副本。 - Gillespie

0

我完全不知道以下的代码是否能够正常工作,但是可能会(至少它可以编译通过:):

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将其转换为正确的类型。

这不起作用是因为 T 可能不是一个结构体 - 字节、整数、长整数等。 - wj32
看起来很接近,但我没有看到指针递增——它总是使用第一个字节。另外,为什么要用void*? - H H
3
“struct” 表示值类型,因此 int,byte 等都是有效的。您不能有指向非值类型的指针,因此结构体是您可以使用的最佳选择。 - samjudson
void*被使用是因为你不知道指针的类型。说实话,我对指针了解很少 - 但希望这能帮助那些正在尝试的人走向正确的方向。 - samjudson
原因是结构体可能也包含对类的引用,编译器会将结构体视为“.NET托管”。 - wj32
1
如果处理大型数组时,循环中调用PtrToStructure可能会变得低效。考虑一次性复制整个数组数据,例如使用kernel32.dll!RtlMoveMemory进行调用。 - Mattias S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接