Use an array of Vector2f structures as an array of float in-place

I have an array of Vector2f structs, each of which contains two floats, and I want to pass it to a function that takes an array of floats. These structures represent 2d coordinates, and I want the final result to be [x0, y0, x1, y1, ... xn, yn] . Some code to demonstrate:

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

This is easy by copying the contents into a new float array, but the data becomes large, and it would be nice not to duplicate it.

This may not be possible due to memory location.

+6
source share
4 answers

If you really don't need to pass a true array, but just something that can be accessed, like an array, you can do something like this (untested):

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

You cannot "recompile" the type in C #, as you can do in C. The closest you can use is to use unsafe code and capture the actual float *, but even then you cannot treat this pointer as a safe array to pass to methods that take an array.


I was experimenting a bit, and using unsafe code I could "convert" the type, but this is a terrible, terrible hack, and you should not do this. However, it demonstrates some interesting things about CLR object header data:

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

Basically, we replace the method table pointer so that the type is now float[] , then we double the length field of the array to compensate. This compiles, starts, and reports that the type is now float[] . However, this could explode the GC later in some spectacular way, and it certainly depends on the implementation, plus this does not apply to x64 versus x86. Nevertheless, it is interesting ... However, the reason is that it is called "unsafe." I hope this helps demonstrate why it cannot be supported in a safe way, since RTTI (via the method table pointer) is baked in the memory where the data is stored.

+3
source

One way to convert it would be to enumerate:

 IEnumerable<Single> GetFloats(IEnumerable<Vector2f> vectorList) { foreach (var vect in vectorList) { yield return vect.x; yield return vect.y; } } 

But it still makes many copies: one for var and one for each value (in the lesson).

Struct are value types, so you should still make copies.

I understand that you would just like to convert the same piece of memory to a new type. But, unfortunately, for your case, arrays in C # are not just a pointer to a piece of memory. There are also some metadata associated with it (for example, Length array) and some special memory alignment. Therefore, I do not think that this can be done otherwise.

edit: Even if you can do it (this is far from my knowledge, but maybe with some IL code release it is possible), you may run into a problem with managed memory management: it can be moved many times. It does not have a static location in memory. Compact memory of the garbage collector after each generation (not every time, but still).

However, this is an interesting question. And I would like other experts to give us some of their light here;)

edit: A more efficient way to avoid garbage collection, as suggested in the comments:

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

edit: Do not answer your question, but a more efficient copy using pointers adapted from an Unsafe code tutorial (note that this requires compiling with /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; } } } } 
+1
source

Now try to answer why this is not possible.

C # is a safe type. This means that only certain conversion (casting) is allowed on compatible types. This explains why this code is not allowed:

 Vector2f[] structs; float[] floats = (float[])structs; 

In fact, C # uses references instead of pointers. One of the differences is that the link is not a static location in memory. The object can be moved during garbage collection.

However, C # allows some pointer arithmetic with unsafe code. To do this, the garbage collector must be notified that the memory should not be moved (and that the links should not be invalidated) for the objects in question. This is done using the fixed keyword.

In other words, to get a pointer to a reference object (the same logic for the structure), you first need to freeze the location of the object in memory (this is also called pinned memory):

 fixed (Vector2f* pStructs = structs) fixed (float* pFloats = floats) { ... 

Now that everything is set, you cannot change the address of these pointers. For example, this is not valid:

 pFloats = (float*)pStructs // this will change the address of pFloats which is fixed: illegal 

Also, you cannot convert the pointer back to a link:

 float[] floats = (float[])pFloats; // not allowed 

In conclusion, as soon as you get a pointer, you can move several bytes from one place to another, but you cannot change the location of the corresponding links (only data can be moved).

Hope the answer to your question.

As an additional note , if you have many critical operations, you can consider implementing it in C ++, expose some high-level functions, and call some functions from C #.

+1
source
  Vector2f[] structs = new Vector2f[] { new Vector2f(1f, 2f), new Vector2f(3f, 4f) } 

Perhaps try once.

And maybe you should try to take 1f, 2f, 3f and 4f from an array, and perhaps you could put it in an array of floats.

0
source

Source: https://habr.com/ru/post/987519/


All Articles