65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (87投票s)

2005年3月21日

CPOL

3分钟阅读

viewsIcon

202552

downloadIcon

2630

在 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")来查看导出了哪些函数。我们会看到 DllRegisterServerDllUnregisterServer。这显然是一个 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 后,它可能不会显示您想要的帮助集合。

如何设置默认帮助集合?

  1. 我相信您已经安装了最新的 Platform SDK。如果您没有,请从 Microsoft 下载。它是免费且有用的。您必须下载并安装它。
  2. SDK\bin 目录下运行 'Depends.exe'。
  3. 选择菜单 'Options'->'Configure External Function Help Collection'。
  4. 选择您想要的帮助集合。您甚至可以选择 "MSDN Online",此加载项将显示在线帮助。这意味着您可以获得帮助而无需安装 MSDN。

如何使用它?

单击 VC6 的菜单 "Tools"->"Customize"->"Add-ins and Macro Files"->"Browse"。然后选择 "VSNetHelp.dll",并单击 "Close"。

将插入符号移动到关键字上,按 F1。您就成功了!

© . All rights reserved.