加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – C/C++LI库中的早期完成和内存泄漏

发布时间:2020-12-15 21:24:41 所属栏目:百科 来源:网络整理
导读:我遇到的问题似乎是在我正在研究的C/C++LI(和C#)项目的早期调用终结器.这似乎是一个非常复杂的问题,我将从代码中提到很多不同的类和类型.幸运的是它是开源的,你可以在这里继续: Pstsdk.Net(mercurial repository)我也尝试在适当的时候直接链接到文件浏览器,
我遇到的问题似乎是在我正在研究的C/C++LI(和C#)项目的早期调用终结器.这似乎是一个非常复杂的问题,我将从代码中提到很多不同的类和类型.幸运的是它是开源的,你可以在这里继续: Pstsdk.Net(mercurial repository)我也尝试在适当的时候直接链接到文件浏览器,这样你就可以在阅读时查看代码.我们处理的大多数代码都在存储库的pstsdk.mcpp文件夹中.

现在的代码处于相当可怕的状态(我正在研究它),我正在处理的代码的当前版本是在Finalization修复程序(UNSTABLE!)分支中.该分支中有两个变更集,为了理解我的冗长问题,我们需要同时处理这两个变更集. (变更集:ee6a002df36f和a12e9f5ea9fe)

对于某些背景,此项目是用C编写的unmanaged library的C/C++LI包装器.我不是该项目的协调员,有几个我不同意的设计决策,因为我相信很多看过这些代码的人会,但我离题了.我们在C/C++LI dll中包含了大部分原始库层,但是在C#dll中展示了易于使用的API.这样做是因为项目的目的是将整个库转换为托管C#代码.

如果您能够获取要编译的代码,则可以使用this test code重现该问题.

问题

名为moved resource management code to finalizers,to show bug的最新变更集显示了我遇到的原始问题.此代码中的每个类都使用相同的模式来释放非托管资源.这是一个例子(C/C++LI):

DBContext::~DBContext()
{
    this->!DBContext();
    GC::SuppressFinalize(this);
}

DBContext::!DBContext()
{
    if(_pst.get() != nullptr)
        _pst.reset();            // _pst is a clr_scoped_ptr (managed type)
                                 // that wraps a shared_ptr<T>.
}

此代码有两个好处.首先,当像这样的类在using语句中时,资源会立即正确释放.其次,如果用户忘记了处理,当GC最终决定完成该类时,将释放非托管资源.

这是这种方法的问题,我根本无法理解,有时,GC会决定最终确定一些用于枚举文件中数据的类.许多不同的PST文件都会发生这种情况,并且我已经能够确定它与被调用的Finalize方法有关,即使该类仍在使用中.

我可以在this file (download)1中始终如一地实现它.早期调用的终结器位于DBAccessor.cpp文件中的NodeIdCollection类中.如果您能够运行上面链接的代码(由于对boost库的依赖性,此项目很难设置),应用程序将因异常而失败,因为_nodes列表设置为null并且_db_由于终结器运行,指针被重置.

1)NodeIdCollection类中的枚举代码是否有任何明显的问题会导致GC在仍在使用时完成此类?

我只能通过下面描述的解决方法使代码正常运行.

难看的解决方法

现在,我能够通过将每个终结器(!classname)中的所有资源管理代码移动到析构函数(~classname)来解决此问题.这已经解决了这个问题,虽然它没有解决我为什么要尽早完成课程的好奇心.

但是,这种方法存在问题,我承认这对设计来说更是个问题.由于代码中指针的大量使用,几乎每个类都处理自己的资源,并且需要处理每个类.这使得使用枚举非常难看(C#):

foreach (var msg in pst.Messages)
   {
      // If this using statement were removed,we would have
      // memory leaks
      using (msg)  
      {
             // code here
      }
   }

作用于集合中的项目的using语句对我来说是错误的,但是,使用这种方法非常有必要防止任何内存泄漏.没有它,即使调用了pst类上的dispose方法,也永远不会调用dispose并且永远不会释放内存.

我有意尝试改变这种设计.这个代码第一次编写时的根本问题,除了我对C/C++LI几乎一无所知之外,我无法将本地类放在托管代码中.我觉得有可能使用范围指针,当类不再使用时会自动释放内存,但我不能确定这是否是一种有效的方法来解决这个问题,或者它是否可行.所以,我的第二个问题是:

2)以无痛方式处理托管类中非托管资源的最佳方法是什么?

详细说明,我可以用最近添加到代码中的clr_scoped_ptr包装器替换本机指针(clr_scoped_ptr.h来自this stackexchange问??题).或者我是否需要将本机指针包装在scoped_ptr< T>之类的内容中?或者smart_ptr< T>?

感谢您阅读所有这些,我知道这很多.我希望我已经足够清楚,以便我可以从比我更有经验的人那里得到一些见解.这是一个很大的问题,我打算在它允许的时候添加赏金.希望有人可以提供帮助.

谢谢!

1此文件是免费提供的PST文件enron dataset的一部分

解决方法

clr_scoped_ptr是我的,来自 here.

如果有任何错误,请告诉我.

即使我的代码不完美,使用智能指针也是解决此问题的正确方法,即使在托管代码中也是如此.

您不需要(也不应该)在终结器中重置clr_scoped_ptr.每个clr_scoped_ptr本身都将由运行时完成.

使用智能指针时,您不需要编写自己的析构函数或终结器.编译器生成的析构函数将自动调用所有子对象上的析构函数,并且每个子对象终结器将在收集时运行.

仔细观察您的代码,NodeIdCollection确实存在错误. GetEnumerator()每次调用时都必须返回一个不同的枚举器对象,以便每个枚举都从序列的开头开始.您正在重复使用单个枚举器,这意味着在连续调用GetEnumerator()之间共享该位置.那很糟.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读