Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在C+;中包装非托管指针时发生堆损坏+/CLI_C#_.net_Garbage Collection_C++ Cli_Mixed Mode - Fatal编程技术网

C# 在C+;中包装非托管指针时发生堆损坏+/CLI

C# 在C+;中包装非托管指针时发生堆损坏+/CLI,c#,.net,garbage-collection,c++-cli,mixed-mode,C#,.net,Garbage Collection,C++ Cli,Mixed Mode,我在使用本机C代码、C++/CLI和C#的.NET应用程序中遇到堆损坏问题。这是我第一次真正进入这里的荒野 应用程序的结构是C#用于GUI和总体控制流,C++/CLI用于包装本机C函数,本机C函数用于处理数据。这些本机C函数通常接受指向数组(例如:int*)和维度的本机指针作为输入。C++/CLI将这些低级函数包装为高级组合处理函数,C#调用高级函数 有时,我确实需要在C#级别分配非托管内存,然后将相同的内存包传递给几个不同的C++/CLI函数 为了在我的C#和C++/CLI层中自由地传递这些数

我在使用本机C代码、C++/CLI和C#的.NET应用程序中遇到堆损坏问题。这是我第一次真正进入这里的荒野

应用程序的结构是C#用于GUI和总体控制流,C++/CLI用于包装本机C函数,本机C函数用于处理数据。这些本机C函数通常接受指向数组(例如:int*)和维度的本机指针作为输入。C++/CLI将这些低级函数包装为高级组合处理函数,C#调用高级函数

有时,我确实需要在C#级别分配非托管内存,然后将相同的内存包传递给几个不同的C++/CLI函数

为了在我的C#和C++/CLI层中自由地传递这些数组,我围绕托管指针创建了一个瘦包装器类。在C++/CLI层定义的名为ContiguousArray的包装器如下所示:

template <typename T>
public ref class ContiguousArray
{
public:
  ContiguousArray<T>(int size)
  {
    _size = size;
    p = (T*) calloc(_size,sizeof(T));
  }

  T& operator[](int i)
  {
    return p[i];
  }

  int GetLength()
  {
    return _size;
  }
  ~ContiguousArray<T>()
  {
    this->!ContiguousArray<T>();
  }

  !ContiguousArray<T>()
  {
    if (p != nullptr)
    {
      free(p);
      p = nullptr;
    }
  }

  T* p;
  int _size;
};

// Some non-templated variants of ContiguousArray for passing out to other .NET languages
public ref class ContiguousArrayInt16 : public ContiguousArray<Int16>
{
  ContiguousArrayInt16(int size) : ContiguousArray<Int16>(size) {}
};
我认为我在使用这个包装类处理非托管内存时非常小心,但我一直在应用程序中看到堆损坏和访问冲突问题。我从不将指向非托管内存的本机指针保留在ContiguousArray对象有效的作用域之外

从理论上讲,这三个用例中的任何一个都有可能导致堆损坏吗?我是否在我的ContinguousArray实现中丢失了一些关键内容?我担心垃圾收集器可能变得有点过于热心,在我真正处理完托管对象之前就清理了它们

用例1:我是否保证在结束大括号之前不会调用终结器?是否有可能.NET已经决定不再使用该对象,并且在我仍然有指向其内部内存的指针时将其清除?GC::KeepAlive是否需要用于堆栈对象

用例2:我是否需要在最后使用GC::KeepAlive来保证在第三次函数调用之前不释放对象?如果我改为写下: nativeFunction(unmanagedArray->p,unmanagedArray->GetLength())


用例3:我看不出这里有什么错误,但也许我遗漏了什么?

首先,我假设
连续数组中的成员被称为
大小而不是
\u大小
只是一个打字错误

在访问违规方面,我认为案例3没有任何错误。在案例2中,在使用其指针完成
nativeFunction
之前,肯定可以对数组进行垃圾收集。我不确定案例1是否有同样的问题。如果使用
GC::KeepAlive
,是否修复了访问冲突

堆损坏可能意味着在
中释放内存时,内存已经被释放!ContiguousArray()
。本机方法是否释放过数组或
ContiguousArrays
是否交换过拥有的数组


另外,检查
calloc
是否没有返回
nullptr
是个好主意。多亏了写下我的问题(最好的老师)的魔力和tsandy和Hans的建议,我已经详细研究了垃圾收集器在处理非托管资源时的行为。以下是我的发现:

我使用的设计模式有缺陷。如果垃圾收集器决定不再使用托管对象句柄(^),即使该句柄仍在作用域中,也可以对其进行垃圾收集。适当(但较慢)的设计模式不允许访问非托管资源,除非通过其托管包装器类的方法。如果允许指向非托管资源的指针或引用从包装器中泄漏出来,那么获取它们的代码需要非常小心,以确保拥有它们的包装器不会被收集/最终确定。因此,像ContiguousArray这样设计的包装器类不是一个好主意

也就是说,这种模式很快!下面是如何逐案抢救的方法

用例1实际上还可以!在C++/CLI中使用堆栈语义可确保在包装器超出范围时进行确定性终结。在包装器超出范围后保留指针仍然是一个错误,但总的来说是安全的。我对我的C++/CLI代码做了大量修改,以强烈支持堆栈语义,包括尽可能使用句柄引用(%)作为仅由我的C++/CLI代码调用的函数的参数

用例2是危险的,需要修复。有时,您无法避免使用句柄,因此需要使用来强制垃圾收集器保留对象,直到KeepAlive调用为止

{
  // Create an array for the low level code
  ContiguousArray<float>^ unmanagedArray = gcnew ContiguousArray<float>(1024);
  cliFunction(unmanagedArray);
  anotherCLIFunction(unmanagedArray);
  float* unmanagedArrayPointer = unmanagedArray->p;
  int returnCode = nativeFunction(unmanagedArrayPointer, unmanagedArray->GetLength());
  GC::KeepAlive(unmanagedArray); // Force the wrapper to stay alive while native operations finish.
  return returnCode;
}
{
//为低级代码创建一个数组
ContiguousArray^unmanagedArray=gcnew ContiguousArray(1024);
cliFunction(非托管Darray);
另一个CLI函数(非托管Darray);
float*unmanagedarraypoter=unmanagedArray->p;
int returnCode=nativeFunction(unmanagedarraypoter,unmanagedArray->GetLength());
GC::KeepAlive(unmanagedArray);//强制包装在本机操作完成时保持活动状态。
返回代码;
}
用例3在技术上是不安全的。在调用finalCLIFunction之后,.NET垃圾收集器可能会立即决定不再需要非托管Darray(取决于finalCLIFunction的实现)。但是,如果不需要的话,用诸如KeepAlive之类的实现细节来加重C代码的负担是没有意义的。相反,不要试图从C#代码访问任何非托管的内容,并确保所有C++/CLI函数的实现都为它们自己的参数调用KeepAlive(如果这些参数是句柄)

int finalCLIFunction(ContiguousArrayInt16^ unmanagedArray)
{
  // Do a bunch of work with the unmanaged array
  Int16* ptr = unmanagedArray->p;
  for(int i=0; i < unmanagedArray->GetLength(); i++)
  {
    ptr[i]++;
  }

  // Call KeepAlive on the calling arguments to ensure they stay alive
  GC::KeepAlive(unmanagedArray);

  return 0;
}
int finalCLIFunction(连续数组int16^非托管数组)
{
//对非托管阵列执行大量工作
Int16*ptr=unmanagedArray->p;
对于(int i=0;iGetLength();i++)
{
ptr[i]++;
}
//对调用参数调用KeepAlive以确保它们保持活动状态
基帕利夫
{
  ContiguousArrayInt16 unmanagedArray = new UnmanagedArray(1024);
  cliFunction(unmanagedArray);
  unmanagedArray = anotherCLIFunctionThatReplacesUnmanagedArray(unmanagedArray); // Unmanaged array is possibly replaced, original gets collected at some point
  returnCode = finalCLIFunction(unmanagedArray);
  // Do something with return code like show the user
} // Memory gets freed at some point
{
  // Create an array for the low level code
  ContiguousArray<float>^ unmanagedArray = gcnew ContiguousArray<float>(1024);
  cliFunction(unmanagedArray);
  anotherCLIFunction(unmanagedArray);
  float* unmanagedArrayPointer = unmanagedArray->p;
  int returnCode = nativeFunction(unmanagedArrayPointer, unmanagedArray->GetLength());
  GC::KeepAlive(unmanagedArray); // Force the wrapper to stay alive while native operations finish.
  return returnCode;
}
int finalCLIFunction(ContiguousArrayInt16^ unmanagedArray)
{
  // Do a bunch of work with the unmanaged array
  Int16* ptr = unmanagedArray->p;
  for(int i=0; i < unmanagedArray->GetLength(); i++)
  {
    ptr[i]++;
  }

  // Call KeepAlive on the calling arguments to ensure they stay alive
  GC::KeepAlive(unmanagedArray);

  return 0;
}