'Shell Programming'에 해당되는 글 3건

  1. 2007.02.08 Using COM to explore the namespace
  2. 2007.02.06 How To Convert a File Path to an ITEMIDLIST
  3. 2007.02.06 ITEMIDLIST management library

Using COM to explore the namespace

Windows 2007. 2. 8. 11:23 posted by deneb

출처 : http://netez.com/2xExplorer/shellFAQ/bas_xplore2.html

There are no two ways about it, using low-level COM to enumerate folders is quirky. Note that I didn't say "difficult", it just takes some time getting used to it. With COM you have to manage objects yourself: create the right one, ask for a specific interface for the functionality you are after, then use it and finally take care to release it. In contrast, in regular API-based programming the operating system is doing the management for you; you just call functions directly, oblivious about objects and things behind the scenes.

To enumerate a folder via COM, you need an IShellFolder pointer to the folder object you're after. We've seen a sketch of how to obtain this interface by parsing a path name in the previous section. The next step is to ask the object to enumerate itself using its EnumObjects method, which creates a new object which exposes an IEnumIDList interface. This enumerator object has the contents we are after, as a collection of local PIDLs, one for each item in the folder.

The best way to illustrate all this is with an example. Let's try to create the COM-equivalent of the EnumerateFolderFS() sample presented earlier, which read the contents of a filesystem folder given a full path to it. The following EnumerateFolder() sample produces the same results in a completely different approach.

#include <shlobj.h>

void EnumerateFolder(LPCTSTR path)
{
   HRESULT hr; // COM result, you'd better examine it in your code!
   hr = CoInitialize(NULL); // initialize COM
   // NOTE: usually COM would be initialized just once in your main()

   LPMALLOC pMalloc = NULL; // memory manager, for freeing up PIDLs
   hr = SHGetMalloc(&pMalloc);

   LPSHELLFOLDER psfDesktop = NULL; // namespace root for parsing the path
   hr = SHGetDesktopFolder(&psfDesktop);

   // IShellFolder::ParseDisplayName requires the path name in Unicode.
   OLECHAR olePath[MAX_PATH]; // wide-char version of path name
   MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, path, -1, olePath, MAX_PATH);

   // parse path for absolute PIDL, and connect to target folder
   LPITEMIDLIST pidl = NULL; // general purpose
   hr = psfDesktop->ParseDisplayName(NULL, NULL, olePath, NULL, &pidl, NULL);
   LPSHELLFOLDER psfFolder = NULL;
   hr = psfDesktop->BindToObject(pidl, NULL, IID_IShellFolder, 
                                 (void**)&psfFolder);
   psfDesktop->Release(); // no longer required
   pMalloc->Free(pidl);

   LPENUMIDLIST penumIDL = NULL; // IEnumIDList interface for reading contents
   hr = psfFolder->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, 
                               &penumIDL);
   while(1) {
      // retrieve a copy of next local item ID list
      hr = penumIDL->Next(1, &pidl, NULL);
      if(hr == NOERROR) {
         WIN32_FIND_DATA ffd; // let's cheat a bit :)
         hr = SHGetDataFromIDList(psfFolder, pidl, SHGDFIL_FINDDATA, &ffd, 
                                  sizeof(WIN32_FIND_DATA));

         cout << "Name = " << ffd.cFileName << endl;
         cout << "Type = " << ( (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                                ? "dir\n" : "file\n" );
         cout << "Size = " << ffd.nFileSizeLow << endl;
         
         pMalloc->Free(pidl);
      }
      // the expected "error" is S_FALSE, when the list is finished
      else break;
   }

   // release all remaining interface pointers
   penumIDL->Release();
   psfFolder->Release();
   pMalloc->Release();

   CoUninitialize(); // shut down COM
}

Oh dear, that sure was hard work. 56 lines of code to do exactly the same thing EnumerateFolderFS() managed in just 23 lines — less than half. But hey, that's C++ baby, either take it or leave it and join the VB club <g>. Since this is the first real COM example I presented, I'll try to be gentle and explain it thoroughly. Many typical issues in COM programming appear in this code:

  • COM initialization. Before you use any of the COM services and objects you need to call CoInitialize. Conversely, after you're done with COM you must call CoUninitialize. Typically an application that relies heavily on COM will initialize the subsystem just once when it starts, not inside each function that utilizes COM. Still it's no harm calling CoInitialize more than once, as long as it is balanced by a CoUninitialize.
    ADVANCED. If your program is multi-threaded, then COM must be initialized in each thread that uses its services. Initializing it once in the main thread won't be enough.
  • Object management. The code sample used four distinct COM objects to read the folder contents. Using desktop's IShellFolder the path was parsed, generating a pidl to the target folder. This second object was created using desktop's BindToObject. The enumerator (third object, the one we were after all along) was created by EnumObjects. Finally the shell memory manager was created by SHGetMalloc to free the various PIDLs that were allocated for us. All COM objects are dynamically created and must be released to free up the associated memory and resources via the object's Release method, inherited from IUnknown. Note that PIDLs are not objects, only pointers to allocated memory. That's why they are Free'd and not Release'd, something that took me some time to realise in the early dayz.
  • UNICODE strings. In case you haven't heard about them, UNICODE strings have wide characters, i.e. 2-bytes each. Apparently the orientals are responsible for this major upset <g>. COM has adopted the UNICODE system whole-heartedly, and most system (and shell) objects expect strings to be wide. The problem is that windows 95/98 (dunno about Me) work with multibyte strings, which are basically regular single-byte strings, despite what their name suggests. If you want your proggy to work in all windows platforms then you'd have to stick with single-byte strings and do manual conversions whenever a wide string is required. That is exactly what MultiByteToWideChar function does above. If you use MFC, you can take advantage the CString class, which simplifies string management and conversions. The drawback is the performance penalty involved for all these conversions; unfortunately there's no way around it, except if you are willing to distribute separate ANSI and UNICODE versions of your programs.
  • Enumerators. These fancy-name objects are frequent denizens of the COM landscape. Any VB people in the audience will be familiar with the term collections. Enumerators are simple objects that hold collections of similar items. The internal organization is transparent; enumerators expose an IEnumxxx interface, as IEnumIDList. Once initialized, items can be accessed sequentially by calling the Next method repeatedly, until some error indicates that the list is exhausted. Personally I feel it's a grand omission that all those objects don't have a method like Count to give the total number of elements in the list. An important detail is that enumerators usually return duplicates of their listed items which you are responsible to cleanup. In the code sample above note that each pidl is Free'd after use.

You can now appreciate why people say that COM is a technology with a steep learning curve. One has to be familiar with dozens of details before even the simplest "Hello COM" program can be build. And you'd better hurry in that climbing while learning, because mikro$oft have already prepared the COM+ mountain for you to climb next, slippery slopes an'all. Will this torment ever end? <g>

Of course, these issues are all for starters. The main dish is learning about all the COM objects supplied by the framework, finding out what they can do for you, and how to work with them, what interfaces they support, etc. The online documentation is guaranteed to be the most complete and up-to-date source of reference information. As they say, if you don't like reading, probably software development is not your ideal vocation. What you read in that good book published in 1998 may already be obsolete, plus there will be dozens of new objects introduced since then.

ADVANCED: Fending for your address space
You may have noticed in the sample code the abundance of NULL arguments to interface methods, most of the time implying a default action. Although this is easy to do, there's a possible risk here, if the NULL is a pointer meant to receive some result from the method. Take for example pdwAttributes parameter of ParseDisplayName. The docs state clearly that by passing NULL you specify that are not interested to receive any attributes at this time. In an ideal world you would be safe, but in this world there are many cowboys out there developing shell and namespace extensions, who wouldn't think twice before attempting to write on a NULL pointer without doing the proper checks first. Since that offending amateur object runs in the same address space as your app, it will drag you down in its demise. The workaround is to provide dummy variables for all such potential trouble-makers; for our example this would mean defining a "DWORD dummyAttrs = 0;" and passing it on to ParseDisplayName, even if you don't have any intention of using it in the end.

Ok, but what about that namespace exploring?

Sorry folks, I got a bit carried away there. So, let's get back to the subject, folder contents enumeration. The EnumerateFolder() sample will produce almost the same results as a the filesystem version EnumerateFolderFS(). Even the order of the items is the same, which hints that EnumObjects down deep must be using FindFirstFile et al for doing the actual folder reading. But almost the same implies there are some differences:

  • We no longer read the '.' and '..' pseudo-items. That's progress since I consider these two plain nuisances. <g>
  • We don't get automatic filename filtering; the best we can do is specify whether we want files, folders or both, via the SHCONTF_FOLDERS and SHCONTF_NONFOLDERS flags that EnumObjects understands.

The most important difference however, is the file date/time information. SHGetDataFromIDList doesn't fill in the file details in WIN32_FIND_DATA as thoroughly as an equivalent FindFirstFile would. Only the modification date is filled in, and even that is rounded to the nearest even second. That's the reason why I mentioned that you need both COM and traditional API to obtain complete information for filesystem folders.

ADVANCED: Stale PIDLs
SHGetDataFromIDList gets all the file data directly from the PIDL, without accessing the disc at all. Microsoft's implementation of filesystem PIDLs stores quite a large amount of data in each SHITEMID (cf. my earlier suggestion), except for those unfortunate creation etc dates of course. This is an advantage since the cached information can be accessed quickly, but it has to be interpreted carefully. A PIDL you obtained yesterday won't necessarily contain accurate information, if for example the file was modified in the meantime. Still, even a stale PIDL is good enough to uniquely identify a file. To convince yourselves, take two PIDLs to the same file, obtained at different times, and see what CompareIDs will return.


Shell을 이용하여 디렉토리 정보등을 알아 올 때 알아야 할 것들을 알차게 설명해 놓았다.

How To Convert a File Path to an ITEMIDLIST

Windows 2007. 2. 6. 23:30 posted by deneb

How To Convert a File Path to an ITEMIDLIST

Article ID : 132750
Last Review : July 11, 2005
Revision : 1.3
This article was previously published under Q132750

SUMMARY

When developing an application that interacts with the Windows Explorer shell, you may need to convert an arbitrary path to a file to an ITEMIDLIST. You can do this using the IShellFolder::ParseDisplayName API.

MORE INFORMATION

Following is an example of how to use the IShellFolder interface to convert the path to the file Readme.txt in the current directory to an ITEMIDLIST. The example is written in C. If the program is written using Visual C++, accessing member functions through the lpVtbl variable is unnecessary.
   LPITEMIDLIST  pidl;
   LPSHELLFOLDER pDesktopFolder;
   char          szPath[MAX_PATH];
   OLECHAR       olePath[MAX_PATH];
   ULONG         chEaten;
   ULONG         dwAttributes;
   HRESULT       hr;

   // 
   // Get the path to the file we need to convert.
   // 
   GetCurrentDirectory(MAX_PATH, szPath);
   lstrcat(szPath, "\\readme.txt");

   // 
   // Get a pointer to the Desktop's IShellFolder interface.
   // 
   if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder)))
   {
       // 
       // IShellFolder::ParseDisplayName requires the file name be in
       // Unicode.
       // 
       MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1,
                           olePath, MAX_PATH);

       // 
       // Convert the path to an ITEMIDLIST.
       // 
       hr = pDesktopFolder->lpVtbl->ParseDisplayName(pDesktopFolder,
                                                     NULL,
                                                     NULL,
                                                     olePath,
                                                     &chEaten,
                                                     &pidl,
                                                     &dwAttributes);
       if (FAILED(hr))
       {
           // Handle error.
       }

       // 
       // pidl now contains a pointer to an ITEMIDLIST for .\readme.txt.
       // This ITEMIDLIST needs to be freed using the IMalloc allocator
       // returned from SHGetMalloc().
       // 

       //release the desktop folder object
         pDesktopFolder->lpVtbl->Release();
   }
				

APPLIES TO
Microsoft Platform Software Development Kit-January 2000 Edition

ITEMIDLIST management library

Windows 2007. 2. 6. 23:18 posted by deneb
출처 : http://www.codeproject.com/shell/pxshlapi.asp

This library simplifies management of Windows Shell ITEMIDLISTs (further PIDLs). For me, it always has been trouble to keep track of all allocated PIDLs and to free them in time. So, I wrote this library.

As time goes, the library has became rather messy (it's because I put there all PIDL-related functions that may be reusable). But it still helps me a lot.

The core of this library is CPidl class. All other functions exported here are mostly supplemental.

Constructors

CPidl::CPidl()
Constructs empty CPidl instance
CPidl::CPidl(LPITEMIDLIST other)
Constructs CPidl instance and attaches (not copies) ITEMIDLIST allocated somewhere else. At destruction time attached ITEMIDLIST will be deallocated.
CPidl::CPidl(const CPidl& other)
Copy constructor. Copies one instance of CPidl into another.
CPidl::CPidl(LPCTSTR pszPath)
Constructs CPidl instance from file or folder path. In case of error, the PIDL will be empty. It uses IShellFolder::ParseDisplayName() for getting PIDL.
CPidl::CPidl(int nSpecialFolder)
Constructs CPidl instance from special folder ID. (See CSIDL_XXX constants in MSDN).

Member functions

LPITEMIDLIST CPidl::Detach()
Detaches contained PIDL from wrapper
LPITEMIDLIST CPidl::Copy()
returns a copy of contained PIDL
void CPidl::Free()
frees contained PIDL. This member function is called automatically at destruction time.
CPidl::operator bool()
conversion operator for using in boolean expressions. Will return true if contained PIDL is not NULL
CPidl::operator LPITEMIDLIST()
Useful conversion operator that allows to use CPidlas a function argument in place of LPITEMIDLIST
CPidl::operator LPITEMIDLIST*()
CPidl::operator LPCITEMIDLIST*()
Useful conversion operator that allows to use CPidl as a function argument in place of LPITEMIDLIST*. Usually the pointer to LPITEMIDLIST is used to receive when a PIDL from function. Make sure you call CPidl::Free() before using CPidl instance for receiving PIDLs!
CPidl& CPidl::operator=(LPITEMIDLIST other)
CPidl& CPidl::operator=(const CPidl& other)
Assignment operators

Global functions

IShellFolderPtr& GetDesktopFolder()
returns a reference to static instance of IShellFolder interface for root namespace folder.
IMallocPtr& GetMalloc()
returns a reference to static instance of IMalloc interface. This interface is essential for working with PIDLs.
int GetItemIDSize(LPCITEMIDLIST pidl)
returns PIDL size in bytes.
int GetItemIDCount(LPCITEMIDLIST pidl)
returns number of elements in PIDL. If you don't know what does it mean, reread "Working with Item ID Lists" article in MSDN.
LPBYTE GetItemIDPos(LPCITEMIDLIST pidl, int nPos)
gets pointer to nPos'th element in PIDL or NULL if nPos exceeds number of elements in PIDL.
LPITEMIDLIST CopyItemID(LPCITEMIDLIST pidl, int cb=-1)
makes a copy of PIDL. cb specifies number of bytes to copy. If it's equal -1, entire PIDL will be copied.
LPITEMIDLIST MergeItemID(LPCITEMIDLIST pidl,...)
Merges two or more PIDLs (usually relative ones) into absolute (or fully-qualified) PIDL. The programmer should know what he's doing when he calls this function :-). Typical usage is to make fully-qualified PIDL from folder PIDL and PIDL returned by IShellFolder::EnumObjects()
int CompareItemID(LPCITEMIDLIST pidl1,LPCITEMIDLIST pidl2)
int CompareItemID(LPCITEMIDLIST pidl1,int nSpecialFolder)
int CompareItemID(int nSpecialFolder,LPCITEMIDLIST pidl2)
Compares two PIDLs. Returns 0 if equal and -1 otherwise.
LPITEMIDLIST GetItemIDFromPath(LPCTSTR pszPath)
returns PIDL for file or folder name. Or NULL if error
HRESULT SHBindToParent(LPCITEMIDLIST pidl, REFIID riid, VOID **ppv, LPCITEMIDLIST *ppidlLast)
Closely resembles standard shell function SHBindToParent(). It was neccessary to write it becuase it's not available on Windows 95/NT. Refer to MSDN for arguments description.
BOOL TrackItemIDContextMenu(LPCITEMIDLIST pidlShellItem, UINT nFlags, LPPOINT ptPoint, HWND hWnd)
BOOL TrackItemIDContextMenu(LPCTSTR pszShellItemPath, UINT nFlags, LPPOINT ptPoint, HWND hWnd)
This function builds, shows and tracks shell context menu for PIDL or or file/folder path. It returns TRUE if user clicked on some context menu item. In case of error or if user didn't choose anything from menu it returns FALSE.
  • LPCITEMIDLIST pidlShellItem, LPCTSTR pszShellItemPath: a PIDL or path name of the shell item to show context menu for.
  • UINT nFlags: a set of TPM_XXX flags. See function TrackPopupMenu() for description
  • LPPOINT ptPoint: a point in screen coordinates where menu should appear.
  • HWND hWnd: a handle to the window - owner of the menu. It cannot be NULL

쉘을 다루는데 있어서 애로사항이 꽃피는 경우가 파다한데 그나마 쉽게 접근 할 수 있는 클래스