Introduction
There are three main files that Visual Studio generates: NCB (Intellisense Database), SUO (User interface layout for the current computer), and APS (partially compiled dialogs and other resources for the IDE to have improved performance). NCB and APS files can become quite large. And with hundreds of solution files, they can quickly add up to many gigabytes of wasted space. Searching for and deleting the files is time-consuming. Also, to my knowledge, there is no way of telling Visual Studio to store these files elsewhere.
Putting all of the files into a single, manageable location is what I set out to do. The project quickly exploded out of proportion as I decided to make it more generic: Symbolic links for Windows applications. After exploring some really crazy ideas (and partially implementing one of them), I eventually realized I was going way beyond the scope of what I wanted to accomplish and decided upon a significantly simpler solution, which I decided was good enough for an article.
This article covers a very rudimentary but effective implementation of symbolic links.
Some people may point out that Vista has symbolic link support in NTFS. BUT that support isn't complete and wouldn't work for me anyway. C:\MOUNT in the screenshot above is a NTFS junction to a USB thumbdrive formatted as FAT. From what I've read, Vista's symbolic links requires NTFS 6 and Vista/Longhorn to be used across the board, which would require reformatting the USB thumbdrive as NTFS, which, in turn, is not recommended. My goal with this project was to reduce the number of writes to my USB drive (this drive stores my working copy of source code from my Subversion repository).
Background
To understand how this project is even possible, it requires a basic understanding of how Windows API hooking works. There are a number of excellent articles here on CodeProject that go in-depth into the PE file format, various ways to hook into a process, and code injection.
At its core, API hooking takes your average ordinary Win32 API call and replaces it with an alternate function that handles the call. If you've ever wondered how anti-virus scanners can scan files opened by any application, the answer is they've hooked CreateFileA/W()
(or the NTDLL.DLL equivalents - or, depending on the scanner, a kernel-level hook).
API hooking is NOT for the faint of heart or beginning programmer. You can royally mess up a system quite easily. But with that said, Detours (a Microsoft Research project) makes it pretty easy to at least get one's feet wet with API hooking. Disclaimer: Detours can't be used commercially without a license from Microsoft.
How It Works
Detours has two different methods of operating. Essentially, you write a DLL, export the functions that get routed, and run either setdll.exe or withdll.exe (both are built as part of the Detours package) on the target EXE which you want the DLL loaded in. Overall, Detours is a pretty slick package once you get past the geek factor. The former modifies the import tables of a target EXE while the latter runs the EXE suspended, injects the DLL into the target process, and then resumes the process. For my case, I wanted to modify Visual Studio so that when I double-clicked a .SLN (Solution) file, it would automagically load the DLL before the .SLN file got loaded and permanently eliminate the risk of loading VS without the DLL.
I started my project by copying the files from the 'src/simple' directory to a 'src/Symlink' directory, tweaked the makefiles for Detours, and renamed the files. I wanted to start off without having to read any of the obtuse documentation. You'll see from the source code that I wanted to add a lot more functionality, but I just ran out of time.
The format for the symbolic links I chose is kind of silly. I chose to implement what are known as "slow symbolic links". Wikipedia has a pretty good description, but basically these symbolic links are similar to ancient *NIX symbolic links. However, this is the only viable solution for me so I'm not going to complain.
If you look at the source code (Symlink.cpp), whenever CreateFile()
is called, if a physical file of the same name exists, that file is used. If the file doesn't exist, then the system looks for a file of the same name but with a .sym tacked onto the end. If that file exists, it is read in and that filename is used. But wait! There's more! If that file doesn't exist, the system checks for another symbolic link...
If you think about it, it is quite possible to throw the system into an infinite loop. To counter this, I picked the number 100 and added some code to make sure that the number of symbolic links followed never exceeded 100. I figure that is a pretty reasonable number.
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;
}
Note that any time an API is hooked, whenever it gets called, it will be slower than the original API. I'm hooking one of the most used APIs (CreateFile
) but only for Visual Studio. Still, I do note a slight performance hit when this hooked interface is active.
Using Symlink.dll with Visual Studio
For the lazy person who just wants to try it out, download the zip file that contains the binaries. Extract all the files to your Visual Studio IDE's main EXE directory. I'm using Visual Studio .NET 2003, so the executable I look for is devenv.exe.
Once the files are extracted, start a command prompt and execute 'setdll /d:Symlink.dll devenv.exe' from within the directory you just extracted the files. For the ultra careful, back up devenv.exe before running the command. You should get output that looks roughly like:
Next, locate any solution file and delete the .ncb/.suo/.aps files and create files with identical names but with an extra .sym on the end. Open each .sym file up in Notepad and type in an absolute path and filename to where the symbolic link points at.
Next, make sure the path exists (the file doesn't have to exist but the path does).
Finally, open the solution file and watch as Visual Studio has no clue that certain files are now located elsewhere.
Points of Interest
For those who know me or are looking at my website, I did not follow the guidelines I laid out in my book Safe C++ Design Principles for a number of different reasons. The main issue was time - I spent several days looking at other solutions and by the time I decided to go with the Detours route, I really needed to get working on other things. Bringing in my base library into a Detours DLL would probably have created a mess. Another reason was because I was hooking both ANSI and Unicode functions in the same DLL, which my base library doesn't support. Finally, I really didn't want to drag in my base library and take the performance hit that generally follows. I really missed using my base library.
As a bonus, however, this tool has the unexpected side effect of maintaining open files between computers. This allows me to move my USB thumbdrive between my main computer and my laptop and back again and still have the same files open up. Before I had this tool, when I moved around, VS somehow detected a difference in the setup and didn't restore my open files (grey blah background). Now each computer has its own set of .SUO files.
I run all my source code off of a single USB thumbdrive as a Subversion working copy (via TortoiseSVN). I have a set of tools that I've built that works around some of TSVN's/VS' weirdness (the C:\MOUNT junction trick was a hack to get VS 2003 to compile projects properly). If you want to duplicate my effort, first get yourself a USB thumbdrive that has really good throughput rates - especially if you use version control software (I have a 1GB DataTraveler Elite - something like 21MB/sec read, 14MB/sec write speeds - part of why this project exists: About 1/5 of the drive is .NCB and .APS files consuming lots of unnecessary space). Also, be sure it is okay to alter project files in every solution file before attempting this. You want to compile and link on the hard drive. Then go from there.
Cool Ideas
I would love to expand this section of the article to include how YOU are using this tool to simplify your personal life.
Known Limitations
SetFileAttributesA/W()
doesn't work because it isn't hooked. This manifests itself with .SUO files. Normally a .SUO file gets the "hidden" attribute. When VS goes to give the file the hidden attribute, it can't find the file, so it silently gives up.
A number of other functions in the source code are documented as being implemented but aren't.
History
- May 27, 2007 - Initial release
One of the things I believe in is not letting CodeProject articles rot. Too many authors have really good articles but the comments have bugs listed that are not being fixed in the published source and the articles "rot" after 3-4 years of ignoring them. In other words, if I'm not fixing things that need to be fixed, send an e-mail my way.