Подтвердить что ты не робот

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

Итак. Я работаю над BHO в IE, и я хочу добавить действие браузера следующим образом:

enter image description here

В Internet explorer он выглядел бы как

enter image description here

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

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

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

4b9b3361

Ответ 1

EDIT: https://github.com/somanuell/SoBrowserAction


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

New button in IE9

То, что я сделал:

1. Выход из защищенного режима

Регистрация BHO должна обновить ключ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy. См. Понимание и работа в защищенном режиме Internet Explorer.

Я выбираю процесс, потому что он отмечен как "лучшая практика" и его легче отлаживать, но RunDll32Policy тоже может сделать трюк.

Найдите файл rgs, содержащий ваши настройки реестра BHO. Это тот, который содержит upadte для ключа реестра '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, если он еще не обработан предыдущим экземпляром.

Скорее, "инжектор" может быть долговечным, в конечном счете, бесконечным процессом, который должен будет постоянно наблюдать за Рабочем столом, обрабатывая новые IEFrames по мере их появления. Единственность процесса может быть гарантирована именованным мьютексом.

В настоящее время я попытаюсь перейти к принципу KISS (Keep It Simple, Stupid). То есть: короткоживущий инжектор. Я точно знаю, что это приведет к специальной обработке в BHO для случая Tab Drag And Drop'ed на рабочий стол, но я увижу это позже.

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

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

  • Выделите индекс 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 получение TB довольно просто:

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.

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

Ответ 2

После дальнейшего обзора я понял, что панель "Избранное и действие" представляет собой просто старую панель инструментов общих элементов управления (ранее я предполагал, что это какой-то пользовательский элемент управления).

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

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

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

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

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

Выполнение, когда я его заработаю;)


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

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

Однако есть еще способ "грубой силы" просто создать новое дочернее окно внутри окна Internet Explorer.

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

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

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 Добавить свой контроль поверх другого приложения, который, похоже, может быть очень в этом контексте.

Ответ 3

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

Код для функции 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. Маршрутизация Click

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

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

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

a) Найти IEFrame в текущей теме
b) Найдите текущую активированную вкладку для найденного IEFrame
c) Найдите нить с вкладкой

Каждый экземпляр BHO будет иметь невидимое окно, созданное с помощью идентификатора потока в нем Text Window. Простой вызов 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 );
}

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

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;
}

Проект github обновлен.

Ответ 4

Вставка Dll - это ответ, приятель.

Здесь вы идете.

Edit:

Конечно. Кажется, вам не нужно делать 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;
}

I used Spy++ to find it.

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

Другой подход состоит в том, чтобы просто создать кнопку в виде всплывающего окна, установить окно IE в качестве родителя, найти позицию "Благоприятности и панель инструментов" и поместить кнопку рядом с ней. Еще проще, но менее элегантно, конечно.

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