4

Why can't I cast an array of arrays in C# to a pointer to pointer?

public int WriteAudio(short[][] audio, uint num_channels, uint channel_len)
{
    int ret = 0;
    unsafe
    {
        fixed (short** d = audio)  // Compiler complains about this
        {
            ret = MyCppDLL.WriteDeinterlacedAudio(d, num_channels, channel_len);
        }
    }
    return ret;
}

I know the following is possible:

public int WriteAudio(short[] audio, uint num_channels, uint channel_len)
{
    int ret = 0;
    unsafe
    {
        fixed (short* d = audio) // No complaints here
        {
            ret = MyCppDLL.WriteInterlacedAudio(d, num_channels, channel_len);
        }
    }
    return ret;
}

Thanks!

Evgeniy Mironov
  • 777
  • 6
  • 22
savetruman
  • 145
  • 7
  • Why can't you use a `Collection` or a multi-dimensional array? – Greg Dec 22 '15 at 23:22
  • The function: `MyCppDLL.WriteDeinterlacedAudio()` is in a C++ wrapper DLL. I don't think a `Collection` or multi-dimensional array can be cast either, can it? – savetruman Dec 22 '15 at 23:48
  • I think you can only have an array of pointers.. not a pointers of pointers... see this if it helps: http://www.tutorialspoint.com/cprogramming/c_array_of_pointers.htm – MaxOvrdrv Dec 23 '15 at 00:23
  • I suspect it's because .NET arrays are more than just data, but also contains metadata and padding. (e.g., `Length`). When you cast an array to a pointer, you're just pointing to the data. But you can't quite do the same with array of arrays since you have to get around the metadata of each inner array. I'm afraid you'll need to do a conversion somewhere. – Jeff Mercado Dec 23 '15 at 01:43
  • This question may be relevant: http://stackoverflow.com/questions/890098/converting-from-a-jagged-array-to-double-pointer-in-c-sharp. – drf Dec 23 '15 at 01:55

2 Answers2

3

To use the double pointer, you can obtain a fixed pointer to each array element, place each element into a new array of double*s, and take a fixed pointer to that array to get the desired double**:

GCHandle[] handles = null;
try
{
    handles = audio
        .Select((z, j) => GCHandle.Alloc(audio[j], GCHandleType.Pinned))
        .ToArray();

    double*[] audioPtr = new double*[audio.Length];
    for (int j = 0; j < audioPtr.Length; j++)
        audioPtr[j] = (double*)handles[j].AddrOfPinnedObject();

    fixed (double** z = audioPtr)
    {
        // call extern method here
    }
}
finally
{   // unpin the memory
    foreach (var h in handles)
    {
        if (h.IsAllocated) h.Free();
    }
}

Note that the accepted answer at Converting from a jagged array to double pointer in C# presents a similar situation, but the accepted answer is incorrect. Another answer to this question uses the same incorrect methodology:

for (int i = 0; i < array.Length; i++)
    fixed (double* ptr = &array[i][0])
    {
        arrayofptr[i] = ptr;
    }

fixed (double** ptrptr = &arrayofptr[0])
{
    //whatever
}

Do not do this! The pointer *ptr is set in a fixed context, but is used outside the fixed context as an address element in arrayofptr. After the fixed block completes, the GC is free to reallocate the memory, at which point *ptr may point to a different object. The answer above prevents this possibility by using GCHandle to allocate a fixed pointer, ensuring that the individual elements in audio will not be relocated in memory until after the native call completes.

Community
  • 1
  • 1
drf
  • 8,461
  • 32
  • 50
  • Thanks for this answer! It looks good. It's vacation time now, but I'll try it out as soon as I can and at that point mark the answer as accepted. – savetruman Dec 24 '15 at 04:26
2

The function: MyCppDLL.WriteDeinterlacedAudio() is in a C++ wrapper DLL. I don't think a Collection or multi-dimensional array can be cast either, can it?

It can, but need to take some pains.

Because short[][] is an array of arrays and since unsafe code supports at most 1 "conversion" from array-pointer at a time in C#, the best you can do it to convert it to array of pointers first before converting it to pointer of pointers

in short, you need to do: array of arrays -> array of pointers -> pointer of pointers

Edit: I decided to put this note after comment by Mr. drf, please refer to his explanation and code. The idea above is to convert array of arrays to array of pointers before converting to pointer to pointers can still be used though. But how to do it safely, please refer to his more complete answer.

Something like this:

public int WriteAudio(short[][] audio, uint num_channels, uint channel_len) {
    int ret = 0;
    unsafe {
            fixed (short* d = &audio[0][0])  //pointer to the address of first element in audio
            {
                short*[] arrOfShortPtr = new short*[audio.Length]; //here is the key!! really, this is now array of pointers!
                for (int i = 0; i < arrOfShortPtr.Length; ++i) {
                    fixed (short* ptr = &audio[i][0]) {
                        arrOfShortPtr[i] = ptr; //like magic, casting from array of arrays to array of pointers
                    }
                }
                fixed (short** ptrOfPtr = &arrOfShortPtr[0]) { //now it is fine... second casting from array of pointers to pointer of pointers
                    //do what you want
                }
            }
        }
        return ret;
    }
Ian
  • 30,182
  • 19
  • 69
  • 107
  • This answer contains an error. After the `for` loop completes, `arrOfShortPtr` contains pointers to non-fixed memory, and the GC can reallocate this memory at any point. – drf Dec 23 '15 at 02:38
  • I updated my answer and refer the way to do it safely to your code and explanation. – Ian Dec 23 '15 at 02:43