Добавить кнопку действия браузера в Internet Explorer BHO

Так. Я работаю над BHO в IE и хочу добавить действие браузера, подобное этому :

введите здесь описание изображения

В Internet Explorer это будет выглядеть примерно так

введите здесь описание изображения

Единственные учебники и документы, которые я нашел, касались создания элементов панели инструментов. Ни один не упомянул этот вариант. Я знаю, что это возможно, потому что Crossrider позволяет вам делать именно это. Я просто не знаю как.

Я не могу найти документацию о том, как реализовать это в BHO. Любые указатели очень приветствуются.

Я пометил это с помощью C#, так как решение C#, вероятно, было бы проще, но решение C++ или любое другое решение, которое работает, также очень приветствуется.


person Benjamin Gruenbaum    schedule 26.01.2014    source источник
comment
Взгляните на github.com/trigger-corp/browser- extensions/tree/master/ie/ для вдохновения.   -  person Rob W    schedule 26.01.2014
comment
@RobW Я надеялся, что ты найдешь этот вопрос. Вы, наверное, один из немногих, кто знает, как это сделать. Если бы вы могли найти это в своем сердце (я искал эту кодовую базу, и я все еще не знаю), чтобы ответить на этот вопрос с кодом и / или автономным загрузчиком BHO, который просто добавляет кнопку и запускает предупреждение JavaScript при нажатии Я буду бесконечно благодарен, и я уверен, что это поможет многим людям. Я не против предложить изрядную награду, если это мотиватор, и я могу заплатить вам, если это стимул, но я сомневаюсь, что время будет стоить налоговой обработки и оформления документов для вас.   -  person Benjamin Gruenbaum    schedule 26.01.2014
comment
@BenjaminGruenbaum: Вы пытались узнать о вариантах поддержки у этого поставщика браузера по вашей проблеме?   -  person hakre    schedule 26.01.2014
comment
@hakre Я отправил электронное письмо команде Microsoft IEBlog - пока нет ответа, но прошло всего два часа, так что я еще не сдался.   -  person Benjamin Gruenbaum    schedule 26.01.2014
comment
Не могли бы вы разместить 2 изображения на stackoverflow.com, так как мой прокси в настоящее время блокирует imgur? Также, возможно, обновите свой вопрос информацией о целевых версиях (Windows и IE)   -  person manuell    schedule 30.01.2014
comment
Изображения @manuell, размещенные на Stack Overflow, размещаются через imgur. Нам нужен IE9+, который выглядит примерно одинаково :)   -  person Benjamin Gruenbaum    schedule 30.01.2014
comment
‹моя жизнь›LOL. Изображения, которые я вижу, например, на щелчке. Извините за фиктивный запрос. Я ненавижу этот прокси. Дома посмотрю.‹/mylife›   -  person manuell    schedule 30.01.2014
comment
Работаю над этим. 2 дня осталось может быть мало.   -  person manuell    schedule 03.02.2014
comment
@manuell на самом деле их три (если вы опубликуете сообщение до того, как оно закончится). Хотя, если вы решите это и не сделаете это - я обещаю вам еще одну награду.   -  person Benjamin Gruenbaum    schedule 03.02.2014
comment
Я постараюсь обновить свой ответ в ближайшие дни, стремясь получить полное руководство по реализации. Обратите внимание на слово try :-)   -  person manuell    schedule 04.02.2014
comment
Отличное начало! Держите меня в курсе прогресса!   -  person Benjamin Gruenbaum    schedule 05.02.2014
comment
Поскольку 2/3 ответов нацелены на C++, я думаю, что редактирование тегов для включения C++ или удаления C# имеет смысл, не так ли?   -  person Rob W    schedule 05.02.2014
comment
@RobW Возможно, я включил C#, потому что это «проще». Я хотел бы также включить С++, не отказываясь от С#, но я действительно не уверен, что отбросить. Я мог бы просто отказаться от С#. Что вы думаете?   -  person Benjamin Gruenbaum    schedule 05.02.2014
comment
winapi подразумевается под bho, поэтому я предлагаю winapi -> c++.   -  person Rob W    schedule 05.02.2014
comment
Я загружу свой полный код на этой неделе. Я не знаю, где и как. Может гитхаб, может кодплекс. Проблема в том, что я не хочу еще больше загромождать свой компьютер и не хочу устанавливать новое вредоносное ПО (например, git для Windows). Я очень доволен svn в командной строке, и у меня может не быть времени, чтобы изучить git cli через несколько дней.   -  person manuell    schedule 10.02.2014
comment
Я опаздываю, но доставлю. Просто добавил новую часть. Аккаунт на гитхабе создан.   -  person manuell    schedule 18.02.2014
comment
@manuell потрясающая работа! Так держать! :)   -  person Benjamin Gruenbaum    schedule 19.02.2014
comment
@manuell какие-нибудь обновления в репозитории GH?   -  person Benjamin Gruenbaum    schedule 02.03.2014
comment
@BenjaminGruenbaum Код готов. Будет разработано позже: работа с Tab Drag & Drop. Я только что потерял больше часа, пытаясь настроить способ загрузки моего проекта на github. Это настоящий беспорядок, и я очень расстроен прямо сейчас. Я попробую еще раз, может быть, завтра.   -  person manuell    schedule 02.03.2014


Ответы (4)


РЕДАКТИРОВАТЬ: https://github.com/somanuell/SoBrowserAction


Вот скриншот моей незавершенной работы.

Новая кнопка в IE9

Что я сделал:

<сильный>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( &regMapEntries[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, то есть к новому процессу инжектора, функция ловушки должна иметь способ узнать, перехвачен ли уже текущий поток (я не хочу просто добавлять ловушку для каждого открытия вкладки, это не чисто)

Локальное хранилище потоков - это путь:

  1. Выделите индекс TLS в DllMain для DLL_PROCESS_ATTACH.
  2. Сохраните новый HHOOK как данные TLS и используйте его, чтобы узнать, перехвачен ли уже поток.
  3. Отцепить при необходимости, когда DLL_THREAD_DETACH
  4. Освободите индекс 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. Обработка панели инструментов

Эта часть может быть значительно улучшена:

  1. Я только что создал черно-белое растровое изображение, и все было в порядке, то есть черные пиксели были прозрачными. Каждый раз, когда я пытался добавить несколько цветов и/или уровней серого, результаты были ужасными. Я совсем не разбираюсь в этих "растровых изображениях в магии панели инструментов"
  2. Размер растрового изображения должен зависеть от текущего размера других растровых изображений, уже находящихся на панели инструментов. Я просто использовал два растровых изображения (один «обычный» и один «большой»)
  3. Возможно, удастся оптимизировать часть, которая заставляет IE «перерисовывать» новое состояние панели инструментов с меньшей шириной адресной строки. Это работает, есть быстрая фаза «перерисовки», включающая все главное окно IE.

См. мой другой ответ на вопрос, так как в настоящее время я не могу редактировать ответ с рабочим форматом кода.

person manuell    schedule 03.02.2014
comment
Это руководство довольно точное. С помощью одной настройки его также можно сделать функциональным в IE11. Добавьте HKCR\{...GUID HERE...}\Implemented Categories\{59fb2056-d625-48d0-a944-1a85b5ab2640}\ в реестр, чтобы включить BHO даже в режиме расширенной защиты. - person Rob W; 04.02.2014
comment
Эй, пожалуйста, добавьте к этому ответу автономный пример. Большое спасибо за усилия :) На вашем месте я бы подумал о публикации на GitHub. - person Benjamin Gruenbaum; 05.02.2014
comment
@BenjaminGruenbaum Спасибо за награду. Я буду обновлять. Никогда не использовал GitHub до сих пор. Увидим позже. - person manuell; 05.02.2014
comment
@BenjaminGruenbaum Обновлено для первой части: выход из защищенного режима, дайте мне знать, что уровень детализации достаточно хорош. - person manuell; 05.02.2014
comment
@manuell, такой уровень детализации очень хорош. Также было бы очень неплохо иметь автономное рабочее решение (где-то размещенное, поэтому я предложил GitHub), доступное для игры и понимания. Обоснование этого вопроса в том, чтобы любой, кто хочет построить BHO с этой функциональностью, мог и понять, как это работает. Кстати, отличная работа! - person Benjamin Gruenbaum; 05.02.2014
comment
Спасибо. Просто дайте мне знать, если вам удалось создать рабочий проект. Я готов исправлять ошибки! - person manuell; 05.03.2014
comment
у меня мало проблем с вашим кодом на github, он компилируется нормально и в основном работает (большинство вызовов не возвращают ошибки, я добавил дополнительные проверки в код bho dll), но значок просто не появляется (вызов createwindow работает и возвращает не null ), я также проверил реестр и обнаружил, что программное обеспечение\микрософт\интернет эксплорер\низкие права\элевацияполитика\$GUID\... не создается regsvr, также добавление вручную ничего не меняет, я проверял это на нескольких установках Windows 7 с ie10 и ie11, есть ли у вас предложения, что может быть не так? - person sss123next; 13.06.2014
comment
В конце недели посмотрю. Быть в курсе. Попробуйте добавить функцию журнала с fopen/fwrite/fclose в AppData\LocalLow\log.txt - person manuell; 15.06.2014
comment
ОБНОВЛЕНИЕ: я обнаружил, что HookCallWndProcRect никогда не вызывается, но SetWindowHookEx в процессе брокера является успешным, поскольку я понимаю, что это может быть связано с неправильными данными реестра, особенно с программным обеспечением\микрософт\интернет эксплорер\низкие права\элевацияполитика\$GUID\ - person sss123next; 15.06.2014
comment
ОБНОВЛЕНИЕ 2: я реализовал ведение журнала в файл как в брокере, так и в самом bho, брокер выполняет всю работу, но HookCallWndProcRet не вызывается внутри bho, брокер успешно вызывает вызов GetProcAddress, а также вызов SetWindowHookEx, я плохо разбираюсь в программировании winapi, поэтому просто застрял здесь .. ., я могу предоставить патч для вашего кода github для журналов - person sss123next; 16.06.2014
comment
@ sss123next Я только что скачал ZIP-файл с github и создал его на новом ПК. Работает отлично. Windows Seven + IE 9. Дайте мне знать, если вы обнаружите проблему с IE10 IE11. - person manuell; 28.06.2014
comment
спасибо за ответ, в настоящее время мы решили разработать панель инструментов ie из-за нехватки времени для решения проблем с этим методом, я все еще не могу заставить это работать в ie11, но сейчас у меня нет на это больше времени, извините... - person sss123next; 01.07.2014

После дальнейшего просмотра я понял, что «панель избранного и действий» — это просто старая обычная панель инструментов общего управления (ранее я предполагал, что это какой-то пользовательский элемент управления).

Я еще не мог настроить свой код и посмотреть, куда он меня приведет, но подход должен немного отличаться от того, что я описал ниже.

Насколько я могу судить, если вы хотите, чтобы на вашей кнопке панели инструментов было изображение, вы должны сначала вставить это изображение в список изображений панели инструментов (TB_GETIMAGELIST для получения списка, TB_ADDBITMAP, чтобы добавить свое изображение).

Теперь мы можем создать наш экземпляр TBBUTTON и отправьте его на нашу панель инструментов с помощью TB_ADDBUTTONS. или сообщение TB_INSERBUTTONS.

Это должно получить кнопку на панели. Но как подключить его к вашему коду?

Панель инструментов будет генерировать сообщение WM_COMMAND, когда кнопка нажата (вероятно, с элементом iCommand структуры TBBUTTON в младшем слове wParam). Поэтому нам просто нужно SetWindowsHookEx с WH_CALLWNDPROC и ждите этого сообщения...

Реализация появится, когда я заставлю ее работать;)


Оригинальный ответ

Как мы обсуждали ранее в чате, у меня есть сомнения в том, что существует официально поддерживаемый способ добавления дополнительных кнопок (или любого элемента пользовательского интерфейса, если уж на то пошло) в этом месте в пользовательском интерфейсе Internet Explorer.

Тем не менее, все еще существует способ «грубой силы» простого создания нового дочернего окна внутри окна Internet Explorer.

До сих пор мне не удалось создать полный пример, главным образом потому, что мои попытки изменить размер панели инструментов, на которой расположены 3 кнопки действий, не увенчались успехом.

Во всяком случае, вот что я смог придумать до сих пор:

internal class MyButtonFactory
{
  public void Install()
  {

    IntPtr ieFrame = WinApi.FindWindowEx(IntPtr.Zero, IntPtr.Zero, "IEFrame", null);
    IntPtr navigationBar = WinApi.FindWindowEx(ieFrame, IntPtr.Zero, "WorkerW", "Navigation Bar");
    IntPtr reBar = WinApi.FindWindowEx(navigationBar, IntPtr.Zero, "ReBarWindow32", null);
    IntPtr controlBar = WinApi.FindWindowEx(reBar, IntPtr.Zero, "ControlBandClass", null);
    IntPtr toolsBar = WinApi.FindWindowEx(controlBar, IntPtr.Zero, "ToolbarWindow32", "Favorites and Tools Bar");

    IntPtr myButton = WinApi.CreateWindowEx(0, "Button", "MySpecialButtonName",
                                            WinApi.WindowStyles.WS_CHILD | WinApi.WindowStyles.WS_VISIBLE, 0, 0, 16,
                                            16,
                                            toolsBar, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

    if (IntPtr.Zero == myButton)
    {
      Debug.WriteLine(new Win32Exception(Marshal.GetLastWin32Error()).Message);
    }

    IntPtr buttonWndProc = Marshal.GetFunctionPointerForDelegate(new WinApi.WndProc(WndProc));
    WinApi.SetWindowLongPtr(new HandleRef(this, myButton), -4, buttonWndProc); // -4 = GWLP_WNDPROC
  }

  [AllowReversePInvokeCalls]
  public IntPtr WndProc(IntPtr hWnd, WinApi.WM msg, IntPtr wParam, IntPtr lParam)
  {
    switch (msg)
    {
      case WinApi.WM.LBUTTONUP:
        MessageBox.Show("Hello World");
        break;
      default:
        return WinApi.DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return IntPtr.Zero;
  }
}

Для этого требуется несколько вызовов Windows API, в результате чего зверь из 1600 строк скопирован вместе с pinvoke.net, поэтому я не буду приводить это в эта почта.

Помимо того факта, что я не смог заставить кнопку хорошо вписаться в панель инструментов, как только я настроил свой собственный обработчик оконных сообщений, кнопка больше не рисуется.

Таким образом, очевидно, что требуется еще много работы, чтобы заставить этот подход работать, но я все равно решил поделиться этим.

Другая идея, которая пришла в голову, заключалась в том, чтобы игнорировать всю панель инструментов и просто поместить кнопку рядом с ней. Возможно, с этим легче справиться.

Во время дикого поиска в Интернете терминов, связанных с Windows API, я также наткнулся на статью CodeProject Добавьте свой элемент управления поверх другого приложения", что может быть очень актуально в данном контексте.

person Der Hochstapler    schedule 27.01.2014
comment
Я создал репозиторий GitHub для этого подхода. К сожалению, в настоящее время все, что он делает, — это сбой IE: P github.com/oliversalzburg/ie-button - person Der Hochstapler; 30.01.2014

Продолжение моего другого ответа.

Код для функции AddBrowserActionForIE9.

void AddBrowserActionForIE9( HWND hWndIEFrame, HWND hWndToolBar ) {

   // do nothing if already done
   LRESULT lr = SendMessage( hWndToolBar, TB_BUTTONCOUNT, 0, 0 );
   UINT ButtonCount = (UINT)lr;
   for ( WPARAM index = 0; index < ButtonCount; ++index ) {
      TBBUTTON tbb;
      LRESULT lr = SendMessage( hWndToolBar, TB_GETBUTTON, index, reinterpret_cast<LPARAM>( &tbb ) );
      if ( lr == TRUE ) {
         if ( tbb.idCommand == 4242 ) return;
      }
   }
   HIMAGELIST hImgList = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETIMAGELIST, 0, 0 );
   HIMAGELIST hImgListHot = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETHOTIMAGELIST, 0, 0 );
   HIMAGELIST hImgListPressed = (HIMAGELIST)SendMessage( hWndToolBar, TB_GETPRESSEDIMAGELIST, 0, 0 );
   // load little or big bitmap
   int cx, cy;
   BOOL bRetVal = ImageList_GetIconSize( hImgList, &cx, &cy );

   HBITMAP hBitMap = LoadBitmap( CCSoBABHO::sm_hModule,
                                 MAKEINTRESOURCE( cx <= 17 ? IDB_BITMAP_SO_LITTLE : IDB_BITMAP_SO_BIG ) );
   int iImage = -1;
   if ( hImgList ) {
      iImage = ImageList_Add( hImgList, hBitMap, NULL );
   }
   if ( hImgListHot ) {
      ImageList_Add( hImgListHot, hBitMap, NULL );
   }
   if ( hImgListPressed ) {
      ImageList_Add( hImgListPressed, hBitMap, NULL );
   }
   TBBUTTON tbb;
   memset( &tbb, 0, sizeof( TBBUTTON ) );
   tbb.idCommand = 4242;
   tbb.iBitmap = iImage;
   tbb.fsState = TBSTATE_ENABLED;
   tbb.fsStyle = BTNS_BUTTON;
   lr = SendMessage( hWndToolBar, TB_INSERTBUTTON, 0, reinterpret_cast<LPARAM>( &tbb ) );
   if ( lr == TRUE ) {
      // force TB container to expand
      HWND hWndBand = GetParent( hWndToolBar );
      RECT rectBand;
      GetWindowRect( hWndBand, &rectBand );
      HWND hWndReBar = GetParent( hWndBand );
      POINT ptNew = { rectBand.left - cx, rectBand.top };
      ScreenToClient( hWndReBar, &ptNew );
      MoveWindow( hWndBand, ptNew.x, ptNew.y, rectBand.right - rectBand.left + cx,
                  rectBand.bottom - rectBand.top, FALSE );
      // force IE to resize address bar
      RECT rect;
      GetWindowRect( hWndIEFrame, &rect );
      SetWindowPos( hWndIEFrame, NULL, rect.left, rect.top, rect.right - rect.left + 1,
                    rect.bottom - rect.top, SWP_NOZORDER );
      SetWindowPos( hWndIEFrame, NULL, rect.left, rect.top, rect.right - rect.left,
                    rect.bottom - rect.top, SWP_NOZORDER );
   }
   if ( hBitMap ) DeleteObject( hBitMap );
   return;
}

<сильный>5. Маршрутизация клика

Самый простой способ прослушать щелчок — просто поймать WM_COMMAND сообщений в хуке и проверить идентификатор команды в wParam. Реальный производственный код может быть более полным (убедитесь, что WM_COMMAND действительно исходит из панели инструментов).

if ( pcwprets && ( pcwprets->message == WM_COMMAND ) ) {
   if ( LOWORD( pcwprets->wParam ) == 4242 ) {
      NotifyActiveBhoIE9( pcwprets->hwnd );
   }
}

Функция NotifyActiveBhoIE9 будет:

а) Найти IEFrame в текущем потоке
б) Найти текущую активированную вкладку для найденного IEFrame
в) Найти поток, в котором размещена вкладка

Каждый экземпляр BHO будет иметь невидимое окно, созданное с идентификатором потока в его тексте окна. Простой вызов FindWindow даст нам это окно, и BHO будет уведомлен сообщением.

Создание приватного окна:

// New Members in CCSoBABHO
static wchar_t * sm_pszPrivateClassName
static void RegisterPrivateClass( void );
static void UnregisterPrivateClass( void );
HWND m_hWndPrivate;
static LRESULT CALLBACK wpPrivate( HWND hWnd, UINT uiMsg,
                                   WPARAM wParam, LPARAM lParam );
static wchar_t * MakeWindowText( wchar_t * pszBuffer, size_t cbBuffer,
                                 DWORD dwTID );
bool CreatePrivateWindow( void );
bool DestroyPrivateWindow( void ) {
   if ( m_hWndPrivate ) DestroyWindow( m_hWndPrivate );
};

// implementation
wchar_t * CCSoBABHO::sm_pszPrivateClassName = L"SoBrowserActionClassName";

void CCSoBABHO::RegisterPrivateClass( void ) {
   WNDCLASS wndclass;
   memset( &wndclass, 0, sizeof( wndclass ) );
   wndclass.hInstance = sm_hInstance;
   wndclass.lpszClassName = sm_pszPrivateClassName;
   wndclass.lpfnWndProc = wpPrivate;
   RegisterClass( &wndclass );
   return;
}

void CCSoBABHO::UnregisterPrivateClass( void ) {
   UnregisterClass( sm_pszPrivateClassName, sm_hInstance );
   return;
}

wchar_t * CCSoBABHO::MakeWindowText( wchar_t * pszBuffer, size_t cbBuffer,
                                     DWORD dwTID ) {
   swprintf( pszBuffer, cbBuffer / sizeof( wchar_t ),
             L"TID_%.04I32x", dwTID );
   return pszBuffer;
}

bool CCSoBABHO::CreatePrivateWindow( void ) {
   wchar_t szWindowText[ 64 ];
   m_hWndPrivate = CreateWindow( sm_pszPrivateClassName,
                                 MakeWindowText( szWindowText,
                                                 sizeof( szWindowText ),
                                                 GetCurrentThreadId() ),
                                 0, 0, 0,0 ,0 ,NULL, 0, sm_hInstance, this );
   return m_hWndPrivate ? true : false;
}

Места вызова:
RegisterPrivateClass звонили в DllMain, когда PROCESS_ATTACH
UnregisterPrivateClass звонили в DllMain, когда PROCESS_DETACH
CreatePrivateWindow звонили в SetSite, когда pUnkSite != NULL
DestroyPrivateWindow звонили в SetSite, когда pUnkSite == NULL

Реализация NotifyActiveBhoIE9:

void CCSoBABHO::NotifyActiveBhoIE9( HWND hWndFromIEMainProcess ) {
   // Up to Main Frame
   HWND hWndChild = hWndFromIEMainProcess;
   while ( HWND hWndParent = GetParent( hWndChild ) ) {
      hWndChild = hWndParent;
   }
   HWND hwndIEFrame = hWndChild;

   // down to first "visible" FrameTab"
   struct ew {
      static BOOL CALLBACK ewp( HWND hWnd, LPARAM lParam ) {
      if ( ( GetWindowLongPtr( hWnd, GWL_STYLE ) & WS_VISIBLE ) == 0 ) return TRUE;
         wchar_t szClassName[ 32 ];
         if ( GetClassName( hWnd, szClassName, _countof( szClassName ) ) ) {
            if ( wcscmp( szClassName, L"Frame Tab" ) == 0 ) {
               *reinterpret_cast<HWND*>( lParam ) = hWnd;
               return FALSE;
            }
         }
         return TRUE;
      }
   };

   HWND hWndFirstVisibleTab = 0;
   EnumChildWindows( hwndIEFrame, ew::ewp,
                     reinterpret_cast<LPARAM>( &hWndFirstVisibleTab ) );
   if ( hWndFirstVisibleTab == 0 ) return;

   // down to first child, (in another process) 
   HWND hWndThreaded = GetWindow( hWndFirstVisibleTab, GW_CHILD );
   if ( hWndThreaded == 0 ) return;
   DWORD dwTID = GetWindowThreadProcessId( hWndThreaded, NULL );
   wchar_t szWindowText[ 64 ];
   HWND hWndPrivate = FindWindow( sm_pszPrivateClassName,
                                  MakeWindowText( szWindowText,
                                                  sizeof( szWindowText ), dwTID ) );
   if ( hWndPrivate ) SendMessage( hWndPrivate, WM_USER, 0, 0 );
}

Невидимое окно связано с БХО классическим: хранением указателя this в Windows Words.

LRESULT CALLBACK CCSoBABHO::wpPrivate( HWND hWnd, UINT uMsg,
                                       WPARAM wParam, LPARAM lParam ) {
   switch( uMsg ) {
      case WM_CREATE: {
         CREATESTRUCT * pCS = reinterpret_cast<CREATESTRUCT*>( lParam );
         SetWindowLongPtr( hWnd, GWLP_USERDATA,
                           reinterpret_cast<LONG_PTR>( pCS->lpCreateParams ) );
         return 0;
      }
      case WM_USER: {
         CCSoBABHO * pThis =
            reinterpret_cast<CCSoBABHO*>( GetWindowLongPtr( hWnd, GWLP_USERDATA ) );
         if ( pThis ) pThis->OnActionClick( wParam, lParam );
         break;
      }
      default: return DefWindowProc( hWnd, uMsg, wParam, lParam );
   }
   return 0;
}

<сильный>6. Обработка случая "TAB DRAG & DROP"

Когда вы «перетаскиваете» вкладку на рабочий стол, IE9 создает новое главное окно IEFrame в новом потоке исходного процесса iexplore.exe, на котором размещается вкладка.

Чтобы обнаружить это, можно просто прослушать событие DISPID_WINDOWSTATECHANGED: используйте метод IWebBrowser2::get_HWND для получения текущего главного окна IE. Если это окно не совпадает с ранее сохраненным, то вкладка была изменена. Затем просто запустите процесс брокера: если у нового родительского фрейма еще нет кнопки, она будет добавлена.

case DISPID_WINDOWSTATECHANGED: {
   LONG lFlags = pDispParams->rgvarg[ 1 ].lVal;
   LONG lValidFlagsMask = pDispParams->rgvarg[ 0 ].lVal;
   LONG lEnabledUserVisible = OLECMDIDF_WINDOWSTATE_USERVISIBLE |
                              OLECMDIDF_WINDOWSTATE_ENABLED;
   if ( ( lValidFlagsMask & lEnabledUserVisible ) == lEnabledUserVisible ) {
      SHANDLE_PTR hWndIEFrame = 0;
      HRESULT hr = m_spIWebBrowser2->get_HWND( &hWndIEFrame );
      if ( SUCCEEDED( hr ) && hWndIEFrame ) {
         if ( reinterpret_cast<HWND>( hWndIEFrame ) != m_hWndIEFrame ) {
            m_hWndIEFrame = reinterpret_cast<HWND>( hWndIEFrame );
            LaunchMediumProcess();
         }
      }
   }
   break;
}

Проект на гитхабе обновлен.

person manuell    schedule 18.02.2014

Инъекция Dll - это ответ, приятель.

Пожалуйста.

Редактировать:

Конечно. Кажется, вам не нужно делать DLL-инъекцию, у BHO есть доступ изнутри процесса IE. Так тогда намного проще.

По сути, вам нужно сначала найти окно. Итак, изменив функцию в соответствии с вашими потребностями, она будет выглядеть так:

BOOL FindFavoritesAndToolsBar(HWND mainWnd, HWND* addressBarWnd, HWND* cmdTargetWnd)
{
  mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "WorkerW" ), NULL );
  mainWnd = ::FindWindowEx( mainWnd, NULL, TEXT( "ReBarWindow32" ), NULL );

  *cmdTargetWnd = ::FindWindowEx
  mainWnd, NULL, TEXT( "ControlBandClass" ), NULL );

  if( *cmdTargetWnd  )
      *addressBarWnd = ::FindWindowEx(
      *cmdTargetWnd, NULL, TEXT( "ToolbarWindow32" ), L"Favorites and Tools Bar" );

  return cmdTargetWnd != NULL;
}

Я использовал Spy++, чтобы найти его.

Остальная логика такая же, как в статье, на которую я ссылался. Вы создаете подкласс для перехвата цикла сообщений и добавляете собственные обработчики событий для собственной кнопки.

Другой подход состоит в том, чтобы просто создать кнопку в виде всплывающего окна, установить окно IE в качестве родителя, найти положение «Панель избранного и инструментов» и расположить кнопку рядом с ней. Еще проще, но менее изящно конечно.

Редактировать 2: Извините, я только что увидел, что повторил часть ответа Оливера. Однако, если вы сделаете то, что я написал выше внутри BHO, кнопка будет вести себя как любая из собственных кнопок IE, и у вас будет полный контроль над ней.

person JonPall    schedule 03.02.2014
comment
Спасибо за попытку помочь. Пожалуйста, рассмотрите возможность добавления деталей. - person Benjamin Gruenbaum; 03.02.2014