Ben*_*aum 38 c# winapi internet-explorer bho browser-action
所以.我在IE中正在开发BHO,我想添加一个像这样的浏览器动作:

在Internet Explorer中它看起来像

我发现的唯一教程和文档是创建工具栏项目.没有提到这个选项.我知道这是可能的,因为crossrider让你做到这一点.我只是不知道如何.
我找不到任何关于如何在BHO中实现这一点的文档.任何指针都非常受欢迎.
我用C#标记这个,因为C#解决方案可能更简单但是C++解决方案,或任何其他有效的解决方案也非常受欢迎.
man*_*ell 20
编辑: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'
}
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
该代码是从ATL实现中借用的DECLARE_REGISTRY_RESOURCEID(在我的情况下,它是VS2010附带的代码,检查您的ATL版本并在必要时更新代码).该IDR_CSOBABHO宏是的资源ID 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);
}
Run Code Online (Sandbox Code Playgroud)
非常感谢MladenJanković.
如何实现经纪人流程?
一个可能的地方是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 );
}
}
}
[...]
Run Code Online (Sandbox Code Playgroud)
2.注入IEFrame线程
看起来这可能是最复杂的部分,因为有很多方法可以做到,每一种都有利有弊.
代理进程,即"注入器",可能是一个短暂的进程,有一个简单的参数(HWND或TID),如果尚未由先前的实例处理,则必须处理唯一的IEFrame.
相反,"注入器"可能是一个长期存在的,最终永无止境的过程,它必须不断地观看桌面,在它们出现时处理新的IE框架.命名互斥锁可以保证该过程的单一性.
暂时,我会尝试使用KISS原则(Keep It Simple,Stupid).那就是:短暂的喷射器.我知道这肯定会在BHO中导致特殊处理,因为Tab拖放到桌面的情况下,但我会在稍后看到.
去那条路线涉及一个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;
}
Run Code Online (Sandbox Code Playgroud)
3.幸存注射:"更加困难"
在主IE过程中临时加载Dll足以向工具栏添加新按钮.但是能够监视WM_COMMAND新按钮需要更多:尽管挂钩过程结束,仍然存在永久加载的Dll和挂钩.一个简单的解决方案是再次挂钩线程,传递Dll实例句柄.
由于每个标签打开将导致新的BHO实例化,因此一个新的注入过程,钩子函数必须有办法知道当前线程是否已经被钩住(我不想只为每个标签开口添加一个钩子,那不干净)
线程本地存储是要走的路:
DllMain,对DLL_PROCESS_ATTACH.HHOOK为TLS数据,并使用它来了解线程是否已被挂钩DLL_THREAD_DETACHDLL_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 );
}
Run Code Online (Sandbox Code Playgroud)
我们现在有一个几乎完整的钩子功能:
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);
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
4.处理工具栏
那部分可能会大大改善:
请参阅我对该问题的其他答案,因为我目前无法使用代码格式编辑答案.
Der*_*ler 11
经过进一步的审查,我意识到"收藏夹和动作工具栏"只是一个普通的旧的通用控件工具栏(我之前认为它是某种自定义控件).
我还没有能够调整我的代码,看看它在哪里,但方法应该与我下面概述的略有不同.
据我所知,如果您希望工具栏按钮有图像,则必须先将该图像插入工具栏图像列表(TB_GETIMAGELIST以检索列表,TB_ADDBITMAP添加图像).
现在我们可以创建我们的TBBUTTON实例并使用TB_ADDBUTTONS或TB_INSERBUTTONS消息将其发送到我们的工具栏.
那应该得到吧台上的按钮.但是如何将它与你的代码挂钩?
WM_COMMAND单击按钮时,工具栏将生成一条消息(可能是低位字中结构的iCommand成员).所以我们只需要使用并等待消息...TBBUTTONwParamSetWindowsHookExWH_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;
}
}
Run Code Online (Sandbox Code Playgroud)
这需要几个Windows API调用,这导致从pinvoke.net复制1600行的野兽,所以我将从这篇文章中省略.
除了我无法让按钮很好地适应工具栏之外,只要我自己设置了自己的窗口消息处理程序,就不再绘制按钮了.
所以显然仍然需要做很多工作才能使这种方法有效,但我认为无论如何我都会分享这个.
想到的另一个想法是忽略整个工具栏,只需将按钮放在旁边即可.也许这更容易处理.
虽然在Web上搜索与Windows API相关的术语,但我也遇到了CodeProject文章Add Your Control On Top Another Application,在这种情况下,它似乎非常相关.