C++ COM项目中输出参数引发内存泄漏的问题咨询:如何正确释放内存?
嘿,我仔细看了你的代码,发现内存泄漏的根源其实是**CompositionHost没遵守COM的引用计数规则**,再加上CreateCompositionHost里有个小逻辑错误,咱们一步步来修复:
1. 先把CreateCompositionHost的逻辑坑填上
你现在的函数里有个明显的判断错误:赋值完*compositionHost后,你检查的是compositionHost(指针的指针)不为空,这永远是真的啊!应该检查的是咱们刚创建的对象指针是否有效。修正后的代码如下:
bool __stdcall FluentCompositor::CreateCompositionHost(HWND hwnd, ICompositionHost** compositionHost) { // 先判断输入参数是否合法 if (compositionHost == nullptr) { return false; } // 先把输出指针置空,避免后续出问题 *compositionHost = nullptr; try { // 创建CompositionHost实例 *compositionHost = reinterpret_cast<ICompositionHost*>(new CompositionHost(hwnd)); // 检查对象是否创建成功 if (*compositionHost != nullptr) { // 划重点:COM规范里,新创建的对象引用计数初始为1,调用者负责Release // 只要你的CompositionHost构造函数把refCount设为1,这里不用额外加AddRef return true; } } catch (const std::bad_alloc&) { // 处理内存分配失败的情况 return false; } return false; }
2. 给CompositionHost加上正确的COM引用计数
这是解决泄漏的核心!COM对象的生命周期完全靠AddRef/Release管理,你的CompositionHost必须实现IUnknown的三个核心方法:AddRef、Release、QueryInterface。
假设你的ICompositionHost已经继承了IUnknown,那CompositionHost的实现应该是这样的:
CompositionHost.h
#pragma once #include "ICompositionHost.h" class CompositionHost : public ICompositionHost { private: // 用long类型保证线程安全,避免多线程下计数混乱 long refCount; HWND hwnd; public: explicit CompositionHost(HWND hwnd); ~CompositionHost(); // 实现IUnknown的纯虚方法 ULONG __stdcall AddRef() override; ULONG __stdcall Release() override; HRESULT __stdcall QueryInterface(REFIID iid, void** ppv) override; // 这里放你的ICompositionHost接口方法 };
CompositionHost.cpp
#include "pch.h" #include "CompositionHost.h" CompositionHost::CompositionHost(HWND hwnd) : refCount(1), hwnd(hwnd) { // 这里写你的构造逻辑,比如初始化和hwnd相关的资源 } CompositionHost::~CompositionHost() { // 这里做资源清理,比如释放和hwnd绑定的合成资源 } ULONG __stdcall CompositionHost::AddRef() { // 用InterlockedIncrement保证多线程下计数正确 return InterlockedIncrement(&refCount); } ULONG __stdcall CompositionHost::Release() { // 同样用线程安全的递减操作 ULONG newCount = InterlockedDecrement(&refCount); // 引用计数归0时,自动销毁对象 if (newCount == 0) { delete this; } return newCount; } HRESULT __stdcall CompositionHost::QueryInterface(REFIID iid, void** ppv) { if (ppv == nullptr) { return E_POINTER; } *ppv = nullptr; // 支持查询IUnknown和ICompositionHost接口 if (iid == IID_IUnknown || iid == IID_ICompositionHost) { *ppv = static_cast<ICompositionHost*>(this); // 返回接口指针前必须调用AddRef AddRef(); return S_OK; } // 不支持的接口返回E_NOINTERFACE return E_NOINTERFACE; }
这里的关键细节:
- 构造函数把
refCount初始化为1,完全符合COM的创建规范 - 用
InterlockedIncrement/InterlockedDecrement保证多线程下计数安全 Release里引用计数归0时自动delete this,这是COM对象自我销毁的标准方式
3. 修正MainWindow里的调用方式
你当前调用CreateCompositionHost用的是&host,虽然WRL的ComPtr支持这种写法,但更规范的是用GetAddressOf(),而且要确保ComPtr在作用域结束时自动释放:
ComPtr<ICompositionHost> host; // 用GetAddressOf()获取输出指针的地址 compositor->CreateCompositionHost(hwnd, host.GetAddressOf());
当host这个ComPtr离开CreateCompositionEffect的作用域时,它的析构函数会自动调用Release。如果此时CompositionHost的引用计数降到0,就会触发delete this,彻底释放内存,不会再泄漏。
4. 顺便优化FluentCompositor的引用计数
你的FluentCompositor已经实现了AddRef/Release,但直接用++ref和--ref不是线程安全的,改成原子操作更稳妥:
ulong __stdcall FluentCompositor::AddRef() { return InterlockedIncrement(&ref); } ulong __stdcall FluentCompositor::Release() { ulong newCount = InterlockedDecrement(&ref); if (newCount == 0) { delete this; return 0; } return newCount; }
最后总结一下
内存泄漏的核心就是CompositionHost没实现正确的COM引用计数,导致ComPtr没法触发对象销毁。按照上面的步骤改完后,ComPtr会自动管理对象生命周期,引用计数归零时对象自动删除,泄漏问题就解决啦!
内容的提问来源于stack exchange,提问作者Selastin

