标准容器在 dll 上泄漏内存

std containers leaking memory on dll

我用 win32 LoadLibrary 加载一个 dll,当我完成它时,我调用 FreeLibrary,销毁 dll 等中分配的所有内存...实际上,内存泄漏问题只发生在标准容器上。似乎他们不愿意在销毁时释放他们的记忆。这是泄漏的代码。

namespace ToolKit
{

  class Game : public GamePlugin
  {
  public:
    void Init(ToolKit::Main* master);
    void Destroy();
    void Frame(float deltaTime, Viewport* viewport);
    void Resize(int width, int height);
    void Event(SDL_Event event);

    std::vector<int> point; // If I remove this line, no leaks are reported.
  };

}

extern "C" TK_GAME_API ToolKit::Game * __stdcall GetInstance()
{
  return new ToolKit::Game(); // Instance is deleted in the caller process than FreeLibrary() is called.
}

GamePlugin 中的所有函数都是空操作,如果没有标准容器,进程不会报告任何内存问题。我把泄漏堵在这儿。为了完成,我分享了我的标准 CRT 内存转储代码。

int main(int argc, char* argv[])
{
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);    
  return ToolKit_Main(argc, argv);
}

加载和卸载 dll 的代码

  void PluginManager::Load(const String& name)
  {
    HINSTANCE hinstLib;
    TKPROC ProcAdd;
    BOOL fRunTimeLinkSuccess = FALSE;

    String dllName = name;
    hinstLib = LoadLibrary(dllName.c_str());

    if (hinstLib != NULL)
    {
      m_moduleHandle = (void*)hinstLib;
      ProcAdd = (TKPROC)GetProcAddress(hinstLib, "GetInstance");

      if (NULL != ProcAdd)
      {
        fRunTimeLinkSuccess = TRUE;
        m_plugin = (ProcAdd)();
        m_plugin->Init(ToolKit::Main::GetInstance());
      }
    }

    if (!fRunTimeLinkSuccess)
    {
      m_reporterFn("Can not load plugin module " + dllName);
    }
  }

  void PluginManager::Unload()
  {
    if (m_plugin)
    {
      m_plugin->Destroy();
      SafeDel(m_plugin);
    }

    if (m_moduleHandle)
    {
      FreeLibrary((HINSTANCE)m_moduleHandle);
      m_moduleHandle = nullptr;
    }
  }

为了进一步说明问题,我在这里描述程序流程:

  1. PluginManager::Load 加载 dll
  2. GetInstance函数是从dll中获取的
  3. GetInstance return 一个插件实例,它存储在 m_plugin
  4. PluginManager::Unload 删除 m_plugin 并释放 dll。

这是重现泄漏的最小案例。 处理方:

#include <stdio.h>
#include <cstdlib>
#include <crtdbg.h>
#include <string>
#include <functional>
#include <Windows.h>
#include "Plugin.h"

using namespace std;

class PluginManager
{
public:
  void Load(const string& plugin);
  void Unload();

public:
  GamePlugin* m_plugin = nullptr;
  void* m_moduleHandle = nullptr;
};

typedef GamePlugin* (__cdecl* TKPROC)();
void PluginManager::Load(const string& name)
{
  HINSTANCE hinstLib;
  TKPROC ProcAdd;
  hinstLib = LoadLibrary(name.c_str());

  if (hinstLib != NULL)
  {
    m_moduleHandle = (void*)hinstLib;
    ProcAdd = (TKPROC)GetProcAddress(hinstLib, "GetInstance");

    if (NULL != ProcAdd)
    {
      m_plugin = (ProcAdd)();
      m_plugin->Init();
    }
  }
}

void PluginManager::Unload()
{
  if (m_plugin)
  {
    m_plugin->Destroy();
    delete m_plugin;
  }

  if (m_moduleHandle)
  {
    FreeLibrary((HINSTANCE)m_moduleHandle);
    m_moduleHandle = nullptr;
  }
}

int main(int argc, char* argv[])
{
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
  PluginManager* pm = new PluginManager();
  pm->Load("plugin.dll");
  pm->Unload();
  delete pm;
  return 0;
}

插件接口:

#pragma once

#ifdef _WIN32
#    ifdef TK_EXPORTS
#        define TK_GAME_API __declspec(dllexport)
#    else
#        define TK_GAME_API __declspec(dllimport)
#    endif
#elif
#    define TK_GAME_API
#endif

struct GamePlugin
{
  virtual void Init() = 0;
  virtual void Destroy() = 0;
  virtual void Frame() = 0;
};

DLL 端:

#define TK_EXPORTS
#include "Plugin.h"
#include <vector>

class Game : public GamePlugin
{
public:
  void Init() {}
  void Destroy() {}
  void Frame() {}

  std::vector<int> point;
};

extern "C" TK_GAME_API GamePlugin * __stdcall GetInstance()
{
  return new Game();
}

完全一样,如果我们去掉std::vector<int> point,就没有泄漏了。

正如在最小情况下所见,我在 dll 中创建插件实例,但在进程/可执行文件中删除它。即使正在释放内存,它也会被报告为泄漏,因为该内存在 dll 中分配并且没有在那里释放。哇那真是麻烦....

有更好的方法可以做到这一点,但如果有人遇到同样的问题,这里有一个快速修复方法。

进程:

void PluginManager::Unload()
{
  if (m_plugin)
  {
    m_plugin->Destroy();
    m_plugin = nullptr; // Clear the memory in where it is allocated.
  }

  if (m_moduleHandle)
  {
    FreeLibrary((HINSTANCE)m_moduleHandle);
    m_moduleHandle = nullptr;
  }
}

DLL:

void Destroy() { delete this; } // Now CRT Debug is happy. No false leak repots.