C#:将通用指针转换为数组

问题描述 投票:2回答:5

我想将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托管类型”,并且我们没有指向those的指针。更令人沮丧的是,没有可以将T限制为“非托管类型”的通用类型约束。是否有内置的.NET函数来执行此操作?有什么想法吗?

c# arrays unsafe
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。

我为结构添加了一种特殊情况,最好具有指定struct layout的类型。在您的情况下,这可能不是问题,但是如果指针数据来自本机C或C ++代码,则指定布局类型很重要(CLR可能会选择对字段进行重新排序以更好地实现内存对齐)。

但是如果指针完全来自托管代码生成的数据,则可以删除该检查。

此外,如果性能是一个问题,那么有比复制字节更好的算法来复制数据。 (请参阅无数的memcpy实现作为参考)


1
投票

似乎问题变成了:如何将泛型类型指定为简单类型。

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

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


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

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


0
投票

从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;
}
© www.soinside.com 2019 - 2024. All rights reserved.