Win32 Q & A

Jeffrey Richter

Jeffrey Richter wrote Advanced Windows, Third Edition (Microsoft Press, 1996) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32-based programming seminars. He can be reached at v-jeffrr@microsoft.com.

Since my WaitForMultipleExpressions column appeared (MSJ, January 1997), I've received email from two readers pointing out that the function does not work with mutex kernel objects. This is because a mutex object has a concept of being owned by a thread, while none of the other kernel objects have this concept. So, when one of my AND threads gets ownership of a mutex, it later abandons the mutex when the thread terminates. If Microsoft ever adds a function to the Win32® API that allows one thread to transfer ownership of a mutex to another thread, my WaitForMultipleExpressions function could easily be fixed to support mutexes properly. Until this function exists, there is no good, clean way for WaitForMultipleExpressions to support mutexes. Sorry folks!

QIn "Fusing Your Applications to the System Through the Windows® 95 Shell" (MSJ, April 1996), you discussed how applications should use the \My Documents directory as the default location for storing a user's documents. But unlike the \Program Files directory, there does not seem to be an entry in the registry specifying the name for the \My Documents directory. I'd hate to hardcode \My Documents into my source code, and even if I did, I still wouldn't know which drive to put it on. How can I find the user's document directory?

Bob Alexander

AThe registry key that you refer to is HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders. Figure 1 shows this registry key's values on my Windows NT® 4.0 machine. This key contains several values that indicate where various special folders are kept on the user's hard drive. On Windows 95, the default location for a user's personal data files was \My Documents, but on Windows NT 4.0 Microsoft changed this to Personal. You can see from Figure 1 that the full path of this directory is D:\WINNT\Profiles\Administrator\Personal as indicated by the Personal data value name in the registry.

Figure 1 Registry Editor

It's possible that your registry doesn't have the Personal registry value. When the operating system is first installed, it does not add these values to this registry key. These values are added when an application calls the SHGetSpecialFolderLocation function for the very first time. Also, this function uses this registry value in an algorithm to determine the user's personal directory, and this algorithm is likely to change in future versions of Windows. Because of this, you should never access this registry key directly.

The Win32 SHLOBJ.H header file prototypes the SHGetSpecialFolderLocation function and defines some symbols for use with this function. The function prototype is shown here, and the symbols can be found in the Win32 SDK documentation.

HRESULT SHGetSpecialFolderLocation(HWND hwndOwner,
                     int nFolder, LPITEMIDLIST* ppidl);

The shell and its functions all work with item IDs, lists of item IDs, and pointers to lists of item IDs. When you call SHGetSpecialFolderLocation, you pass a special folder symbol in the nFolder parameter. To get the user's personal directory, pass CLSID_PERSONAL for this parameter. The function then allocates a block of memory that contains a contiguous list of item IDs. Each item ID is a binary representation of an item in the shell's namespace. The root of the shell's namespace is the Desktop, and a list of item IDs is the binary representation of an item's location in
the namespace starting from the Desktop. SHGetSpecialFolderLocation's last parameter, ppidl, is a pointer to an LPITEMIDLIST variable that you must allocate (you just allocate the pointer, not any additional memory). This pointer's value is set to point to the block of memory allocated for the special folder's item ID list just before the SHGetSpecialFolderLocation function returns.

You must convert an item ID list to a fully qualified path name that the file system understands. Calling SHGetPathFromIDList does this:

BOOL SHGetPathFromIDList(LPCITEMIDLIST pidl, 
LPSTR pszPath);

The pidl parameter is a pointer to an ID list (returned from SHGetSpecialFolderLocation), and the pszPath parameter identifies a character buffer that gets the folder's path name. The path's buffer must be at least _MAX_PATH characters long.

The last step, which is easy to forget, is to free the item ID list. The SHGetSpecialFolderLocation function allocates memory for the item ID list by using the calling process's task allocator. You are responsible for freeing this memory by using the task's allocator. You get the task's allocator by calling SHGetMalloc:

HRESULT SHGetMalloc(LPMALLOC * ppMalloc); 

This function returns a pointer to an IMalloc interface that you can use to free the item ID list. Figure 2 demonstrates all the steps necessary to get the full path of a special folder.

Figure 2 Getting the Full Path

void WorkWithSpecialFolder() {
   // Allocate a pointer to an Item ID list
   LPITEMIDLIST pidl;

   // Get a pointer to an item ID list that
   // represents the path of a special folder
   HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &pidl);

   // Convert the item ID list's binary
   // representation into a file system path
   char szPath[_MAX_PATH];
   BOOL f = SHGetPathFromIDList(pidl, szPath);

   // Allocate a pointer to an IMalloc interface
   LPMALLOC pMalloc;

   // Get the address of our task allocator's IMalloc interface
   hr = SHGetMalloc(&pMalloc);

   // Free the item ID list allocated by SHGetSpecialFolderLocation
   pMalloc->Free(pidl);

   // Free our task allocator
   pMalloc->Release();

   // Work with the special folder's path (contained in szPath)
·
·
·
}

QI am writing an application that needs to display a file's full path name. However, I always want to display the file's long file name. I see that the Win32 API offers a function called GetShortPathName:

DWORD GetShortPathName(LPCTSTR lpszLongPath, 
                LPTSTR lpszShortPath, DWORD cchBuffer); 

This function accepts a long path name and returns the short path name equivalent. What I need is a function that does the opposite. How can I convert a short path name to its long path name equivalent?

Holly B. MacKechnie

ABoy, it sure does seem odd that Win32 doesn't offer a GetLongPathName function, and I have absolutely no idea why that is. I have needed such a function, and since Win32 didn't offer it I wrote one myself. My function parsed the individual components of the short path name. I passed each component to the Win32 FindFirstFile function, which initializes a WIN32_FIND_DATA data structure with information about the component. One of the members in this structure is cFileName, and it contains the long name of the component. I took each component's long name and appended it to a new buffer. As you can imagine, this was rather inconvenient and there should be an operating system function that does the same thing.

Just recently, I've been playing around a bit more with the shell functions and have come across an easier way to convert a short path name to its long path name equivalent. Figure 3 shows a small program that takes a short path name as a command-line argument and converts it to its long path name equivalent. Because the shell (and COM interface) functions expect only Unicode strings, this program uses Unicode characters exclusively, which is why you see the explicit W suffix on the various Win32 functions that I call.

Figure 3 ShortPathToLongPath.cpp

/******************************************************************************
Module name: ShortPathToLongPath.cpp
Written by:  Jeffrey Richter
Notices:     Written 1997 by Jeffrey Richter
******************************************************************************/

#include <Windows.h>
#include <ShlObj.h>
#include <wchar.h>

///////////////////////////////////////////////////////////////////////////////

int WINAPI WinMain (HINSTANCE hinst, HINSTANCE hintPrev, LPSTR pszCmdLine, 
                    int nCmdShow) {
   int nNumArgs = 0;
   LPWSTR pszShortPathNameW = CommandLineToArgvW(GetCommandLineW(), 
                                                 &nNumArgs)[1];

   LPSHELLFOLDER psfDesktop = NULL;
   ULONG chEaten = 0;
   LPITEMIDLIST pidlShellItem = NULL;
   WCHAR szLongPath[_MAX_PATH] = { 0 };

   // Get the Desktop's shell folder interface
   HRESULT hr = SHGetDesktopFolder(&psfDesktop);

   // Request an ID list (relative to the desktop) for the short pathname
   hr = psfDesktop->ParseDisplayName(NULL, NULL, pszShortPathNameW, &chEaten, 
                                     &pidlShellItem, NULL);
   psfDesktop->Release();  // Release the desktop's IShellFolder
   
   if (FAILED(hr)) {

      // If we couldn't get an ID list for short pathname, it must not exist.      
      lstrcpyW(szLongPath, L"Error: Path not found!");

   } else {

      // We did get an ID list, convert it to a long pathname
      SHGetPathFromIDListW(pidlShellItem, szLongPath);

      // Free the ID list allocated by ParseDisplayName
      LPMALLOC pMalloc = NULL;
      SHGetMalloc(&pMalloc);
      pMalloc->Free(pidlShellItem);
      pMalloc->Release();
   }

   // Display short pathname in caption and the long pathname in the box.
   MessageBox(NULL, szLongPath, pszShortPathNameW, MB_OK);

   return(0);
}

///////////////////////////////// End of File /////////////////////////////////

The program starts out by calling GetCommandLineW to get the Unicode version of the application's command line. This function returns a zero-terminated string that includes the full path name of the application's executable. To parse this string into individual arguments, I then call CommandLineToArgvW. This function has a full implementation on Windows NT, but is not fully implemented on Windows 95. For this reason, the code in Figure 3 will only work on Windows NT. To make it run on Windows 95, you will have to write additional code that parses the Unicode command line. (Oddly enough, Windows 95 does implement the GetCommandLineW function—go figure!)

Armed with the short, Unicode path name, I then call SHGetDesktopFolder to get the IShellFolderInterface for the desktop. I can now call the desktop's ParseDisplayName function, passing it the short path name. ParseDisplayName will build an ID list as it traverses the user's file system searching for the specified file. As each subdirectory is encountered, ParseDisplayName appends another ID to the ID list. Once I have the ID list, I release the desktop's IShellFolder interface.

This ID list is, of course, a binary representation of the file residing in the shell's namespace. Now I need to convert this binary representation into its string equivalent. This is the job of the SHGetPathFromIDListW function:

BOOL SHGetPathFromIDListW(LPCITEMIDLIST pidl, 
                         LPWSTR pszPath);

This function takes a pointer to an ID list and builds a string representation identifying the path to the shell object. The secret is that this function always constructs the string using the long name of each subdirectory and file instead of its short name.

Finally, as in the answer to the previous question, you must remember that it is your responsibility to free the ID list returned by the ParseDisplayName function.

QI was reading about thread-local storage in Advanced Windows, Third Edition (Microsoft Press, 1996). You state that static thread-local storage doesn't work properly if a DLL is explicitly loaded at runtime (via the LoadLibrary function). Is it possible for a DLL to know whether it is being loaded implicitly or explicitly? And if so, can the DLL fail initialization if it is loaded explicitly? If the DLL can somehow know how it was loaded, then it can report an error if it was using static TLS.

Raja

AIf you look up the DllEntryPoint function in the Win32 SDK documentation, you'll see the following prototype for the function:

BOOL DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, 
                   LPVOID lpvReserved);

Back in the days of Windows NT 3.1, the documentation for this function stated that the lpvReserved parameter was reserved and that you shouldn't reference it. However, Microsoft has recently documented this parameter: it tells the DLL whether it is being implicitly or explicitly loaded. The value is nonzero when the DLL is being implicitly loaded. Note that this value is not zero or one—it is zero or nonzero!

If you want a DLL to fail initialization when it is implicitly loaded, write your DllMain function as shown in Figure 4. The comments in the DLL_PROCESS_ATTACH code indicate some other assumptions that are safe to make if a DLL is implicitly loaded. Also note that the fImpLoad parameter contains useful information when a DLL_PROCESS_DETACH notification is processed. For this notification, fImpLoad indicates why the DLL is being unloaded. If fImpLoad is nonzero, the DLL is being unloaded because the process is terminating. If fImpLoad is zero, the DLL is unloading because a thread called FreeLibrary.

Figure 4 DllMain

BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, BOOL fImpLoad) {
   BOOL fInitializeOk = TRUE;  // Assume successful initialization

   switch (fdwReason) {
      case DLL_PROCESS_ATTACH:
         if (fImpLoad) {
            // DLL is being loaded at process load time.
            // The primary thread is executing this code
            // No other threads exist in this process at this time.
         } else {
            // DLL is being loaded because LoadLibrary was called.
            // This code is being executed by any thread in the process. We 
// don't know how many threads are already running in this process. fInitializeOk = FALSE; // Fail the DLL's initialization } break; case DLL_PROCESS_DETACH: if (fImpLoad) { // DLL is being unloaded because the process is terminating. } else { // DLL is being unloaded because FreeLibrary was called. } break; case DLL_THREAD_ATTACH: // Do any per-thread initialization here. break; case DLL_THREAD_DETACH: // Do any per-thread cleanup here. break; } return(fInitializeOk); }

Note that the meaning of the fImpLoad parameter is not defined when processing a DLL_THREAD_ATTACH or DLL_THREAD_DETACH notification.

Have a question about programming in Win32? Send your questions via email to Jeffrey Richter: v-jeffrr@microsoft.com.

This article is reproduced from Microsoft Systems Journal. Copyright © 1997 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S. and Canada, or (303) 678-0439 in all other countries. For other inquiries, call (415) 905-2200.