简体   繁体   中英

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

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.

What about using GHandles to get the IntPtr to an array. I made a copy first to avoid overwriting data by the client. Unfortunately, you need to keep a GHandle in order to call .Free() to avoid memory leaks. This drives the decision to keep not only an IntPtr in the outputs, but also the GHandle and the byte length FYI.

static class Program
{
    static void Main(string[] args)
    {
        Inputs inputs = new Inputs();
        inputs.Cells = new double[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
        
        var outputs = Prepare(inputs);
        Console.WriteLine(outputs.CellPtr);            

        Console.WriteLine("Finish.");
    }

    static Outputs Prepare(Inputs inputs)
    {
        Outputs outputs = new Outputs();
        outputs.ByteSize = Buffer.ByteLength(inputs.Cells);
        var temp = new double[inputs.Cells.GetLength(0), inputs.Cells.GetLength(1)];
        Buffer.BlockCopy(inputs.Cells, 0, temp, 0, outputs.ByteSize);
        outputs.Handle = GCHandle.Alloc(inputs.Cells, GCHandleType.Pinned);
        outputs.CellPtr = outputs.Handle.AddrOfPinnedObject();
        return outputs;
    }

}
public class Inputs
{
    public double[,] Cells { get; set; }
}

public class Outputs : IDisposable
{
    public IntPtr CellPtr { get; set; }
    public int ByteSize { get; set; }
    public GCHandle Handle { get; set; }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // dispose managed resources here
            }

            Handle.Free();
            CellPtr = IntPtr.Zero;
            ByteSize = 0;

            disposedValue = true;
        }
    }

    ~Outputs()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(false);
    }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM