从 .NET 代码中销毁非托管对象

Posted

技术标签:

【中文标题】从 .NET 代码中销毁非托管对象【英文标题】:Destruct Unmanaged object From .NET code 【发布时间】:2011-09-27 20:22:02 【问题描述】:

我编写了一个 C++ 库,它通过 C++/CLI 包装器向我的 VB.NET 应用程序公开。

我担心通过包装器传送到 VB.NET 应用程序的对象。为了使用库中的类,我为它们编写了包装器,并且包装器类包含指向该类的非托管实例的指针。在包装类的析构函数中,我删除了非托管指针指向的内存。

如果包装的 .NET 库将这些类实例之一传递给 VB.NET 应用程序并且 VB.NET 应用程序使用它并继续前进(不保存对它的引用); .NET 垃圾收集器会出现并处理此类实例,从而导致在类的析构函数中释放非托管内存吗?如果我引用了包装类实例指向的同一内存,这将导致错误。

如果是这种情况,那么我将复制包装器中的所有数据,以确保我的包装器不会与库的本机部分共享任何数据。如果不是这种情况,那么我是否必须在包装的类实例上调用某种 dispose 方法才能破坏非托管对象?

【问题讨论】:

【参考方案1】:

在 CLI 中,您只需使用析构函数语法 (~MyClass()),C++/CLI 编译器就会为您在类上创建一个 IDisposable 实现。

当在非托管代码中调用Dispose method 时,将调用这个“析构函数”(它不是真的,它只是语法)。您可以在这里进行调用以释放资源。

如果您想实现终结器,那么您将使用新的析构函数语法 (!MyClass())。这应该会释放与“析构函数”中相同的资源。

最后,在您的托管代码中,您只需引用IDisposable 实现,然后对其调用Dispose,很可能通过using statement。

【讨论】:

如果我同时声明析构函数 (~MyClass()) 和终结器 (!MyClass()) 并在我的 c++/cli 包装器中实现它们,我想我可能会有点混淆,然后在我将它们转换为 IDisposable 之后,我将自动能够对这些对象调用 dispose。我想我听说调用 dispose 可能很危险,或者那可能是在调用垃圾收集器,我不记得了。 @Ian:调用Dispose 确实调用垃圾收集器。这是完全错误的。析构函数 (~) 用于 IDisposable 实现,而终结器 (!) 用于终结器(如果计划用于终结,则会被垃圾收集器调用)。一般来说,如果您的 CLI 类中有非托管资源,您应该实现 IDisposable(显然,~ 非常容易)以便您的托管调用者可以尽快处理非托管资源。【参考方案2】:

你有点混淆了。是的,在 vb.net 代码停止引用您的 C++/CLI 类之一之后,最终将在对象被收集后调用终结器。请注意,这是 finalizer,它与 dispose 无关。您的 C++/CLI 对象应同时提供析构函数(由 Dispose() 调用)和终结器。

否则没有内存损坏的危险。只有当垃圾收集器找不到对该对象的任何实时引用时,才会调用终结器。由于没有留下任何引用,因此您不可能意外访问已删除的本机对象。检查this answer 的标准模式。

【讨论】:

好吧,如果我在库中有一个仍然引用该内存的本机指针,那么垃圾收集器可能(间接地)处理它并且我在库中的非托管引用会导致错误,对吧?我猜垃圾收集器不会检查 c++/cli 类中的非托管指针,看看它们是否指向指向其他地方的内存。 不,这取决于你。比如说,使用 shared_ptr。您所知道的是托管代码将永远不会再次使用它。如果托管代码无法对它做任何事情,请考虑保持该对象活动是否仍然有意义。我不知道这是否相关。 好的,谢谢。就我而言,它确实有意义,因为库有一个持久的对象列表,但有时这些对象会传递给 .NET 应用程序。我只需要确保我进行了数据复制并删除了我复制到的内存。 "由于没有剩余引用,因此您不可能意外访问已删除的本机对象" FALSE。终结器队列中的其他对象可能仍会尝试使用它。 投反对票的湿毯子先生又出现了。关于管道的有趣对话,让我们看看它去哪里获得洞察力。卡敦克,结束。

以上是关于从 .NET 代码中销毁非托管对象的主要内容,如果未能解决你的问题,请参考以下文章

利用IDisposable接口构建包含非托管资源对象

如何在运行时使用 ITypeLib 和 ITypeInfo 从 C#.NET 调用 COM 非托管代码?

从 .NET 应用程序中销毁 COM 对象

Net Framework 中托管代码与非托管代码的区别

c# 托管与非托管

在托管代码中填充非托管数组