我有一个数组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;
}
}
通过将内容复制到一个新的浮点数组中很容易,但是数据变得很大并且不复制它会很好。
由于内存布局,这可能无法实现。
答案 0 :(得分: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;
}
}
}
你将无法像在C中那样“重铸”C#中的类型。最接近的是使用不安全的代码并获取实际的浮点数*,但即使这样你也无法对待指针就像一个安全数组,用于传递给接受数组的方法。
我做了一些实验,并且有可能使用不安全的代码来“转换”类型,但这是一个可怕的,可怕的黑客,你不应该真的这样做。然而,它演示了一些有关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();
}
基本上我们替换方法表指针以使类型现在看起来是float[]
,然后我们将数组的长度字段加倍以进行补偿。现在编译,运行和报告类型float[]
。也就是说,这可能会在以后以某种壮观的方式炸毁GC,并且它肯定非常依赖于实现,而且这不涉及x64与x86。尽管如此,有趣的是......有一个原因,这被称为“不安全”。希望这有助于说明为什么不能以安全的方式支持它,因为RTTI(通过方法表指针)被烘焙到存储数据的内存中。
答案 1 :(得分:1)
转换它的一种方法是实现枚举:
IEnumerable<Single> GetFloats(IEnumerable<Vector2f> vectorList)
{
foreach (var vect in vectorList)
{
yield return vect.x;
yield return vect.y;
}
}
但是这仍然有很多副本:一个是var
,一个是每个值(当屈服时)。
Struct是值类型,因此您无论如何都必须进行复制。
据我所知,您希望能够将同一块内存转换为新类型。但不幸的是,对于你的情况,C#中的数组不仅仅是指向一块内存的指针。还有一些与之关联的元数据(如数组的Length
)和一些特殊的内存对齐。所以我认为不可能这样做。
编辑即使你可以做到(这远非我的专业知识,但也许有一些IL代码可能会发出它),你可能会遇到托管内存处理方式的问题:它可以移动很多次。它在内存中没有静态位置。垃圾收集器紧凑存储器在每一代集合之后(不是每次,但仍然)。
这是一个有趣的问题。我希望其他专家在这里给我们一些启示;)
编辑:避免一些垃圾收集的更有效方法,就像评论中建议的那样:
float[] GetFloats(Vector2f[] vectorArray)
{
var floats = new float[vectorArray.Length*2];
for (int i = 0; i < vectorArray.Length; ++i)
{
floats[i*2] = vectorArray[i].x;
floats[i*2 + 1] = vectorArray[i].y;
}
return floats;
}
编辑:仍然不是您问题的直接答案,而是使用指针的更有效的副本,改编自Unsafe Code Tutorial(请注意,这需要使用/unsafe
进行编译) :
static unsafe void FastCopy(Vector2f[] src, float[] dst, Int32 count)
{
if (src == null || dst == null)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen < count ||
dstLen < count*2)
{
throw new ArgumentException();
}
// The following fixed statement pins the location of
// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (Vector2f* pSrc = src)
{
fixed (float* pDst = dst)
{
byte* ps = (byte*)pSrc;
byte* pd = (byte*)pDst;
count *= 8;
// Loop over the count in blocks of 4 bytes, copying a
// float (4 bytes) at a time:
for (int n = 0; n < count/4; n++)
{
*((float*)pd) = *((float*)ps);
pd += 4;
ps += 4;
}
}
}
}
答案 2 :(得分:1)
现在试着回答为什么不可能。
C#是一种类型安全的语言。这意味着在兼容类型上只允许某些转换(强制转换)。这解释了为什么不允许使用此代码:
Vector2f[] structs;
float[] floats = (float[])structs;
实际上,C#使用引用而不是指针。其中一个区别是引用不是内存中的静态位置。在垃圾回收期间可以移动对象。
但是,C#允许使用unsafe
代码进行一些指针运算。为此,必须通知垃圾收集器不应为所考虑的对象移动内存(并且不得使引用无效)。这是通过fixed
关键字完成的。
换句话说,要获取指向引用对象的指针(结构的逻辑相同),首先需要冻结内存中的对象位置(这也称为固定内存):
fixed (Vector2f* pStructs = structs)
fixed (float* pFloats = floats)
{
...
现在一切都已修复,你不能更改那些指针的地址。例如,这是不允许的:
pFloats = (float*)pStructs // this will change the address of pFloats which is fixed: illegal
此外,您无法将指针转换回引用:
float[] floats = (float[])pFloats; // not allowed
总之,一旦获得指针,您就可以将一些字节从一个位置移动到另一个位置,但是您无法更改相应引用的位置(只能移动数据)。
希望这能回答你的问题。
作为旁注,如果您有很多性能关键型操作,可以考虑在C ++中实现它,公开一些高级函数并从C#中调用一些函数。
答案 3 :(得分:0)
Vector2f[] structs = new Vector2f[]
{
new Vector2f(1f, 2f),
new Vector2f(3f, 4f)
}
也许尝试一次。
也许你应该尝试从阵列中取出1f,2f,3f和4f,而不是将其放入浮点阵列中。