【发布时间】:2021-11-21 04:37:16
【问题描述】:
我用 win32 LoadLibrary 加载了一个 dll,当我完成它时,我调用 FreeLibrary,破坏了在 dll 等中分配的所有内存......实际上,内存泄漏问题只发生在 std 容器中。似乎他们不愿意在销毁时释放他们的记忆。这是泄露的代码。
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 中的所有函数都是无操作的,如果没有 std 容器,进程不会报告任何内存问题。我把漏水堵在这里。为了完成,我将分享我的标准 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 返回一个插件实例,它存储在 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,就没有泄漏。
【问题讨论】:
-
您的假设不正确,FreeLibrary 不会导致调用删除游戏;您必须提供一个显式函数,与 GetInstance 相反,它将删除游戏实例。并注意 GetInstance 调用的数量与 DeleteInstance 调用的数量相匹配,它们应该被引用计数
-
您真的希望
GetInstance()每次调用时都返回一个新对象吗?如果调用者没有跟踪并删除您有泄漏的实例。如果您尝试实现单例,那么您所拥有的是不正确的。 -
@PKramer 创建实例时,我将它存储在 m_plugin 中并在 PluginManager::unload 中销毁它
-
@RetiredNinja 我调用 getinstance,存储对象,在适当的时候删除对象。名称会导致混淆,getinstance 只调用一次,并且由进程保证。导致泄漏的唯一原因是 std::vector 类,我不知道为什么。
-
没有泄露。它可重复用于以后的分配。如果您不需要此功能(通常具有更好的性能)并且真的想从检查器中抑制该报告,您可以实现一个始终调用 malloc 和 free 或
malloc_alloc(如果可用)的自定义分配器。
标签: c++ windows dll memory-leaks stdvector