Search code examples
c#doubleintptr

Convert double[,] to IntPtr C#


I need to convert a double array in c# to an IntPtr to properly send it to my c DLL. I have successfully been able to convert from IntPtr to double[,] using the method from this answer. Ive also found another answer that is close to what I need but deals with 1D arrays.

Im missing something in my logic and get the error code after a crash: Managed ' has exited with code -1073740940 (0xc0000374).

This is what I have so far

            IntPtr p = Marshal.AllocHGlobal(rows * columns);
            for (int i = 0; i < rows; i++) //rows
            {
                double[] temp = new double[columns];

                for (int x = 0; x < columns; x++)
                    temp[x] = input.cells[i, x];

                Marshal.Copy(temp, 0, p, columns);

                p = (IntPtr)(p.ToInt64() + IntPtr.Size);
            }

            toReturn.cells = p;
            Marshal.FreeHGlobal(p);
            return toReturn;

toReturn.cells is my IntPtr inside a struct that I return. cells is structured as cells[rows, columns]

IntPtr's are still very new to me.

Edit: thanks to harold. their suggestions worked beautifully


Solution

  • There are various things wrong with that.

    First, rows * columns is not the size of the data, it's only the total number of elements. The elements are not one byte each, but eight, or sizeof(double) if you prefer.

    Second, p is updated by p = (IntPtr)(p.ToInt64() + IntPtr.Size); (ie advancing it by 4 or 8 bytes depending on how big pointers are in the current mode), but you've written columns * 8 (or, columns * sizeof(double)) bytes of data. Advancing p by less than columns * 8 makes the writes overwrite each other, so not all data ends up in the result. By the way, the complicated conversions here are actually not necessary, you can add directly to an IntPtr, since .NET 4.

    Third, p is changed in the loop, which is not bad on its own, but it's done in a way that loses track of the original pointer. toReturn.cells = p; and Marshal.FreeHGlobal(p); use a p which does not refer to the area that you allocated, they use a p which now points just past the end of the data (well it would point there if p was updated by the right amount). The original p must be remembered.

    Fourth, freeing the data before returning means that it now no longer exists, so whatever code this data is passed to, still doesn't have it: it has a pointer to nothing which will be invalid to use (it may accidentally work, but it's dangerous and wrong).

    The first three points are easy to fix, but the last one needs a non-local change to how your application works: the memory cannot be freed here, but it should be freed at some point, namely when the user of the data is done with it.

    Some fixes applied:

            int stride = columns * sizeof(double);
            IntPtr p = Marshal.AllocHGlobal(rows * stride);
            for (int i = 0; i < rows; i++) //rows
            {
                double[] temp = new double[columns];
    
                for (int x = 0; x < columns; x++)
                    temp[x] = input.cells[i, x];
    
                Marshal.Copy(temp, 0, p + stride * i, columns);
            }
    
            toReturn.cells = p;
            return toReturn;
    

    Keep in mind you should still free the memory at the appropriate time.