(gelöst)[WinAPI] Befinde ich mich in einer Suche?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Die gute Nachricht ist schonmal, dass ich von meinen zwei Sekunden eine auf std::vector::push_back() verschwende … also zur restlichen Sekunde.

Der kritische Teil ist die erste Achtelsekunde, in der das Explorer-Fenster gesucht wird. In der Zeit darf man es wirklich nicht schließen. Da ich bereits direkt bei Programmstart das Handle des Fensters habe, könnte ich es sperren, um ein Schließen zu verhindern. Ich schätze, dass die Fotogalerie genau das macht. Ist mir aber zu dreckig.

Danach geht eine Sekunde für die Enumeration der Dateien drauf, *aber*: Ist die Enumeration einmal gestartet, läuft sie auch voll durch, falls das Fenster in der Zwischenzeit geschlossen wurde. Jedenfalls, wenn das Verzeichnis real auf der Festplatte existiert. Ist es eine Suche, wird die Enumeration abgebrochen. Das ist konsistent mit dem, was Chromanoid am Anfang des Threads geschrieben hat:
Je nachdem wie schnell man den Explorer schließt und wie lange die Suche dauert und wie viele Ergebnisse kommen, funktioniert das mit der "Diashow" oder nicht.
Chromanoid hat geschrieben:Also ich benutze Win 10 und wenn ich da die Fotogalerie geöffnet habe und direkt nach dem Öffnen das nächste Bild will, dauert das ein Weilchen.
Also machen sie es wie ich, dass sie im Hintergrund-Thread enumerieren.
Den Explorer konnte ich auch schnell schließen und dann konnte ich auch nicht das nächste Bild anschauen.
Dann haben sie das gegenüber Windows 7 geändert; hier geht es (und in meinem Code auch). Ich hoffe, dass sie das in der Fotogalerie geändert haben (und nicht in der Explorer-Implementierung!)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Alexander Kornrumpf »

Chromanoid hat geschrieben:Zudem ist es bei meinem System so, dass solange die Suche noch nicht abgeschlossen ist, das Weiterklicken in der Diashow vorläufig nicht oder sogar nie funktioniert. Ich hatte dann auch noch mal kurz IfranView ausprobiert, da nimmt er glaube ich die nächste Datei nach Namen, egal was im Explorer ist. Die Funktion der Fotogalerie, sei sie auch noch so fragil, finde ich besser und intuitiver. Wenn die "nächstes Bild"-Funktion mal nicht funktioniert, schließe ich glaube ich unbewusst die Fotogalerie und öffne das Bild erneut, komplett im Autopilot...
Ohne dir widersprechen zu wollen, ist es interessant, wie verschieden wir da sind. Nur zum allgemeinen Amüsement: IrfanView ist bei Neuinstallationen immer eines der ersten Tools was ich herunterlade und es ist bei mir auch Default-App für all things graphics. Bei allen Quirks die es hat, habe ich es noch nie komisch oder unintuitiv gefunden, was beim scrollen als nächstes kam, was tatsächlich stört, ist dass es auch nicht-Bilder öffnen will und, ich bin nicht ganz sicher, Probleme hat, wenn das Ende des Ordners erreicht ist. Ich bin damit nicht 100% zufrieden, aber der Windows-Bildbetrachter erschien allein vom Funktionsumfang her nie eine ernstzunehmende Alternative. Das Fenster schließen und neu öffnen zu müssen, weil etwas aufgehört hat zu funktionieren würde mir wahrscheinlich als Zumutung auffallen. Die Windows-Suche halte ich, vielleicht liegt es an mir, für praktisch nicht funktional, auf dem System auf dem ich wirklich mal regelmäßig was suchen muss, hatte ich irgendwann aufgegeben und Google-Desktop installiert. Jetzt hat Google das eingestellt. Wie bei IrfanView ist keine der Alternativen perfekt, aber für mich immer noch besser als das Standardverhalten.

Und das ist nichtmal Anti-MS-Sentiment. Betriebssysteme an sich können sie ja inzwischen offenbar einigermaßen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Ja; ich kann die Standardsuche auch nicht ausstehen. Was sowas betrifft: Die Schnittstellen, die ich nutze, sind nicht Explorer-spezfifisch sondern müssten von *jeder* Art von Datei-Explorer unterstützt werden (jedenfalls drängt MS die Entwickler in der Dokumentation stark dazu, das zu implementieren). Es gibt also Hoffnung, dass mein Viewer (und die Fotogalerie?) auch mit anderen Datei-Explorern und Suchprogrammen funktionieren. Das zu testen passt aber nun wirklich nicht mehr in meinen Terminplan.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Spiele Programmierer »

ie Windows-Suche halte ich, vielleicht liegt es an mir, für praktisch nicht funktional
Ich schließe mich an. Windows XP und zuvor gab es eine prima Dateisuche - dann wurde sie derart ruiniert, dass sie nun völlig unbrauchbar ist. Man findet damit praktisch nie das, was man sucht. Ich frage mich wirklich, was Microsoft sich dabei gedacht hat.

Zum Glück gibt es Alternativen. Ich nutze seit einiger Zeit "Agent Ransack". Das findet in einem Bruchteil der Zeit die Dateien, bei denen die Windows Suche nach einer langen Wartezeit aufgegeben hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Habe den Flaschenhals gefunden – IEnumIDList::Next() sollte man nicht für jeden Item einzeln aufrufen, sondern sich gleich ein Array zurückgeben lassen. Suche mit 20.000 Bildern enumeriert nun in unter einer halben Sekunde.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Bleibt der Flaschenhals mit dem Suchen des geöffneten Bildes. Ich kriege ja eine Liste mit den IDs jedes Items – aber ich weiß nicht, welche ID zu dem Pfad gehört, mit dem mein Programm gestartet wird (wo ich also anfangen muss). Also konvertiere ich alle IDs zu Pfaden und vergleiche die mit dem Pfad der Datei, die geöffnet wird.

Bei 20.000 Items ist das ganz schön viel Arbeit (fast eine Sekunde).

Meine Hoffnung war, dass ich den Pfad der geöffneten Datei direkt in eine ID umwandeln könnte und dann nur IDs vergleichen müsste. Denn – zur Hölle – dafür sind IDs ja da! Dummerweise sind die IDs nicht einzigartig, sondern das Verzeichnis spuckt mir eine Liste von IDs relativ zum Hauptverzeichnis aus, aber die ID der geöffneten Datei ist absolut. Ich kriege die ums Verrecken nicht verglichen.

Nur bin ich total verwirrt, wie das jetzt mit Suchen ist. Dort kann jede Datei in einem eigenen Verzeichnis liegen, also dürften die IDs niemals singe-level sein. Außerdem sind Suchen keine echten Dateipfade; da kriege ich auch keine ID des aktuellen Verzeichnisses. Es ist zum kotzen.

Ich könnt’s vielleicht eingrenzen, indem ich zuerst die aktuell im Fenster markierten Items vergleiche (meist nur einer statt 20.000), und nur auf den langsamen Algorithmus zurückfalle, falls das keinen Treffer ergibt. Aber WTF, was ist das für ein Design?!

Nachtrag: Okay; wenn ich zuerst nur die ausgewählten Items durchsuche, reduziert das von 750 ms auf 6 4 ms. Und ich musste mir eine eigene Funktion für bitweise Vergleiche von IDLs schreiben. Schmutzig, aber gut.

Nachtrag 2: Lustig ist auch der Speicherverbrauch des Explorers: 2 KiB pro Datei in meinem Prozess. Mit 20.000 Dateien also 40 MiB Overhead. Beachtlich für ein Programm, das allein nicht einmal 4 MiB verbraucht. Trennt man die Verbindung, ist der Speicher auf einen Schlag wieder frei.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Sooo … multi-Threading ist drin.

Die Initialisierung ist 0.12 s nach dem Item-Doppelklick fertig. Wenn man innerhalb dieser Zeit das Quellfenster schließt oder den Fokus verschiebt, sind die Zurück- und Weiter-Knöpfe nicht verfügbar. Das ist aber ein so kurzes Zeitfenster, dass man es nur mit böser Absicht schafft (rapide Kombination aus ENTER und Mausklicks) oder unter extremer Systemlast.

Danach wird enumeriert. Das dauert bestenfalls 0.15 s (bei fast leeren Ordnern); schlimmstenfalls bis zu 1.5 s (erste Abfrage bei zehntausenden Suchergebnissen). Klickt man in dieser Zeit Zurück oder Weiter, reagiert die UI mit kurzer Verzögerung (wenn der Enum-Thread fertig ist). In normaler Nutzung fällt das nicht auf bzw. äußert sich höchstens als sehr kurzer Lag. Schließt man in dieser Zeit das Fenster, beeinträchtigt das nicht die Funktion.

Dateien löschen macht ebenfalls nichts kaputt. Sie werden dann einfach übersprungen.

Jetzt sollte ich eigentlich Ordneränderungen und neu eingefügte Dateien überwachen und dann neu enumerieren und Listen abgleichen und so, aber das waren schon sechs(!!!) wirklich harte Nächte und ich habe einfach keinen Bock mehr. (Ich habe auch nur so viel Zeit investiert weil ich mal Shell-Programmierung kennenlernen wollte. Was für eine bescheuerte Schnapsidee.)

Der Stand auf Windows-Fotogalerie-Level muss vorerst reichen. In den nächsten Tagen bügle ich vielleicht noch die Randfälle glatt (Navigieren durch komplett leere Ordner und so) und dann gibt’s hier den Quelltext, damit nach mir niemand mehr diese Shell-Folter ertragen muss.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Okay, an dieser Stelle bin ich echt sauer auf Windows XP. Die Implementierung von IEnumIDList::Next() ist geradezu lächerlich.

Windows 7, 8, 8.1, 10:
Hey Windows! Hier ist ein Puffer für 1000 Items. Mach voll!
1000 Items hat das Verzeichnis nicht. Es hat nur drei. Hier sind drei!
Windows XP:
Hey Windows! Hier ist ein Puffer für 1000 Items. Mach voll!
1000 Items hat das Verzeichnis nicht. Es hat nur drei. Hier … ist EINER!
Im Fach nennt man das Short Reads, und passiert z.B. auch bei ReadFile() und kein Schwein fängt sowas ab. Aber trotzdem … irre.

Unter Windows XP darf außerdem der dritte Parameter nicht NULL sein, obwohl MSDN das explizit erlaubt.

Weiterhin gibt es für Elemente andere IDs zurück wenn sie ausgewählt sind als wenn sie nicht ausgewählt sind. Also funktionieren *beide* Tricks, die die Enumerationszeit von zwei Sekunden auf 0.2 gedrückt haben, nicht mehr. WTF?!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Der Plan war:
  1. Quelltext kommentieren
  2. Prüfen, ob’s auf XP läuft
  3. Quelltext hier hochladen
Bei 2. habe ich die Fehler oben gefunden und noch schnell korrigiert; den anderen Rechner eingeschaltet um was zu drucken … und die Sicherung ist rausgeflogen. Jetzt habe ich
  • list.cpp
  • list.cpp~RF1f2347dc.TMP
… und beide bestehen aus 17 KiB Nullen. Es gibt keine Sicherheitskopien des ganzen.

Any Advice? Ich nutze gerade 7-Zip, um die Rohdaten des Volumes zu kopieren. Die werde ich dann nach Quelltext durchsuchen, der wie die verlorenen Daten aussieht.

Nachtrag: 12 KiB im Hexdump wiedergefunden … bleiben zwei fehlende Sektoren

Nachtrag 2: Dummerweise finde ich das Ganze *mehrfach* wieder; so ziemlich jede Version von heute liegt irgendwie in einzelne Sektoren zerstückelt vor. Schönes Puzzle.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

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!

Schnittstelle:

Code: Alles auswählen

struct FolderIterator;

// 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
extern FolderIterator * tryToEnumerateShellNeighbors(wchar_t const * pathOrNull);

// Marks the current position as point of failure for “next()”. Use this to avoid running in circles forever.
extern void setSentinel(FolderIterator &);

// 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
extern bool next(
	FolderIterator & ,
	wchar_t *        result,
	bool             reverse
);

extern void destroy(FolderIterator &);
Implementierung:

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.
Zuletzt geändert von Krishty am 12.06.2016, 04:33, insgesamt 3-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Helmut »

Danke für die Mühe :) Zu schade, dass ich keine Software entwickle, wo ich das einbauen könnte.

Hast du übrigens an den Spezialfall gedacht, dass ein Bild im Tempordner geöffnet wird? Dann deaktiviert die Fotogallerie nämlich die Bildnavigation, weil dann zu völlig zusammenhangslosen Bildern gewechselt werden würde. Das passiert hauptsächlich, wenn Bilder aus anderen Programmen geöffnet werden.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Stimmt -- kommt auch noch rein; danke :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (gelöst)[WinAPI] Befinde ich mich in einer Suche?

Beitrag von Krishty »

Für den Desktop wird das ganze erst ab Windows Vista klappen. Raymond Chen schreibt:
The Old New Thing — Manipulating the positions of desktop icons hat geschrieben:The third parameter describes the types of windows we are looking for. We use the special SWC_DESKTOP flag (available starting in Windows Vista) to say, "Hey, I know the desktop isn't the sort of thing people think of when they go looking for Explorer windows, but I know what I'm talking about, so let me have it."
Und weiter:
StackOverflow – Desktop icon file path hat geschrieben:
To get the desktop, use IShellWindows::FindWindowSW with the SWC_DESKTOP flag. […] – Raymond Chen Apr 4 '12 at 16:23

Raymond Chen, thanks a lot. But IShellWindows::FindWindowSW supports SWC_DESKTOP flag only for Vista and higher. […] – Sergey Yukish Apr 19 '12 at 15:54

It's not supported on Windows XP. The interface for getting this information was not added until Vista. – Raymond Chen Apr 19 '12 at 16:37
Kurz: Bis Vista gab es unter Windows schlicht keine praktikable Möglichkeit, an das IShellView des Desktops zu kommen. (Das ist nicht zwingend schlecht – sie haben es möglicherweise gemacht, damit nicht jede Anwendung nach Belieben den Desktop zumüllen kann.)

Nachtrag: Funktioniert ab Vista und ist oben nachgetragen.

Nachtrag: Die Fallback-Lösung ist auch eingebaut; falls man also via Kommandozeile startet oder das Fenster sehr schnell schließt, wird zumindest noch alphabetisch enumeriert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten