所以。我正在 IE 中开发 BHO,我想添加如下浏览器操作:
在 Internet Explorer 中,它看起来像
我发现的唯一教程和文档是关于创建工具栏项的。没有人提到这个选项。我知道这是可能的,因为 crossrider 让你做这件事。我只是不知道怎么做。
我找不到任何关于如何在 BHO 中实现它的文档。任何指针都非常受欢迎。
我用 C# 标记了它,因为 C# 解决方案可能更简单,但 C++ 解决方案或任何其他可行的解决方案也非常受欢迎。
所以。我正在 IE 中开发 BHO,我想添加如下浏览器操作:
在 Internet Explorer 中,它看起来像
我发现的唯一教程和文档是关于创建工具栏项的。没有人提到这个选项。我知道这是可能的,因为 crossrider 让你做这件事。我只是不知道怎么做。
我找不到任何关于如何在 BHO 中实现它的文档。任何指针都非常受欢迎。
我用 C# 标记了它,因为 C# 解决方案可能更简单,但 C++ 解决方案或任何其他可行的解决方案也非常受欢迎。
编辑:https ://github.com/somanuell/SoBrowserAction
这是我正在进行的工作的屏幕截图。
我做的事情:
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( ®MapEntries[1], 0, sizeof(ATL::_ATL_REGMAP_ENTRY));
regMapEntries[0].szKey = L"MODULEPATH";
regMapEntries[0].szData = sm_szModulePath;
return ATL::_pAtlModule->UpdateRegistryFromResource(IDR_CSOBABHO, bRegister,
regMapEntries);
}
该代码是从 ATL 实现中借用的DECLARE_REGISTRY_RESOURCEID
(在我的情况下,它是 VS2010 附带的代码,请检查您的 ATL 版本并在必要时更新代码)。IDR_CSOBABHO
宏是REGISTRY
在 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 实例化,因此是一个新的注入器进程,钩子函数必须有办法知道当前线程是否已经被钩住(我不想只为每个选项卡打开添加一个钩子,那不干净)
线程本地存储是要走的路:
DllMain
,对于DLL_PROCESS_ATTACH
.HHOOK
为 TLS 数据,并使用它来了解线程是否已被挂钩DLL_THREAD_DETACH
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. 处理工具栏
这部分可能会大大改进:
请参阅我对该问题的其他答案,因为我目前无法使用代码格式编辑答案。
经过进一步审查,我意识到“收藏夹和操作工具栏”只是一个普通的旧通用控件工具栏(我之前认为它是某种自定义控件)。
我还不能调整我的代码,看看它会把我带到哪里,但这种方法应该与我在下面概述的略有不同。
据我所知,如果您希望工具栏按钮具有图像,则必须首先将该图像插入工具栏图像列表(TB_GETIMAGELIST
检索列表,TB_ADDBITMAP
添加图像)。
现在我们可以创建我们的实例并将其与or消息TBBUTTON
一起发送到我们的工具栏。TB_ADDBUTTONS
TB_INSERBUTTONS
那应该会在栏上获得按钮。但是如何将它连接到您的代码?
WM_COMMAND
单击按钮时,工具栏将生成一条消息(可能与结构的iCommand
成员TBBUTTON
在 的低字中wParam
)。所以我们只需要等待SetWindowsHookEx
并WH_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,这似乎与这种情况非常相关。
继续我的另一个答案。
函数的代码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
呼叫时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 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项目已更新。
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;
}
其余逻辑与我链接的文章相同。您子类化以拦截消息循环,并为您自己的按钮添加您自己的事件处理程序。
另一种方法是只创建一个按钮作为弹出窗口,将IE窗口设置为父窗口,找到“收藏夹和工具栏”的位置,并将按钮定位在它旁边。更容易,但当然不那么优雅。
编辑2:对不起,我刚刚看到我回应了奥利弗的一些回答。但是,如果您在 BHO 中执行我上面所写的操作,则该按钮将表现为任何 IE 自己的按钮,并且您可以完全控制它。