Профилировщик 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>, Затем я вызываю методы, не обращая внимания на фактический тип объекта, реализующего метод.

Это приводит меня к двум вопросам:

  1. В контексте COM / ATL безопасно ли извлекать производные интерфейсы, сохранять их в родительском интерфейсе, как описано выше, а затем вызывать из него функции?
  2. Очевидно, что родительский интерфейс не знает функций в производных интерфейсах. Как я могу проверить, является ли указатель производным интерфейсом (например, 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 невелики, однако оба будут работать просто отлично, и вы вряд ли заметите оптимизацию.

Другие вопросы по тегам