【问题标题】:Convert double[,] to IntPtr C#将 double[,] 转换为 IntPtr C#
【发布时间】:2021-03-11 15:33:58
【问题描述】:

我需要将 c# 中的双精度数组转换为 IntPtr 以正确地将其发送到我的 c DLL。我已经能够使用the method from this answer 成功地将 IntPtr 转换为 double[,]。我还发现 another answer 接近我需要的但处理一维数组。

我在我的逻辑中遗漏了一些东西,并在崩溃后得到错误代码:Managed ' has exited with code -1073740940 (0xc0000374)。

这是我目前所拥有的

            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 是我返回的结构中的 IntPtr。 单元格结构为单元格[行、列]

IntPtr 对我来说还是很陌生。

编辑:感谢哈罗德。他们的建议效果很好

【问题讨论】:

  • 你有double[][]吗?该代码似乎正在使用double [,] input
  • @BenVoigt 是的,你是对的。我目前正在查看 cmets 和答案,看看我是否可以解决问题。谢谢你们的cmets!

标签: c# double intptr


【解决方案1】:

这有很多问题。

首先,rows * columns 不是数据的大小,它只是元素的总数。每个元素不是一个字节,而是八个,或者sizeof(double),如果您愿意的话。

其次,pp = (IntPtr)(p.ToInt64() + IntPtr.Size); 更新(即根据当前模式下指针的大小将其推进 4 或 8 个字节),但您已经编写了columns * 8(或columns * sizeof(double))字节的数据。将p 推进小于columns * 8 会使写入相互覆盖,因此并非所有数据都最终出现在结果中。对了,这里的复杂转换其实没必要,直接加个IntPtr即可,.NET 4起。

第三,p 在循环中被改变了,这本身还不错,但是它以一种丢失原始指针的方式完成。 toReturn.cells = p;Marshal.FreeHGlobal(p); 使用 p 不指代您分配的区域,他们使用 p 现在指向刚刚超过数据末尾(如果 p 是更新了适当的数量)。原来的p一定要记住。

第四,在返回之前释放数据意味着它现在不再存在,所以无论这个数据传递给什么代码,仍然没有它:它有一个指向无效的指针(它可能不小心工作,但这是危险和错误的)。

前三点很容易解决,但最后一点需要对应用程序的工作方式进行非本地更改:这里无法释放内存,但应该在某个时间点释放它,即当数据处理完毕。

应用了一些修复:

        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;

请记住,您仍应在适当的时间释放内存。

【讨论】:

  • 非常感谢。这在很多方面都非常有帮助。根据您的建议,我在函数外部分配了 IntPtr 并将其传入,因此我可以在完成后释放内存。当我说我的单元格被格式化为单元格[行,列]时,我也弄错了。情况正好相反。祝你好运!
  • 您可以使用Buffer.Copy() 提取C# 中的一行二维数组,如果正确,它会很快并消除上面的内部循环。事实上,您可能不需要temp,而您可以在Marshal.Copy() 中完成所有操作(也许)。
  • @JohnAlexiou 没有采用 2D 数组的 Marshal.Copy 的重载,但我同意有多种选择可以改进这一点
  • @harold - here is a fiddle 展示了如何使用 Buffer.BlockCopy() 从二维数组中提取一维行。
【解决方案2】:

如何使用GHandlesIntPtr 获取到数组中。我先做了一个副本以避免客户端覆盖数据。不幸的是,您需要保留GHandle 才能调用.Free() 以避免内存泄漏。这促使我们决定在输出中不仅保留IntPtr,还保留GHandle 和字节长度供参考。

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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 1970-01-01
    • 1970-01-01
    • 2015-02-07
    • 2012-10-04
    • 2014-09-07
    相关资源
    最近更新 更多