Sooo; wiederhergestellt. Die Datei lag in drei Teilen vor; die beiden letzten haben nicht zusammengepasst. Ich schließe einfach mal, dass Visual Studio im Augenblick des Stromausfalls eine Sicherheitskopie anlegen wollte und das Dateisystem mit dem letzten Sektor beschäftigt war, als der Strom ausfiel. Warum dann *beide* Dateien genullt waren, weiß ich nicht.
Hier ist der Quelltext. Ganz schnell, bevor wieder was kaputtgeht!
Code: Alles auswählen
#define STRICT // use type-safe Windows declarations
#define STRICT_TYPED_ITEMID // use type-safe shell declarations
#include <ShlObj.h> // shell objects & namespaces
#include <Shlwapi.h> // “PathRemoveFileSpecW()”
#pragma comment(lib, "Shlwapi.lib")
// Allocators to avoid STL/exception bloat:
namespace {
template <typename T> T * tryToReserve() {
return reinterpret_cast<T *>(HeapAlloc(GetProcessHeap(), 0, sizeof(T)));
}
template <typename T> T * tryToReserve(int count) {
return reinterpret_cast<T *>(HeapAlloc(GetProcessHeap(), 0, count * sizeof(T)));
}
template <typename T> bool tryToResize(T * & address, int count) {
if(auto newAddress = reinterpret_cast<T *>(HeapReAlloc(GetProcessHeap(), 0, address, count * sizeof(T)))) {
address = newAddress;
return true;
}
return false;
}
void release(void * address) {
HeapFree(GetProcessHeap(), 0, address);
}
}
struct FolderIterator {
using ItemID = ITEMID_CHILD *;
enum Status {
pending,
ok,
error
// you may want to add more specific error codes
};
// Setting up the iterator takes a lot of time (at least 0.13 seconds), so it is performed in a worker thread and hopefully
// finishes before any user input arrives.
HANDLE toThread; // “nullptr” when no work is being done
Status volatile status; // set by worker thread
IFolderView * view;
IShellFolder * folder;
// The ID of each item in the shell view.
// • the shell’s “IEnumIDList” does not allow reverse iteration, so it must be extracted to flat memory
// • maximal size is 0x7FFFFFFF due to the nature of “IFolderView::ItemCount()”
ItemID * itemIds;
int numberOfItems;
// Index of the current item.
int current;
// Index of an item that has been marked as start. Without it, cyclic enumerations would never stop.
int start;
// When the iterator is created in a separate thread, it needs access to initial data.
WCHAR * initialPath;
HWND initialForegroundWindow;
};
void destroy(FolderIterator &);
// Helpers:
namespace {
// Waits for completion of pending operations and checks whether the iterator is valid. Should be the first call of each API
// function.
bool isValid(FolderIterator & it) {
// Is an enumeration still in progress?
if(nullptr != it.toThread) {
// Wait for the operation to complete:
WaitForSingleObject(it.toThread, INFINITE);
CloseHandle(it.toThread);
it.toThread = nullptr;
// Destroy the operations’s initial data:
if(nullptr != it.initialPath) {
release(it.initialPath);
it.initialPath = nullptr;
}
}
return FolderIterator::Status::ok == it.status;
}
// Computes the given item’s path.
// • returns “false” on failure
// • requires up to “MAX_PATH” characters (due to “STRRET”s internal buffer being “MAX_PATH” characters long)
bool currentPathOf(
IShellFolder & folder,
FolderIterator::ItemID const id,
WCHAR * result
) {
auto status = false;
// Get the item’s path. I’m not entirely sure, but I think this is its “parsable display name”.
STRRET path;
path.uType = STRRET_WSTR; // politely ask for UTF-16
if(SUCCEEDED(folder.GetDisplayNameOf(id, SHGDN_FORPARSING, &path))) {
// The returned string may be of unexpected encoding, so guarantee UTF-16 via “StrRetToStr()”.
// • “StrRetToStrW()” deletes the orignal string
// • I can’t find any hints on graceful failure, so I ignore it (better have a leak than a double-free)
WCHAR * pathUTF16;
if(SUCCEEDED(StrRetToStrW(&path, id, &pathUTF16))) {
if(wcslen(pathUTF16) < MAX_PATH) {
wcscpy(result, pathUTF16);
status = true;
}
CoTaskMemFree(pathUTF16);
}
}
return status;
}
// Compares two PIDLs for bitwise equality.
// • applies for child IDLs to the same folder
// • there may be WinAPI calls for that, but I didn’t find any
bool equalBitwise(
ITEMIDLIST const * a,
ITEMIDLIST const * b
) {
for(;;) {
auto lenA = a->mkid.cb;
auto lenB = b->mkid.cb;
if(0 == lenA) {
return 0 == b->mkid.cb; // they equal if they’re both ending
}
if(0 == lenB) {
return false; // “b” ended, but not “a”
}
// Compare the data lengths. This is important to avoid out-of-bounds reads when comparing the payload.
if(lenA != lenB) {
return false;
}
// Compare the payloads. Consider: “sizeof cb” is already included in its value.
auto toABytes = a->mkid.abID;
auto toBBytes = b->mkid.abID;
auto const toEndOfABytes = toABytes + (lenA - sizeof a->mkid.cb);
do {
if(*toABytes != *toBBytes) {
return false;
}
} while(++toBBytes, ++toABytes < toEndOfABytes);
// Now begins the next ID.
a = (ITEMIDLIST const *)toABytes;
b = (ITEMIDLIST const *)toBBytes;
}
}
void releaseItems(FolderIterator::ItemID * items, int count) {
for(auto it = items; it < items + count; ++it) {
CoTaskMemFree(*it);
}
release(items);
}
// Enumerates all items in the given list.
// • returns “false” on failure
// • otherwise, “releaseItems()” after use
bool enumerate(
FolderIterator::ItemID * & result,
int & count,
IEnumIDList & items
) {
auto status = false;
auto constexpr blockSize = 1024;
result = tryToReserve<FolderIterator::ItemID>(blockSize);
if(nullptr != result) {
// Retrieve the list content:
// • on Windows XP, “IEnumIDList::Next()” reads just one item at a time (i.e. must be called in a loop)
// • on later systems, reads are unrestricted, which is significantly faster than iteration (10-fold?)
// Therefore, retrieve in a loop.
// A an error occured
// B or no more items were retrieved (not covered by A — even out-of-range reads return partial success)
count = 0;
for(;;) {
if(false == tryToResize(result, count + blockSize)) {
break; // out of memory — abort
}
ULONG idsRetrieved;
if(FAILED(items.Next(blockSize, result + count, &idsRetrieved))) {
break; // error — abort
}
count += int(idsRetrieved);
if(0 == idsRetrieved) {
status = true;
break; // done
}
}
if(false == status) {
releaseItems(result, count); // clean up what has been enumerated so far
}
}
return status;
}
bool iteratorFromView(
FolderIterator & result,
IDispatch & view
) {
auto status = false;
// Getting a view of the currently open folder is mostly jumping through OOP hoops:
// • a folder view (“IFolderView”) is a kind of shell view (“IShellView”)
// • shell views are obtained from browsers (“IShellBrowser”)
// • browsers are services, so the current window must be a service provider (“IServiceProvider”)
IServiceProvider * serviceProvider;
if(SUCCEEDED(view.QueryInterface(IID_IServiceProvider, (void**)&serviceProvider))) {
IShellBrowser * shellBrowser;
if(SUCCEEDED(serviceProvider->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&shellBrowser))) {
IShellView * shellView;
if(SUCCEEDED(shellBrowser->QueryActiveShellView(&shellView))) {
if(SUCCEEDED(shellView->QueryInterface(IID_IFolderView, (void**)&result.view))) {
// All item information is relative to the folder that is being viewed:
if(SUCCEEDED(result.view->GetFolder(IID_PPV_ARGS(&result.folder)))) {
// Enumerate all items in the folder view (not in the folder itself) — this minds the view order. E.g.:
// • if the user sorts files by date (ascending), get the results sorted by date (ascending)
// • if the user is searching a drive, get the search results in the displayed order
IEnumIDList * items;
if(SUCCEEDED(result.view->Items(SVGIO_FLAG_VIEWORDER + SVGIO_ALLVIEW, IID_IEnumIDList, (void**)&items))) {
result.current = 0;
result.start = 0;
status = enumerate(result.itemIds, result.numberOfItems, *items);
items->Release();
}
if(false == status) {
result.folder->Release();
}
}
if(false == status) {
result.view->Release();
}
}
shellView->Release();
}
shellBrowser->Release();
}
serviceProvider->Release();
}
return status;
}
bool iteratorFromFolder(
FolderIterator & result,
WCHAR const * path
) {
auto status = false;
// Get the folder’s IDL:
if(auto folderIDL = ILCreateFromPath(path)) {
// Any folder must be navigated from the root (the Desktop):
IShellFolder * desktop;
if(SUCCEEDED(SHGetDesktopFolder(&desktop))) {
// Navigate to the folder:
if(SUCCEEDED(desktop->BindToObject(folderIDL, nullptr, IID_IShellFolder, (void**)&result.folder))) {
// Get a list of all files in the folder (likely in alphabetical order):
IEnumIDList * items;
if(SUCCEEDED(result.folder->EnumObjects(nullptr, SHCONTF_STORAGE, &items))) {
result.view = nullptr;
result.current = 0;
result.start = 0;
status = enumerate(result.itemIds, result.numberOfItems, *items);
items->Release();
}
if(false == status) {
result.folder->Release();
}
}
desktop->Release();
}
ILFree(folderIDL);
}
return status;
}
// Creates a new iterator for the content of the given explorer window.
// • returns “false” on failure
// • otherwise, must be “destroy()”ed after use
bool iteratorFromWindow(
FolderIterator & result,
HWND const explorerWindow
) {
auto status = false;
// Get the list of all currently open Shell windows:
IShellWindows * shellWindows;
if(SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, (void**)&shellWindows))) {
// The Desktop is treated special:
// • it is not listed in “shellWindows” — it must be obtained via the Desktop’s PIDL
// • its handle is NOT obtained via “GetDesktopWindow()” (returns the background window) but via “GetShellWindow()”
if(explorerWindow == GetShellWindow()) {
VARIANT desktopPIDL;
desktopPIDL.vt = VT_I4;
desktopPIDL.intVal = CSIDL_DESKTOP;
VARIANT empty;
empty.vt = VT_EMPTY;
long dummy; // this is critical although MSDN states it’s not
// Test for full success — “FindWindowSW()” may return “S_FALSE” on failure (Windows XP!):
IDispatch * dispatch;
if(S_OK == shellWindows->FindWindowSW(&desktopPIDL, &empty, SWC_DESKTOP, &dummy, SWFO_NEEDDISPATCH, &dispatch)) {
status = iteratorFromView(result, *dispatch);
dispatch->Release();
}
} else {
// Iterate all open Shell windows (but not the Desktop) via index:
// • the index must be a signed integer type (4-B unsigned has a special meaning)
// • check “Item()” for full success (even out-of-range accesses succeed partially)
IDispatch * dispatch;
VARIANT shellWindowIndex;
shellWindowIndex.vt = VT_I4;
shellWindowIndex.lVal = 0;
while(false == status && S_OK == shellWindows->Item(shellWindowIndex, &dispatch)) {
// Don’t know why this uses “IWebBrowserApp” specifically:
// • “IWebBrowserApp” is deprecated and information is impossible to find
// • for some reason, “IWebBrowserApp::get_HWND()” works fine, but “IShellBrowser::GetWindow()” is useless
IWebBrowserApp * webBrowser;
if(SUCCEEDED(dispatch->QueryInterface(IID_IWebBrowserApp, (void**)&webBrowser))) {
// Is this the window we’re looking for?
HWND suspectHWND;
if(SUCCEEDED(webBrowser->get_HWND((LONG_PTR*)&suspectHWND)) && suspectHWND == explorerWindow) {
// Create the iterator:
status = iteratorFromView(result, *dispatch);
}
webBrowser->Release();
}
dispatch->Release();
++shellWindowIndex.lVal; // next window
}
}
shellWindows->Release();
}
return status;
}
// Seeks forward to the item at the requested path, based on the given folder view.
// • the path must be absolute and parsable
// • returns “false” on failure
// • often significantly faster than a folder seek because it uses selections to narrow down the desired item
// • does not work with fast selection changes and on Windows XP, though
bool seekUsingView(
FolderIterator & iterator,
IFolderView & view,
WCHAR const * requestedPath
) {
// There doesn’t seem to be any other method than extracting each item’s path and comparing it to the requested path:
// • all IDLs are child IDLs
// • the only way to retrieve absolute IDLs is a roundtrip IDL -> parsable path -> IDL
// — cannot combine with folder IDL: virtual folders like searches and libraries have no IDLs in the first place
// — for searches and libraries, items can be at completely different paths
//
// This process is incredibly slow (~10,000 items/s), but the number of items can be reduced drastically if only *selected*
// items are compared (typically, this is just one — the file that launched the application). Having found the IDL, it can be
// found in the item list via binary comparison of IDL (much faster; ~100.000.000 items/s).
if(0 == iterator.numberOfItems) {
return false; // nothing to seek
}
// Enumerate all selected items in the view:
auto status = false;
FolderIterator::ItemID * selectedItemIDS;
int selectedItemCount;
IEnumIDList * items;
if(SUCCEEDED(iterator.view->Items(SVGIO_SELECTION, IID_IEnumIDList, (void**)&items))) {
if(enumerate(selectedItemIDS, selectedItemCount, *items)) {
if(0 < selectedItemCount) {
// Extract each item’s path:
WCHAR candidatePath[MAX_PATH];
auto toSelectedID = selectedItemIDS;
auto const toEndOfSelectedIDs = selectedItemIDS + selectedItemCount;
do {
// Is this the path we’re looking for?
if(currentPathOf(*iterator.folder, *toSelectedID, candidatePath) && 0 == wcscmp(requestedPath, candidatePath)) {
// Search that very same ID in the item list:
auto toID = iterator.itemIds;
auto const toEndOfIDs = toID + iterator.numberOfItems;
do {
if(equalBitwise(*toSelectedID, *toID)) {
// Found it — assign the iterator; clean up; leave!
iterator.current = int(toID - iterator.itemIds);
status = true;
goto found;
}
} while(++toID < toEndOfIDs);
}
} while(++toSelectedID < toEndOfSelectedIDs);
found:
;
}
releaseItems(selectedItemIDS, selectedItemCount);
}
items->Release();
}
return status;
}
// Seeks forward to the item at the requested path.
// • the path must be absolute and parsable
// • returns “false” on failure
// • incredibly slow (~10,000 items/s), but the only fallback solution for fast selection changes and Windows XP
bool seekUsingFolder(
FolderIterator & iterator,
WCHAR const * requestedPath
) {
if(0 == iterator.numberOfItems) {
return false; // nothing to seek
}
// Check each item’s path (XP fallback):
WCHAR candidatePath[MAX_PATH];
auto toID = iterator.itemIds;
auto const toEndOfIDs = toID + iterator.numberOfItems;
do {
if(currentPathOf(*iterator.folder, *toID, candidatePath) && 0 == wcscmp(requestedPath, candidatePath)) {
iterator.current = int(toID - iterator.itemIds);
return true;
}
} while(++toID < toEndOfIDs);
return false;
}
// Entry point for a thread which initializes the given iterator.
// • requires a shell window handle (typically, this is the foreground window)
// • “initialPath” may be “nullptr” to start at the first item; otherwise it seeks to the according item
DWORD WINAPI iteratorInitializer(void * toContext) {
auto & result = *reinterpret_cast<FolderIterator *>(toContext);
// The shell is COM-based:
if(SUCCEEDED(CoInitializeEx(0, COINIT_MULTITHREADED))) {
auto status = false;
// Try both enumeration methods:
// 1. the foreground window’s item order (if such a window is open, in foreground, and its content is stable)
// 2. the folder’s item order (usually alphabetic)
if(iteratorFromWindow(result, result.initialForegroundWindow)) {
status = true;
} else {
// Try to use the folder’s default item order:
WCHAR folderPath[MAX_PATH];
wcscpy(folderPath, result.initialPath);
PathRemoveFileSpecW(folderPath);
if(iteratorFromFolder(result, folderPath)) {
status = true;
}
}
// The folder is enumerated; now seek the initial item (if requested):
if(status && nullptr != result.initialPath) {
if(nullptr != result.view && seekUsingView(result, *result.view, result.initialPath)) {
// OK
} else if(seekUsingFolder(result, result.initialPath)) {
// slow, but still OK
} else {
// The iterator is OK, but it won’t behave like the caller expects it — abort!
destroy(result);
status = false;
}
}
if(status) {
result.status = FolderIterator::Status::ok;
} else {
result.status = FolderIterator::Status::error;
}
CoUninitialize();
}
return 0;
}
} // locals
// Creates an iterator for all items in the shell window that is currently in the foreground.
// • returns “nullptr” on failure
// • otherwise, must be “destroy()”ed after use
// • “path” may be “nullptr” to start at the first item; otherwise it seeks to the according item
FolderIterator * tryToEnumerateShellNeighbors(WCHAR const * path) {
// Reserve the iterator:
if(auto toResult = tryToReserve<FolderIterator>()) {
// Gather all initialization data:
toResult->initialForegroundWindow = GetForegroundWindow();
if(nullptr != (toResult->initialPath = tryToReserve<WCHAR>(wcslen(path) + 1))) {
wcscpy(toResult->initialPath, path);
// Start the initialization and return immediately:
toResult->status = FolderIterator::Status::pending;
if(nullptr != (toResult->toThread = CreateThread(nullptr, 0, &iteratorInitializer, toResult, 0, 0))) {
return toResult;
}
release(toResult->initialPath);
}
release(toResult);
}
return nullptr;
}
// Marks the current position as point of failure for “next()”. Use this to avoid running in circles forever.
void setSentinel(FolderIterator & iterator) {
if(false == isValid(iterator)) {
return;
}
iterator.start = iterator.current;
}
// Proceeds to the next (or previous) item in the folder and returns its path.
// • returns “false” when the end set by “setEnd()” is reached or if the folder is empty
bool next(
FolderIterator & iterator,
WCHAR * result,
bool reverse
) {
if(false == isValid(iterator)) {
return false;
}
if(0 == iterator.numberOfItems) {
return false;
}
auto nextIndex = iterator.current;
if(reverse) {
// If at the start, start over at the end.
if(0 == nextIndex) {
nextIndex = iterator.numberOfItems;
}
--nextIndex;
} else {
// Proceed to the next item. If reaching the end, start over.
++nextIndex;
if(nextIndex == iterator.numberOfItems) {
nextIndex = 0;
}
}
if(nextIndex == iterator.start) { // Are we back where we started?
return false;
}
if(currentPathOf(*iterator.folder, iterator.itemIds[nextIndex], result)) {
iterator.current = nextIndex;
return true;
}
return false;
}
void destroy(FolderIterator & iterator) {
isValid(iterator); // wait for pending operations
if(nullptr != iterator.view) {
iterator.view->Release();
}
iterator.folder->Release();
releaseItems(iterator.itemIds, iterator.numberOfItems);
release(&iterator);
}
Getestet unter Windows 7 & Windows XP. Funktioniert in Ordnern wie in Suchen. Ausnahme: Funktioniert nicht auf Windows XPs Desktop, weil MS diese Schnittstelle erst mit Vista eingeführt hat.