1

Let me start off by saying I am VERY inexperienced with the workings of COM, but I have been tasked with debugging an issue for someone else

I have two COM projects named pvTaskCOM and pvFormsCOM and each has many Interfaces, but the two I am concerned with are:

ITaskActPtr which is in pvTaskCOM

IChartingObjectPtr which is in pvFormsCOM

The line of code causing my problem is:

ITaskActPtr pTaskAct = m_pChartObj;

Where m_pChartObj is an IChartingObjectPtr. The problem I was encountering was that pTaskAct was NULL after this assignment in one workflow, but fine in most other workflows. I dived into what is happening here using the debugger and found it is looking at the wrong COM entries during the QueryInterface. In the workflows that work fine, QueryInterface grabs entries from pvTaskCOM/pvTaskAct.h:

BEGIN_COM_MAP(CTaskAct)
  COM_INTERFACE_ENTRY(ITaskAct)
  .
  .
  .
END_COM_MAP()

Which contains the Interface I'm trying to cast to, and QueryInterface returns S_OK.

But in this other workflow m_pChartObj is instantiated in the same way, but QueryInterface for some strange reason looks inside pvFormsCOM/ChartingObject.h

BEGIN_COM_MAP(CChartingObject)
  COM_INTERFACE_ENTRY(IChartingObject)
  .
  .
  .
END_COM_MAP()

which does NOT contain the ITaskAct we are trying to cast to, and so QueryInterface returns E_NOINTERFACE.

The question I have is what could cause it to be looking at two different COM's for the same line of code? Is it some sort of inheritance issue? I just need a step in the right direction.

user3281826
  • 277
  • 1
  • 2
  • 8

2 Answers2

2

In the workflows that work fine, QueryInterface grabs entries from pvTaskCOM/pvTaskAct.h

It shouldn't be.

This line:

ITaskActPtr pTaskAct = m_pChartObj;

Is doing this under the hood:

ITaskAct *pTaskAct = NULL;
m_pChartObj->QueryInterface(IID_ITaskAct, (void*)&pTaskAct);

It is asking the IChartingObject's implementing object if it supports the ITaskAct interface, and if so to return a pointer to that implementation. So this code should only be looking at the entries of the COM_MAP for the CChartingObject class. It should not be looking at the CTaskAct class at all.

But in this other workflow m_pChartObj is instantiated in the same way, but QueryInterface for some strange reason looks inside pvFormsCOM/ChartingObject.h

That is the correct behavior, since that is where CChartingObject is actually implemented. If there is no entry for ITaskAct in the COM_MAP of CChartingObject, then the correct behavior is for CChartingObject::QueryInterface() to fail with an E_NOINTERFACE error.

So, the real problem is that your "working" workflows are actually flawed, and your "non working" workflow is doing the correct thing.

what could cause it to be looking at two different COM's for the same line of code? Is it some sort of inheritance issue?

No. The "working" workflows are corrupted, plain and simple. Calling QueryInterface() on an IChartingObject interface should be calling CChartingObject::QueryInterface(), but it is clearly calling CTaskAct::QueryInterface() instead. So either

  • the IChartingObject* pointer is actually pointing at a CTaskAct object instead of a CChartingObject object

  • something has corrupted memory and the IChartingObject's vtable is the unsuspecting victim.

I would suspect the former. So, in the "working" workflows, make sure the IChartingObject* pointer is actually pointing at the correct object. It sounds like someone took an ITaskAct* and type-casted it to a IChartingObject* without using QueryInterface(). Or they called QueryInterface() on some object and asked it for IID_ITaskAct instead of IID_IChartingObject but then saved the returned pointer in an IChartingObject* pointer instead of an ITaskAct* pointer.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

You are probably getting a bit lost in the plumbing. This is C++ code that was meant to make COM a bit less draconian. An important aspect of COM is that client code only ever works with interfaces. It doesn't know anything about objects. An interface is a simple contract, a list of functions that you can call. IChartingObject would have, say, a Paint() function. ITaskAct would have, no real idea, something "tasky", a Schedule() function.

Note how m_pChartObj is a pretty misleading name. It stores an interface pointer, not an object. But not uncommon, it is easy to think of an interface pointer as an object pointer if the object implements only one interface or has a "dominant" interface that you'd use all the time. Hiding the object inside the server code is a very strong goal in COM, you can only ever make interface calls.

So the ITaskActPtr pTaskAct = m_pChartObj; basically announces, "I have a chart, I want to make task functions calls next". Like Schedule(). That requires COM to ask the chart object implementation "do you know anything about the task interface contract?". Inevitably it has to consult back to the server, in the interface map for the CChartingObject where IChartingObject came from, to see if it also implements ITaskAct.

So what you see happening is entirely normal. The answer is "no".

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536