#c #memory #directx #direct3d
#c #память #directx #direct3d
Вопрос:
Я наткнулся на утечку в моем приложении Direct3D, и в итоге я исправил ее, но я думаю, что причина утечки заключалась в моем непонимании того, как Direct3D обрабатывает свою память и интерфейсы.
Я не смог найти окончательную статью / руководство по ней (пожалуйста, предоставьте ее, если она у вас есть), но из того, что я собрал, она работает как таковая:
- Каждый раз, когда вы вызываете
Get
метод, количество ссылок на возвращаемый объект увеличивается. Итак, если я вызываюGetRenderTarget
, количество ссылок на отображаемую поверхность увеличивается. - Вызов
Release
интерфейса уменьшает его количество ссылок. Эти первые два пункта в совокупности, по сути, означают: каждый раз, когда вы получаете интерфейс, освобождайте его после того, как закончите с ним. - Когда количество ссылок достигает 0, экземпляр удаляется.
Я не совсем уверен, правильно ли это, но, похоже, это работает на практике. Если бы кто-нибудь мог уточнить / подтвердить, как это работает, это было бы здорово.
P.S, есть ли какие-либо гарантии, реализованные при выпуске интерфейсов? Вызов Release
любого количества раз в обратном буфере, похоже, не наносит никакого ущерба (что хорошо, но я не уверен, почему это не так).
Ответ №1:
Direct3D основан на COM, технологии, которой по меньшей мере 15 лет. Кажется, многие люди утверждают, что COM мертв, и по этой причине многие упускают это из виду, но реальность такова, что в Windows есть много вещей, включая Direct3D и MS new Media Foundation, которые все основаны на COM.
Я настоятельно рекомендую вам взглянуть на общее программирование COM. Существует множество книг и ресурсов, но многие из них довольно старые, но это нормально, потому что основа технологии не менялась в течение очень долгого времени.
В основном то, что вы наблюдали, — это подсчет ссылок на интерфейс. COM основан исключительно на доступе к объектам через интерфейсы, которые, как мне известно, являются производными от базового интерфейса. IUnknown реализует методы AddRef() и Release(), и ваше приложение несет ответственность за вызов AddRef() всякий раз, когда вы сохраняете локальную копию указателя, и за вызов Release () всякий раз, когда эта локальная копия больше не нужна.
Когда у вас есть методы с параметрами вывода интерфейса (например, IFoo ** ppObj ), это означает, что вызываемый объект возвращает вам интерфейс, и теперь, когда он у вас есть, вы по-прежнему несете ответственность за вызов Release () всякий раз, когда вы с ним закончите.
Как только вы освоитесь с этим, я бы посоветовал вам начать использовать интеллектуальный класс CComPtr для хранения локальных переменных и переменных-членов (по-прежнему передавать необработанные значения интерфейса между вызовами функций, нет необходимости в типах параметров интеллектуального указателя). Он позаботится обо всем вашем подсчете ссылок. Также не делайте это практикой вызова release «любое количество» раз. Это может сработать сегодня, потому что объект реализован как синглтон, или, возможно, что-то еще удерживает его, но это может измениться со следующим патчем или следующей версией. Всегда следуйте правилам. Если у вас есть интерфейс, когда он вам не нужен, вызовите Release() ровно один раз. Если вы скопировали указатель интерфейса, обязательно вызовите AddRef() ровно один раз.
Комментарии:
1. Отличный ответ, спасибо. Я бы никогда не вызывал
Release
больше одного раза на практике, я просто спрашивал, почему это не разорвало программу на части, когда я пытался разобраться в этом и попробовал это.
Ответ №2:
Применение семантики addref / release намного шире, чем технология COM. Существует простое правило, с которым должен столкнуться один CreateObject()
(или CreateTexture
, или GetRenderTarget
, или GetBackBuffer
и т.д.) один Release()
, один AddRef()
должен столкнуться с одним Release()
.
В COM IUnknown::Release()
возвращает количество ссылок на объект. Это может ввести вас в заблуждение, и вы можете подумать: «Хм … я просто вызываю Release()
, пока он не вернет 0, и у меня не будет утечек. ???? ПРИБЫЛЬ!!!!!111″ < — Это неправильно! AddRef
может вызываться самим Direct3D или библиотекой 3rd_party, в которую вы передаете этот объект, или чем-то еще за пределами вашего приложения. Один Release
за один AddRef
. Вы должны вызывать Release
, когда вам больше не нужен объект, не тратьте впустую системные ресурсы. Вы сказали:
Вызов Release любое количество раз в обратном буфере, похоже, не наносит никакого ущерба
Это ничего не значит. Может быть, вы так нравитесь Вселенной или вам просто слишком повезло, что вы не получаете исключений из D3D.
Интеллектуальные указатели (такие как CComPtr
) могут значительно упростить вашу жизнь, если вы будете их использовать. В этом случае вам не нужно вызывать Release
явно, он вызывается в CComPtr
dtor, если он назначен некоторому объекту.
void get_surface(IDirect3DDevice9 *pDevice)
{
IDirect3DSurface9 *surf0;
IDirect3DSurface9 *surf1;
CComPtr<IDirect3DSurface9> surf2;
CComPtr<IDirect3DSurface9> surf3;
CComPtr<IDirect3DSurface9> surf4;
pDevice->GetRenderTarget( 0, surf0 ); // surface reference counter incremented, you should call Release() for this
surf1 = surf0; // surface reference count is not incremented, you shouldn't call Release() for this
pDevice->GetRenderTarget( 0, surf2 ); // surface reference counter incremented
CComPtr<IDirect3DSurface9> surf3 = surf0; // surface reference counter incremented
surf0->Release(); // release for pDevice->GetRenderTarget( 0, surf0 );
surf2.Release(); // .Release() used not ->Release() - it is important
surf4.Release(); // nothing happens because surf4 == 0
} // surf3.Release() is called in surf3 destructor
Также вы можете #define D3D_DEBUG_INFO
перед включением заголовков direct 3d переключиться на среду выполнения debug d3d. Это полезно при поиске утечек в приложении d3d.
Да пребудет с вами CComPtr
Сила.
Комментарии:
1. Большое спасибо за ответ. Я не имел в виду, что я думал, что вызов
Release
сто раз был хорошей идеей или что я когда-либо думал об этом, мне просто было любопытно, почему это не сломало его. 🙂
Ответ №3:
Объекты D3D являются COM-объектами, и они используют базовую систему подсчета ссылок для управления временем жизни объекта. (Дополнительную информацию о компонентной объектной модели см. в Википедии или в статье MSDN «Управление временем жизни объекта«)
Количество ссылок изменяется исключительно с помощью методов AddRef
/ Release
, и некоторые другие функции вызывают эти методы.
Создание объекта, а также вызов определенных Get
методов, которые возвращают объект, производный от IUnknown
класса, будут вызывать AddRef
внутренне для увеличения количества ссылок, поэтому вам нужно будет вызывать Release
для каждого вызова, когда вы закончите с объектом.
Если вы передаете объект другой функции или классу, который хранит копию точки (даже временно), этот класс / функция должен вызывать, AddRef
чтобы гарантировать, что объект не освобождается во время его использования (и Release
чтобы сигнализировать, что это сделано).
Когда счетчик ссылок достигает 0 при вызове Release
, объекту выдается сигнал о том, что, возможно, настало подходящее время для удаления удерживаемых ресурсов, но это может произойти не сразу. Также нет защиты при многократном вызове Release. Счетчик ссылок не станет отрицательным, но он не будет выполнять никакой другой проверки работоспособности (потому что на самом деле это невозможно), поэтому вы можете вызвать нестабильность приложения, пытаясь освободить ссылки, которые вы не удерживаете.
Ответ №4:
Да, вы правы. Это называется подсчетом ссылок и гарантирует, что объекты будут активны до тех пор, пока они используются, и больше не будут. Для обеспечения соблюдения этого правила можно использовать множество интеллектуальных указателей — оба shared_ptr
и (C 11) unique_ptr
допускают вызов пользовательских средств удаления Release()
. Это позволяет легко управлять временем жизни объектов Direct3D точно так же, как вы бы делали для любого другого объекта в вашем приложении. Вам не нужно начинать включать библиотеки ATL и CComPtr, чтобы использовать интеллектуальные указатели с COM-интерфейсами.