使用 Detours 为应用程序创建符号链接






4.68/5 (9投票s)
OR - 我如何欺骗 Visual Studio 将 .NCB、.SUO 和 .APS 文件存储在其他地方。

引言
Visual Studio 会生成三个主要文件:NCB(Intellisense 数据库)、SUO(当前计算机的用户界面布局)和 APS(部分编译的对话框和其他资源,供 IDE 提高性能)。NCB 和 APS 文件可能会变得很大。而且,如果有很多解决方案文件,它们很快就会累积到数 GB 的浪费空间。搜索和删除这些文件非常耗时。此外,据我所知,没有办法告诉 Visual Studio 将这些文件存储在其他地方。
我的目标是将所有文件放在一个统一、易于管理的位置。这个项目很快就超出了我的预期,因为我决定让它更通用:为 Windows 应用程序创建符号链接。在探索了一些疯狂的想法(并部分实现了一个)之后,我最终意识到我想要完成的事情超出了我的能力范围,并决定采用一个明显更简单的解决方案,我认为这个方案足够写一篇好文章了。
本文介绍了一种非常基础但有效的符号链接实现。
有些人可能会指出 Vista 在 NTFS 中支持符号链接。但这种支持并不完整,而且对我来说也没有用。上面截图中的 *C:\MOUNT* 是一个 NTFS 目录连接到格式化为 FAT 的 USB 闪存驱动器。根据我所了解的情况,Vista 的符号链接需要 NTFS 6 和 Vista/Longhorn 才能在整个系统中使用,这需要将 USB 闪存驱动器重新格式化为 NTFS,而这又是不推荐的。我这个项目的目标是减少对我的 USB 驱动器的写入次数(该驱动器存储了我 Subversion 存储库的源代码工作副本)。
背景
要理解这个项目是如何实现的,就需要对 Windows API 挂钩的工作原理有一个基本的了解。CodeProject 上有许多优秀的文章,深入探讨了 PE 文件格式、挂钩进程的各种方法以及代码注入。
本质上,API 挂钩会将普通的 Win32 API 调用替换为一个备用函数来处理该调用。如果您想知道杀毒软件如何扫描任何应用程序打开的文件,答案是它们挂钩了 `CreateFileA/W()`(或 *NTDLL.DLL* 中的等效函数,或者,取决于杀毒软件,是内核级别的挂钩)。
API 挂钩不适合胆小或初级程序员。您很容易就会把系统搞得一团糟。但话虽如此,Detours(一个微软研究院的项目)可以让您相对容易地开始接触 API 挂钩。免责声明:未经微软许可,Detours 不能用于商业用途。
工作原理
Detours 有两种不同的操作方法。基本上,您编写一个 DLL,导出被路由的函数,然后运行 `setdll.exe` 或 `withdll.exe`(两者都作为 Detours 包的一部分构建),将其应用到您想要加载 DLL 的目标 EXE 文件上。总的来说,一旦您克服了技术难度,Detours 是一个非常出色的软件包。前者会修改目标 EXE 的导入表,而后者会在目标进程暂停的情况下运行 EXE,将 DLL 注入目标进程,然后恢复进程。在我的情况下,我想修改 Visual Studio,这样当我双击一个 *.SLN*(解决方案)文件时,它会在 *.SLN* 文件加载之前自动加载 DLL,从而永久消除在没有 DLL 的情况下加载 VS 的风险。
我开始我的项目,将文件从 'src/simple' 目录复制到 'src/Symlink' 目录,调整了 Detours 的 makefile,并重命名了文件。我想在不阅读任何晦涩的文档的情况下开始。从源代码中可以看到,我想添加更多功能,但时间不够用了。
我选择的符号链接格式有点奇怪。我选择实现所谓的“慢符号链接”。维基百科对此有很好的描述,但基本上这些符号链接类似于古老的 *NIX 符号链接。但是,这是我唯一可行的解决方案,所以我不会抱怨。
如果您查看源代码 (*Symlink.cpp*),当调用 `CreateFile()` 时,如果同名的物理文件存在,则使用该文件。如果文件不存在,系统会查找一个同名的文件,但末尾附加 `.sym`。如果该文件存在,则读取该文件,并使用该文件名。但等等!还有更多!如果该文件不存在,系统会检查另一个符号链接……
仔细想想,完全有可能让系统陷入无限循环。为了应对这种情况,我选择了数字 100,并添加了一些代码来确保跟随的符号链接数量不超过 100。我认为这是一个相当合理的数字。
HANDLE WINAPI Routed_CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile)
{
int x;
LPSTR TempFile = NULL, TempFile2;
HANDLE FilePtr;
x = 0;
if ((dwCreationDisposition == CREATE_ALWAYS
|| dwCreationDisposition == CREATE_NEW
|| dwCreationDisposition == OPEN_ALWAYS)
&& Real_GetFileAttributesA(lpFileName) == (DWORD)-1)
{
FilePtr = INVALID_HANDLE_VALUE;
}
else
{
FilePtr = Real_CreateFileA(lpFileName, dwDesiredAccess,
dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes,
hTemplateFile);
}
while (FilePtr == INVALID_HANDLE_VALUE
&& x < 100
&& (TempFile2 =
CalculateSymlinkFileA(TempFile != NULL ? TempFile
: lpFileName)) != NULL)
{
if (TempFile != NULL) delete [] TempFile;
TempFile = TempFile2;
if ((dwCreationDisposition == CREATE_ALWAYS
|| dwCreationDisposition == CREATE_NEW
|| dwCreationDisposition == OPEN_ALWAYS)
&& Real_GetFileAttributesA(TempFile) == (DWORD)-1)
{
FilePtr = INVALID_HANDLE_VALUE;
}
else
{
FilePtr = Real_CreateFileA(TempFile, dwDesiredAccess,
dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes,
hTemplateFile);
}
x++;
}
if (FilePtr == INVALID_HANDLE_VALUE
&& (dwCreationDisposition == CREATE_ALWAYS
|| dwCreationDisposition == CREATE_NEW
|| dwCreationDisposition == OPEN_ALWAYS))
{
FilePtr = Real_CreateFileA((TempFile != NULL ? TempFile
: lpFileName), dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes, hTemplateFile);
}
if (TempFile != NULL) delete [] TempFile;
return FilePtr;
}
请注意,任何时候挂钩 API,当它被调用时,都会比原始 API 慢。我挂钩的是最常用的 API 之一(`CreateFile`),但仅限于 Visual Studio。尽管如此,我确实注意到在挂钩接口激活时存在轻微的性能损失。
将 Symlink.dll 与 Visual Studio 一起使用
对于想要尝试的懒人来说,下载包含二进制文件的 zip 文件。将所有文件解压到您的 Visual Studio IDE 的主 EXE 目录。我使用的是 Visual Studio .NET 2003,所以我要找的可执行文件是 `devenv.exe`。
文件解压后,启动命令提示符,并在刚刚解压文件的目录中执行 `'setdll /d:Symlink.dll devenv.exe'`。对于非常谨慎的用户,请在运行命令前备份 `devenv.exe`。您应该会看到类似以下的输出:

接下来,找到任何解决方案文件,删除 `.ncb`/`.suo`/`.aps` 文件,然后创建名称相同但末尾带有 `.sym` 的文件。在 Notepad 中打开每个 `.sym` 文件,并输入符号链接指向的绝对路径和文件名。
接下来,确保路径存在(文件不必存在,但路径必须存在)。
最后,打开解决方案文件,看看 Visual Studio 完全不知道某些文件现在位于其他位置。
关注点
对于认识我的人或者查看我网站的人来说,我没有遵循我在我的书 *《安全 C++ 设计原则》* 中制定的指南,原因有很多。主要问题是时间——我花了好几天时间研究其他解决方案,等到我决定使用 Detours 路线时,我真的需要着手处理其他事情了。将我的基础库引入 Detours DLL 可能会制造混乱。另一个原因是,我在同一个 DLL 中挂钩了 ANSI 和 Unicode 函数,而我的基础库不支持这一点。最后,我真的不想引入我的基础库并承担通常随之而来的性能损失。我真的很怀念使用我的基础库。
不过,作为一项附加好处,这个工具具有意想不到的副作用,即可以在不同计算机之间维护打开的文件。这允许我在我的主计算机和笔记本电脑之间来回移动我的 USB 闪存驱动器,并且仍然可以打开相同的文件。在我拥有这个工具之前,当我移动时,VS 某种程度上检测到设置不同,没有恢复我的打开文件(灰色模糊背景)。现在每台计算机都有自己的一组 *.SUO* 文件。
我将所有源代码都放在一个 USB 闪存驱动器上作为 Subversion 工作副本(通过 TortoiseSVN)。我有一套工具,可以解决 TSVN/VS 的一些奇怪问题(*C:\MOUNT* 目录连接技巧是为了让 VS 2003 正确编译项目)。如果您想复制我的工作,首先,请准备一个吞吐量非常高的 USB 闪存驱动器——特别是如果您使用版本控制软件(我有一个 1GB DataTraveler Elite——读取速度约为 21MB/秒,写入速度约为 14MB/秒——这个项目之所以存在,部分原因就是如此:驱动器的大约 1/5 是 *.NCB* 和 *.APS* 文件,占用了大量不必要的空间)。此外,在尝试此操作之前,请务必确认可以修改所有解决方案中的项目文件。您应该在硬盘驱动器上编译和链接。然后从那里进行。
很棒的想法
我很想扩展文章的这一部分,包括您如何使用这个工具来简化您的个人生活。
已知限制
`SetFileAttributesA/W()` 不起作用,因为它没有被挂钩。这会体现在 *.SUO* 文件上。通常,*.SUO* 文件会被设置为“隐藏”属性。当 VS 尝试为文件设置隐藏属性时,它找不到该文件,因此会默默地放弃。
源代码中的许多其他函数被记录为已实现但实际上并未实现。
历史
- 2007 年 5 月 27 日 - 首次发布
我坚信的一件事是,不要让 CodeProject 的文章“腐烂”。太多的作者写了很好的文章,但评论中列出的 bug 没有在发布的源代码中得到修复,而且文章在被忽视 3-4 年后就“腐烂”了。换句话说,如果我没有修复需要修复的问题,请给我发邮件。