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...
Пока на собственное сообщение не было ответов, его можно удалить.