4
[StructLayout(LayoutKind.Sequential)]
public struct Demo
{
    double X;
    double Y;
}


var data = new Demo[128];

FillWithMeaningfulValues(data);

double[] doubles;

Copy(data, out doubles); // ?

How do I copy the demo array into the doubles array without having to for(...) through each element? In C++, I would use memcpy, but in C# I did not find what I need in Marshal.Copy.

void MethodIDoNotWantToUse(Demo[] demo, out double[] doubles)
{
    doubles = new double[demo.Length * 2];
    for(int i = 0, j = 0; i < demo.Length; ++i)
    {
        doubles[j++] = demo[i].X;
        doubles[j++] = demo[i].Y;
    }  
}

void MethodIWouldPreferToUse(Demo[] demo, out double[] doubles)
{
    doubles = new double[demo.Length * 2];
    memcopy(doubles, demo, demo.Length * 2 * sizeof(double));
}
4
  • You can use simple LINQ query to achieve the result: double[] doubles = data.SelectMany(d => new[] { d.X, d.Y }).ToArray();. Commented May 14, 2014 at 8:33
  • 1
    @UlugbekUmirov That will still iterate through them one-by-one, though. He's trying to copy a whole block at once. Commented May 14, 2014 at 8:36
  • @TimGoodman You're right, but I don't see much performance benefit of bulk copy if there are only 128 elements. Commented May 14, 2014 at 8:36
  • That's just an example. My real data will be much larger (and also more complex structs, but still only composed of blittable types). I've asked because I know the C++ way but didn't know the C# way to achieve an efficient copy. However, you're right that for many cases, your solution-in-comment would be a perfectly viable answer. Commented May 14, 2014 at 8:39

3 Answers 3

7

You'll do something like this. Marshal.Copy do provides you what you need.

Demo[] array = new Demo[2];
array[0] = new Demo {X = 5.6, Y= 6.6};
array[1] = new Demo {X = 7.6, Y = 8.6};
GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
    IntPtr pointer = handle.AddrOfPinnedObject();
    double[] copy = new double[array.Length*2];//This length may be calculated
    Marshal.Copy(pointer, copy, 0, copy.Length);
}
finally
{
    if (handle.IsAllocated)
        handle.Free();
}
Sign up to request clarification or add additional context in comments.

1 Comment

Yes. I've used this technique for shuffling audio around in memory. +1
6

Since the struct is blittable, the array of the struct is blittable. Therefore you can pin the array of struct and copy into the double array with Marshal.Copy.

void CopyDemoArrayToDoubleArray(Demo[] demo, out double[] doubles)
{
    doubles = new double[demo.Length * 2];
    GCHandle gch = GCHandle.Alloc(demo, GCHandleType.Pinned);
    try
    {
        IntPtr demoPtr = gch.AddrOfPinnedObject();
        Marshal.Copy(demoPtr, doubles, 0, doubles.Length);
    }
    finally
    {
        gch.Free();
    }
}

You might do well to benchmark this against the simpler for loop that you want to avoid. It is plausible that the for loop will perform perfectly adequately.

Comments

2

It's possible to write a generic method that can convert arrays of any compatible type (by "compatible" I mean "elements must be value types and the size of the elements must be compatible").

You can use P/Invoke to call the Windows API CopyMemory() method.

However, bear in mind that there may not be any performance advantage to doing it this way; you should perform careful timings to be sure.

[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

public TOut[] ConvertArray<TIn, TOut>(TIn[] input) where TIn:struct where TOut:struct
{
    if (input == null)
        throw new ArgumentNullException("input");

    int sizeTIn   = Marshal.SizeOf(typeof(TIn));
    int sizeTOut  = Marshal.SizeOf(typeof(TOut));
    int sizeBytes = input.Length*sizeTIn;

    if ((sizeBytes % sizeTOut) != 0)
        throw new ArgumentException("Size of input type is not compatible with size of output type.");

    int sizeOut = sizeBytes/sizeTOut;

    var output = new TOut[sizeOut];

    GCHandle inHandle  = GCHandle.Alloc(input,  GCHandleType.Pinned);
    GCHandle outHandle = GCHandle.Alloc(output, GCHandleType.Pinned);

    try
    {
        IntPtr inPtr  = inHandle.AddrOfPinnedObject();
        IntPtr outPtr = outHandle.AddrOfPinnedObject();

        CopyMemory(outPtr, inPtr, (uint)sizeBytes);
    }

    finally
    {
        outHandle.Free();
        inHandle.Free();
    }

    return output;
}

For your example, you could call this like so:

Demo[] test = new Demo[10];

for (int i = 0; i < 10; ++i)
    test[i] = new Demo {X = i, Y = i};

var result = ConvertArray<Demo, double>(test);

for (int i = 0; i < 20; ++i)
    Console.WriteLine(result[i]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.