将最新的 MSDN 与 VC6 集成并挂钩 COM 函数






4.95/5 (87投票s)
在 VC6 中集成最新的 MSDN 并 Hook COM 的函数。
引言
2001 年 10 月之后,在 VC6 中使用最新的 MSDN 按 F1 无法获得帮助。微软已经将帮助格式从 CHM 更改为文档浏览器。
- 是否可以在 VC6 中使用最新的 MSDN?
- 答案是肯定的!
详细说明
首先,我们必须知道当您按下 F1 时 VC6 如何调出帮助。如果您有一个调试器,例如 Soft-ICE,这很容易。
如果您没有安装 MSDN,在您按下 F1 后,VC6 将弹出一个消息框,提示您尚未安装 MSDN。在 MessageBox
上设置一个断点。按下 F1,调试器将弹出。查看堆栈。您将看到此函数是从 "C:\Program Files\Common Files\Microsoft Shared\VS98\vshelp.dll" 调用的。
然后我们使用 Dependency Walker(Visual Studio Tools 中的 "depends")来查看导出了哪些函数。我们会看到 DllRegisterServer
和 DllUnregisterServer
。这显然是一个 COM。
使用 VC6 创建一个简单的控制台项目。将以下行添加到您的 .cpp 文件中。
#import "C:\Program Files\Common Files\Microsoft Shared\VS98\vshelp.dll"
并构建您的项目。打开 Debug 目录,您会找到 "vshelp.tlh" 和 "vshelp.tli"。打开 "vshelp.tlh",您会看到
struct __declspec(uuid("854d7ac0-bc3d-11d0-b421-00a0c90f9dc4"))
IVsHelpSystem : IUnknown
{
//
// Wrapper methods for error-handling
//
HRESULT KeywordSearch (
LPWSTR pszKeyword,
long dwFlags,
long dwReserved );
HRESULT ALinkSearch (
LPWSTR pszALink,
long dwFlags,
long dwReserved );
...
当您按下 F1 时,会调用 KeywordSearch
函数。因此,如果我们替换此函数并调用最新的 MSDN 的帮助函数,它应该可以工作。
那么如何做到这一点呢?由于它是一个 COM 接口,因此很容易 hook 它。
基本上,COM 接口是一个带有虚表的 C++ 类。它有一个指向一个表的指针,该表包含所有虚函数的地址。并且这个表由所有实例共享。所以我们只需要更改表中的函数地址即可。并且这个接口看起来像
struct _IVsHelpSystemVtbl{ HRESULT (STDMETHODCALLTYPE *QueryInterface)(IUnknown * This, REFIID riid, void **ppvObject); ULONG (STDMETHODCALLTYPE *AddRef)(IUnknown * This); ULONG (STDMETHODCALLTYPE *Release)(IUnknown * This); HRESULT (STDMETHODCALLTYPE *KeywordSearch)(IUnknown * This, LPWSTR pszKeyword, long dwFlags, long dwReserved); }; struct _IVsHelpSystem { struct _IVsHelpSystemVtbl *lpVtbl; };
我们现在需要做的是创建此 COM 的一个实例。我们将获得该表的地址。现在我们需要知道如何调出最新 MSDN 的帮助。看起来它没有被文档化。它也是一个 COM。
将以下行添加到您的 .cpp 文件中。您将获得 COM 定义。
#import "C:\Program Files\Common Files\Microsoft Shared\MSEnv\vshelp.tlb"
在 "vshelp.tlh" 中,您可以找到函数
HRESULT DisplayTopicFromF1Keyword ( _bstr_t pszKeyword );
显然,这就是我们所需要的。因此,在函数 KeywordSearch
中,调用 DisplayTopicFromF1Keyword
,它将启动最新的 MSDN。
这是 hook 函数 KeywordSearch
的代码
HRESULT hr = theHelp.CreateInstance(__uuidof(VsHelp::DExploreAppObj)); if (SUCCEEDED(hr)) { HRESULT hr = vc6Help.CreateInstance( __uuidof(VsHelpServices::VsHelpServices)); if (SUCCEEDED(hr)) { iHelp = (_IVsHelpSystem *)vc6Help.GetInterfacePtr(); TRACE1("iHelp = %x\n", iHelp); TRACE1("lpVtbl = %x\n", iHelp->lpVtbl); TRACE1("KeywordSearch = %x\n", iHelp->lpVtbl->KeywordSearch); OldKeywordSearch = iHelp->lpVtbl->KeywordSearch; DWORD dwOldProtect; if (VirtualProtect(iHelp->lpVtbl, sizeof( _IVsHelpSystemVtbl), PAGE_READWRITE, &dwOldProtect)) iHelp->lpVtbl->KeywordSearch = MyKeywordSearch; } }
这是 MyKeywordSearch
的代码。在此函数中,我们将使用 Dependency Walker 指定的帮助集合作为默认帮助集合。如果指定了默认帮助集合,我们将检查它是否为 "MSDN Online"。如果是,我们将调用 ShellExecute
启动浏览器以显示帮助。否则,我们将从注册表中查找帮助集合的文件名。
HRESULT __stdcall MyKeywordSearch(IUnknown * This, LPWSTR pszKeyword, long dwFlags, long dwReserved) { ASSERT(theHelp != NULL); LONG lResult; HKEY hKey; lResult = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Dependency Walker\\External Help", 0, KEY_READ, &hKey); if (lResult == ERROR_SUCCESS) { CHAR szCollection[MAX_PATH]; DWORD cbCollection = MAX_PATH; lResult = RegQueryValueEx(hKey, "Collection", NULL, NULL, (LPBYTE)szCollection, &cbCollection); if (lResult == ERROR_SUCCESS) { TRACE1("use collection: %s\n", szCollection); if (stricmp(szCollection, "Online") == 0) { WCHAR szURL[1024]; DWORD dwURL = sizeof(szURL); lResult = RegQueryValueExW(hKey, L"URL", NULL, NULL, (LPBYTE)szURL, &dwURL); if (lResult == ERROR_SUCCESS) { WCHAR *p = wcsstr(szURL, L"%1"); if (p) { p[1] = L's'; WCHAR szLink[1024]; if (_snwprintf(szLink, sizeof(szLink)/sizeof(WCHAR), szURL, pszKeyword) > 0) { TRACE1("use collection: %S\n", szLink); ShellExecuteW(NULL, L"open", szLink, NULL, NULL, SW_SHOWNORMAL); RegCloseKey(hKey); return S_OK; } } } } /* end Online */ else { HKEY hHelp; lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\MSDN\\7.0\\Help\\0x0409", 0, KEY_READ|KEY_ENUMERATE_SUB_KEYS, &hHelp); if (lResult == ERROR_SUCCESS) { DWORD dwIndex = 0; CHAR szGuid[MAX_PATH]; while (RegEnumKey(hHelp, dwIndex++, szGuid, MAX_PATH) == ERROR_SUCCESS) { HKEY hGuid; if (RegOpenKeyEx(hHelp, szGuid, 0, KEY_READ, &hGuid) != ERROR_SUCCESS) continue; CHAR szCollection2[MAX_PATH]; DWORD cbCollection2 = MAX_PATH; lResult = RegQueryValueEx(hGuid, NULL, NULL, NULL, (LPBYTE)szCollection2, &cbCollection2); if (lResult == ERROR_SUCCESS && stricmp(szCollection, szCollection2) == 0) { cbCollection2 = MAX_PATH; lResult = RegQueryValueEx(hGuid, "Filename", NULL, NULL, (LPBYTE)szCollection2, &cbCollection2); if (lResult == ERROR_SUCCESS && strnicmp(szCollection2, "ms-help://", sizeof("ms-help://")-1) == 0) { TRACE1("use collection: %s\n", szCollection2); theHelp->SetCollection(szCollection2, ""); RegCloseKey(hGuid); break; } } RegCloseKey(hGuid); } /* end RegEnumKey(hHelp) */ RegCloseKey(hHelp); } } /* end not Online */ } RegCloseKey(hKey); } theHelp->SyncIndex(pszKeyword, 1); theHelp->DisplayTopicFromF1Keyword(pszKeyword); return S_OK; }
简单吧?
您必须记住两件事。首先,我们必须在全局空间中声明 vc6Help 并在程序退出时释放它。这是因为如果不再有实例,vshelp.dll 将被释放。如果是这样,那么我们修改的内容将随之消失。然后第二,我们必须调用 VirtualProtect
才能使虚表变为可写状态,否则您将无法修改虚表,因为它只读。
有时,您可能安装了几个帮助集合。按下 F1 后,它可能不会显示您想要的帮助集合。
如何设置默认帮助集合?
- 我相信您已经安装了最新的 Platform SDK。如果您没有,请从 Microsoft 下载。它是免费且有用的。您必须下载并安装它。
- 在 SDK\bin 目录下运行 'Depends.exe'。
- 选择菜单 'Options'->'Configure External Function Help Collection'。
- 选择您想要的帮助集合。您甚至可以选择 "MSDN Online",此加载项将显示在线帮助。这意味着您可以获得帮助而无需安装 MSDN。
如何使用它?
单击 VC6 的菜单 "Tools"->"Customize"->"Add-ins and Macro Files"->"Browse"。然后选择 "VSNetHelp.dll",并单击 "Close"。
将插入符号移动到关键字上,按 F1。您就成功了!