Should I be using CComptr , boost::share_ptr, or boost::intrusive_ptr for COM

Started by
16 comments, last by iMalc 14 years, 4 months ago
Hi, I am trying to figure out the best route as to how to handle COM object. More specific a few DirectX objects. From the looks of things there are three options to be had. CComPtr boost::shared_ptr with custom deleter boost::intrusive_ptr To be honest I can't really figure out what the pro's and con's are of one versus the other. Intrusive seems to remove the ref count all together. Beyond that I am open for some advice. Regards Chad
Advertisement
You should prefer CComPtr - neither shared_ptr nor intrusive_ptr is designed to work with COM. Although they may appear to work, they won't do reference counting correctly, as mentioned here.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by ApochPiQ
You should prefer CComPtr - neither shared_ptr nor intrusive_ptr is designed to work with COM. Although they may appear to work, they won't do reference counting correctly, as mentioned here.


It says shared_ptr can work with a custom deleter no?

Regards

Chad

Quote:Original post by chadsxe
It says shared_ptr can work with a custom deleter no?
It will "sort of" work. Shared pointers will not incrememnt/decrement the ref-count of the COM pointer (assuming you've set it up correctly, it'll increment the ref-count once when you first create a shared_ptr, then when all copies of that shared_ptr are destroyed, the ref-count will be decremented once).

You should generally prefer CComPtr, though. That's what it was designed for, and there's not much point (in my opinion) trying to shoe-horn shared_ptr into something it's not just for the sake of "consistency" or some other aesthetic reason.
Quote:Original post by Codeka there's not much point (in my opinion) trying to shoe-horn shared_ptr into something it's not just for the sake of "consistency" or some other aesthetic reason.
I guess you got a point.

Regards

Chad

Actually intrusive_ptr works fine with com objects. Indeed, this is *exactly* the point of intrusive_ptr. To allow integration with objects with their own defined reference counting model.

Here's some code I use for simplifying the use of COM objects. Feel free to use it

//This template is only enabled if T is a subclass of IUnknown (i.e. a com pointer)template<typename T>void intrusive_ptr_add_ref(	T* t,	typename boost::enable_if<boost::is_base_of<::IUnknown, T> >::type* dummy=0){	t->AddRef();}//This template is also only enabled if T is a subclass of IUnknown (i.e. a com pointer)template<typename T>void intrusive_ptr_release(	T* t,	typename boost::enable_if<boost::is_base_of<::IUnknown, T> >::type* dummy=0){	t->Release();}namespace com{	//My proposed alternate of CComPtr.  This version is unimplemented	//so that we can explicitly designate which types the class should	//exist for (namely, those types that derive from IUnknown)	template<class T, class Enable=void>	class ptr;	template<class T>	class ptr<		T,  		typename boost::enable_if<boost::is_base_of<::IUnknown, T> >::type	>	{	public:		typedef boost::intrusive_ptr<T>	type; 	private:		//This prevents the common error of writing something like		//com::ptr<IDirect3D9> var;		//You must actually write		//com::ptr<IDirect3D9>::type var;		ptr();		~ptr();	};	//The rest of what follows is used for prodiving a simplified	//way of calling API functions that expect an Ixxxx** and returns	//a newly allocated COM pointer with a ref count of 1.  The normal	//approach is to use CComPtr::operator&, but this is dangerous.	template<typename T>	typename ptr<T>::type take_ptr(T* t)	{		return ptr<T>::type(t, false);	}	template<typename T>	class recv_ptr_t	{	public:		recv_ptr_t(typename ptr<T>::type& r) : auto_(r), raw_(NULL) {}		~recv_ptr_t() {auto_ = take_ptr(raw_);}		operator T**() { return &raw_; }		operator void**() { return reinterpret_cast<void**>(&raw_); }	private:		T* raw_;		typename ptr<T>::type& auto_;	};	template<class T> 	recv_ptr_t<typename T::element_type> recv_ptr(T& rptr)	{		return recv_ptr_t<typename T::element_type>(rptr);	}	template<typename Dest, typename Src>	ptr<Dest> query_interface(Src p, const IID& iid = __uuidof(Dest))	{		void* pv = NULL;		p->QueryInterface(iid, &pv);		Dest* p2 = static_cast<Dest*>(pv);		return ptr<Dest>(p2, false);	}}


Here's a code snippet that uses the above stuff:

com::ptr<IDirect3D9>::type d3d(::Direct3DCreate9(DX_SDK_VERSION), false);com::ptr<IDirect3DDevice9>::type device;d3d->CreateDevice(   D3DADAPTER_DEFAULT,    D3DDEVTYPE_HAL,    NULL,   D3DCREATE_HARDWARE_VERTEXPROCESSING,   &present_params,   com::take_ptr(device));
Yes, but the point is, you have to do extra work to make intrusive_ptr work correctly with COM's reference counting system. Can you at all justify doing that over using the CComPtr class which is already guaranteed to do the correct thing?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by chadsxe
To be honest I can't really figure out what the pro's and con's are of one versus the other. Intrusive seems to remove the ref count all together. Beyond that I am open for some advice.


See the previous post for a solution using intrusive_ptr, but I figured I would also respond to this to explain exactly what intrusive_ptr is for and how it works.

shared_ptr manages its own internal reference count. This is great most of the time, but lots of other objects manage their own reference counts and you don't always have the ability to change the way this operates. COM is a great example, if you don't use their internal reference counting scheme your program will leak memory, and calling delete on an IUnknown* is incorrect.

The way intrusive_ptr works is by having the programmer tell intrusive_ptr how to find the *actual* reference counting functions to use. Instead of incrementing its own internal reference count, it just calls this function.

Naturally, if it's going to invoke a function for you, you have to wonder if you're decreasing your application's performance. You already have add ref and release functions, why should you waste efficiency *first* calling some wrapper function, and *then* calling the real function? It is for this reason that intrusive_ptr requires you to implement your wrapper functions as free functions (i.e. global functions). This way they can be inlined and completely eliminate any overhead of the intrusive_ptr internals calling your AddRef or release function. Specifically, an intrusive_ptr<T> is going to try to write:

intrusive_ptr_add_ref(internal_ptr_);


and

intrusive_ptr_release(internal_ptr_);


So your global function must be defined accordingly. For the case of COM, there are an unbounded number of possible types. In fact, anything that derives from IUnknown is a COM object. Using some slightly more advanced techniques, you can define a templated intrusive_ptr_xxx function that is located as a match for an invocation if and only if its argument is IUnknown*, or a pointer to a subclass of IUnknown. So that's where the enable_if stuff comes from in the previous code sample.

If you don't understand how it works it's probably fine. The important thing is that you can just "use it" and it should work.
Quote:Original post by ApochPiQ
Yes, but the point is, you have to do extra work to make intrusive_ptr work correctly with COM's reference counting system. Can you at all justify doing that over using the CComPtr class which is already guaranteed to do the correct thing?


Absolutely.

a) CComPtr is part of ATL. I don't want to bring in an entire library when I only need one class from it. Especially if I don't actually need that class.

b) overloaded operator& is DANGEROUS, DANGEROUS, DANGEROUS. In fact, due to overloaded operator&, it's not guaranteed to do the correct thing in all circumstances.

c) despite the fact that the code to make it work does not look simple, using it actually is very simple.
Quote:Original post by ApochPiQ
Yes, but the point is, you have to do extra work to make intrusive_ptr work correctly with COM's reference counting system. Can you at all justify doing that over using the CComPtr class which is already guaranteed to do the correct thing?
Indeed. "Can" does not equal "should".
CComPtr does many other nice things for you such as making QueryInterface calls easier, etc.

cache_hit:
(Edited)
a) I think you can apply the same logic of this one to boost. One is probably more likely to already be using ATL than one is to be using boost.

b) Not overloading operator & is very inconvenient (3x in capitals [smile]). CAdapt is provided as a means of removing that behaviour.

The only reason to use an intrusive_ptr would be for job security! [razz]
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

This topic is closed to new replies.

Advertisement