Keep the plumbing out of my living room

0

Full article

When it comes to writing software and deciding where to store files for your users, Windows makes it easy by setting certain areas of the system aside, and grouping them into two general categories.

  • Files your app requires to run, or creates while running (i.e. logs), should go in program files, application data, etc. Keep them away from the user - they don't want them, and you don't want them to notice them.
  • Files a user creates with your app (i.e. a spreadsheet in Excel) should go in my documents, music, desktop, etc. Keep them near and dear to the user, so they can find them again, back them up, sync them using Dropbox, share them, whatever.

These special folders aren't necessarily a specific location. They vary by version of Windows, and users can even change their location manually. But that doesn't matter, because Windows abstracts away the exact location to make life easier for developers, and you should totally take advantage of that.

Why's it matter?

Here's what my My Documents currently looks like - loads of stuff I don't care about and shouldn't be aware of on a daily basis. Amazon is storing encrypted ebook files that are useless outside their app. ShareX stores log files and plugins, Microsoft some out-of-the-box templates, and Zoom.. well... an empty directory. ๐Ÿ™„

It sucks. It's like I've got this nice little area that's supposed to be just mine, but every plumber, electrician, and hvac specialist decided to just run everything through the living room. There's wires dangling from the mantle, plumbing running in front of the tv screen, duct work across the sofa. Crap everywhere. Thanks.

If I want to sync My Documents between several machines, I don't need ShareX's logs on both. I can install the Kindle app on multiple machines to download books - those encrypted files are worthless to me.

Well, what can a dev do? (spoiler: plenty)

Windows has had this concept of special folders going back to Windows 95. That's right, like 25 years ago! You can view the folders in the registry editor, but don't use them from there. Seriously, it's a long and sad story apparently. On a side note, let no one say Microsoft takes backwards compatibility lightly.

The keen observer might've noticed some bright red text that totally stood out from everything else in Raymond Chen's long, sad story - those are (outdated) Windows API calls for getting special folders. The new hotness is SHGetKnownFolderPath, although SHGetFolderPath works as well (it simply calls the former).

What you can do in C++

If you're using c++, here's something I cobbled together. This might be ugly for c++.. or it might be ugly just because it's c++. It uses the SHGetKnownFolderPath function directly.

#include <windows.h>
#include <iostream>
#include <shlobj.h>
using namespace std;

#pragma comment(lib, "shell32.lib")

int main() {
    PWSTR path = NULL;
    HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &path);

    if (result != S_OK)
        std::cout << "Error: " << result << "\n";
    else
        wprintf(L"%ls\n", path);

    cin.get();  // pause to view result
    return 0;
}

// output: C:\Users\Grant\Documents

What you can do in C#

The .NET Framework makes things even easier. I mean, it's so easy one wonders why an app like ShareX that appears to be written in C# would be storing log files in my living room documents folder at all. ๐Ÿ˜ 

using System
using static System.Environment;

namespace GetKnownFolder
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(GetFolderPath(SpecialFolder.MyDocuments));
            Console.ReadLine();
        }
    }
}

// output: C:\Users\Grant\Documents

If you investigate further, .NET is actually wrapping the API call for us. ๐Ÿ‘

int hresult =
  Win32Native.SHGetFolderPath(IntPtr.Zero,                    /* hwndOwner: [in] Reserved */
                              ((int)folder | (int)option),    /* nFolder:   [in] CSIDL    */
                              IntPtr.Zero,                    /* hToken:    [in] access token */
                              Win32Native.SHGFP_TYPE_CURRENT, /* dwFlags:   [in] retrieve current path */
                              sb);                            /* pszPath:   [out]resultant path */

What you can do in Python

If you're using Python, you can install pywin32 (a wrapper for the Win32 API calls) and specify a constant special item ID list (CSIDL) value.

from win32com.shell import shell, shellcon
print shell.SHGetFolderPath(0, shellcon.CSIDL_MYPICTURES, None, 0)
print shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)

# Output:
# C:\Users\Grant\Pictures
# C:\Users\Grant\Documents

Okay, I don't know Python that well. This might only work for Python 2, plus the CSIDL values have been supplanted by something newer (although, you know.. backwards compatibility and all).

There are options. Mostly quite easy ones.

So, let's recap and drive the point home, because I've been subtle up to now. If you're using a language from the last couple of decades, to write something for Windows that needs access to the file system, more likely than not there's a way to access the special system folders.

Please look into it. Please store anything your app needs in something called application data, or local data, or local application data, or really anything with "application" or "program" in the name. I know "My Documents" might sound like something that belongs to you, but it really doesn't. It's mine. Mine. ALL MINE!

Alrighty then. Thanks!

Author

Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.


Comments