c# internet-explorer winapi bho browser-action

c# - Agregue el botón de acción del navegador en Internet Explorer BHO



internet-explorer winapi (4)

Asi que. Estoy trabajando en un BHO en IE y quiero agregar una acción del navegador como esta:

En Internet Explorer, se vería algo así como

Los únicos tutoriales y documentos que encontré estaban relacionados con la creación de elementos de la barra de herramientas. Ninguno mencionó esta opción. Sé que esto es posible porque Crossrider te permite hacer esto exactamente. Simplemente no sé cómo.

No encuentro ninguna documentación sobre cómo implementar esto en un BHO. Cualquier puntero es bienvenido.

Lo etiqueté con C # ya que una solución C # probablemente sería más simple, pero una solución C ++ o cualquier otra solución que funcione también es muy bienvenida.


Continuación de mi otra respuesta.

Código para la función 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. Enrutar el clic

La forma más sencilla de escuchar el clic es simplemente capturar los mensajes WM_COMMAND en el gancho y verificar el Id del comando en wParam . El código de producción real puede ser más completo (verifique que WM_COMMAND provenga de la barra de herramientas).

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

La función NotifyActiveBhoIE9 :

a) Encuentre el IEFrame en el hilo actual
b) Encuentre la pestaña actual activada para el IEFrame encontrado
c) Encuentra el hilo que aloja la pestaña

Cada instancia de BHO tendrá una ventana invisible creada con el identificador de subprocesos en su texto de ventana. Una simple llamada a FindWindow nos dará esa ventana y se notificará a BHO con un mensaje.

Creando la ventana privada:

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

Llamar a los sitios:
RegisterPrivateClass llamado en DllMain , cuando PROCESS_ATTACH
UnregisterPrivateClass llamado en DllMain , cuando PROCESS_DETACH
CreatePrivateWindow llamado en SetSite , cuando pUnkSite != NULL
DestroyPrivateWindow llamado en SetSite , cuando pUnkSite == NULL

La implementación de 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 ); }

La ventana invisible está conectada al BHO con una clásica: almacenando this puntero en palabras de 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. Procesamiento del caso "TAB DRAG & DROP"

Cuando "arrastra y suelta" una pestaña en el escritorio, IE9 crea una nueva ventana principal IEFrame, en un nuevo hilo en el proceso fuente iexplore.exe, que aloja la pestaña.

Para detectar eso, una solución simple es escuchar el evento DISPID_WINDOWSTATECHANGED : use el método IWebBrowser2::get_HWND para recuperar la ventana principal actual de IE. Si esa ventana no es la misma que la guardada anteriormente, entonces la pestaña se ha vuelto a crear. Luego, simplemente inicie el proceso del intermediario: si el nuevo marco principal aún no tiene el botón, se agregará.

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

El proyecto github ha sido actualizado.


Después de una revisión más profunda, me di cuenta de que la "barra de herramientas de favoritos y acción" es simplemente una antigua barra de herramientas de controles comunes (anteriormente asumí que era algún tipo de control personalizado).

Todavía no podía ajustar mi código y ver a dónde me lleva, pero el enfoque debería ser ligeramente diferente de lo que describo a continuación.

Por lo que puedo decir, si quiere que su botón de la barra de herramientas tenga una imagen, primero debe insertar esa imagen en la lista de imágenes de las barras de herramientas ( TB_GETIMAGELIST para recuperar la lista, TB_ADDBITMAP para agregar su imagen).

Ahora podemos crear nuestra instancia TBBUTTON y enviarla a nuestra barra de herramientas con el mensaje TB_ADDBUTTONS o TB_INSERBUTTONS .

Eso debería tener el botón en la barra. ¿Pero cómo conectarlo a tu código?

La barra de herramientas generará un mensaje WM_COMMAND cuando se WM_COMMAND en el botón (probablemente con el miembro iCommand de la estructura TBBUTTON en la palabra baja del wParam ). Así que solo necesitamos SetWindowsHookEx con WH_CALLWNDPROC y esperar ese mensaje ...

Se acerca la implementación cuando lo haga funcionar;)

Respuesta original

Como mencionamos anteriormente en el chat, tengo mis dudas de que haya una forma oficialmente compatible para agregar botones adicionales (o cualquier elemento de UI para ese asunto) en esa ubicación en la IU de Internet Explorer.

Sin embargo, todavía existe la forma de "fuerza bruta" de simplemente crear una nueva ventana secundaria dentro de la ventana de Internet Explorer.

Hasta ahora, no he podido crear un ejemplo completo, principalmente porque mis intentos de cambiar el tamaño de la barra de herramientas, en la que se encuentran los 3 botones de acción, han fallado.

De todos modos, esto es lo que podría llegar hasta ahora:

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

Esto requiere un par de llamadas API de Windows, lo que resultó en una bestia de 1600 líneas copiadas desde pinvoke.net , por lo que omitiré eso de esta publicación.

Además del hecho de que no pude conseguir que el botón encajara bien en la barra de herramientas, tan pronto como establezco mi propio manejador de mensajes de ventana, el botón ya no se dibuja.

Entonces, obviamente, todavía se necesita mucho trabajo para que este enfoque funcione, pero de todos modos pensé en compartir esto hasta el momento.

Otra idea que me vino a la mente fue ignorar toda la barra de herramientas y simplemente colocar el botón al lado. Tal vez es más fácil de manejar.

Mientras buscaba locamente en la web los términos relacionados con la API de Windows, también encontré el artículo de CodeProject. Agregue su control en la parte superior. Otra aplicación , que parece que podría ser muy relevante en este contexto.


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

Aquí hay una captura de pantalla de mi trabajo en progreso.

Las cosas que hice:

1. Escapar del modo protegido

El registro de BHO debe actualizar la clave HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Internet Explorer/Low Rights/ElevationPolicy . Consulte Descripción y trabajo en modo protegido Internet Explorer.

Elijo el camino del proceso porque se señala como "mejor práctica" y es más fácil de depurar, pero la RunDll32Policy puede hacer el truco.

Ubique el archivo rgs que contiene su configuración de registro BHO. Es el que contiene el upadte a la clave de registro ''Browser Helper Object'' . Agregue a ese archivo lo siguiente:

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

El GUID debe ser uno nuevo, no use el mío, use un Generador GUID. El valor de 3 para la política asegura que el proceso del intermediario se lanzará como un proceso de integridad medio. La macro %MODULEPATH% NO es predefinida.

¿Por qué usar una macro? Puede evitar ese nuevo código en su archivo RGS, siempre que su MSI contenga esa actualización en el registro. Como tratar con MSI puede ser doloroso, a menudo es más fácil proporcionar un paquete de "autoregistro completo". Pero si no usa una macro, entonces no puede permitir que el usuario elija el directorio de instalación. El uso de una macro permite actualizar dinámicamente el registro con el directorio de instalación correcto.

¿Cómo hacer que la macro funcione? Ubique la macro DECLARE_REGISTRY_RESOURCEID en el encabezado de su clase BHO y DECLARE_REGISTRY_RESOURCEID . Agregue la siguiente definición de función en ese encabezado:

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

Ese código se toma prestado de la implementación de ATL para DECLARE_REGISTRY_RESOURCEID (en mi caso, es el enviado con VS2010, verifique su versión de ATL y actualice el código si es necesario). La macro IDR_CSOBABHO es la ID de recurso del recurso IDR_CSOBABHO agrega el RGS en su archivo RC.

La variable sm_szModulePath debe contener la ruta de instalación del proceso de intermediario EXE. Elijo convertirlo en una variable miembro pública estática de mi clase BHO. Una forma simple de configurarlo es en la función DllMain . Cuando regsvr32 carga su Dll, se llama a DllMain y el registro se actualiza con la buena ruta.

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

Muchas gracias a Mladen Janković.

Cómo lanzar el proceso Broker?

Un posible lugar es en la implementación de SetSite . Se lanzará muchas veces, pero trataremos con eso en el proceso mismo. Más adelante veremos que el proceso de intermediario se puede beneficiar al recibir como argumento el HWND para el IEFrame de alojamiento. Esto se puede hacer con el método IWebBrowser2::get_HWND . Supongo que aquí tienes un miembro de 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. Inyectar los hilos IEFrame

Parece que esta puede ser la parte más compleja, porque hay muchas maneras de hacerlo, cada una con sus pros y sus contras.

El proceso del intermediario, el "inyector", puede ser de corta duración, con un argumento simple (un HWND o un TID), que tendrá que tratar con un IEFrame único, si no lo ha procesado una instancia anterior.

Más bien, el "inyector" puede ser un proceso de larga duración, que nunca acabará, que tendrá que mirar continuamente el Escritorio, procesando nuevos IEFrames a medida que aparecen. La unicidad del proceso puede estar garantizada por un Mutex con nombre.

Por el momento, intentaré seguir el principio de KISS (Keep It Simple, Stupid). Es decir: un inyector de corta duración. Sé con certeza que esto llevará a un manejo especial, en el BHO, para el caso de un Tab Drag y Drop''ed al escritorio, pero lo veré más adelante.

Seguir esa ruta implica una inyección de Dll que sobrevive al final del inyector, pero lo delegaré en el propio Dll.

Aquí está el código para el proceso del inyector. Instala un gancho WH_CALLWNDPROCRET para el hilo que aloja el IEFrame, usa SendMessage (con un mensaje específico registrado) para activar inmediatamente la inyección Dll, y luego elimina el gancho y lo termina. BHO Dll debe exportar una devolución de llamada HookCallWndProcRet llamada HookCallWndProcRet . Las rutas de error se omiten.

#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. Sobrevivir a la inyección: "engancharme más fuerte"

La carga temporal del Dll en el proceso principal de IE es suficiente para agregar un nuevo botón a la barra de herramientas. Pero poder monitorear el WM_COMMAND para ese nuevo botón requiere más: un Dll permanentemente cargado y un gancho aún en su lugar a pesar del final del proceso de enganche. Una solución simple es enganchar el hilo nuevamente, pasando el identificador de la instancia Dll.

Como cada apertura de pestañas conducirá a una nueva instanciación de BHO, por lo tanto un nuevo proceso de inyector, la función de gancho debe tener una forma de saber si el hilo actual ya está enganchado (no quiero simplemente agregar un gancho para cada apertura de pestaña, eso no está limpio)

Thread Local Storage es el camino a seguir:

  1. Asigne un índice TLS en DllMain , para DLL_PROCESS_ATTACH .
  2. Almacene el nuevo HHOOK como datos TLS, y use eso para saber si el hilo ya está enganchado
  3. Desenganche si es necesario, cuando DLL_THREAD_DETACH
  4. Libere el índice TLS en DLL_PROCESS_DETACH

Eso lleva al siguiente código:

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

Ahora tenemos una función de gancho casi completa:

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

El código para AddBrowserActionForIE9 se editará más tarde.

Para IE9, obtener la TB es bastante simple:

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. Procesamiento de la barra de herramientas

Esa parte puede mejorarse en gran medida:

  1. Acabo de crear un mapa de bits en blanco y negro, y todo estaba bien, es decir: los píxeles negros eran transparentes. Cada vez que intenté agregar algunos colores y / o niveles de gris, los resultados fueron horribles. No soy fluido, en absoluto, con esos "mapas de bits en la magia de la barra de herramientas"
  2. El tamaño del mapa de bits debería depender del tamaño actual de los otros mapas de bits que ya están en la barra de herramientas. Acabo de utilizar dos mapas de bits (uno "normal" y uno "grande")
  3. Es posible optimizar la parte que obliga a IE a "volver a dibujar" el nuevo estado de la barra de herramientas, con un ancho menor para la barra de direcciones. Funciona, hay una fase de "redibujado" rápida que involucra a toda la ventana principal de IE.

Vea mi otra respuesta a la pregunta, ya que actualmente no puedo editar la respuesta con el formato de código funcionando.


La inyección Dll es la respuesta, amigo.

Aquí tienes .

Editar:

Por supuesto. Parece que no tiene que hacer inyección DLL, BHO tiene acceso desde el interior del proceso de IE. Entonces, es mucho más fácil.

Básicamente, primero debe encontrar la ventana. Entonces, modificando la función para satisfacer sus necesidades, se verá así:

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

El resto de la lógica es la misma que el artículo que vinculé. Haga una subclase para interceptar el bucle de mensajes y agregue sus propios manejadores de eventos para su propio botón.

Otro enfoque es simplemente crear un botón como una ventana emergente, establecer la ventana de IE como principal, encontrar la posición de la "Barra de herramientas y favoritos", y colocar el botón adyacente a ella. Aún más fácil, pero menos elegante, por supuesto.

Editar 2: Lo siento, acabo de ver, me hice eco de algunas de las respuestas de Oliver. Sin embargo, si haces lo que escribí arriba dentro del BHO, el botón se comportará como cualquiera de los botones de IE y tendrás control total sobre él.