Профилировщик CLR: приведение и вызов функций в стиле COM из производных интерфейсов
У меня есть необходимость разработать специализированный CLR-профилировщик. Профилировщики CLR должны быть реализованы как реализация COM-сервера ICorProfilerCallback
или более новая версия в настоящее время до 5. Инициализация Profiler происходит в методе обратного вызова Initialize(IUnknown* pICorProfilerInfoUnk)
, Это дает возможность сделать QueryInterface
на предоставленной IUnknown
возражать и получать указатели на ICorProfilerInfo
интерфейсы. Начиная с.NET 4.5, есть ICorProfilerInfo
, ICorProfilerInfo2
, ICorProfilerInfo3
, а также ICorProfilerInfo4
, с каждой новой версией, обеспечивающей дополнительную функциональность. В идеале я хотел бы получить указатель на последнюю доступную версию и позволить виртуальным таблицам выяснить, что такое реальный объект.
if (FAILED(pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo4, (LPVOID*)&m_pICorProfilerInfo)))
{
if (FAILED(pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo3, (LPVOID*)&m_pICorProfilerInfo)))
{
if (FAILED(pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo2, (LPVOID*)&m_pICorProfilerInfo)))
{
if (FAILED(pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo, (LPVOID*)&m_pICorProfilerInfo)))
{
AtlTrace(TEXT("[Initialize] Failed to retrieve any ICorProfilerInfo~ interface."));
return S_FALSE;
}
}
}
}
Обратите внимание, что во всех случаях указатель на возвращаемый интерфейс является одной и той же переменной m_pICorProfilerInfo
, который имеет тип CComQIPtr<ICorProfilerInfo>
, Затем я вызываю методы, не обращая внимания на фактический тип объекта, реализующего метод.
Это приводит меня к двум вопросам:
- В контексте COM / ATL безопасно ли извлекать производные интерфейсы, сохранять их в родительском интерфейсе, как описано выше, а затем вызывать из него функции?
- Очевидно, что родительский интерфейс не знает функций в производных интерфейсах. Как я могу проверить, является ли указатель производным интерфейсом (например,
ICorProfilerInfo2
) а бросить это на такое?
В тестировании пока #1, как правило, кажется, все в порядке. Но я бы предпочел подтверждение или совет. Я гораздо более неуверен в пункте № 2. Например, ICorProfilerInfo
имеет SetEnterLeaveFunctionHooks
функция в то время как ICorProfilerInfo2
имеет SetEnterLeaveFunctionHooks2
функция. Я хотел бы сделать что-то вроде следующего псевдокода:
if (m_pICorProfilerInfo IS ICorProfilerInfo2)
{
((ICorProfilerInfo2) m_pICorProfilerInfo)->SetEnterLeaveFunctionHooks2(...)
}
else
{
m_pICorProfilerInfo->SetEnterLeaveFunctionHooks(...)
}
Любой совет о том, как это можно сделать, был бы очень признателен.
1 ответ
1) хорошо для этих типов интерфейса, они были созданы, чтобы всегда наследовать предыдущую версию. Таким образом, v-таблица ICorProfilerInfo4 включает в себя все методы 3 предыдущих версий. Это, конечно, не всегда так для COM-интерфейсов, но работает здесь. Опасно, что вызов метода ICorProfilerInfo4 при получении интерфейса ICorProfilerInfo3 приведет к сбою вашей программы. Компилятор не поможет вам избежать неприятностей.
2) нет оператора IS в C++, вы можете сделать это в COM, вызвав QueryInterface() снова. Или вы можете просто установить переменную, которая указывает, какую версию интерфейса вы получили. Использование QI позволяет избежать сбоев при неправильной проверке версии и позволяет компилятору помочь вам правильно понять код.
Я бы порекомендовал вам сначала каталогизировать функции профилировщика, которые вам действительно нужны. Вы рискуете добавить слишком большую гибкость, такую, которая заставляет вас писать код, который вы никогда не будете использовать, и отправлять программу, которая не была полностью протестирована. Различия между FunctionEnter2 и FunctionEnter3 невелики, однако оба будут работать просто отлично, и вы вряд ли заметите оптимизацию.