【问题标题】:Read Write C++ Dynamic Arrays in C# (InteropServices)在 C# (InteropServices) 中读写 C++ 动态数组
【发布时间】:2015-03-13 20:51:55
【问题描述】:

我有一个 C++ 类,它有一个 header(matrixheader.h),这样:

#pragma once

class M
{
    public:
        M(int m,int n);
        void MSet(int m,int n,double d);
        double MGet(int m,int n);
        ~M();
    private:
        double** mat;
};

类在(matrixbody.cpp)中定义如下:内置于Win32平台。

#pragma once
#include "matrixhead.h"

M::M(int m,int n)
{
  mat = new double*[m];
  for (int i = 0; i < m; i++)
  {
    mat[i] = new double[n];
  }
}

void M::MSet(int m,int n,double d)
{
    mat[m][n] = d;
}

double M::MGet(int m,int n)
{
    double d = mat[m][n];
    return d;
}

M::~M()
{
    delete[] mat;
}

我已经为类做了一个包装器,如下所示(matrixwrapper.cpp):包装器也是在Win32平台上构建的。

#include "matrixhead.h"
#include "matrixbody.cpp"

extern "C" __declspec(dllexport) void* Make(int m,int n)
{
    M o(m,n);
    return &o;
}

extern "C" __declspec(dllexport) void setData(void* mp,int m,int n,double d)
{
    M* ap = (M*)mp;
    M a = *ap;
    a.MSet(m,n,d);
}

extern "C" __declspec(dllexport) double getData(void* mp,int m,int n)
{
    M* bp = (M*)mp;
    M b = *bp;
    double d = b.MGet(m,n);
    return d;
}

我将类导入 C# 并尝试从 C# 调用 C++ dl 方法:

using System;
using System.Runtime.InteropServices;


namespace wrappertest
{
 class Program
   {
    [DllImport("matrixwrapper.dll")]
    unsafe public static extern void* Make(int m,int n);

    [DllImport("matrixwrapper.dll")]
    unsafe public static extern void setData(void* mp,int m, int n,double d);

    [DllImport("matrixwrapper.dll")]
    unsafe public static extern double getData(void* mp,int m, int n);

    static unsafe void Main(string[] args)
    {
        void* p = Make(10, 10);
        setData(p,10,1,10);
        Console.WriteLine(getData(p,10,1));
    }
  }
}

但是当我尝试从 C# 运行 C++ dll 方法时,我收到以下错误

1//试图读取或写入受保护的内存。这通常表明在 x64 中运行 C# 代码时其他内存已损坏。

2//在x86 Active/x86或AnyCPU平台上运行时,试图加载格式不正确的程序。

问题:

1//以上代码有什么问题?

2//考虑到我的最终目标是在 C++ 中创建一个 2d 动态数组并在数组中读取/写入数据,例如上面来自 C# 的 matrixheader.h 文件中的一个 double**mat?还有其他的吗?如何实现它?

【问题讨论】:

  • 你知道Make返回一个指向栈的指针吗?这非常很糟糕......
  • 那么我们该如何解决这个问题呢?你能给我指出一篇进一步解释这个错误的文章吗?
  • 您也可以创建一个 C++/CLI 包装器,并在 C# 中使用更直接、更简洁。
  • 顺便说一句,您的矩阵类泄漏内存,请使用std::vector 代替动态数组。或者至少确保在删除父数组之前也删除所有子数组。最好的选择就是在 C# 中创建数组,这里不需要 C++。
  • @Aditya 在 C# 中,var test = new int[10000 * 10000]; 工作得很好。

标签: c# c++ dllimport dllexport interopservices


【解决方案1】:

让我们先做简单的事情:

在 x86 Active/x86 或 AnyCPU 平台上运行时尝试加载格式不正确的程序。

这仅仅意味着您的平台不匹配。您要么尝试在 x64 .NET 运行时加载 x86 C++ dll,要么相反。

以下错误才是真正的问题:

试图读取或写入受保护的内存。这通常表明在 x64 中运行 C# 代码时其他内存已损坏。

这是意料之中的,因为您的Make 函数会在堆栈上创建一个对象,然后返回一个指向它的指针。当你读回这个对象时,堆栈上的内容已经改变(堆栈正在被重用),mat 指针指向其他地方,很可能指向未分配的内存。

see this answer 我更深入地了解这个问题(它是 C#,但它是同一个问题)。

您必须分配一些动态内存来解决您的问题。你可以试试:

extern "C" __declspec(dllexport) void* Make(int m,int n)
{
    M* o = new M(m,n);
    return o;
}

当然,如果您不想泄漏内存,则必须再创建一种方法来执行匹配的delete

另外,就像 Mgetz 在 cmets 中指出的那样,M 类本身存在内存泄漏。析构函数中的delete[] mat; 调用不会释放每个分配的内存块。你在构造函数m + 1 中调用new 次,这意味着你必须在析构函数中调用delete[] m + 1 次,每个new 一次。您可能应该将 mn 保留为课堂中的字段(至少 m 是强制性的,以了解您必须对 delete[] 进行多少次调用)。

更好的解决方案是使用单个数组而不是锯齿状数组。您将该数组中的索引i, j 计算为i * m + j。你也可以使用std::vector 或者干脆用 C# 来做:

public class M
{
    private double[] _items;
    private int _m;
    private int _n;

    public M(int m, int n)
    {
        _items = new double[m * n];
        _m = m;
        _n = n;
    }

    public this[int i, int j]
    {
        // Here, you should perform a bounds check on i and j against _m and _n
        get { return _items[i * _m + j]; }
        set { _items[i * _m + j] = value; }
    }
}

【讨论】:

  • 他甚至不需要 C++,他可以用 C# 创建整个东西,而且可能比互操作的成本要快。如果出于某种原因他确实需要 C++,他可能应该只使用 m*n 长度 std::vector 然后索引。单独分配矩阵的每一行实际上要昂贵得多,并且会使索引更慢。
  • @Mgetz 绝对是,我打算提出同样的建议,但你打败了我;)
  • @Mgetz 和 Lucas 谢谢大家,我会采纳你的建议并实施它们。我知道我可以按照 Mgetz 的建议使用向量类并展平数组,但我想弄清楚哪种方法是因此上述实现更快
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-09
  • 1970-01-01
  • 1970-01-01
  • 2022-01-28
  • 2022-01-05
相关资源
最近更新 更多