savetruman
savetruman

Reputation: 145

Cast "array of arrays" to "pointer to pointer" in C#

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!

Upvotes: 4

Views: 2090

Answers (2)

drf
drf

Reputation: 8699

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.

Upvotes: 4

Ian
Ian

Reputation: 30813

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

Upvotes: 2

Related Questions