Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    51
  • comments
    129
  • views
    82855

On smart COM pointers...

Sign in to follow this  
Muhammad Haggag

711 views

DISCLAIMER: The source code in this post was NOT tested on a compiler. Beware of the mighty typo.

DISCLAIMER2: I tried to make this "scripture" clear. It isn't.

Often, programmers do not manage the lifetime of COM objects properly. This is usually due to one of 2 things:
- They don't get it: This is common among the green ones
- They forget: It sometimes happens
For example, one could forget to Release() an interface, or forget to AddRef() an interface on copying, ...etc.

The bad thing about this is that it introduces memory leaks - memory that you won't use anymore, yet it's not freed.

For example:


#include
#pragma comment(lib, "d3d9.lib")

class type_a
{
public:
type_a(IDirect3D9* d3d)
{
// Keep a copy of the d3d interface
m_d3d = d3d;
}

void do_something()
{
// Do something with the d3d interface
UINT num_adapters = m_d3d->GetAdapterCount();
}

private:
IDirect3D9* m_d3d;
};

class type_b
{
public:
type_b(IDirect3D9* m_d3d)
{
// Keep a copy of the d3d interface
m_d3d = d3d;
}

void do_another_thing()
{
// Do something with the d3d interface
HMONITER monitor = m_d3d->GetAdapterMonitor(D3DADAPTER_DEFAULT);
}

private:
IDirect3D9* m_d3d;
};

int main()
{
IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION); // Refcount = 1
type_a a(d3d); // Refcount = 1
type_b b(d3d); // Refcount = 1

// Release the d3d interface, we no longer need it in main()
d3d->Release(); // Refcount = 0, interface destroyed

/*
Bug: The following functions call methods on an IDirect3D9 interface , while the
interface had been released, and the object destroyed
*/

a.do_something();
b.do_another_thing();

return 0;
}


Here we have two classes type_a and type_b. Objects of both classes needs to use the COM object that exposes the interface IDirect3D9. A d3d object's created, and an interface to that object is obtained. Next, the objects a and b are created, and each keeps a copy of the IDirect3D9 interface pointer.

The problem comes from the fact that the creator of the object has released the interface to that object, before calling the methods on a and b. That is, now both a.d3d and b.d3d point to junk. There's no interface any more.

To solve this, you need to do the following:


#include
#pragma comment(lib, "d3d9.lib")

class type_a
{
public:
type_a(IDirect3D9* d3d)
{
// Increment refcount because we'll keep a local copy
d3d->AddRef();

// Keep a copy of the d3d interface
m_d3d = d3d;
}

~type_a()
{
if(m_d3d)
{
m_d3d->Release();
m_d3d = 0;
}
}

void do_something()
{
// Do something with the d3d interface
UINT num_adapters = m_d3d->GetAdapterCount();
}

private:
IDirect3D9* m_d3d;
};

class type_b
{
public:
type_b(IDirect3D9* m_d3d)
{
// Increment refcount because we'll keep a local copy
d3d->AddRef();

// Keep a copy of the d3d interface
m_d3d = d3d;
}

~type_b()
{
if(m_d3d)
{
m_d3d->Release();
m_d3d = 0;
}
}
void do_another_thing()
{
// Do something with the d3d interface
HMONITER monitor = m_d3d->GetAdapterMonitor(D3DADAPTER_DEFAULT);
}

private:
IDirect3D9* m_d3d;
};

int main()
{
IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION); // Refcount = 1
type_a a(d3d); // Refcount = 2
type_b b(d3d); // Refcount = 3

// Release the d3d interface, we no longer need it in main()
d3d->Release(); // Refcount = 2

/*
No problem here, object not destroyed yet
*/

a.do_something();
b.do_another_thing();

return 0;
/*
Destructors of 'a' and 'b' are invoked, and they both release the interface, causing
the refcount to go down to 0
*/

}


What we've done here is AddRef in the constructor, and Release in the destructor - nothing ground-breaking. Except that:
- We have to remember to do it
- We have to write the same code everytime

Smart COM pointers greatly simplify the lifetime-management burden, as we'll see shortly. We'll examine the CComPtr smart pointer, defined in atlbase.h - part of the ATL (Active Template Library), which comes with Visual C++.
For those who don't have Visual C++, don't worry at all. If you can't find any free COM pointers around, drop a comment and we can just write one of our own.

CComPtr offers a lot of functionality, of which we're interested in:
* It AddRef()'s the interface it holds when copied
* It Release()'s the interface it holds when destroyed, or assigned to 0 (NULL)

Note that CComPtr hides the methods AddRef() and Release() of the interface pointer, because you'll no longer need them. It forces you to use a replacement Release() instead, on the smart pointer itself.

To access the methods of CComPtr class, you use the dot operator '.', and to access the interface methods you use the arrow operator '->'.

So, the above code example would look like this with smart pointers:


#include
#pragma comment(lib, "d3d9.lib")

class type_a
{
public:
type_a(IDirect3D9* d3d)
{
// Keep a copy of the d3d interface
m_d3d = d3d; // Refcount incremented automagically
}

void do_something()
{
// Do something with the d3d interface
UINT num_adapters = m_d3d->GetAdapterCount(); // Call methods as usual
}

private:
CComPtr m_d3d;
};

class type_b
{
public:
type_b(IDirect3D9* m_d3d)
{
// Keep a copy of the d3d interface
m_d3d = d3d; // Refcount incremented automagically
}

void do_another_thing()
{
// Do something with the d3d interface
HMONITER monitor = m_d3d->GetAdapterMonitor(D3DADAPTER_DEFAULT);
}

private:
CComPtr m_d3d;
};

int main()
{
/*
Note that we don't say CComPtr, nope. CComPtr knows that its going
to store a pointer to the template parameter, so we just do CComPtr
(no asterisk)
*/

CComPtr d3d.Attach(Direct3DCreate9(D3D_SDK_VERSION));
/*
We use the "Attach" method instead of direct assignment because the returned
interface pointer has a refcount of 1, and assignment will increment the refcount
on the returned interface, making it 2.
Attach binds the smart pointer to the interface pointer without incrementing refcount
*/


type_a a(d3d); // refcount = 2
type_b b(d3d); // refcount = 3

// Release the d3d interface, we no longer need it in main()
//d3d->Release(); This is illegal
d3d.Release();

a.do_something();
b.do_another_thing();

return 0;
/*
Destructors for 'a' and 'b' are invoked, which in turn invoke the destructors
for a.m_d3d and b.m_d3d, which releases the interface automagically
*/

}


I cannot stress enough the importance of smart COM pointers. In short, use them. You'll forgot about DirectX memory leaks forever.
Sign in to follow this  


1 Comment


Recommended Comments

I love smart COM pointers. This r0cks. One of the reasons why I love DirectX. In your face OpenGL coders[...well they could code their own smart pointers actually...].

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!