для получения указателя com-интерфейса от другого com-интерфейса на тот же объект нам нужно использовать только метод QueryInterface
, который всегда реализуется любым интерфейсом.поэтому код в вашем случае должен быть следующим:
IAction* pAction;
IExecAction* pExecAction;
IEmailAction* pEmailAction;
HRESULT hr;
if (SUCCEEDED(hr = pAction->QueryInterface(IID_PPV_ARGS(&pExecAction))))
{
// use pExecAction
pExecAction->Release();
}
if (SUCCEEDED(hr = pAction->QueryInterface(IID_PPV_ARGS(&pEmailAction))))
{
// use pExecAction
pEmailAction->Release();
}
, даже если один интерфейс наследует от другого, использование c / c ++ cast всегда неверно.например
pExecAction = static_cast<IExecAction*>(pAction);
pEmailAction = static_cast<IEmailAction*>(pAction);
этот код корректен из синтаксиса c ++, потому что оба IExecAction : IAction
и IEmailAction : IAction
наследуются от IAction
.и это приведение (если принять во внимание расположение этих 3 интерфейсов) даст вам равные двоичные значения для pExecAction
и pEmailAction
.но pExecAction
не может иметь такое же двоичное значение, как pEmailAction
.должно быть
assert((void*)pEmailAction != (void*)pExecAction);
почему?потому что pEmailAction
и pExecAction
имеют разные виртуальные функции в одной позиции в vtable .например на 10-й позиции в таблице IExecAction
должен быть указатель на метод get_Path
.с другой стороны на 10-ю позицию в таблице IEmailAction
должен быть указатель на метод get_Server
.если (void*)pEmailAction == (void*)pExecAction
- они будут иметь одинаковые указатели на vtable.а указатель на какую функцию - get_Path
или get_Server
будет в 10-й позиции?в результате указатель на эти 2 интерфейса не может быть одинаковым (указывать на одну и ту же память).так как минимум один static_cast
здесь (может быть и то и другое) дают неверный результат.Чтобы понять, как работает QueryInterface
и почему указатели на pExecAction
и pEmailAction
будут разными - нам нужно искать реализацию.реализация интерфейсов - это некоторый класс, который наследует (обычно) от всех этих интерфейсов и реализует его следующим образом:
class CAction : IExecAction, IEmailAction
{
virtual ULONG STDMETHODCALLTYPE AddRef( );
virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, void **ppvObject)
{
PVOID pvObject;
if (riid == __uuidof(IAction))
{
pvObject = static_cast<IExecAction*>(this);
// or can be
pvObject = static_cast<IEmailAction*>(this);
}
else if (riid == __uuidof(IExecAction))
{
pvObject = static_cast<IExecAction*>(this);
}
else if (riid == __uuidof(IEmailAction))
{
pvObject = static_cast<IExecAction*>(this);
}
else
{
*ppvObject = 0;
return E_NOINTERFACE;
}
*ppvObject = pvObject;
AddRef();
return S_OK;
}
};
выглядит так, что static_cast<IExecAction*>(this);
всегда будет давать другое двоичное значение для сравнения static_cast<IEmailAction*>(this);
-CAction
будет содержать 2 разных vtables - один для IExecAction
и один для IEmailAction
.они имеют общую начальную часть (9 записей), но затем различаются.static_cast<IExecAction*>(this);
и static_cast<IEmailAction*>(this);
возвращают 2 разных (всегда) указателя на эти 2 разные таблицы.когда для IAction*
мы выбираем return или первый или второй vtable указатель.оба будут правильными.какой указатель возвращает реализацию - мы не можем знать (расположение фактического класса, который реализует IExecAction
, IEmailAction
нам неизвестно)