7

I've implemented a directory walking algorithm for the Windows Shell using IShellItem, IShellFolder, IStorage, IStream, etc. All is well and good. I can even walk inside shell namespace extensions (e.g. .zip) files.

However, I have problems extracting (regular) file sizes when files are being used by some other program with exclusive access.

AFAIK, there is nothing but the STATSTG structure that gives more information than the file's name. There are essentially 3 ways to get a hold of a STATSTG for a IShellItem:

  1. Iterate using IEnumSTATSTG instead of IEnumIDList. Instead of invoking IShellFolder::EnumObjects(), get the IStorage for the folder and invoke IStorage::EnumElements(). You now get STATSTG structures directly.
  2. Get the IStorage for the IShellItem and invoke IStorage::Stat().
  3. Get the IStream for the IShellItem and invoke IStream::Stat().

I would really like to use #1 because it would give me all the information I need. However, I cannot get it to enumerate folder contents. I successfully extract the IStorage for the folder: it's own Stat() gives me the proper folder name. I successfully extract the IEnumSTATSTG, but the first call to Next(1, &item, NULL) returns S_FALSE and terminates the enumeration.

I would fallback to use #2 as it is still not so bad, but extracting the IStorage for regular disk files produces an error using both of IShellItem::BindToHandler(0, BHID_Storage, ...) and IShellFolder::BindToStorage(child, ...).

I finally tried #3 although it just plains seems wrong and it succeeds as long as files are not being used with exclusive access by another program.

I've googled around a bit and found several code snippets that use approach #3.

Question: Can anyone explain how I'm supposed to get the file's STATSTG without using approach #3?

Should approach #1 work, or does the IStorage implementation for regular folders simply not produce listings? Should approach #2 work or is the IStorage implementation simply not implemented for regular files?

Environment: Windows Vista Ultimate 32-bit, Visual Studio 2008 Express. Using C++, no ATL, all custom COM wrappers (in-house, may be suitably modified assuming somwthing is wrong there).

André Caron
  • 44,541
  • 12
  • 67
  • 125
  • Note that the file size of an open file is rather meaningless. Even if you found a function that returned it, the value could change by the very next instruction. – MSalters Nov 15 '10 at 10:06
  • 2
    By that logic, checking the very existance of the file is meaningless. It could be deleted by another process before your next instruction is executed. I'm well aware of those problems, but implementing file browsing capabilities requires you to display outdated information :-) – André Caron Nov 15 '10 at 15:48

2 Answers2

1

Even with the accepted answer, it took some doing.

The first thing you need is the Windows Properties reference. From there you have to know that you want to go into System.Size. From there you get the two important pieces of information:

System.Size

The system-provided file system size of the item, in bytes.

shellPKey = PKEY_Size
typeInfo
       type = UInt64

Knowing that it's a UInt64, you can then get ahold of the IShellItem2 interface, in order to use one of the many property-getting methods:

//Get the IShellItem2 interface out of the IShellItem object
IShellItem2 si2 = shellItem as IShellItem2;

//Get the file fize (in bytes)
UInt64 fileSize;
si2.GetUInt64(PKEY_Size, ref fileSize);
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Thanks for taking the time to post this answer! I'm no longer in a position to validate its relevance though (I no longer work in that environment). I hope it can still be useful for posterity. – André Caron Feb 28 '16 at 16:24
1

Have you tried getting hold of the IShellItem2 interface, and then querying for the value of the PKEY_Size property?

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
  • No, I haven't tried that yet, mainly because it doesn't work for Windows XP (I go for compatibility with older systems when I can). Seems I don't have a choice, though. I'll check this out. – André Caron Nov 15 '10 at 15:53
  • Any idea what header/version macro I need to get `PKEY_Size` constant to be defined? I keep googling but get lots of database related stuff. I guess `PKEY` is mistaken "primary key"... – André Caron Nov 15 '10 at 16:14
  • Not at my desktop right now. I found it in one of the files in the Windows 7.0A\Include directory. You might need to install a newer Platform SDK. – Roger Lipscombe Nov 15 '10 at 16:32
  • 2
    Indeed, I grepped for it in the Platform SDK's Include folder and it didn't come out. I've settled for using `IShellFolder2::GetDetailsEx()` and it workds like a charm. It's compatible XP too! – André Caron Nov 15 '10 at 16:52
  • @AndréCaron PROPERTYKEY – xenophōn Oct 15 '19 at 06:26