Получение событий от работающего WMPlayer
От: Weiss  
Дата: 29.04.15 13:30
Оценка: 1 (1)
Коллеги,

Мне нужно отлавливать события о работе уже запущенного wmplayer: начало/конец проигрывания видео. Т.е. требуется в моем процессе получить некий интерфейс объекта-плейера, работающего в адресном пространстве wmplayer.exe и прицепить к нему мой ивент синк. Не могу подключиться к нему легальным способом, т.е. инжекты на рассматриваю. Плагин к wmplayer — тоже не хотелось бы рассматривать. ОС: Windows 8 x64.

Вот что наисследовал. Когда wmplayer стартует он регистрирует свой IUnknown в ROT с таким DisplayName: "clsid:91778246-9BE4-4713-A651-E833B853CC30:!session:2".

Попробовал следующие способы подключения к нему:
1) Через GetActiveObject (из oleauto.h): в качестве CLSID задавал {91778246-9BE4-4713-A651-E833B853CC30}:

const wchar_t MediaPlayerClsidString [] = L"{91778246-9BE4-4713-A651-E833B853CC30}";
CLSIDFromString(MediaPlayerClsidString, &clsid);
HRESULT hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);

получаю hr 0x800401e3 Operation unavailable.

2) Через GetActiveObject: в качестве CLSID задавал {22D6F312-B0F6-11D0-94AB-0080C74C7E95}, который является clsid от coclass из библиотеки типа C:\Windows\System32\msdxm.tlb:
получаю hr 0x800401e3 Operation unavailable.

3) Через ROT, используя IRunningObjectTable::EnumRunning. Т.о. получаю moniker, который идентифицирую по DisplayName=="clsid:91778246-9BE4-4713-A651-E833B853CC30:!session:2". С помощью IRunningObjectTable::GetObject(rot, moniker) получаю IUnknown.
С этого IUnknown пытаюсь скверить интерфейсы: IWMPCore, IWMPPlayer, IWMPPlayer2 и т.д. — все что есть в хедере wmp.h. Кроме них еще и IDispatch, IDispatchEx, IInternalUnknown. На все получаю E_NOINTERFACE.
Также попробовал скверить интерфейсы с iid-ами {22D6F311-B0F6-11D0-94AB-0080C74C7E95} и {20D4F5E0-5475-11D2-9774-0000F80855E6}, которые посмотрел в той же библиотеке типа msdxm.tlb — это интерфейсы IMediaPlayer и IMediaPlayer2. Тоже получаю E_NOINTERFACE.

4) Прошелся под дизассемблером по QueryInterface того IUnknown, который получил через моникер из ROT. Обнаружил что можно скверить интерфейсы:
IMarshal, IMarshal2, IStdIdentity, IAgileObject, IProxyManager, IAggregatedIdentity, IRemUnknown. Меня заинтересовал из них IProxyManager, видимо через него можно было бы создать прокси к объекту, который живет в адресном пространстве процесса wmplayer.exe. IProxyManager действительно можно скверить, но как с ним работать дальше пока не нашел.

Т.о. в данный момент у меня есть только последний метод, дающий призрачную надежду на успех предприятия.
И мне подумалось, может я двигаюсь не в тех направлениях? Или у меня постановка задачи неверна?
Мнения?
Отредактировано 29.04.2015 14:00 Weiss . Предыдущая версия . Еще …
Отредактировано 29.04.2015 13:42 Weiss . Предыдущая версия .
Re: Получение событий от работающего WMPlayer
От: Vi2 Удмуртия http://www.adem.ru
Дата: 29.04.15 20:16
Оценка:
Здравствуйте, Weiss, Вы писали:

W>Мне нужно отлавливать события о работе уже запущенного wmplayer: начало/конец проигрывания видео. Т.е. требуется в моем процессе получить некий интерфейс объекта-плейера, работающего в адресном пространстве wmplayer.exe и прицепить к нему мой ивент синк. Не могу подключиться к нему легальным способом, т.е. инжекты на рассматриваю. Плагин к wmplayer — тоже не хотелось бы рассматривать. ОС: Windows 8 x64.


W>Вот что наисследовал. Когда wmplayer стартует он регистрирует свой IUnknown в ROT с таким DisplayName: "clsid:91778246-9BE4-4713-A651-E833B853CC30:!session:2".


Это "Class Moniker" вкупе с "Item Moniker".

W>Попробовал следующие способы подключения к нему:

W>1) Через GetActiveObject (из oleauto.h): в качестве CLSID задавал {91778246-9BE4-4713-A651-E833B853CC30}:

W>const wchar_t MediaPlayerClsidString [] = L"{91778246-9BE4-4713-A651-E833B853CC30}";

W>CLSIDFromString(MediaPlayerClsidString, &clsid);
W>HRESULT hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);

W>получаю hr 0x800401e3 Operation unavailable.


W>2) Через GetActiveObject: в качестве CLSID задавал {22D6F312-B0F6-11D0-94AB-0080C74C7E95}, который является clsid от coclass из библиотеки типа C:\Windows\System32\msdxm.tlb:

W>получаю hr 0x800401e3 Operation unavailable.

Это способ — способ через GetActiveObject — работает, если в РОТ зарегистрирован моникер с таким DisplayName: "!CLSID", т.е. неполный или несвязываемый "Item Moniker".

W>3) Через ROT, используя IRunningObjectTable::EnumRunning. Т.о. получаю moniker, который идентифицирую по DisplayName=="clsid:91778246-9BE4-4713-A651-E833B853CC30:!session:2". С помощью IRunningObjectTable::GetObject(rot, moniker) получаю IUnknown.

W>С этого IUnknown пытаюсь скверить интерфейсы: IWMPCore, IWMPPlayer, IWMPPlayer2 и т.д. — все что есть в хедере wmp.h. Кроме них еще и IDispatch, IDispatchEx, IInternalUnknown. На все получаю E_NOINTERFACE.
W>Также попробовал скверить интерфейсы с iid-ами {22D6F311-B0F6-11D0-94AB-0080C74C7E95} и {20D4F5E0-5475-11D2-9774-0000F80855E6}, которые посмотрел в той же библиотеке типа msdxm.tlb — это интерфейсы IMediaPlayer и IMediaPlayer2. Тоже получаю E_NOINTERFACE.

В принципе, правильный путь, но нужно точно знать, какой интерфейс требовать.

W>4) Прошелся под дизассемблером по QueryInterface того IUnknown, который получил через моникер из ROT. Обнаружил что можно скверить интерфейсы:

W>IMarshal, IMarshal2, IStdIdentity, IAgileObject, IProxyManager, IAggregatedIdentity, IRemUnknown. Меня заинтересовал из них IProxyManager, видимо через него можно было бы создать прокси к объекту, который живет в адресном пространстве процесса wmplayer.exe. IProxyManager действительно можно скверить, но как с ним работать дальше пока не нашел.

Что ты право, тебе нужно ходить не в своём процессе, а в процессе wmplayer — там будут совершенно другие интерфейсы.

W>Т.о. в данный момент у меня есть только последний метод, дающий призрачную надежду на успех предприятия.

W>И мне подумалось, может я двигаюсь не в тех направлениях? Или у меня постановка задачи неверна?
W>Мнения?
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re: Получение событий от работающего WMPlayer
От: Weiss  
Дата: 12.05.15 10:30
Оценка:
Уф, удалось таки раскопать эту тему. К сожалению через ROT взаимодействовать с wmplayer.exe не получится, т.к. он регает в ROT только два объекта для поддержки технологии AutoPlay (с CD, camera, etc) — вот они: wmp!CWMPAutoplay и wmp!CAutoPlayDeviceHandler. Они не предоставляют нужных мне интерфейсов для взаимодействия с wmplayer из другого процесса (исследовал на Windows 7 x86).

Решить задачу помог MS-овский Office Communicator. У него есть галка "Pause Windows Media Player for calls, video calls, and conferences", работает это так: когда происходит входящий звонок в communicator.exe, а в wmplayer проигрывается видео, то при поднятии трубки видео паузится. Дизассемблирование это механики позволило открыть следующее: communicator.exe использует IServiceProvider и IWMPRemoteMediaServices для взаимодействия с работающим wmplayer.exe — это все называется "Remoting the Windows Media Player Control". Хитрость была в создании IOleObject для взаимодействия с wmplayer.exe и задании ему IOleClientSite. Вот такой получился PoC (WmplayerStateMonitor.exe, обработка ошибок опущена):

int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr = CoInitialize(NULL);

    // создаем CLSCTX_INPROC_SERVER, но взаимодействуем с COM-сервером, работающим в отдельном процессе wmplayer.exe, т.е. мы не эмбедим контрол плеера:
    CComPtr<IOleObject> oleObject;
    hr = CoCreateInstance(CLSID_WindowsMediaPlayer, NULL, CLSCTX_INPROC_SERVER, __uuidof(IOleObject), reinterpret_cast<void**>(&oleObject));
    
    // задаем клиент-сайт, без этого взаимодействие не работает
    MyWmplayerRemoteHost host;
    hr = oleObject->SetClientSite(&host);
    
    // подключаем наш слушатель событий
    CComQIPtr<IConnectionPointContainer> connPointContainer = oleObject;
    CComPtr<IConnectionPoint> connPoint;
    hr = connPointContainer->FindConnectionPoint(__uuidof(IWMPEvents), &connPoint);
    
    MyWmplayerEventHandler handler;
    DWORD handlerCookie;
    hr = connPoint->Advise(&handler, &handlerCookie);

    for (;;) {
        // прокачиваем события, т.к. наше взаимодействие с wmplayer.exe происходит через STA:
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    ...


Класс MyWmplayerRemoteHost реализует интерфейсы IOleClientSite и IServiceProvider:

class MyWmplayerRemoteHost: public IOleClientSite, public IServiceProvider, public IWMPRemoteMediaServices
{
    // IUnknown:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) {
        // поддерживаем IID_IUnknown и IID_IServiceProvider
    }
    ...
    // IOleClientSite: все методы возвращают E_NOTIMPL
    virtual HRESULT STDMETHODCALLTYPE SaveObject(void) { return E_NOTIMPL; }
    ...
    // IServiceProvider: 
    virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID, REFIID riid, void **ppvObject) {
        // если riid==__uuidof(IWMPRemoteMediaServices), возвращаем IWMPRemoteMediaServices
    }
    // IWMPRemoteMediaServices:
    virtual HRESULT STDMETHODCALLTYPE GetServiceType(BSTR *pbstrType)
    {
        *pbstrType = SysAllocString(L"Remote");    // т.о. мы указываем что мы удаленный клиент без UI
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetApplicationName(BSTR *pbstrName)
    {
        *pbstrName = SysAllocString(L"Wmplayer State Monitor");    // название нашего приложения
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetScriptableObject(BSTR *pbstrName, IDispatch **ppDispatch)    { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE GetCustomUIMode(BSTR *pbstrFile)                { return E_NOTIMPL; }
};


и класс MyWmplayerEventHandler — это наш обработчик событий об изменениях в wmplayer.exe:

class MyWmplayerEventHandler : public IWMPEvents
{
    // IUnknown:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) {
        // поддерживаем IID_IUnknown и IID_IWMPEvents
    }
    ...
    // IWMPEvents: нас интересует только изменение playState, остальные обработчики - заглушки
    virtual void STDMETHODCALLTYPE OpenStateChange(long NewState) {}

    virtual void STDMETHODCALLTYPE PlayStateChange(long NewState) {
        std::wcout << "New play state: " << PrintPlayState(static_cast<WMPPlayState>(NewState)) << std::endl;
    }
    ...
};


Меня интересовало именно получение событий от работающего wmplayer.exe. Но описанным способом можно и управлять им:

CComQIPtr<IWMPPlayer> player = oleObject;

CComPtr<IWMPControls> controls;
hr = player->get_controls(&controls);

hr = controls->pause();


Особенности работы описанного механизма: если wmplayer.exe запущен до WmplayerStateMonitor.exe, то монитор при старте коннектится к нему. Если wmplayer завершится раньше, то WmplayerStateMonitor продолжает ужерживать COM-сервер вместе с процессом wmplayer.exe. Если WmplayerStateMonitor запущен раньше чем wmplayer, то при старте wmplayer реюзает COM-сервер в процессе wmplayer.exe, запущенный WmplayerStateMonitor. Чтобы пользователь не беспокоился о том, почему у него запущен wmplayer.exe, хотя он его не запускал, можно подключаться к wmplayer только когда он уже запущен и отключаться когда процесс остановлен. Но мониторинг старта/стопа процессов уже выходит за рамки рассмотренной темы исследования.
Отредактировано 12.05.2015 11:40 Weiss . Предыдущая версия . Еще …
Отредактировано 12.05.2015 11:38 Weiss . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.