所以。我正在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注册表设置的文件。它是包含注册表项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%
宏不是预定义的宏。
为什么要使用宏?如果您的MSI包含对注册表的更新,则可以避免在RGS文件中使用该新代码。由于与MSI打交道可能很痛苦,因此提供“完全自注册”程序包通常会更容易。但是,如果您不使用宏,则无法允许用户选择安装目录。使用宏允许使用正确的安装目录动态更新注册表。
如何使宏工作?DECLARE_REGISTRY_RESOURCEID
在您的BHO类的标题中找到该宏并将其注释掉。在该标头中添加以下函数定义:
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
宏是的资源IDREGISTRY
资源添加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);
}
非常感谢Mladen Jankovic。
如何处理经纪人流程?
在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原则(保持简单,愚蠢)。也就是说:喷油器寿命短。我可以肯定的是,对于将Tab拖放到桌面的情况,这将在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足以将新按钮添加到工具栏。但是,要监视WM_COMMAND
新按钮的功能还需要更多:永久加载的Dll和尽管挂接过程已结束的钩子仍在原位。一个简单的解决方案是再次传递线程,并传递Dll实例句柄。
由于每个凸舌开口都将导致新的BHO实例化,从而导致新的注入器过程,因此钩子函数必须有一种方法来知道当前线程是否已经被钩住(我不想为每个凸舌开口添加一个钩子,那不干净)
线程本地存储是必经之路:
DllMain
中为分配TLS索引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.处理工具栏
该部分可能会大大改善:
请参阅我对该问题的其他答案,因为我当前无法使用代码格式工作来编辑答案。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句