I am reading text from a notepad opened by my program.and this is my code
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
public static string GetText(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
throw new ArgumentNullException("hwnd");
IntPtr handler = FindWindowEx(hwnd, new IntPtr(0), "Edit", null);
int length = SendMessageGetTextLength(handler, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++;
StringBuilder sb = new StringBuilder(length);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)sb.Length, sb);
return sb.ToString();
}
return String.Empty;
}
It is getting the text but in a special encoding.
For example, if the text entered is 'hello' it gets '興梀ȿڳㇺ'.
What is the encoding of this text so I can decode it to ASCII?
Your problem is in fact that you are passing sb.Length in the WM_GETTEXT message, when in fact you should be passing sb.Capacity or even just length.
I would do it like this:
if (length > 0 && length < int.MaxValue)
{
StringBuilder sb = new StringBuilder(length+1);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)length+1, sb);
return sb.ToString();
}
I'd also point out that WM_GETTEXT will not return more than 64k characters to the length < int.MaxValue isn't what you need.
Of course, in the longer run it may be better to use the Unicode throughout so that you can support international text.
I personally would always opt for using the Unicode APIs and use the following p/invoke declarations:
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx",
CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
Since you're writing in managed code, you may as well use the managed code automation interfaces, which does all the interop for you. Why reinvent the wheel?
using System.Windows.Automation;
public static string GetText(IntPtr hwnd)
{
IntPtr hwndEdit = FindWindowEx(hwnd, IntPtr.Zero, "Edit", null);
return (string)AutomationElement.FromHandle(hwndEdit).
GetCurrentPropertyValue(AutomationElement.NameProperty);
}
You can even make the automation do the FindWindowEx for you:
public static string GetText(IntPtr hwnd)
{
var editElement = AutomationElement.FromHandle(hwnd).
FindFirst(TreeScope.Subtree,
new PropertyCondition(
AutomationElement.ClassNameProperty, "Edit"));
return (string)editElement.GetCurrentPropertyValue(AutomationElement.NameProperty);
}
Related
I want to get all text of a window.
I prepared below code.But i can only get window captions/titles.
How can i get all text written inside a window ?
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
IntPtr xero = new IntPtr(0);
var x = FindWindowByCaption(xero, process.MainWindowTitle);
int length = GetWindowTextLength(x);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(x, sb, sb.Capacity);
}
}
You need to enumerate all child windows of the top level windows. You can use EnumChildWindows API in order to accomplish that.
Here is the sample code i have written in C# for you
internal class Program
{
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, StringBuilder lParam);
const uint WM_GETTEXT = 0x000D;
static bool EnumAllChilds(IntPtr hWnd, IntPtr lParam)
{
StringBuilder sb = new StringBuilder(2048);
SendMessage(hWnd, WM_GETTEXT, new IntPtr(sb.Capacity), sb);
if (!string.IsNullOrEmpty($"{sb}"))
{
Console.WriteLine($"\t{hWnd:X}\t{sb}");
}
EnumChildWindows(hWnd, EnumAllChilds, lParam);
return true;
}
static bool EnumTopLevel(IntPtr hWnd, IntPtr lParam)
{
StringBuilder sb = new StringBuilder(2048);
SendMessage(hWnd, WM_GETTEXT, new IntPtr(sb.Capacity), sb);
Console.WriteLine($"TopLevel: hWnd: {hWnd:X}\t{(string.IsNullOrEmpty($"{sb}") ? "No Caption" : $"{sb}")}");
// Call for child windows
EnumChildWindows(hWnd, EnumAllChilds, lParam);
return true;
}
static void Main(string[] args)
{
// Call for TopLevel windows
EnumWindows(EnumTopLevel, IntPtr.Zero);
Console.ReadLine();
}
}
I have to enter the file path in a text box of windows popup
I have tried a piece of the code which can return the handle of a window based on its name.
class Program
{
private const int WM_GETTEXT = 13;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
static void Main(string[] args)
{
IntPtr Hwnd = FindWindow(null, "Choose File to Upload");
// Alloc memory for the buffer that recieves the text
IntPtr Handle = Marshal.AllocHGlobal(100);
// send WM_GWTTEXT message to the notepad window
int NumText = (int)SendMessage(Hwnd, WM_GETTEXT, (IntPtr)50, Handle);
// copy the characters from the unmanaged memory to a managed string
string Text = Marshal.PtrToStringUni(Handle);
}
my pop looks like:
I need to set data in a text box of the window.
I made before a program where I could open a process of the notepad and while it's opened, be able to write in it from the C# program console. Now I'm trying to do the same but with the excel, I can run the process, I can open it and I can kill it. But when I try to write in it with the SendMessage() method, nothing happens, is there a way I can do this? Or am I missing something? Thanks!
Here's what I tried so far
Declarations
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
//include SendMessage
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, int lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
const uint WM_PASTE = 0x302;
const int WM_SETTEXT = 0X000C;
const int WM_GETTEXTLENGTH = 0x000E;
const int EM_SETSEL = 0x00B1;
const int EM_REPLACESEL = 0x00C2;
static void Main(string[] args)
{
//Abre o programa
Process prcss = new Process();
prcss.StartInfo.FileName = "excel.exe";
prcss.Start();
string aux = prcss.StartInfo.FileName;
//Verifica se o processo está a correr
Process[] processlist = Process.GetProcesses();
Code to write in it with the SendMessage().
case "2":
while (true)
{
//Testar com o SendMessage
Console.WriteLine("\nTexto: \n");
string texto = Console.ReadLine();
if (aux.Length == 0)
{
return;
}
if (prcss != null)
{
IntPtr notepadTextbox = FindWindowEx(prcss.MainWindowHandle, IntPtr.Zero, "edit", null);
int length = SendMessageGetTextLength(notepadTextbox, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (!notepadTextbox.Equals(IntPtr.Zero))
{
//sending the message to the textbox
SendMessage(notepadTextbox, WM_SETTEXT, 0, texto);
SendMessage(notepadTextbox, EM_SETSEL, length, length);
SendMessage(notepadTextbox, EM_REPLACESEL, 1, texto + "\n");
}
}
Console.WriteLine("Sair? (S)im / (N)ão");
sair = Console.ReadLine();
if (sair == "s" || sair == "S")
{
IntPtr k = prcss.MainWindowHandle;
SetForegroundWindow(k);
prcss.Kill();
break;
}
}
I have a function in WinForms C# app that sends a string (from a textbox) to an active CMD window, using a button.
Unfortunately, if the textbox contains multiple zeros (0000x000F22000), it returns just one zero: 0x0F220
How can I fix this?
private void but_run_Click(object sender, EventArgs e)
{
uint wparam = 0 << 29 | 0;
int i = 0;
for (i = 0; i < textBox1.Text.Length; i++)
{
//PostMessage(child, WM_KEYDOWN, (IntPtr)Keys.Enter, (IntPtr)wparam);
PostMessage(cmdHwnd, WM_CHAR, (int)textBox1.Text[i], 0);
}
PostMessage(cmdHwnd, WM_KEYDOWN, (IntPtr)Keys.Enter, (IntPtr)wparam);
}
You could try using the lParam to specify repeat key presses. Also, pay attention - PostMessage has lParam as the fourth parameter (wParam is before lParam), you're mixing it up in your code.
Next, don't use (int)someChar. You should use the Encoding classes to get byte values from chars.
Use SendMessage instead of PostMessage. PostMessage is asynchronous and can complicate a lot of stuff for you. You don't need the asynchronicity, so don't use it.
Next, why use WM_CHAR? I'd say WM_SETTEXT would be way more appropriate - you can send the whole text at once. Just be careful about using the native resources (eg. the string). To make this as easy as possible, you can make yourself an overload of the SendMessage method:
const uint WM_SETTEXT = 0x000C;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, unit Msg,
IntPtr wParam, string lParam);
You can then simply call:
SendMessage(cmdHwnd, WM_SETTEXT, IntPtr.Zero, textBox1.Text);
I've managed to do it like this:
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindow(IntPtr ZeroOnly, string lpWindowName);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
const int WM_CHAR = 0x0102;
public void sendText(string pText, string pCaption)
{
IntPtr wndHndl = FindWindow(IntPtr.Zero, pCaption);
char[] tmpText = pText.ToCharArray();
foreach (char c in tmpText)
{
System.Threading.Thread.Sleep(50);
PostMessage(wndHndl, WM_CHAR, (IntPtr)c, IntPtr.Zero);
}
}
Where pText is the input string and pCaption is title of the window.
I am having to write an application that communicates with a third-party program (AOL, I'm sorry. :()
Doing a lot of research I found some ways to do this with P/Invoke, and for the most part it works okay, but it crashes upon subsequent trials, specifically with SendMessage. I'm outlining the crashing code below.
All of this was ported to .NET from old, old Visual Basic files. It's archaic as it can be, and I understand if it's not doable - I was just hoping there was a better way than Visual Basic 4.0 to get this done.
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle,
IntPtr childAfter,
string className,
IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint="SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode , EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessageByString(HandleRef hWnd,
UInt32 Msg,
IntPtr wParam,
String lParam);
public IntPtr FindClientWindow()
{
IntPtr aol = IntPtr.Zero;
IntPtr mdi = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr rich = IntPtr.Zero;
IntPtr aollist = IntPtr.Zero;
IntPtr aolicon = IntPtr.Zero;
IntPtr aolstatic = IntPtr.Zero;
aol = Invoke.FindWindow("AOL Frame25", null);
mdi = Invoke.FindWindowEx(aol, IntPtr.Zero, "MDIClient", null);
child = Invoke.FindWindowEx(mdi, IntPtr.Zero, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
do
{
child = Invoke.FindWindowEx(mdi, child, "AOL Child", null);
rich = Invoke.FindWindowEx(child, IntPtr.Zero, "RICHCNTL", null);
aollist = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Listbox", null);
aolicon = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Icon", null);
aolstatic = Invoke.FindWindowEx(child, IntPtr.Zero, "_AOL_Static", null);
if (rich != IntPtr.Zero &&
aollist != IntPtr.Zero &&
aolicon != IntPtr.Zero &&
aolstatic != IntPtr.Zero)
return child;
}
while (child != IntPtr.Zero)
;
return child;
}
IntPtr room = IntPtr.Zero;
IntPtr child = IntPtr.Zero;
IntPtr length = IntPtr.Zero;
IntPtr roomHandle = IntPtr.Zero;
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
HandleRef n = new HandleRef(IntPtr.Zero, room);
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
// This is the line that keeps crashing on me.
SendMessageByString(n, 0x000D, new IntPtr( length.ToInt32() + 1 ), str);
public IntPtr FindChildByClass(IntPtr parent, string child)
{
return Invoke.FindWindowEx(parent, IntPtr.Zero, child, null);
}
You are using the Wide byte SendMessage..ie for Wide Characters, have you tried the normal Sendmessage..
public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
I also notice it's like as if you are trying to change the value based on the handle of the richtextbox's control hence the looking around for the AOL's client window in another process...is that correct?
That could be the source of the problem, directly modifying a control that belongs to a window that is not yours (your program is managed, modifying a unmanaged process's window)...that could explain why it crashed. Can you clarify what is the hex constants for?
Edit: When you use the WM_GETTEXTLENGTH and WM_GETTEXT, they are part of the Windows Messages to retrieve the text length and the actual text from the control. If you look here and see what pinvoke.net has to say about them..When you issue a 'SendMessage', with WM_GETTEXTLENGTH and WM_GETTEXT, you are telling Windows - 'Hey, get me the length of the text in that associated handle which I've given you in the parameter n. Just occurred to me, worth trying out...I would get rid of those SendMessage pinvokes and use just this one..
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, StringBuilder lParam);
//If you use '[Out] StringBuilder', initialize the string builder with proper length first.
child = FindClientWindow();
room = FindChildByClass(child, "RICHCNTLREADONLY");
length = SendMessage(n, 0x000E, IntPtr.Zero, IntPtr.Zero);
StringBuilder sbBuf = new StringBuilder(length);
SendMessageByString(room, 0x000D, new IntPtr( length.ToInt32() + 1 ), out sbBuf); // this is the line that keeps crashing on me.
Try that and get back here ... :)
Hope this helps,
Best regards,
Tom.
Did you manage to solve the "Attempted to read or write protected memory." error? t0mm13b's answer seems to allocate a StringBuilder whose buffer is one character too small (to accommodate the trailing '\0').
Here's code that works for me:
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern Int32 SendMessageByString(IntPtr wnd, UInt32 msg, Int32 WParam, StringBuilder output);
const int WM_GETTEXTLENGTH = 0x000e;
const int WM_GETTEXT = 0x000d;
public static string GetText(IntPtr hWnd)
{
int len = SendMessageByString(hWnd, WM_GETTEXTLENGTH, 0, null);
var sb = new StringBuilder(len + 1); // +1 is for the trailing '\0'
SendMessageByString(hWnd, WM_GETTEXT, sb.Capacity, sb);
return sb.ToString();
}
I was getting a crash from Marshal.PtrToStringUni(bf) statement in similar situation where SendMessage was returning a "wrong size" for a text length with WM_GETTEXTLENGTH argument (the control class was "RICHEDIT50W"; multi-line text).
I had tried adding 1, 10, 100 (to text length query result) and still would get an error even though (later on) the text length was equal what was returned from the first call (WM_GETTEXTLENGTH).
My solution was: I multiplied the result with 2 then I trimmed it.
I did use Marshal.AllocHGlobal(sz) and then Marshal.Release(bf), so there was no problem with memory efficiency. My guess is that for multi-line texts Marshal.AllocHGlobal(sz) wasn't making enough space in the memory even with exact text size (+1).
Maybe the return character within the text (vbCr, vbLf) requires more memory: I found nothing to explain this isue, but doubling the size worked for me.