Мне нужно отлавливать события о работе уже запущенного 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}:
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 действительно можно скверить, но как с ним работать дальше пока не нашел.
Т.о. в данный момент у меня есть только последний метод, дающий призрачную надежду на успех предприятия.
И мне подумалось, может я двигаюсь не в тех направлениях? Или у меня постановка задачи неверна?
Мнения?
Здравствуйте, 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>Мнения?
Уф, удалось таки раскопать эту тему. К сожалению через 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_NOTIMPLvirtual 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"); // т.о. мы указываем что мы удаленный клиент без UIreturn 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. Но описанным способом можно и управлять им:
Особенности работы описанного механизма: если wmplayer.exe запущен до WmplayerStateMonitor.exe, то монитор при старте коннектится к нему. Если wmplayer завершится раньше, то WmplayerStateMonitor продолжает ужерживать COM-сервер вместе с процессом wmplayer.exe. Если WmplayerStateMonitor запущен раньше чем wmplayer, то при старте wmplayer реюзает COM-сервер в процессе wmplayer.exe, запущенный WmplayerStateMonitor. Чтобы пользователь не беспокоился о том, почему у него запущен wmplayer.exe, хотя он его не запускал, можно подключаться к wmplayer только когда он уже запущен и отключаться когда процесс остановлен. Но мониторинг старта/стопа процессов уже выходит за рамки рассмотренной темы исследования.