39

所以。我正在 IE 中开发 BHO,我想添加如下浏览器操作

在此处输入图像描述

在 Internet Explorer 中,它看起来像

在此处输入图像描述

我发现的唯一教程和文档是关于创建工具栏项的。没有人提到这个选项。我知道这是可能的,因为 crossrider 让你做这件事。我只是不知道怎么做。

我找不到任何关于如何在 BHO 中实现它的文档。任何指针都非常受欢迎。

我用 C# 标记了它,因为 C# 解决方案可能更简单,但 C++ 解决方案或任何其他可行的解决方案也非常受欢迎。

4

4 回答 4

21

编辑:https ://github.com/somanuell/SoBrowserAction


这是我正在进行的工作的屏幕截图。

IE9 中的新按钮

我做的事情:

1. 脱离保护模式

BHO 注册必须更新HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy密钥。请参阅了解和在保护模式下工作 Internet Explorer。

我选择过程方式是因为它被称为“最佳实践”并且更易于调试,但它RunDll32Policy也可以解决问题。

找到rgs包含 BHO 注册表设置的文件。它包含对 Registry Key 的更新'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 生成器。policy的3值确保代理进程将作为中等完整性进程启动。该%MODULEPATH%宏不是预定义的。

为什么要使用宏? 您可以避免在 RGS 文件中使用新代码,前提是您的 MSI 包含对注册表的更新。由于处理 MSI 可能会很痛苦,因此提供“完全自注册”包通常更容易。但是如果你不使用宏,你就不能让用户选择安装目录。使用宏允许使用正确的安装目录动态更新注册表。

如何使宏工作? 在 BHO 类的标题中找到DECLARE_REGISTRY_RESOURCEID宏并将其注释掉。在该标头中添加以下函数定义:

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在 RC 文件中添加 RGS的资源的资源 ID。

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

}

非常感谢 Mladen Janković。

如何启动 Broker 流程​​?

一个可能的地方是在SetSite实现中。它将被启动多次,但我们将在过程本身中处理它。稍后我们将看到代理进程可能会受益于接收托管 IEFrame 的 HWND 作为参数。这可以通过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 原则(保持简单,愚蠢)。那就是:一个短命的注射器。我确信这将导致在 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. 幸存注射:“更努力地勾住我”

在 IE 主进程中临时加载 Dll 足以在 Toolbar 中添加一个新按钮。但是能够监控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,获取 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. 处理工具栏

这部分可能会大大改进:

  1. 我刚刚创建了一个黑白位图,一切都很好,那就是:黑色像素是透明的。每次我尝试添加一些颜色和/或灰度级时,结果都很糟糕。对于那些“工具栏魔术中的位图”,我一点也不流利
  2. 位图的大小应取决于工具栏中已有的其他位图的当前大小。我只使用了两张位图(一张“普通”,一张“大”)
  3. 可以优化强制IE“重绘”工具栏新状态的部分,地址栏的宽度较小。它有效,有一个涉及整个 IE 主窗口的快速“重绘”阶段。

请参阅我对该问题的其他答案,因为我目前无法使用代码格式编辑答案。

于 2014-02-03T23:57:53.130 回答
11

经过进一步审查,我意识到“收藏夹和操作工具栏”只是一个普通的旧通用控件工具栏(我之前认为它是某种自定义控件)。

我还不能调整我的代码,看看它会把我带到哪里,但这种方法应该与我在下面概述的略有不同。

据我所知,如果您希望工具栏按钮具有图像,则必须首先将该图像插入工具栏图像列表(TB_GETIMAGELIST检索列表,TB_ADDBITMAP添加图像)。

现在我们可以创建我们的实例并将其与or消息TBBUTTON一起发送到我们的工具栏。TB_ADDBUTTONSTB_INSERBUTTONS

那应该会在栏上获得按钮。但是如何将它连接到您的代码?

WM_COMMAND单击按钮时,工具栏将生成一条消息(可能与结构的iCommand成员TBBUTTON在 的低字中wParam)。所以我们只需要等待SetWindowsHookExWH_CALLWNDPROC等待该消息...

当我开始工作时,实施即将到来;)


原始答案

正如我们之前在聊天中讨论的那样,我怀疑是否有官方支持的方式可以在 Internet Explorer UI 的该位置添加其他按钮(或任何与此相关的 UI 元素)。

但是,仍然存在在 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 调用,这导致从pinvoke.net一起复制了一个 1600 行的野兽,所以我将在这篇文章中省略它。

除了我无法让按钮很好地融入工具栏这一事实之外,一旦我设置了我自己的窗口消息处理程序,按钮就不再被绘制。

所以显然仍然需要做很多工作才能使这种方法起作用,但我想我还是会分享这个。

想到的另一个想法是忽略整个工具栏,只需将按钮放在它旁边。也许这更容易处理。

在网上疯狂搜索 Windows API 相关术语时,我还看到了 CodeProject 文章Add Your Control On Top Another Application,这似乎与这种情况非常相关。

于 2014-01-27T22:42:43.397 回答
2

继续我的另一个答案。

函数的代码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在钩子中捕获消息并检查命令 Id in wParam。实际生产代码可能更完整(验证 WM_COMMAND 确实来自工具栏)。

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

NotifyActiveBhoIE9功能将:

a) 在当前线程中查找 IEFrame
b) 为找到的 IEFrame 查找当前激活的选项卡
c) 查找托管该选项卡的线程

每个 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呼叫时SetSitepUnkSite != NULL
DestroyPrivateWindow被呼叫SetSitepUnkSite == 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 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 会在源 iexplore.exe 进程的新线程中创建一个新的 IEFrame 主窗口,以承载该选项卡。

要检测到这一点,一个简单的解决方案是监听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项目已更新。

于 2014-02-18T15:27:50.130 回答
1

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 自己的按钮,并且您可以完全控制它。

于 2014-02-03T18:23:22.963 回答