VSS:Visual SourceSafe 的协议处理程序






4.33/5 (9投票s)
2001年6月28日
6分钟阅读

189222

1760
本文介绍如何将协议(示例中为“vss:”)挂接到自定义处理程序,以通过自动化打开 Visual SourceSafe 存储库中的文档。
引言
试想一下,您只需单击鼠标即可打开 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,那么会按顺序搜索 Folder3、Folder2、Folder1 和 C:\。
- 在注册表项 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