使用Vector2f结构数组作为就地浮点数组

31e*_*384 6 c# struct

我有一个数组Vector2f结构,每个结构包含两个浮点数,我想将它传递给一个采用浮点数组的函数.这些结构代表2d坐标,我想要最终结果[x0, y0, x1, y1, ... xn, yn].一些代码来演示:

using System;
using System.Runtime.InteropServices;

public class Test
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Vector2f
    {
        float x;
        float y;

        public Vector2f(float x, float y)
        {
            this.x = x;
            this.y = y;
        }
    }

    public static void Main()
    {
        Vector2f[] structs = new Vector2f[]
        {
            new Vector2f(1f, 2f),
            new Vector2f(3f, 4f)
        };

        // I want this to contain 1f, 2f, 3f, 4f
        // But Syntax error, cannot convert type!
        float[] floats = (float[])structs;
    }
}
Run Code Online (Sandbox Code Playgroud)

通过将内容复制到新的浮点数组中,这很容易,但数据变大,不复制它会很好.

由于内存布局,这可能无法实现.

Dan*_*ant 3

如果您实际上不需要传递真正的数组,而只是像数组一样可以访问的东西,您可以执行以下操作(未经测试):

public sealed class FloatArrayAdaptor : IReadOnlyList<float>
{
    private Vector2f[] _data;

    public FloatArrayAdaptor(Vector2f[] data)
    {
        _data = data;
    }

    public IEnumerator<float> GetEnumerator()
    {
        for (int i = 0; i < _data.Length; i++)
        {
            yield return _data[i].x;
            yield return _data[i].y;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int Count
    {
        get { return 2*_data.Length; }
    }

    public float this[int index]
    {
        get
        {
            //TODO: Add appropriate range checking and whatnot
            int i = index>>1;
            bool isX = (index & 0x1) == 0;
            return isX ? _data[i].x : _data[i].y;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您将无法像在 C 中那样在 C# 中“重新转换”类型。最接近的方法是使用不安全代码并获取实际的 float*,但即便如此,您也不能将该指针视为用于传递给采用数组的方法的安全数组。


我做了一些实验,可以使用不安全的代码来“转换”类型,但这是一个非常非常糟糕的黑客行为,你实际上不应该这样做。尽管如此,它还是演示了有关 CLR 对象头数据的一些有趣的事情:

    public static unsafe void Main()
    {
        Vector2f[] data = new Vector2f[10];
        float[] dummy = new float[1];

        //NOTE: This is horrible and you should never actually do it
        //After this code, the original data array cannot be used safely anymore
        fixed (void* rawData = &data[0])
        fixed (void* rawDummy = &dummy[0])
        {
            int* intData = (int*)rawData;
            int* intDummy = (int*)rawDummy;

            //method table pointer is at X-4-sizeof(IntPtr)
            //This is what identifies the type via RTTI
            //We're going to forge our identity and change our size to change our type
            //This assumes x86
            intData[-2] = intDummy[-2];

            //Our length is doubled
            intData[-1] = 2*intData[-1];
        }

        if (data.GetType() == typeof(float[]))
            Console.WriteLine("Type is now float[]!");

        float[] floatData = (float[])(object)data;

        Console.ReadLine();
    }
Run Code Online (Sandbox Code Playgroud)

基本上,我们替换了方法表指针,使类型现在看起来是 a float[],然后我们将数组的长度字段加倍来进行补偿。编译、运行并报告类型为 now float[]。也就是说,这很可能会在以后以某种惊人的方式破坏 GC,而且它肯定非常依赖于实现,而且这不涉及 x64 与 x86。不过,有趣的是……不过,这被称为“不安全”是有原因的。希望这有助于说明为什么不能以安全的方式支持它,因为 RTTI(通过方法表指针)被烘焙到存储数据的内存中。