404
404

Reputation: 8562

GetPrivateProfileString inconsistent behaviour when compiled with different Visual Studio versions

We have a c++ program compiled long ago that uses GetPrivateProfileString. The lpFileName arg is just the name of an ini file, so the expected behaviour according to the documentation is that it should look for the file in the Windows directory:

lpFileName [in] The name of the initialization file. If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.

Recently the program was recompiled (with no code changes) and redeployed, which resulted in the program not being able to read the ini file anymore.

Comparisons between the two programs with Process Monitor showed that the old program was attempting to look for the file in %HOMEPATH%\Windows rather than C:\Windows.

As a test I compiled the following code with Visual Studio 2012 and ran it on a 2003 server, passing in some random file name on the command line:

void wmain(int argc, wchar_t* argv[])
{
    wchar_t *appName = L"xxx";
    wchar_t *keyName = L"yyy";
    wchar_t *defValue = L"default value";
    wchar_t buffer[1024];
    DWORD bufferSize = sizeof(buffer);
    wchar_t *fileName = argv[1];

    DWORD result = GetPrivateProfileString(
        appName,
        keyName,
        defValue,
        buffer,
        bufferSize,
        fileName
        );

    wprintf(L"result: %d\n", result);
}

Using Process Monitor to monitor for that file name, I could see it looked for the file in C:\Windows - as expected. However when the exact same code is compiled in VS 2003 and run on 2003 server, it looks for the file in %HOMEPATH%\Windows.

Back to the original, non-recompiled program. I noticed that on a Windows 2008 server, if I simply double click the exe, it looks for the file in %HOMEPATH%\Windows. However if I right-click the exe and Run As Administrator, it looks for the file in C:\Windows! But on Windows Server 2003 it always looks in %HOMEPATH%\Windows.

This isn't consistent with the documentation!

Upvotes: 1

Views: 908

Answers (1)

Cody Gray
Cody Gray

Reputation: 244732

The behavior is almost certainly related to Terminal Server. When you have Terminal Server (later renamed to Remote Desktop Services in Server 2003 and later SKUs) installed, any attempts to access the system (Windows) directory get redirected to a user-local Windows directory (%HOMEPATH%\Windows).

This is documented under the "remarks" section of the documentation for GetWindowsDirectory(), which is presumably what GetPrivateProfileString() is using under the hood when you don't specify a full path and it falls back to its 16-bit Windows behavior of looking in the system directory.

If you are linking your binary with the IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE flag (set in the Project Property Pages under Linker → System → Terminal Server → /TSAWARE:YES), then it assumes you are aware of this behavior and understand that Terminal Servers are different than normal client machines. Thus, it gives you the actual Windows directory, instead of the private Windows directory.

Otherwise, if you're linking with /TSAWARE:NO, then you get the backwards-compatibility behavior that redirects requests for the system Windows directory to the private user-local Windows directory.

Presumably the default setting of this flag changed some time between VS 2003 and VS 2012. I don't know when exactly, but modern versions of Visual Studio do assume you are Terminal Server-aware.

Raymond Chen blogged about this once: When will GetSystemWindowsDirectory return something different from GetWindowsDirectory?


Anyway, this is all just academic curiosity. Your program is buggy, and it was long before you discovered this curiosity. An application should never write data into the Windows directory, whether the system Windows directory or a pseudo-user-local Windows directory. There are two ways of getting around this:

  1. Stop calling GetPrivateProfileString() altogether and, as David Heffernan suggested, use a third-party INI-file parser. Google will turn up several results. simpleini looks like a good choice.

  2. If you aren't ready to make the change yet, you should at a minimum be specifying a full path to your INI file. That will avoid the 16-bit Windows behavior of dumping files in the system directory, but you're still at the mercy of the other crustiness of this ancient API.

Upvotes: 2

Related Questions