РЕДАКТИРОВАТЬ: https://github.com/somanuell/SoBrowserAction
Вот скриншот моей незавершенной работы.
Что я сделал:
<сильный>1. Выход из защищенного режима
Регистрация BHO должна обновить ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy
. см. Общие сведения и работа в защищенном режиме Internet Explorer.
Я выбираю процессный способ, потому что он отмечен как «лучшая практика» и его легче отлаживать, но RunDll32Policy
тоже может помочь.
Найдите файл rgs
, содержащий параметры реестра BHO. Это тот, который содержит обновление для ключа реестра 'Browser Helper Object'
. Добавьте в этот файл следующее:
HKLM {
NoRemove SOFTWARE {
NoRemove Microsoft {
NoRemove 'Internet Explorer' {
NoRemove 'Low Rights' {
NoRemove ElevationPolicy {
ForceRemove '{AE6E5BFE-B965-41B5-AC70-D7069E555C76}' {
val AppName = s 'SoBrowserActionInjector.exe'
val AppPath = s '%MODULEPATH%'
val Policy = d '3'
}
}
}
}
}
}
}
GUID должен быть новым, не используйте мой, используйте генератор GUID. Значение 3
для политики гарантирует, что процесс брокера будет запущен как процесс средней степени целостности. Макрос %MODULEPATH%
НЕ является предопределенным.
Зачем использовать макрос? Вы можете избежать этого нового кода в своем RGS-файле при условии, что ваш MSI содержит это обновление реестра. Поскольку работа с MSI может быть болезненной, зачастую проще предоставить пакет «полной саморегистрации». Но если вы не используете макрос, вы не можете позволить пользователю выбирать каталог установки. Использование макроса позволяет динамически обновлять реестр, указывая правильный каталог установки.
Как заставить макрос работать? Найдите макрос DECLARE_REGISTRY_RESOURCEID
в заголовке вашего класса BHO и закомментируйте его. Добавьте следующее определение функции в этот заголовок:
static HRESULT WINAPI UpdateRegistry( BOOL bRegister ) throw() {
ATL::_ATL_REGMAP_ENTRY regMapEntries[2];
memset( ®MapEntries[1], 0, sizeof(ATL::_ATL_REGMAP_ENTRY));
regMapEntries[0].szKey = L"MODULEPATH";
regMapEntries[0].szData = sm_szModulePath;
return ATL::_pAtlModule->UpdateRegistryFromResource(IDR_CSOBABHO, bRegister,
regMapEntries);
}
Этот код заимствован из реализации ATL для DECLARE_REGISTRY_RESOURCEID
(в моем случае это тот, который поставляется с VS2010, проверьте свою версию ATL и при необходимости обновите код). Макрос IDR_CSOBABHO
— это идентификатор ресурса REGISTRY
, добавляющего RGS в ваш файл RC.
Переменная sm_szModulePath
должна содержать путь установки EXE-файла процесса посредника. Я решил сделать его общедоступной статической переменной-членом моего класса BHO. Один простой способ настроить это в функции DllMain
. Когда regsvr32
загружает вашу Dll, вызывается DllMain
, и реестр обновляется правильным путем.
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
if ( dwReason == DLL_PROCESS_ATTACH ) {
DWORD dwCopied = GetModuleFileName( hInstance,
CCSoBABHO::sm_szModulePath,
sizeof( CCSoBABHO::sm_szModulePath ) /
sizeof( wchar_t ) );
if ( dwCopied ) {
wchar_t * pLastAntiSlash = wcsrchr( CCSoBABHO::sm_szModulePath, L'\\' );
if ( pLastAntiSlash ) *( pLastAntiSlash ) = 0;
}
}
return _AtlModule.DllMain(dwReason, lpReserved);
}
Большое спасибо Младену Янковичу.
Как запустить брокерский процесс?
Одним из возможных мест является реализация SetSite
. Он будет запускаться много раз, но мы разберемся с этим в самом процессе. Позже мы увидим, что процесс брокера может выиграть от получения в качестве аргумента HWND для размещения IEFrame. Это можно сделать с помощью метода IWebBrowser2::get_HWND
. Я полагаю, что у вас уже есть IWebBrowser2*
член.
STDMETHODIMP CCSoBABHO::SetSite( IUnknown* pUnkSite ) {
if ( pUnkSite ) {
HRESULT hr = pUnkSite->QueryInterface( IID_IWebBrowser2, (void**)&m_spIWebBrowser2 );
if ( SUCCEEDED( hr ) && m_spIWebBrowser2 ) {
SHANDLE_PTR hWndIEFrame;
hr = m_spIWebBrowser2->get_HWND( &hWndIEFrame );
if ( SUCCEEDED( hr ) ) {
wchar_t szExeName[] = L"SoBrowserActionInjector.exe";
wchar_t szFullPath[ MAX_PATH ];
wcscpy_s( szFullPath, sm_szModulePath );
wcscat_s( szFullPath, L"\\" );
wcscat_s( szFullPath, szExeName );
STARTUPINFO si;
memset( &si, 0, sizeof( si ) );
si.cb = sizeof( si );
PROCESS_INFORMATION pi;
wchar_t szCommandLine[ 64 ];
swprintf_s( szCommandLine, L"%.48s %d", szExeName, (int)hWndIEFrame );
BOOL bWin32Success = CreateProcess( szFullPath, szCommandLine, NULL,
NULL, FALSE, 0, NULL, NULL, &si, &pi );
if ( bWin32Success ) {
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
}
}
}
[...]
<сильный>2. Внедрение потоков IEFrame
Похоже, что это может быть самой сложной частью, потому что есть много способов сделать это, каждый со своими плюсами и минусами.
Брокерский процесс, «инжектор», может быть недолговечным, с одним простым аргументом (HWND или TID), который должен иметь дело с уникальным IEFrame, если он еще не обработан предыдущим экземпляром.
Скорее, «инжектор» может быть долгоживущим, в конечном итоге бесконечным процессом, который должен будет постоянно наблюдать за рабочим столом, обрабатывая новые IEFrame по мере их появления. Уникальность процесса может быть гарантирована именованным мьютексом.
В настоящее время я попытаюсь придерживаться принципа KISS (Keep It Simple, Stupid). То есть: недолговечный инжектор. Я точно знаю, что это приведет к специальной обработке в BHO случая перетаскивания вкладки на рабочий стол, но я увижу это позже.
Этот путь предполагает внедрение Dll, которое сохраняется после окончания инжектора, но я делегирую это самой Dll.
Вот код процесса инжектора. Он устанавливает хук WH_CALLWNDPROCRET
для потока, в котором размещается IEFrame, использует SendMessage
(с определенным зарегистрированным сообщением) для немедленного запуска внедрения Dll, а затем удаляет хук и завершает работу. BHO Dll должна экспортировать обратный вызов CallWndRetProc
с именем HookCallWndProcRet
. Пути ошибок опущены.
#include <Windows.h>
#include <stdlib.h>
typedef LRESULT (CALLBACK *PHOOKCALLWNDPROCRET)( int nCode, WPARAM wParam, LPARAM lParam );
PHOOKCALLWNDPROCRET g_pHookCallWndProcRet;
HMODULE g_hDll;
UINT g_uiRegisteredMsg;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, char * pszCommandLine, int ) {
HWND hWndIEFrame = (HWND)atoi( pszCommandLine );
wchar_t szFullPath[ MAX_PATH ];
DWORD dwCopied = GetModuleFileName( NULL, szFullPath,
sizeof( szFullPath ) / sizeof( wchar_t ) );
if ( dwCopied ) {
wchar_t * pLastAntiSlash = wcsrchr( szFullPath, L'\\' );
if ( pLastAntiSlash ) *( pLastAntiSlash + 1 ) = 0;
wcscat_s( szFullPath, L"SoBrowserActionBHO.dll" );
g_hDll = LoadLibrary( szFullPath );
if ( g_hDll ) {
g_pHookCallWndProcRet = (PHOOKCALLWNDPROCRET)GetProcAddress( g_hDll,
"HookCallWndProcRet" );
if ( g_pHookCallWndProcRet ) {
g_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
if ( g_uiRegisteredMsg ) {
DWORD dwTID = GetWindowThreadProcessId( hWndIEFrame, NULL );
if ( dwTID ) {
HHOOK hHook = SetWindowsHookEx( WH_CALLWNDPROCRET,
g_pHookCallWndProcRet,
g_hDll, dwTID );
if ( hHook ) {
SendMessage( hWndIEFrame, g_uiRegisteredMsg, 0, 0 );
UnhookWindowsHookEx( hHook );
}
}
}
}
}
}
if ( g_hDll ) FreeLibrary( g_hDll );
return 0;
}
<сильный>3. Инъекция выживания: "зацепи меня сильнее"
Временной загрузки Dll в основном процессе IE достаточно, чтобы добавить новую кнопку на панель инструментов. Но чтобы отслеживать WM_COMMAND
для этой новой кнопки, требуется нечто большее: постоянно загруженная Dll и хук, остающийся на месте, несмотря на завершение процесса хука. Простое решение — снова перехватить поток, передав дескриптор экземпляра Dll.
Поскольку каждое открытие вкладки приведет к созданию нового экземпляра BHO, то есть к новому процессу инжектора, функция ловушки должна иметь способ узнать, перехвачен ли уже текущий поток (я не хочу просто добавлять ловушку для каждого открытия вкладки, это не чисто)
Локальное хранилище потоков - это путь:
- Выделите индекс TLS в
DllMain
для DLL_PROCESS_ATTACH
.
- Сохраните новый
HHOOK
как данные TLS и используйте его, чтобы узнать, перехвачен ли уже поток.
- Отцепить при необходимости, когда
DLL_THREAD_DETACH
- Освободите индекс TLS в
DLL_PROCESS_DETACH
Это приводит к следующему коду:
// DllMain
// -------
if ( dwReason == DLL_PROCESS_ATTACH ) {
CCSoBABHO::sm_dwTlsIndex = TlsAlloc();
[...]
} else if ( dwReason == DLL_THREAD_DETACH ) {
CCSoBABHO::UnhookIfHooked();
} else if ( dwReason == DLL_PROCESS_DETACH ) {
CCSoBABHO::UnhookIfHooked();
if ( CCSoBABHO::sm_dwTlsIndex != TLS_OUT_OF_INDEXES )
TlsFree( CCSoBABHO::sm_dwTlsIndex );
}
// BHO Class Static functions
// --------------------------
void CCSoBABHO::HookIfNotHooked( void ) {
if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
if ( hHook ) return;
hHook = SetWindowsHookEx( WH_CALLWNDPROCRET, HookCallWndProcRet,
sm_hModule, GetCurrentThreadId() );
TlsSetValue( sm_dwTlsIndex, hHook );
return;
}
void CCSoBABHO::UnhookIfHooked( void ) {
if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
if ( UnhookWindowsHookEx( hHook ) ) TlsSetValue( sm_dwTlsIndex, 0 );
}
Теперь у нас есть почти законченная функция ловушки:
LRESULT CALLBACK CCSoBABHO::HookCallWndProcRet( int nCode, WPARAM wParam,
LPARAM lParam ) {
if ( nCode == HC_ACTION ) {
if ( sm_uiRegisteredMsg == 0 )
sm_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
if ( sm_uiRegisteredMsg ) {
PCWPRETSTRUCT pcwprets = reinterpret_cast<PCWPRETSTRUCT>( lParam );
if ( pcwprets && ( pcwprets->message == sm_uiRegisteredMsg ) ) {
HookIfNotHooked();
HWND hWndTB = FindThreadToolBarForIE9( pcwprets->hwnd );
if ( hWndTB ) {
AddBrowserActionForIE9( pcwprets->hwnd, hWndTB );
}
}
}
}
return CallNextHookEx( 0, nCode, wParam, lParam);
}
Код для AddBrowserActionForIE9
будет отредактирован позже.
Для IE9 получить ТБ довольно просто:
HWND FindThreadToolBarForIE9( HWND hWndIEFrame ) {
HWND hWndWorker = FindWindowEx( hWndIEFrame, NULL,
L"WorkerW", NULL );
if ( hWndWorker ) {
HWND hWndRebar= FindWindowEx( hWndWorker, NULL,
L"ReBarWindow32", NULL );
if ( hWndRebar ) {
HWND hWndBand = FindWindowEx( hWndRebar, NULL,
L"ControlBandClass", NULL );
if ( hWndBand ) {
return FindWindowEx( hWndBand, NULL,
L"ToolbarWindow32", NULL );
}
}
}
return 0;
}
<сильный>4. Обработка панели инструментов
Эта часть может быть значительно улучшена:
- Я только что создал черно-белое растровое изображение, и все было в порядке, то есть черные пиксели были прозрачными. Каждый раз, когда я пытался добавить несколько цветов и/или уровней серого, результаты были ужасными. Я совсем не разбираюсь в этих "растровых изображениях в магии панели инструментов"
- Размер растрового изображения должен зависеть от текущего размера других растровых изображений, уже находящихся на панели инструментов. Я просто использовал два растровых изображения (один «обычный» и один «большой»)
- Возможно, удастся оптимизировать часть, которая заставляет IE «перерисовывать» новое состояние панели инструментов с меньшей шириной адресной строки. Это работает, есть быстрая фаза «перерисовки», включающая все главное окно IE.
См. мой другой ответ на вопрос, так как в настоящее время я не могу редактировать ответ с рабочим форматом кода.
person
manuell
schedule
03.02.2014