Frank Dzaebel, erstellt am: 10.10.2009,
zuletzt geändert: 10.10.2009
Kategorie: C# Sprache,
.NET-Version: 3.5, [Download]
Es soll Leute geben, die denken, GetWindowsText liefert keine
oder falsche Ergebnisse bei externen Prozessen. Der Artikel zeigt, dass GetWindowText
durchaus dafür eingesetzt werden kann.

Zunächst ist die Ansicht nicht verwunderlich. Besagt die Dokumentation zu
GetWindowText doch:
"GetWindowText cannot retrieve
the text of a control in another application."
Man beachte schon einmal "Control" und nicht "Window", was einen Unterschied macht
und auch gern durcheinandergebracht
wird.
Leider wird dann aber nicht nachgelesen. Denn es steht klar dort:
"If the target window is owned by another process and
has a caption, GetWindowText retrieves the window caption text!"
Wie selbst C++ Fachleute hier manchmal irren, sieht man an folgendem
Newsgroup-Thread / [Artikel].
Hier wird u.a. fälschlich behauptet: "dass
GetWindowText
nicht zuverlässig für andere Prozesse geht" [10.10.2009
15:00]. Daß es, und in welchen Szenarien es geht, wird in
meinem Artikel bewiesen und steht auch in der Doku (s.o.). Aktuell versucht Kalmbach
in seinem Artikel nun durch bewusste Fehlinterpretation von Postings seine Falschaussagen zu kaschieren [Details].
Weitere gute Artikel u.a.: "The Old New Thing : The secret life of GetWindowText". Es gibt auch OS-spezifische
Unterschiede.
Weiterhin gibt es auch andere Prozess-Hooking-Verfahren als
AttachThreadInput die Prozesse binden, sodass Texte über GetWindowText erhalten
werden können. Hier Detail-Informationen.
[11.10.2009 18:59] jetzt wird's im [Artikel]
lustig. Der Mann kennt sich offenbar überhaupt nicht aus.
Dort steht tatsächlich, AttachThreadInput hätte keinen Einfluss, im
Szenario des
Postings des Fragenden im Thread.
Zur Erinnerung, der Fragende benutzt
GetFocus. Wer der Dokumentation von GetFocus folgt, erkennt schnell:
You can associate
your thread's message queue with the windows
owned by another
thread by using the
AttachThreadInput function.
AttachThreadInput ist hier "unbedingt nötig",
um die korrekte Funktion von GetFocus zu gewährleisten.
Unfassbar, aber offensichtlich will der Autor von [Artikel]
seine falschen Aussagen im Thread durch solche Fehlinterpretationen reinwaschen.
Der folgende Quellcode enstand aus obigem Posting und
dient zur Verifizierung des Problems. Er kann aber auch zur Hand genommen werden,
um entsprechende Implementationen in C# zu benutzen oder PInvoke-Deklarationen zu
kopieren.
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
namespace AttachThreadGetFocus
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Process notepadProcess;
int maxSB = 0xff;
uint winProcessId;
IntPtr handle;
private void Form1_Shown(object sender, EventArgs e)
{
this.Show(); Application.DoEvents();
notepadProcess = Process.Start("notepad");
notepadProcess.WaitForInputIdle();
handle = notepadProcess.MainWindowHandle;
}
void GetText(int capability, IntPtr handle, ref StringBuilder sb)
{
sb = new StringBuilder(capability);
int retVal = SendMessage(handle, WM_GETTEXT, new IntPtr(capability), sb);
}
#region PInvoke Deklarationen
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(
IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern bool AttachThreadInput(uint idAttach,
uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
static extern IntPtr GetFocus();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessage(IntPtr hWnd, int msg,
IntPtr wParam, StringBuilder lParam);
public const int WM_GETTEXT = 0x000D;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
#endregion
private void button1_Click(object sender, EventArgs e)
{
IntPtr foreWindow = handle;
IntPtr pt = base.Handle;
if (pt == foreWindow) return;
uint winPID = GetWindowThreadProcessId(foreWindow, out winProcessId);
uint idAttach = GetWindowThreadProcessId(base.Handle, out winProcessId);
if (AttachThreadInput(idAttach, winPID, true))
{
SetForegroundWindow(handle);
IntPtr focus = GetFocus();
if (focus != IntPtr.Zero)
{
Control ctrl = Control.FromHandle(focus);
if (ctrl != null)
{
this.Text = ctrl.Name;
}
else
{
StringBuilder lpClassName = new StringBuilder(maxSB);
// GetText(maxSB, handle, ref lpClassName); //SendMessage
GetWindowText(handle, lpClassName, maxSB); //GetWindowText
SetForegroundWindow(this.Handle);
this.Text = lpClassName.ToString();
if (this.Text.Contains(notepadProcess.MainWindowTitle))
{
lblGetWindowText_funktioniert.Text =
"GetWindowText funktioniert auf externen " +
"Prozessen mit AttachThreadInput!";
}
Application.DoEvents();
}
}
AttachThreadInput(idAttach, winProcessId, false);
}
}
public string GetText(IntPtr hWnd)
{
int length = GetWindowTextLength(hWnd);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!notepadProcess.HasExited)
notepadProcess.Kill(); // Dont do that, nur für Test-Zwecke
}
}
}