【问题标题】:C++ Output Parameter Causes Memory LeakC++ 输出参数导致内存泄漏
【发布时间】:2021-10-13 02:50:51
【问题描述】:

我正在开发一个基于 COM 的 C++ 项目,我的代码有一个带有 out 参数的函数,该函数将一个对象作为输入并将一个类的新实例分配给它。但是当我使用 CRT Debugging 时,我发现函数中有一些内存泄漏,这里是函数的代码。

bool __stdcall FluentCompositor::CreateCompositionHost(HWND hwnd, ICompositionHost** compositionHost)
{
    if (compositionHost != nullptr)
    {
        *compositionHost = reinterpret_cast<ICompositionHost*>(new CompositionHost(hwnd));
        if (compositionHost != nullptr)
        {
            return true;
        }
    }
    else
    {
        return false;
    }
    return false;
}

此函数接受ICompositionHost 的对象并使用CompositionHost 对象对其进行初始化,我应该在哪里释放内存以避免内存泄漏。

我使用 ComPtr 调用函数,但仍然存在内存泄漏

ComPtr<ICompositionHost> host;
compositor->CreateCompositionHost(hwnd,host.GetAddressOf());

完整代码:

FluentCompositor.cpp

#include "pch.h"
#include "ICompositionHost.h"
#include "IFluentCompositor.h"
#include "CompositionHost.h"
#include "FluentCompositor.h"

FluentCompositor::FluentCompositor() :ref(1)
{
}

ulong __stdcall FluentCompositor::AddRef()
{
    return (++ref);
}

ulong __stdcall FluentCompositor::Release()
{
    if (--ref == 0)
    {
        delete this;
        return 0;
    }
    return ref;
}

HRESULT __stdcall FluentCompositor::QueryInterface(REFIID iid, LPVOID* ppv)
{
    if (iid == IID_IFluentCompositor || iid == IID_IUnknown)
    {
        *ppv = (void*)this;
        AddRef();
    }
    else
    {
        *ppv = NULL;
    }
    return (*ppv == NULL) ? E_NOINTERFACE : S_OK;
}

HRESULT __stdcall CreateFluentCompositor(void** compositor)
{
    if (compositor != nullptr)
    {
        *compositor = reinterpret_cast<void*>(new FluentCompositor());
        if (compositor != nullptr)
        {
            return S_OK;
        }
    }
    else
    {
        return E_INVALIDARG;
    }
    return E_FAIL;
}

bool __stdcall FluentCompositor::CreateCompositionHost(HWND hwnd, ICompositionHost** compositionHost)
{
    if (compositionHost != nullptr)
    {
        *compositionHost = reinterpret_cast<ICompositionHost*>(new CompositionHost(hwnd));
        if (compositionHost != nullptr)
        {
            return true;
        }
    }
    else
    {
        return false;
    }
    return false;
}

MainWindow.cpp

#include "pch.h"
#include "MainWindow.h"

int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    const wchar_t className[] = L"Fluent Compositor";

    WNDCLASS wc = {
        .lpfnWndProc = WndProc,
        .hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase),
        .hCursor = LoadCursor(nullptr, IDC_ARROW),
        .lpszClassName = className,
    };

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP, className, L"Fluent Compositor Sample", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, wc.hInstance, nullptr);
    if (hwnd == nullptr)
    {
        return 0;
    }

    CreateCompositionEffect(hwnd);

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    _CrtDumpMemoryLeaks();
    return 0;
}

bool CreateCompositionEffect(HWND hwnd)
{
    auto fluentCompositorLib = LoadLibrary(L"FluentCompositor.dll");
    if (!fluentCompositorLib)
    {
        return false;
    }

    CreateCompositor = (CreateFluentCompositor)GetProcAddress(fluentCompositorLib, "CreateFluentCompositor");
    if (!CreateCompositor)
    {
        return false;
    }

    CreateCompositor(&compositor);
    if (compositor == nullptr)
    {
        return false;
    }

    ComPtr<ICompositionHost> host;
    compositor->CreateCompositionHost(hwnd,&host);
    return true;
}

LRESULT __stdcall WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hwnd, msg, wparam, lparam);
}

MainWindow.h

#pragma once
#include "IFluentCompositor.h"

using namespace Microsoft::WRL;

extern "C" IMAGE_DOS_HEADER __ImageBase;

ComPtr<IFluentCompositor> compositor;

typedef BOOL(__stdcall* CreateFluentCompositor)(IFluentCompositor** compositor);
CreateFluentCompositor CreateCompositor;

bool CreateCompositionEffect(HWND hwnd);
LRESULT __stdcall WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

【问题讨论】:

  • 您应该在停止使用内存时释放内存。似乎您不想破坏此函数中的对象,因此泄漏的问题在其他地方,更具体地说,compositionHost 超出范围
  • 但我将 ComPtr 对象作为参数传递给函数,所以 ComPtr 释放它对吗?
  • ICompositionHost 似乎是一个 COM 接口。 COM 接口是引用计数的。当引用计数达到零时,您释放实现该接口的对象。这是(大概)在CompositionHost 中实现的。要取得进展,您必须停止思考错误:您的函数 “采用ICompositionHost 的对象”。它接收指向ICompositionHost 接口的指针的地址。
  • 内存泄漏是您最少的问题。您需要解决更严重的问题。就像使用 C++ 的分配器到 move memory across DLL boundaries。或者盲目地让 C++ 异常跨越 ABI 边界。或者没有正确报告错误,只是留下一个未初始化的[out] 参数。就像,有很多错误,甚至很难弄清楚从哪里开始。
  • CompositionHost 你的吗?例如,是否正在实施它?如果是这样,您的IUnknown 实现是否正确管理了引用计数,或者您是否使用 ATL 工具包类来为您处理? COM 接口引用计数不是火箭科学,但它很容易搞砸,因为它是正确的。还。如果您在 same host 智能指针实例上调用 CreateCompsitionHost 方法多个而没有中间 Release 您绝对会泄漏内存,因为任何先前的接口指针都是托管将不会正确发布。

标签: c++ oop winapi visual-c++ interface


【解决方案1】:

您对reinterpret_cast(以及一般的类型转换)的使用是完全错误的。如果您的类实现了正确的接口,则无需手动转换它们(可能在QueryInterface() 中除外),编译器将为您隐式执行正确转换。

另外,您没有正确检查new 的返回值。或者,就此而言,处理new 默认情况下在失败时抛出异常而不返回nullptr 的可能性。如果您想在失败时使用nullptr,请改用nothrow 版本的new

另外,当使用ComPtr 时,应该使用其重载的operator&amp; 而不是GetAddressOf() 方法。特别是如果ComPtr 已经拥有一个接口。 GetAddressOf() 不会释放接口(这就是为什么有一个单独的ReleaseAndGetAddressOf() 方法),但operator&amp; 会。

试试这个:

#include <new>

HRESULT __stdcall FluentCompositor::QueryInterface(REFIID iid, LPVOID* ppv)
{
    if (!ppv) return E_POINTER;

    if (iid == IID_IFluentCompositor)
    {
        *ppv = static_cast<IFluentCompositor*>(this);
        /* alternatively:
        IFluentCompositor *comp = this;
        *ppv = comp;
        */
    }
    else if (iid == IID_IUnknown)
    {
        *ppv = static_cast<IUnknown*>(static_cast<IFluentCompositor*>(this));
        /* alternatively:
        IFluentCompositor *comp = this;
        IUnknown *unk = comp;
        *ppv = unk;
        */
    }
    else
    {
        *ppv = nullptr;
    }

    if (!*ppv)
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

// similar for CompositionHost::QueryInterface() ...

HRESULT __stdcall CreateFluentCompositor(void** compositor)
{
    if (!compositor) return E_POINTER; // not E_INVALIDARG
    // make sure FluentCompositor has a refcount of 1 when created!
    *compositor = static_cast<IFluentCompositor*>(new(std::nothrow) FluentCompositor);
    return (*compositor) ? S_OK : E_FAIL;
}

bool __stdcall FluentCompositor::CreateCompositionHost(HWND hwnd, ICompositionHost** compositionHost)
{
    if (!compositionHost) return false;
    // make sure CompositionHost has a refcount of 1 when created!
    *compositionHost = new(std::nothrow) CompositionHost(hwnd);
    return (*compositionHost);
}
ComPtr<ICompositionHost> host;
compositor->CreateCompositionHost(hwnd, &host);

或者,在创建对象时考虑在内部使用ComPtr,例如:

#include <new>

HRESULT __stdcall CreateFluentCompositor(void** compositor)
{
    if (!compositor) return E_POINTER; // not E_INVALIDARG
    // make sure FluentCompositor has a refcount of 0 when created,
    // as the ComPtr constructor will increment it!
    ComPtr<IFluentCompositor> obj(new(std::nothrow) FluentCompositor);
    return (obj) ? obj->QueryInterface(IID_IFluentCompositor, compositor) : E_FAIL;
}

bool __stdcall FluentCompositor::CreateCompositionHost(HWND hwnd, ICompositionHost** compositionHost)
{
    // make sure CompositionHost has a refcount of 0 when created,
    // as the ComPtr constructor will increment it!
    ComPtr<ICompositionHost> obj(new(std::nothrow) CompositionHost(hwnd));
    return ((obj) && (obj->QueryInterface(IID_ICompositionHost, reinterpret_cast<void**>(compositionHost)) == S_OK));
}

让你的对象以 0 而不是 1 的引用计数开始是个好主意,因为它们不知道它们是否将与接口指针或对象指针一起使用(如果它们甚至与指针一起使用完全)。不要增加一个对象的引用计数,除非它实际上分配给一个接口指针,该指针是 AddRef()'ed 并且需要是 Release()'d。

【讨论】:

  • 您好,它解决了 CompositonHost 的问题,但 FluentCompositor 仍然存在内存泄漏。
  • @trickymind 请更新您的问题以显示使用CreateFluentCompositor()的代码
  • 我更新了代码,请您检查一下
  • @trickymind "我更新了代码" - 你所做的只是用我给你的代码替换你所有的原始代码。但是您没有包含我要求的代码。调用CreateFluentCompositor() 的代码在哪里?我已恢复您的编辑。请包括我的要求。不要要求人们查看外部网站上的代码。
猜你喜欢
  • 2014-03-15
  • 1970-01-01
  • 2017-02-20
  • 2019-10-21
  • 2016-03-14
  • 2012-02-05
  • 2011-02-15
  • 2019-04-21
  • 1970-01-01
相关资源
最近更新 更多