Laden...

Wie kann ich aus einer gescannten Datei das Gerät auslesen/ändern, das diese Datei gescannt hat?

Erstellt von OXO vor 3 Jahren Letzter Beitrag vor 3 Jahren 2.222 Views
O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren
Wie kann ich aus einer gescannten Datei das Gerät auslesen/ändern, das diese Datei gescannt hat?

Hallo zusammen,

in "Windows Fax- und Scan" sehe ich nach dem Scannen eines Dokumens/Bilds in der Spalte "Qulle" den Scanner, der diese Datei gescannt hat.

Irgendwo muss diese Information in der Datei auch ausgelesen bzw. gesetzt werden können.

Wie kann ich dies aus der Datei nachträglich per C# auslesen bzw. abändern?

5.658 Beiträge seit 2006
vor 3 Jahren

Das wird sicherlich ein Eintrag in den EXIF-Daten des Bildes sein. Du kannst dir die Einträge anschauen, indem du mit der rechten Maustaste auf die Datei klickst, und dann unter "Eigenschaften" die "Details"-Seite aufrufst. Zum Auslesen mit C# gibt es fertige Bibliotheken.

Weeks of programming can save you hours of planning

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Leider sehe ich schon keine Informationen in Windows, die diese Information enthalten.

Habe noch mit der WindowsAPICodePack-ShellExtension Informationen ausgelesen, aber leider findet sich diese auch nicht bei den ganzen DeviceInformationen. Zumindest finde ich sie dort nirgends 😦

5.658 Beiträge seit 2006
vor 3 Jahren

Wenn die Infos nicht in der Datei stehen, kannst du sie auch nicht auslesen oder ändern.

Wenn beim nächsten Öffnen der Datei die Werte wieder angezeigt werden, sind sie aber wohl doch darin gespeichert.

Kannst du mal erklären, was du _eigentlich _vor hast? Um was für Dateien handelt es sich hier? Was willst du damit erreichen? Deine anderen Beiträge ([gelöst] SendMessage an ListView in anderem Prozess und Message Hook für andere Anwendungen) deuten irgendwie auf eine sehr unüberlegte Herangehensweise hin.

Weeks of programming can save you hours of planning

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Mein Vater nutzt "Windows-Fax- und -Scan" zum Scannen. Dort werden die Dateinamen für jeden neuen Scan erst einmal benannt mit "Bild". Ist diese Datei bereits vorhanden, wird ein neuer Scan "Bild (2)", "Bild (3)" etc. genannt. Löscht man z.B. "Bild (2)", wird für einen nächsten Scan diese Lücke gefüllt und es entsteht nicht "Bild (4)", sondern "Bild (2)".

Mein Vater möchte die Dateien gerne durchnummiert haben.

Ich habe ihm daher ein Tool mit einem FileSystemWatcher geschrieben, der diese neuen Dateien umbenennt, sobald sie in dem Verzeichnis nach dem Scannen erzeugt wurden.

Es gab bei ihm aber mehrere Umstände, die ich gerne aus Anwendersicht glatt gezogen hätte. So kamen nach und nach bei der Lösungsfindung neue fragen auf. Natürlich nicht nur bezogen auf dieses Problem, sondern auch allgemeiner Natur, weil ich mich zwischendurch gefragt habe, wie sowas gehen könnte.

Bei ihm war es so, dass z.B. der FileSystemWatcher immer 2x angesprungen ist, obwohl ich die Message in der CallBack-Funktion des OnCreated-Events auf "Created" abgefragt habe. Ich vermute, dass der Scanner-Treiber erst temporär eine Datei aufmacht und am Ende hab ich dann 2x das Event. Dann war die Feststellung, dass im ListView 2 Dateien auftauchen, die gescannte und meine neu benannte Datei. Nur, meine umbenannte Datei hatte die Information in der Spalte "Quelle" verloren. Diese stand als eine unbekannte Quelle bei der Datei im ListView. Die Original-Datei vom Scanner nach dem Scan, hatte diese Information aber dran. Dann wollte ich das zuerst von der alten Datei auslesen und in meine neue Datei übernehmen. Bin aber auch wieder davon abgekommen, da das ListView eh einen falschen Namen hatte.

Dann bin ich zwischenzeitlich zu dem Entschluss gekommen, dass ich mit meinem Umkopieren doch solange warte, bis der Scanner-Treiber seine Datei wirklich fertig erzeugt hat und ich erst dann umkopieren kann. Das hab ich dann gemacht, damit war aber das ListView nicht mehr Up-to-Date und hat die Datei so angezeigt, wie sie ursprünglich nach dem Scannen benannt war und nicht, wie ich sie neu genannt hab. Dafür stand aber die Information mit der Scanner-Quelle drin.

Somit war mein nächster Ansatz, ob ich nicht das ListView entsprechend umbenennen könnte nach der Umkopier-Aktion. Ich hatte gesehen, dass das ListView nicht neu initialisiert wird, egal was für Refresh-Aktionen ich da per API versucht hatte. Alles natürlich auch unter dem "Forschungsgedanken" wie macht man x oder y ganz generell. Da das mit dem ListView, wie Du aus den anderen Threads raus gelesen hast, etwas tricky war, kamen mehrere Überlegungen rein.

Zwischendrin dachte ich mir auch, gut vielleicht könnte ich mich auch einfach an die Nachrichten des ListView dran hängen und vor dem Einfügen den Eintrag manipulieren. Jetzt ließ sich das Problem ja anderweitig über das TreeView lösen, aber wie man die Nachrichten von anderen Controls abfängt und wie man sich rein hängen könnte, interessiert mich trotzdem noch, auch wenn das nicht als Lösung genommen wird 😃

5.658 Beiträge seit 2006
vor 3 Jahren

Wow. So viel Arbeit, um die Dateireihenfolge von einem 30 Jahre altem Programm zu ändern, für das du nichtmal den Quellcode hast. In der Zeit hättest du ein eigenes Scan-Tool schreiben können.

Da wird dir wohl niemand weiterhelfen können.

Deinem Vater würde ich ein moderneres Tool zum Scannen empfehlen, bei dem man die Dateinamen selbst festlegen kann.

Weeks of programming can save you hours of planning

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Rein unter mit einer Aufwand <-> Nutzen Brille gebe ich Dir völlig Recht.
Allerdings sehe ich das auch unter dem Lern-Aspekt und das war ne interessante und teils lehrreiche Reise bis hierhin 😃

Vor allem die Erkenntnis, dass die Pointer ja irgendwie in fremdem Speicher liegen müssen und da die Dinge adressieren.

Vieles ist mir aber noch unklar, wie z.B. wo ich diese ganzen Werte für die Konstanten und auch die Strukturen für die API-Funktionen alle für C# nachschlagen kann? In der MSDN stehen die im C++ Code und teils mit Unterstrukturen und Pointern darauf. Mir ist immer noch nicht ganz klar, wo ich diese Informationen alle richtig für C# her bekomme und wie ich die richtig zusammenbasteln muss, damit die alle richtig versorgt sind.

4.938 Beiträge seit 2008
vor 3 Jahren

Für WinAPI ist es das einfachste, du installierst im VS zusätzlich C++ (und damit das Windows SDK) - in den Headerdateien (u.a. <windows.h> oder z.B. <commctrl.h>) stehen dann die Deklarationen der Funktionen mit den Strukturen etc.

Ansonsten finden sich viele Funktionen und Strukturen auch bei PInvoke.net (wofür es auch ein VS Add-In gibt).

Und teilweise einfach bei konkreten Namen von Typen, Strukturen oder Konstanten/Enums im Internet danach suchen (leider fehlen die internen Werte von Konstanten/Enums in der offiziellen MS-Doku).

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Für WinAPI ist es das einfachste, du installierst im VS zusätzlich C++ (und damit das
>
) - in den Headerdateien (u.a. <windows.h> oder z.B. <commctrl.h>) stehen dann die Deklarationen der Funktionen mit den Strukturen etc.

Was hältst Du davon, das gleich in einer C++ Datei in der eigenen C#-Anwendung zu realisieren? Oder macht es die Problematik dann von dieser zurück in die C#-Anwendung zu kommen nicht kleiner als direkt die Funktionen in C# anzusprechen und alle Typen zu recherchieren?

Ansonsten finden sich viele Funktionen und Strukturen auch bei
>
(wofür es auch ein VS Add-In gibt).

Und teilweise einfach bei konkreten Namen von Typen, Strukturen oder Konstanten/Enums im Internet danach suchen (leider fehlen die internen Werte von Konstanten/Enums in der offiziellen MS-Doku).

So habe ich das jetzt auch gemacht, aber das ist schon ne recht mühsame Recherche hatte ich den Eindruck. Irgendwie kenne ich die MSDN von früher noch, wo ich auch hin und wieder noch in VB vor .NET-Zeiten API-Calls gemacht hab. Da kam mir das in der MSDN transparenter vor und die Werte der Konstanten waren genauer beschrieben, so zumindest meine Erinnerung.

Nehmen wir gleich mal als Parade-Beispiel die SetWindowsHookExA-Beschreibung aus der MSDN. Da sind gleich mehrere Parameter, bei denen ich mich frag, wie ich die in die C#-Welt übertrage und richtig abbilde? HHOOK, HOOKPROC, HINSTANCE. Genauso bestimmte Pointer-Typen


HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

Gibt es ne gute Einstiegsseite, bei der ich die generellen Grundprinzipien zum Umgang mit Pointern, Marshal etc. für Win API-Aufrufe erklärt sind?

4.938 Beiträge seit 2008
vor 3 Jahren

Du meinst eine eigene C++/CLI Assembly im Projekt zu verwenden (anstatt P/Invoke)? Aber auch dort mußt du ja dann C#-Datentypen zur Übergabe benutzen, die man ersteinmal wissen muß.

Für die Erzeugung der P/Invoke-Schnittstelle in C# aus C heraus kannst du den PInvoke Interop Assistant benutzen (ich kenne zwar nur die Vorgängerversion, die dort verlinkt ist, aber optisch und benutzertechnisch war diese doch sehr gewöhnungsbedürftig - und das hat sich hoffentlich geändert). Jedoch habe ich dort direkt keine EXE (bzw. Installer) gesehen, so daß man das Projekt wohl selber bauen muß.
Evtl. dann doch die ältere Version verwenden: auf CodePlex Archive: clrinterop "download archive" nutzen und das gesamte Archiv (~35MB) herunterladen. Die Anwendung heißt "winsiggen.exe" (und dort dann den 3. Tab "SigImp Translate Snippet" benutzen).

Edit: Ich habe noch eine Doku dazu gefunden: P/Invoke Interop Assistant - Overview

Außerdem gibt es wohl noch andere Tools: Re-inventing the p/invoke generator

Und wenn man sucht, dann findet man noch mehr: P/Invoke (eine Umsetzung der meisten System-DLLs, wenn auch nicht vollständig).

Noch ein Edit:
Ich habe selber mal den Source-Code (C#) vom "PInvoke Interop Assistant" heruntergeladen und erfolgreich kompiliert (Projektdatei ist für VS 2015).
Es gibt nur noch 2 Tabs: "SigImp Search" und "SigImp Translate Snippet" - und das Generieren von P/Invoke-Deklarationen klappt immer noch. 😉

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Mir sind auch noch ein paar interessante Links untergekommen. Zum einen mit Grundlagen für das Marshaling und auch noch mit Datentypen, die man häufig in den C/C++ Deklarationen in der MSDN zu den Funktionen findet und was für ein Managed-Datentyp das ist. Vielleicht interessant für das eigene Erstellen der Strukturen:

Interop-Marshalling

Standard-Marshalling Verhalten

Marshallen von Daten mit Platformaufruf

Zuordnen von HRESULT Werten und Ausnahmen

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Hab mich jetzt mit dem Thema Hooking herum geschlagen und experimentiert.

Ist schon sehr aufwändig, aber ich hab es hinbekommen und es wird jetzt eine C++-DLL verwendet, mit der ich einen Hook setzen kann (z.B.

WH_CALLWNDPROC

). Von dort aus verzweigt es dann auch zurück in ein C# Progrogramm. Hier kann ich einige Messages sehen und bin auch soweit gekommen, dass ich eine

LVM_INSERTITEMW

verändere und den Text hierüber im Ziel-ListView des anderen Prozesses austausche.

Eine ganz interessante Erfahrung war, dass man Speicher für die Pointer zu den Strings immer im Ziel-Prozess allokieren muss und von der Managed-Welt dort hin rüber schieben muss. Eigentlich logisch, aber trotzdem gewöhnungsbedürftig. Ebenfalls, dass ich noch den lparam aus dem

CWPSTRUCT

wieder auspacken muss, um dann bei den Messages auf ein

LVM_INSERTITEMW

reagieren kann. Danach noch den lparam in ein

LV_ITEM

-Struct pressen. Also schon alles mit viel Aufwand verbunden.
Hattet Ihr mich nicht schon gewarnt? 😛

Beim Lesen bzw. auch beim Anlegen der Strings muss man immer eine size mit angeben. Da ist mir ehrlich gesagt bis jetzt noch nicht klar, wie viel ich denn hierfür reservieren muss bzw. woher ich z.B. beim Lesen weiß, wie viel ich reservieren soll? Genauso beim Schreiben/Anlegen der Strings hatte ich den Eindruck, ich muss auf meine str.Length noch eine Konstante oben drauf hauen muss, um eine größere size zu reservieren, damit es funktioniert.

Dann ist es so, dass man auch immer wieder mit

OpenProcess/VirtualAlloc/WriteMemory

arbeiten muss. Kann es sein, dass ich auch einen Gegenpart zum OpenProcess und dem VirtualAlloc brauche, um alles wieder freizugeben? Ich wundere mich nämlich, dass alles recht träge wirkt. Weiß aber auch nicht, ob das nicht einfach so ist?

Auch das Freigeben des pszTextPtrForeign wäre vermutlich noch sinnvoll, aber dann kann ich das ja nicht mehr erfolgreich zurückgeben, wenn ich den Speicher für diesen Pointer schon freigegeben hab.


public static IntPtr GetPtrToStringInProcessMemory(Process Process, string str)
{
	IntPtr ProcessHandle = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, Process.Id);

	const int stringMaxAddedSize = 128;
	int maxStringSize = 0;

	if (String.IsNullOrEmpty(str)) // not initialized
		str = new string('x', 32);

	int size = str.Length + stringMaxAddedSize;
	if (size > maxStringSize)
		maxStringSize = size;

	IntPtr pszTextPtrForeign = VirtualAllocEx(ProcessHandle, IntPtr.Zero, (uint)size, AllocationType.Commit, MemoryProtection.ReadWrite);
	IntPtr TextPtrLocal = Marshal.StringToHGlobalAuto(str);

	bool Ok = WriteProcessMemory(ProcessHandle, pszTextPtrForeign, TextPtrLocal, size, IntPtr.Zero);
	if (!Ok) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
	
	Marshal.FreeHGlobal(TextPtrLocal);

	return pszTextPtrForeign;
}

4.938 Beiträge seit 2008
vor 3 Jahren

Ja sicherlich mußt du jeweils die Gegenpart-Funktion aufrufen (Open/Close, Alloc/Free).
Am besten immer in die WinAPI-Doku schauen, also z.B. OpenProcess:

When you are finished with the handle, be sure to close it using the
>
function.

Gleiches für VirtualAlloc -> VirtualFree.

O
OXO Themenstarter:in
86 Beiträge seit 2020
vor 3 Jahren

Dachte ich mir schon, dass ich das noch nicht ganz richtig gemacht hab - Mist 😉
Bei Benutzung der Methode von oben ist es aber schwierig den sicheren Zeitpunkt für das Abräumen zu bestimmen.

Den Speicher für den pszTextPtrForeign kann ich z.B. in der Routine noch gar nicht frei geben, da der ja meinen Pointer im Prozess für den pszText das LV_ITEM repräsentiert. Genauso hab ich ein Problem mit dem allokierten Speicher, solange ich noch im Weiterleiten einer veränderten LVM_INSERTITEMW Message in meinem CallBack bin. Irgendwie darf ich das ja alles erst abräumen, wenn die komplette Nachricht am Ziel angekommen und verarbeitet ist.
Dazu ist im Hook-Callback eigentlich gar keine Möglichkeit.

Denke, zumindest das CloseHandle(...) auf den Prozess-Handle könnte ich noch in der Routine unterbringen, da dieser nach dem WriteProcessMemory(...) wohl nicht mehr gebraucht wird

Der Grund für das langsame Update des ListView nach dem Verändern meiner Hook-Message kann das aber nicht sein, oder könnte das eine Rolle spielen? Da kann man nämlich darauf warten, bis das ListView seine paar Einträge geladen hat.