Discussion:
Wie Textbox eines Fremdprogramms auslesen
(zu alt für eine Antwort)
Klaus Mayer
2010-01-21 12:05:27 UTC
Permalink
Hi,
ich möchte in Google Maps eine Ortsmarke setzen.
Dazu öffnet sich ein Fenster, in welchem in 2 Textboxen die Latitude-
und Longitude-Werte stehen.
Mit FindWindowByCaption bekomme ich ja den Handle des Fensters.
Aber wie bekomme ich den Handle und die Werte der beiden Textboxen?
Hab schon alles Mögliche mit FindWindowEx probiert, leider erfolglos.

Für Eure Tipps dankend,

Klaus
Konrad Neitzel
2010-01-21 12:43:39 UTC
Permalink
Hallo Klaus,

das auslesen von fremden Fenstern kann man nicht immer klar vorhersagen.
Nach meiner Erfahrung hilft es aber immer, sich einmal das Fenster über
Tools wie Spy++ anzusehen.

Glücklicherweise nutzen die Entwickler meist immer die üblichen
Controls, so dass man auch mit den üblichen Nachrichten die Daten
abfragen kann.

Was man in Tools wie Spy++ immer gut sehen kann ist auch die
Hirarchie-Struktur in einem Fenster. Die paar Fenster, die ich auslesen
/ analysieren musste, hatten eine interne Struktur, durch die ich immer
erst durch bin. (Also Child-Fenster auflisten und dann vom
entsprechenden Child Fenster aus weiter geschaut.)

Von einer TextBox (So es eine normale TextBox ist) würde ich erwarten,
dass es auf WM_GETTEXT entsprechend antwortet.

Also wäre das dann in etw folgendes:
- Fenster finden (Das hattest Du ja schon entsprechend gemacht.)
- Durch die Struktur helfen dir FindWindow(Ex). Oder eben
EnumChildWindows zur Auflistung aller Children
- Dann WM_GETTEXT / WM_GETTEXTLENGTH sollten die Nachrichten am Ende
sein.

Ich selbst habe keine Erfahrung mit dem Auslesen von TextBox-Controls.
Den Titel eines Fensters bekommt man so, daher ist es wahrscheinlich,
dass dies bei dem WM_GETTEXT ebenso ist. Bei dem Auslesen einer ListBox
musste ich über OpenProcess / VirtualAllocEx Speicher reservieren und
dann das Ergebnis am Ende per ReadProcessMemory kopieren.

Links:
- Die einzelnen genannten Funktionen finden sich in der msdn library
beschrieben
- http://pinvoke.net hilft bei den Definitionen in C#
- CWindow bei Codeproject ist evtl. auch interessant:
http://www.codeproject.com/KB/dialog/CWindow.aspx

Ich hoffe, ich konnte etwas helfen.

Mit den besten Grüßen,

Konrad
Klaus Mayer
2010-01-21 12:58:33 UTC
Permalink
Danke, Konrad.

Soweit ist alles schick:

IntPtr window = FindWindowByCaption(IntPtr.Zero, ("Google Earth - Neu:
Ortsmarke"));
IntPtr group = FindWindowEx(window, IntPtr.Zero,
null,"lat_lon_group_");
IntPtr box = FindWindowEx(group, IntPtr.Zero, null, "latitude_");

Aber mit dem Inhalt der TextBox hapert es immer noch gewaltig... :(
Hätte jemand noch einen Tipp?

Grüße,

Klaus
Konrad Neitzel
2010-01-21 15:48:47 UTC
Permalink
Hallo Klaus,

mit FindWindowEx solltest Du ja die handle. Hast Du geprüft, dass die
wirklich richtig kommen? Denn dann hast Du es ja schon fast ganz. Denn
dann fehlt ja hoffentlich nur noch das WM_GETTEXT.

Dazu bauchst Du die Funktion SendMessage (Hatte ich bei der ersten
Antwort vergessen zu erwähnen.). Parameter sind:
a) Handle zum Control
b) Message (Hier must Du die C / C++ Header ansehen. Dort ist WM_GETTEXT
definiert)
c) wParam und lParam - hier muss man dann auch die Definition von
WM_GETTEXT ansehen. Diese Parameter können halt unterschiedlich sein je
nach Nachricht.

Laut MSDN benötigt WM_GETTEXT:
wParam ist cchTextMax - maximum number of characters
lParam ist lpszText - Long poiner to the buffer that is to receive the
text.

Das sollte also relativ einfach gehen. Einfach einen großen Buffer
registrieren und dann entsprechend übergeben. Jetzt so aus dem Kopf ist
es aber schwer, aber so in der Art würde ich es wohl machen:
StringBuilder text = new StringBuilder(260);
int chars = NativeMethods.SendMessage(handle, Win32Constants.WM_GETTEXT,
text, text.Capacity)
if (chars > 0)
// text.ToString() holds the string
else
// We got no text!

Dies wäre mein erster Versuch. Was passiert ist: Dein Prozess sendet dem
anderen Prozess die Aufforderung, doch bitte einmal den Text zu
kopieren. Nun kann es aber passieren, dass der Prozess dies nicht machen
kann, da er keine Rechte hat um auf die Speicheradresse zuzugreifen.
Dann wird das alles etwas komplizierter - den Weg habe ich schon einmal
etwas angerissen. Sollte es notwendig sein, führe ich das aber auch noch
gerne etwas detailierter aus (*Daumen drück, dass es nicht notwendig
ist*).

Ach ja:
- NativeMethods ist bei mir eine Klasse, in der ich die Dllimports
packe. Immer internal und im gleichen Namensraum.
- Win32Constants enthält bei mir die ganzen Defiitionen der Konstanten.

Damit Du die Konstanten nicht raussuchen musst (Sind immer relativ blöd,
da man sich in der Header Datei einen Wolf suchen kann. Dort ist alles
mit einem Offset angegeben, wobei die Makros teilweise recht interessant
und aufschlussreich sind.):

/// <summary>
/// An application sends a WM_GETTEXT message to copy the text
that corresponds to a window into a buffer provided by the caller.
/// </summary>
public const uint WM_GETTEXT = 0x000D;
/// <summary>
/// An application sends a WM_GETTEXTLENGTH message to determine
the length, in characters, of the text associated with a window.
/// </summary>
public const uint WM_GETTEXTLENGTH = 0x000E;

Ich hoffe, dies hilft etwas weiter. Ich werde mich auf jeden Fall am
Wochenende einmal hinsetzen und einen Blog-Eintrag dazu schreiben. Sowas
sehe ich hier als Frage zum zweiten Mal. Könnte sinnvoll sein, es einmal
in Ruhe richtig auszuformulieren statt so drauf los zu schreiben ...

Hoffe, ich konnte etwas weiterhelfen.

Mit den besten Grüßen,

Konrad
Klaus Mayer
2010-01-21 16:03:08 UTC
Permalink
Hab weiter getestet:

const int EM_GETLINECOUNT = 0x00BA;
const int EM_LINELENGTH = 0x00C1;
const int EM_LINEINDEX = 0x00BB;
const int EM_GETLINE = 0x00C4;

string result = "";

IntPtr window = FindWindowByCaption(IntPtr.Zero, ("Google
Earth - Neu: Ortsmarke"));
IntPtr group = FindWindowEx(window, IntPtr.Zero, null,
"lat_lon_group_");
IntPtr box = FindWindowEx(group, IntPtr.Zero, null,
"latitude_");

int lineLength = (int)SendMessage(box, EM_LINELENGTH,
(IntPtr)0, IntPtr.Zero);

if (lineLength > 0)
{
IntPtr apiBuffer = IntPtr.Zero;
try
{
apiBuffer = Marshal.AllocHGlobal(lineLength);
byte[] lengthInfo = BitConverter.GetBytes((short)
lineLength);
Marshal.Copy(lengthInfo, 0, apiBuffer, 2);
SendMessage(box, EM_GETLINE,(IntPtr)0, apiBuffer);
byte[] clrBuffer = new byte[lineLength];
Marshal.Copy(apiBuffer, clrBuffer, 0, lineLength);
string row = String.Empty;
for (int i = 0; i < clrBuffer.Length; i++)
{
row += Convert.ToChar(clrBuffer[i]);
}
if (row.EndsWith("\0"))
{
row = row.Remove(row.Length - 1, 1);
}
result = (row);
}
finally
{
if (apiBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(apiBuffer);
}
}
}

Klappt prima mit irgendwelchen fix zusammengeschraubten .Net-
TextBoxen. Aber das Google-Maps-Fenster sträubt sich beharrlich.
Grmpf!
Any Idea?

Schöne Grüße und dickes Danke!

Klaus
Konrad Neitzel
2010-01-21 16:26:16 UTC
Permalink
Hallo Klaus,

die .Net Controls sind nicht identisch mit den "Win32" Controls. Und
daher hatte ich auch geschrieben, dass man da halt genau schauen muss.
Ich habe die Fenster analysiert und geschaut, mit was für Nachrichten
die Controls gefüllt bzw. gelesen wurden und habe dann geschaut, ob
diese den "bekannten" Nachrichten entsprachen.

Das Problem ist, dass Du der Entwickler ja alles gemacht haben konnte.
Wobei für standard-Controls hoffentlich kein Hersteller zu viel Arbeit
investiert hat und etwas nachgebaut hat. So gibt es teilweise
Control-Libs, aber die sind dann oft seperat und wenn man die heraus
bekommt (Binary mit Abhängigkeiten untersuchen. Das kann dann
interessant sein!), dann hat man evtl. auch im Nu eine schöne Lib
gefunden, die sich importieren und dann nutzen liesse. Ich selbst bin
aber bisher immer über standard-Controls gestolpert ....

Ich selbst orientiere mich bei WIN32 Applikationen immer an den Header
Dateien. Das EM_GETTEXT konnte ich aber nicht wirklich finden bei meiner
Suche in den Header Dateien des Windows SDK 6.0A. Von daher wäre dies
bei mir zumindest nicht erste Wahl.

Könntest Du bitte einmal im spy++ (oder ähnlichem Tool) nachsehen, von
welcher Classe deine Eingabefelder sind?

Mit den besten Grüßen,

Konrad
Konrad Neitzel
2010-01-21 16:43:31 UTC
Permalink
Hallo,

hatte da dann kurz etwas falsch im Kopf. Nach EM_GETTEXT muss ich ja
auch nicht schauen. Geht ja um EM_GETLINE und das findet sich in
WinUser.h. War ich schlicht blind.

Was ich so bei Dir gefunden habe an Code sieht auch gut aus. Wenn es
nicht geht, dann liegt das einfach daran, dass das Control auf diese
Nachricht nicht reagiert und Du nach einer anderen schauen solltest.
Hast Du einmal diese WM_GETTEXT ausprobieren können?

Mit den besten Grüßen,

Konrad
Klaus Mayer
2010-01-21 18:38:30 UTC
Permalink
Habs auch mit WM_GETTEXT getestet. Ebenfalls negativ.
Hab die SubControls auch noch mal durch eine EnumChildWindows-Methode
verifiziert. Namen stimmen.
IuSpy sagt, die GoogleEarth - Controls wären von der QTool / QWidget-
Klasse. Vermutlich liegt
dort das Problem. (Und sicherlich auch darin, das ich zu Anfang
fälschlicherweise von GoogleMaps gesprochen habe).
Hat vllt. noch jemand eine Idee???


Schöne Grüße,

Klaus
Konrad Neitzel
2010-01-21 21:15:11 UTC
Permalink
Hallo Klaus!
Post by Klaus Mayer
IuSpy sagt, die GoogleEarth - Controls wären von der QTool / QWidget-
Klasse. Vermutlich liegt
Was genau willst Du machen? Ich weiss, dass so Dienste wie Google Earth auch
Interfaces bieten. Google Earth kenne ich nun nicht, aber dieses MS Mappoint
oder wie sich der Serverdienst auch immer nennen mag, hat ein Schnitstelle,
die einfach zu nutzen ist (Kenne ich aber auch nur, weil im WCF Self-Paced
Training Kit der Service als Beispiel hergenommen wird!) Also evtl. kann man
auch einen anderen Lösungsweg finden, wenn das mit dem Auslesen nicht
funktionieren sollte ...
Post by Klaus Mayer
dort das Problem. (Und sicherlich auch darin, das ich zu Anfang
fälschlicherweise von GoogleMaps gesprochen habe).
Hat vllt. noch jemand eine Idee???
Nunja, jetzt wissen wir, dass es sich um eine Applikation handelt, die auf
QT basiert.
Ein SDK für Windows gibt es z.B. unter:
http://qt.nokia.com/downloads/sdk-windows-cpp
Dokumentation unter http://doc.qt.nokia.com/4.6/

Generell ist QT eine Lib, die es ermöglicht eine Applikation für viele
unterschiedliche Ziele zu schreiben. Dadurch ist es leider so, dass die
Windows Basics hier nicht wirklich zwingend zum tragen kommen. Wenn ich das
Gelesene richtig verstanden habe, dann können die Widgets ganz intern
verwaltet werden ohne ein Windows Handle zu bekommen (was aber bei der
Google Applikation nicht der Fall zu sein scheit. Du hast ja ein Windows
Handle bei den Controls).

Ich habe mir dieses SDK mal herunter geladen und mir mal den Code etwas
angetan.
Vom Prinzip her ist es einfach so aufgebaut, dass die QApplication die
Windows events verarbeitet und als QEvents and die jeweiligen Empfänger
zurück gibt. Die Widgets selbst empfangen keine Windows Messages. (Nach
meinem Verständnis ist dies bei Windows anders. Hier verarbeitet jedes
"Window" die Messages und gibt diese bei "Nichtbearbeitung halt einfach
weiter).

Was es aber auch gibt (in einem Bereich, der wohl von einer Art Designer
erstellt wird) sind so Funktionen qt_metacall. Die sehen selbst recht
vielversprechend aus. Nur eben fehlt mir der Überblick um die
Verhaltensweise wirklich zu verstehen oder gar zu wissen, wie man da
testweise etwas aufrufen könnte. (Und es kann auch sein, dass ich komplett
daneben liege und das hier in eine ganz falsche Richtung geht!)
Dies wäre z.B. in qt\src\gui\tmp\moc\debug_shared\moc_qlineedit.cpp die
funktion int QLineEdit::qt_metacall(QMetaObject::Call _c, int _id, void
**_a).
Was mich aber stört ist, dass sofort der call an die Basisklasse
weitergegeben wird. Denn wenn _c == QMetaObject::ReadProperty und _id == 1
dann wird über _a[0] der Inhald der ListBox zurückgegeben.... Es könnte also
sein, dass es tatsächlich möglich ist und ich da irgendwas gesehen habe ...

An der Stelle streiche ich aber jetzt die Segel, denn mit QT und C++ möchte
ich nichts (mehr) am Hut haben. Aber vielleicht kann man hier ja mal QT
Spezialisten fragen, was so möglich ist, sprich: Die Frage in ein anderes
Forum tragen. Denn von C# ist es ja mitlerweile abgekoppelt hin zu einer
Frage: kann man mit einer Windows Message so ein QT Widget auslesen oder
nicht.

Ich fürchte, dass ich Dir nicht mehr helfen kann. Solltest Du eine Lösung
finden, dann würde mich das aber auch sehr interessieren.

Mit den besten Grüßen,

Konrad
Klaus Mayer
2010-01-22 06:46:17 UTC
Permalink
Ich gebs mit diesem Lösungsversuch auch erstmal auf und schau mir das
GEarth-Interface mal an.
Primär ging es mir um eine schnelle Darstellung/Ermittlung von
Geokoordinaten in Zusammenarbeit mit meinem
mittlerweile unangenehm großen Fotoarchiv (Exif-Daten lesen /
ergänzen).
Mal sehen, was das Interface leisten kann.

Vielen vielen Dank für Deine Tipps! :)
und schöne Grüße


Klaus
Klaus Mayer
2010-01-22 19:03:39 UTC
Permalink
Für Interessierte:
Das Google Earth Interface macht einen recht guten Eindruck und lässt sich
sehr entspannt in ein Projekt einbinden.
Eine Fernsteuerung des Programms lässt sich damit problemlos realisieren wie
auch das Ermitteln von Koordinaten.
Problemstellung damit gelöst.

Schöne Grüße,

Klaus

Konrad Neitzel
2010-01-21 17:55:48 UTC
Permalink
Hallo Klaus,

ich habe mir nun einmal einfach eine InputBox aus einem einfachen VBS Script
geöffnet. Dort ist die Class "Edit". Was ich direkt im ispy++ gesehen habe:
WM_GETTEXT sollte funktionieren. Zumindest bei dem dort verwendeten Control.
Zumindest wurde mir der Inhalt direkt im ispy++ angezeigt.

Am Einfachsten kannst Du dann hierzu wirklich CWindow auf CodeProject
verwenden. Dort sollte eine Property Text und TextUnsafe vorhanden sein und
eine von beiden sollte direkt funktionieren.

(Wobei diese Aussage auf der TextBox basiert, die ich angeschaut habe. Wie
schon gesagt kann da ja jeder Hersteller prinzipiell machen, was er
möchte.... Kann also in deinem Fall alles anders sein!)

Mit den besten Grüßen,

Konrad
Klaus Mayer
2010-01-21 19:39:36 UTC
Permalink
Hab den Spaß auch mit CWindow gemacht. Dort wird lediglich
als Text die Bezeichnung des Controls ausgegeben. Und als Klasse "QWidget".
Der spy++ bringt auch nichts verwertbares. Kein Keydown o.ä.

Fragende Grüße,

Klaus
Loading...