标准容器在 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;
}
}
为了进一步说明问题,我在这里描述程序流程:
- PluginManager::Load 加载 dll
- GetInstance函数是从dll中获取的
- GetInstance return 一个插件实例,它存储在 m_plugin
- 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.
我用 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;
}
}
为了进一步说明问题,我在这里描述程序流程:
- PluginManager::Load 加载 dll
- GetInstance函数是从dll中获取的
- GetInstance return 一个插件实例,它存储在 m_plugin
- 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.