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

VSS:Visual SourceSafe 的协议处理程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (9投票s)

2001年6月28日

6分钟阅读

viewsIcon

189222

downloadIcon

1760

本文介绍如何将协议(示例中为“vss:”)挂接到自定义处理程序,以通过自动化打开 Visual SourceSafe 存储库中的文档。

Sample Image - vssprotocolhandler.gif

引言

试想一下,您只需单击鼠标即可打开 Visual SourceSafe 数据库中的文档。

无需再打开 SourceSafe 客户端,浏览到正确的项目树,然后从文档的上下文菜单中选择“查看最新版本”。在上面的截图中,您会看到 Word 文档中有三个指向项目文档的链接;这些链接带有新的 VSS: 协议前缀。单击 VSS: 链接将触发下面提供的处理程序,并通过 Shell 关联(.doc -> Word)打开指定的文档。换句话说:“VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc”将启动 Visual SourceSafe,在项目“$/Projects/VSS ProtHandler”中查找 TD-VSSProtocolHandler.doc,并尝试使用“shellexec”打开它。所有这些都是通过自动化完成的,您根本看不到 SourceSafe 客户端!

当然,您很熟悉 HTTP:、FTP: 和其他协议的使用。VSS: 协议几乎可以以相同的方式在(内网)网页、Windows 运行框、Word 文档或快捷方式中使用。所有协议请求都由 URL.dll(Internet Explorer 安装的一个 DLL)处理。URL.dll 会尝试查找协议的注册信息并调用已注册的处理程序。对于 HTTP:,很可能是 Internet Explorer;对于 VSS:,则是 VSSProtocolHandler.exe

请注意,用户必须安装 SourceSafe 客户端(才能使用其自动化对象),并且其计算机必须能够访问 VSS 数据库。

本文介绍两个原则:

  • 将“VSS:”协议注册并挂接到处理程序 VSSProtocolHandler.exe
  • 处理协议请求:让处理程序通过 Visual SourceSafe 数据库中的协议行,使用自动化打开指示的文档。

注册和挂接“VSS:”协议

实际上,“VSS:”协议与“MAILTO:”协议类似。研究这个协议让我知道了如何挂接协议!只需在注册表中进行几行修改,即可注册该协议。

[HKEY_CLASSES_ROOT]
  [vss]
    (Default) = "URL:VSS Protocol"
    URL Protocol = ""
    [DefaultIcon]
      (Default) = "VSSProtocolHandler.exe"
    [shell]
      [open]
        [command]
          (Default) = "c:\whatever\VSSProtocolHandler.exe "%1""

秘密在于字符串条目 URL Protocol,其值为一个空字符串。现在 URL.dll 就将“VSS”识别为一个协议。

处理协议请求

当挂接注册后,每当遇到“VSS:xxxx”时,URL.dll 都会调用 VSSProtocolHandler.exe。该参数必须由处理程序解析。

VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc

最终会产生以下命令行:

c:\whatever\VSSProtocolHandler.exe 
    "VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc"

请注意,协议前缀仍然存在于参数中。处理程序需要解析它并检查“VSS:”或“VSS://”前缀。参数的其余部分用于在 SourceSafe 数据库中查找文档。

源代码存档包含了一个完整的 Visual C++ 6 项目作为处理程序。它是纯粹的 Win32 和自动化(无 MFC)。请注意,一旦项目成功编译,项目设置会自动注册“VSS:”协议,就像 COM 组件项目一样。

该代码在 Windows 2000 上开发,并且仅在 Windows 2000 上进行了测试。

第一部分是解析参数。

//////////////////////////////////////////////////////
// WinMain
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

    char szArgument[1024] = {0};
    LPSTR pszVSSFile = NULL;

    // Use a copy of the argument
    StrNCpy(szArgument, lpCmdLine, 1023);

    // Remove the quote from the string...
    PathUnquoteSpaces(szArgument);

    char *pszTemp = szArgument;
    while (*pszTemp)
    {
        if (*pszTemp == '\\')
            *pszTemp = '/';
        pszTemp++;
    }

    if (strlen(szArgument) == 0)
    {
        ReportError(pszErrorNoArgumentSpecified);
        exit(1);
    }
    // Check if the vss protocol string is present
    else if (_strnicmp(szArgument, "vss://$/", 8) != 0)
    {
        // Check if the slashslash is missing... that's ok too
        if (_strnicmp(szArgument, "vss:$/", 6) != 0)
        {
            // Check for "/register".
            if (_strnicmp(szArgument, "/register", 9) == 0)
            {
                RegisterVSSProtocol(_strnicmp(szArgument, 
                    "/registerq", 10) == 0? true: false);
                exit(0);
            }
            else if (_strnicmp(szArgument, "/?", 2) == 0)
            {
                ShowHelp();
                exit(0);
            }
            else
            {
                ReportError(pszErrorProtocolInvalidArgument, 
                                        szArgument, pszAbout);
                exit(1);
            }
        }
        else
            pszVSSFile = szArgument + 4;    // Point past the protocol
    }
    else
        pszVSSFile = szArgument + 6;        // Point past the protocol


    if (strlen(pszVSSFile) == 0)
    {
        MessageBox(NULL, pszErrorNoVSSFileSpecified, 
                               pszAppName, MB_ICONERROR);
        return 1;
    }

接下来是启动 COM,实例化一个 Visual SourceSafe 对象实例,查找注册表中当前的 SourceSafe 数据库,并调用 ViewVSSFile() 来获取目标文件。

    // Init COM Libraries
    HRESULT hr;
    if (FAILED(hr=CoInitialize(NULL)))
    {
        ReportError(pszErrorFailedToInitCOM, hr);
        exit(1);
    }

    CLSID clsid;

    // Create SourceSafe Automation object.
    if(SUCCEEDED(hr=CLSIDFromProgID(L"SourceSafe", &clsid)))
    {
        IVSSDatabase *pVSSDBObject = NULL;
        if (SUCCEEDED(hr=CoCreateInstance(clsid, NULL, 
          CLSCTX_ALL, IID_IVSSDatabase, (void**)&pVSSDBObject)))
        {
            long    lErr = 0;
            HKEY    hKey;
            if ((lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                       _T("SOFTWARE\\Microsoft\\SourceSafe"),
                       0,
                       KEY_READ,
                       &hKey)) == ERROR_SUCCESS)
            {
                DWORD   dwType = REG_SZ;
                char    szVSSDatabasePath[512] = {0};
                DWORD   dwSize = 511;

                if ((lErr = RegQueryValueEx(hKey,
                            _T("Current Database"),
                            NULL,
                            &dwType,
                            (LPBYTE)szVSSDatabasePath,
                            &dwSize)) == ERROR_SUCCESS && dwType == REG_SZ)
                {

                    if (!ViewVSSFile(pVSSDBObject, 
                        szVSSDatabasePath, pszVSSFile))
                    {
                        // Opening the file from the
                        // current VSS database FAILED.
                        // Now try the other known databases.
                        // Exercise for the reader...


                    }
                }
                else
                    ReportError(pszErrorFindingVSSCurrentDatabase);

                RegCloseKey(hKey);
            }
            else
                ReportError(pszErrorOpeningVSSRegKey, lErr);

            pVSSDBObject->Release();
        }
        else
            ReportError(pszErrorCreatingVSSAutomationObject, hr);
    }
    else
        ReportError(pszErrorFindingVSSAutomationObject, hr);

    CoUninitialize();

ViewVSSFile() 会打开指定的数据库(srcsafe.ini),查找指定的项(文件),检查它是否是文件且未被标记为“已删除”,并对其执行“Get”命令,从而在 Temporary 目录中生成一个文件。该文件使用 ShellExecuteW(UNICODE 版本,因为参数(宽字符串)是 UNICODE 且我当时很懒)执行。

bool ViewVSSFile(IVSSDatabase *pVSSDBObject, 
       char *pszVSSDatabasePath, char *pszVSSFile)
{
  bool    bViewSucceeded = false;
  HRESULT hr = S_OK;
  // Note: pszDatabasePath is a BUFFER with sufficient space...

  PathRemoveBackslash(pszVSSDatabasePath);
  strcat(pszVSSDatabasePath, "\\srcsafe.ini");
  CComBSTR bsVSSIniFile(pszVSSDatabasePath);

  // Open the sourcesafe database under the current
  // logged on username and (cached) password
  // by entering empty strings.
  if(SUCCEEDED(hr=pVSSDBObject->Open(bsVSSIniFile, L"", L"")))
  {
    IVSSItem *pIVSSItem = NULL;
    // bsVSSFile("$/Courses/Cursus evaluatie.doc");
    CComBSTR bsVSSFile(pszVSSFile);

    //MessageBox(NULL, "DATABASE IS OPEN!", 
                           pszAppName, MB_ICONINFORMATION);
    if(SUCCEEDED(hr=pVSSDBObject->get_VSSItem(bsVSSFile, 
                                                0, &pIVSSItem)))
    {
      int nItemType = -1;
      // Test if the VSS item is a file and not a project.
      if (SUCCEEDED(hr = pIVSSItem->get_Type(&nItemType)))
      {
        if (nItemType == VSSITEM_FILE)
        {
          VARIANT_BOOL vbDeleted;
          if (SUCCEEDED(hr = 
            pIVSSItem->get_Deleted(&vbDeleted)))
          {
            if (vbDeleted == VARIANT_FALSE)
            {
              // Get the filename from
              // the specified SourceSafe filepath.
              char *pszTargetFileName= 
                  StrRChrA(pszVSSFile, NULL, '/');
              pszTargetFileName++;

              char szTempFile[_MAX_PATH] = {0};

              if (GetTempPath(_MAX_PATH, szTempFile) > 0)
              {
                StrCat(szTempFile, pszTargetFileName);

                CComBSTR bsLocal(szTempFile);

                if (SUCCEEDED(hr=pIVSSItem->Get(&bsLocal, 
                                         VSSFLAG_REPREPLACE))) 
                // VSSFLAG_USERROYES | VSSFLAG_REPREPLACE)))
                {
                  // bsLocal now contains a path
                  // to a file in the temp folder,
                  // which is the extracted file.
                  int hInst = (int)ShellExecuteW(NULL, L"open", 
                    (BSTR)bsLocal, NULL, NULL, SW_SHOWMAXIMIZED);
                  if (hInst <= 32)
                    ReportError(pszErrorOpeningTempDocument, 
                               hInst, szTempFile, pszVSSFile);
                  else
                    bViewSucceeded = true;
                }
                else
                  ReportError(pszErrorGettingVSSDocument, hr, 
                    pszVSSFile, pszVSSDatabasePath, szTempFile);
              }
              else
                ReportError(pszErrorCantDetermineTempDir);
            }
            else
              ReportError(pszErrorVSSFileMarkedDeleted, pszVSSFile);
          }
          else
            ReportError(pszErrorRetrievingVSSFileDeletedState, hr);
        }
        else
          ReportError(pszErrorVSSItemIsNotAFile, pszVSSFile);
      }
      else
        ReportError(pszErrorRetrievingVSSFileType, hr);

      pIVSSItem->Release();
    }
    else
      ReportError(pszErrorFindingVSSDocument, 
              hr, pszVSSFile, pszVSSDatabasePath);
  }
  else
    ReportError(pszErrorOpeningVSSDatabase, hr, pszVSSDatabasePath);

  return bViewSucceeded;
}

还有一些额外的代码用于处理协议的注册和错误消息,这些都相当直接。

演示可执行文件

演示存档包含项目的(发布版本)可执行文件。将其解压到某个地方,然后通过 Windows 运行框(开始 | 运行)使用参数“/register”运行它,例如:“C:\path\VSSProtocolHandler.exe /register”。该处理程序将注册“VSS:”协议并将自己设置为处理程序。现在您就可以使用该协议了。您可能希望在 VSS 打开命令(pVSSDBObject->Open(bsVSSIniFile, L"", L""))中硬编码您的 SourceSafe 用户名和密码,而不是使用空字符串。空字符串会使 SourceSafe 使用当前的 Windows 用户名和缓存的密码。调试之前,您还必须在项目的“设置”对话框的“调试”选项卡中设置一个参数行(“VSS://$/path_to_existing_file_in_VSS_database”)。

进一步

本项目是一个“概念验证”,从未打算成为一个防傻瓜、用户就绪的应用程序。我将不再对其进行任何开发,但我仍然有一些想法,您可能会发现它们很有趣去实现:

  • 更好的 SourceSafe 数据库“登录”代码。当前代码假设您使用 Windows 登录名登录 SourceSafe 数据库。它还假设您至少已经登录过该数据库一次;登录/密码由 Windows 缓存,在这种情况下会自动提供。
  • 如果用户使用多个 SourceSafe 数据库,并且在当前 SourceSafe 数据库中找不到文档,那么处理程序可以打开其他数据库,直到找到文件(或找不到)。SourceSafe 在注册表中存储所有使用的数据库,所以这应该不难找到。
    • ssapi.dll 所在的目录中搜索 srcsafe.ini
    • ssapi.dll 路径的每个目录中搜索 srcsafe.ini。换句话说,如果 ssapi.dll 位于 C:\Folder1\Folder2\Folder3\SSAPI.DLL,那么会按顺序搜索 Folder3Folder2Folder1C:\
    • 在注册表项 HKEY_LOCAL_MACHINE\Software\Microsoft\SourceSafe 中,通过名为“API Current Database”的值指定的目录中搜索 srcsafe.ini
    • 在注册表项 HKEY_LOCAL_MACHINE\Software\Microsoft\SourceSafe 中,通过名为“SCCProviderPath”的值指定的目录中搜索 srcsafe.ini

顺便说一下:MS Office 应用程序不将“VSS:”协议识别为超链接并自动加下划线。您必须使用“插入超链接”(CTRL+K)命令来输入 Visual SourceSafe 超链接。

一篇 MSDN 文章介绍了“Note:”协议,该协议的工作方式与“VSS”协议相同,但挂接到 notepad.exe。然而,这个示例不起作用,因为 notepad.exe 收到“Note://file.ext”作为参数,但无法处理“Note://”前缀,从而导致无法打开文档。

参考文献

  • Q167134 - HOWTO Open Visual SourceSafe to a Specific Project
  • Q169928 - HOWTO Open a SourceSafe Database with OLE Automation
  • Q175758 - HOWTO Trap Visual SourceSafe OLE Errors
  • Q176350 - HOWTO Open Visual SourceSafe to a Specific Database
  • Q201431 - HOWTO Write Automation for Visual SourceSafe 5_0-6_0
  • Microsoft Visual SourceSafe OLE Automation (Ken Felder, 1995 年 10 月)
  • ssauterr - SourceSafe Automation errors
  • ssauto - Visual C++ Header File for Visual SourceSafe
  • Visual SourceSafe 常见问题解答
  • vsstree - VSS OLE Sample Code Written in Visual C
© . All rights reserved.