0

Problem:

In COM you occasionally find functions with signatures like this:

HRESULT STDMETHODCALLTYPE GetColorContexts( 
        UINT cCount,
        IWICColorContext **ppIColorContexts,
        UINT *pcActualCount)

The problem this presents for me is that ppIColorContexts must be an initialized array of IWICColorContext *. I have tried referencing the first element of a Vector of ATL::CComPtr<IWICColorContext> with no such luck it won't trigger the () operator so it complains about a type mismatch.

Attempted solutions:

  • vector<ATL::CComPtr<IWICColorContext>> failed due to type mismatch, as noted in the comments this has other issues as CComPtr overloads operator & which breaks STL containers. It seems that this was fixed in C++11 and was included in the STL in VC2010
  • BOOST_SCOPE_EXIT_ALL works but still means I'm manually managing the lifetime of the COM objects which is something I'd like to get away from.

Unattempted solutions:

  • Custom data structure - this is likely what I'll have to do if there is not a more elegant solution, but at least it would allow me to take advantage of destruction semantics properly.
  • Attach a CComPtr after this call - I dislike this solution because it leaves me with a period of execution where the resource may not get released if something goes wrong.
  • std::unique_ptr<IWICColorContext[]> with a custom deleter - I have yet to fully explore this possibility but it would ensure that the COM objects would always get released.
Mgetz
  • 5,108
  • 2
  • 33
  • 51
  • It wants an array, just pass a plain old array. Any wrapping with smart pointers has to be done after the call. – Hans Passant Sep 11 '13 at 13:13
  • 1
    I think you want something like this: `vector > > vec; GetColorContexts(vec.size(), &vec[0].m_T, ...);`. `CComPtr` cannot be placed into STL containers directly, because it overloads `operator&` which violates container requirements. That's why `CAdapt` was invented. – Igor Tandetnik Sep 11 '13 at 13:17
  • @HansPassant I was hoping to avoid that but if that is what I must needs do it is what I must needs do... still I was hoping to avoid `new` – Mgetz Sep 11 '13 at 14:03
  • @HansPassant what about a `std::unique_ptr` with a custom deleter? Still not ideal... but it should always be released. – Mgetz Sep 11 '13 at 16:55
  • 1
    @IgorTandetnik that was [fixed in C++11](http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx) and the fix was included in the TR1 release in VC2010 – Mgetz Sep 11 '13 at 18:31
  • In this case, what is this "type mismatch" of which you speak? Exactly what code did you try, and exactly what error message did you get for it? – Igor Tandetnik Sep 11 '13 at 18:51
  • @IgorTandetnik I wasn't using the access to the pointer like you're demonstrating (as I don't think that `IWICColorContext*[]` and `ATL::CComPtr[]` are going to access the same) I could be wrong but it would seem like making the assumption that the two different array types will access the same is a bit dangerous. – Mgetz Sep 11 '13 at 19:01
  • It so happens that the size of `CComPtr` is exactly the same as the size of the raw pointer it wraps as its only data member. `&vec[0]` (where `vec` is a `vector >`) should work in practice, though yes, technically, it's cheating (specifically, an undefined-behavior-prone sin known as "type punning"). But then, you can't use COM and not cheat: `QueryInterface(..., (void**)&p)` is technically illegal, too, I believe. – Igor Tandetnik Sep 11 '13 at 20:42

3 Answers3

2

I would do it by passing a vector of raw pointers to the function, then copying to another vector of CComPtr.

std::vector<IWICColorContext *> vec(5, NULL);
UINT nActualCount = 0;
GetColorContexts(vec.size(), &vec[0], &nActualCount);
std::vector<CComPtr<IWICColorContext> > results(vec.begin(), vec.begin() + nActualCount);

The only unfortunate part is that the CComPtr constructor performs an AddRef so you must do a corresponding Release on the raw pointers before they're lost.

for (auto it = vec.begin(); it != vec.end(); ++it)
    if (*it != NULL)
        (*it)->Release();
vec.clear();
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • While it wouldn't be the most efficient thing to do... I could flip this on its head and create a `std::vector` from the `vector` this would not create reference issues and since the number of contexts is small per image anyway it wouldn't be a drain. – Mgetz Sep 18 '13 at 21:20
1

Ultimately the solution was described by igor tandetnik in the comments above:

Basically in VC2010+ ATL::CComPtr has a sizeof that is the same as the pointer they represent (e.g. sizeof(ATL::CComPtr<IWICColorContext>) == sizeof(IWICColorContext*)), as best I can tell this is because they have no virtual functions and thus need no vTable. This is however highly dangerous as it's relying on a compiler implementation detail. Thus the following works:

std::vector<ATL::CComPtr<IWICColorContext> > > vec(5);
// CComPtrs are created and initialized here
GetColorContexts(vec.size(), &vec[0].m_T, ...);

Mark brought up a very good point that the solution above was completely dependent on compiler implementation which is dangerous. However the solution of only attaching ATL::CComPtr after the GetColorContexts call was not palattable either as it would not have been exception safe.

Ultimately my solution (tested this morning) is to create a vector<IWICColorContext*> temporarily from the vector<CComPtr<IWICColorContext>> this temporary vector does not increment the ref count and allows me to maintain exception safety.

Mgetz
  • 5,108
  • 2
  • 33
  • 51
-1

I think that you need something like that:

long lSize = 0;
ptr->GetColorContexts(cCount, NULL, &lSize);//return required amount of contexts
IWICColorContext** ppColorContexts = NULL;
ppColorContexts = new IWICColorContext*[lSize];
ptr->GetColorContexts(cCount, ppColorContexts, &lSize);
//use something to wrap received raw interfaces with CComPtr - 
//for example use for loop to pass them to new container,
//which stores CComPtr<IWICColorContext>
KKas
  • 5
  • 2