C# PostMessage - How to prevent data loss? - c#

I have an application that will form a packet and send the packet data to an external program to send. I have everything working, but my only method I know that doesn't require the window to be the foremost is PostMessage. However, it seems to always lose 0-2 characters at the beginning of the message. Is there a way I can make a check to prevent the loss? I've tried looping GetLastError() and re-sending it if it's 0, but it doesn't help any. Here's the code I've gotten so far:
public void SendPacket(string packet)
{
//Get window name
IntPtr hWnd = Window.FindWindow(null, "???????????");
//Get the first edit box handle
IntPtr edithWnd = Window.FindWindowEx(hWnd, IntPtr.Zero, "TEdit", "");
//Get the handle for the send button
IntPtr buttonhWnd = Window.FindWindowEx(hWnd, IntPtr.Zero, "TButton", "SEND");
//Iterate twice to get the edit box I need
edithWnd = Window.FindWindowEx(hWnd, edithWnd, "TEdit", "");
edithWnd = Window.FindWindowEx(hWnd, edithWnd, "TEdit", "");
foreach (Char c in packet)
{
SendCheck(c, edithWnd);
}
//Press button
TextSend.PostMessageA(buttonhWnd, 0x00F5, 0, 0);
//Clear the edit box
TextSend.SendMessage(edithWnd, 0x000C, IntPtr.Zero, "");
}
public void SendCheck(char c, IntPtr handle)
{
//Send the character
TextSend.PostMessageA(handle, 0x102, c, 1);
//If error code is 0 (failure), resend that character
if (TextSend.GetLastError() == 0)
SendCheck(c, handle);
return;
}
And here are the definitions in TextSend class:
[DllImport("Kernel32.dll")]
public static extern int GetLastError();
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string s);

The fact that you're finding a TEdit and a TButton makes me think that the target application was written in Delphi. If so, depending on the version of Delphi it may or may not be a Unicode application. You're calling PostMessageA instead of PostMessageW which means it's sending a single-byte Ansi char instead of a 16-bit Unicode char from the c# application.
Do you have source to the target application? Stuffing data in an edit box and clicking a button seems a bit fragile. If you can modify the target application there are certainly other options available than to send one character at a time.

Related

Russian characters in with SetWindowTextW in C#

I basically use this:
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool SetWindowTextW(IntPtr hWnd, string lpString);
SetWindowTextW(HWnd, "лфорфпылвоарпфлыьтвмлафывафыва")
to set the title of a window but the title ends up as:
ð╗Ðäð¥ÐÇÐäð┐Ðïð╗ð▓ð¥ð░ÐÇð┐Ðäð╗ÐïÐîÐéð▓ð╝ð╗ð░ÐäÐïð▓ð░ÐäÐïð▓ð░
I think that this has something to do with incorrect encoding.
Interestingly it seems to work if I type the string into a TextBox and send the property textbox.Text to the same function.
I get a similar string from an API so just typing it in and saving the output is not possible.
(I know the text in the code above is just random characters but the result is a similar mess with actual words)
You can directly use SendMessage to send a WM_SETTEXT message to a foreign Window:
From the Remarks section of SetWindowText:
If the target window is owned by the current process, SetWindowText
causes a WM_SETTEXT message to be sent to the specified window or
control. If the control is a list box control created with the
WS_CAPTION style, however, SetWindowText sets the text for the
control, not for the list box entries.
To set the text of a control in another process, send the WM_SETTEXT
message directly instead of calling SetWindowText.
Charset = CharSet.Auto is used to correctly marshal the string. The target operating system requirements are determined automatically (C# would mark it as ANSI otherwise).
See also: Charsets and marshaling.
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
const uint WM_SETTEXT = 0X000C;
IntPtr hWnd = [TheWindowHandle];
IntPtr russianPtr = Marshal.StringToHGlobalUni("лфорфпылвоарпфлыьтвмлафывафыва");
SendMessage(hWnd, WM_SETTEXT, IntPtr.Zero, russianPtr);
Marshal.FreeHGlobal(russianPtr);
Try this, with the L for literal prefixing.
SetWindowTextW(HWnd, L"лфорфпылвоарпфлыьтвмлафывафыва")

SendKeys.SendWait in English when the keyboard set to other language

In my application i am using SendKeys.SendWait to send text to screen:
SendKeys.SendWait("password");
The text is on English but when the keyboard set to other language the text that SendKeys.SendWait type is set in other language and not in English
Any suggestions how to make sure that the text will set only in English ?
I did a quick test using SendKeys.Send to send text to a couple of input fields. It sends the same text regardless of whether I have the keyboard in English or another language, so I'm not sure why you see a different result. Example:
SendKeys.Send("username");
SendKeys.Send("{TAB}");
SendKeys.Send("påsswørd");
SendKeys.SendWait("{ENTER}");
One possibility is that you could change the keyboard to English temporarily before calling SendKeys, then set it back to whatever it was before. There is an excellent example of the technique in this answer.
Another option is to use Win32 API functions to send messages to the window. The problem will be how to find the right windows to send the text to though. I'm not sure it could be done reliably. Here's an example (not tested):
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, string lParam);
// Windows message constants
const int WM_SETTEXT = 0x000C;
public void DoLogin(string username, string password)
{
// Get handle for current active window
IntPtr hWndMain = GetForegroundWindow();
if (!hWndMain.Equals(IntPtr.Zero))
{
IntPtr hWnd;
// Here you would need to find the username text input window
if ((hWnd = FindWindowEx(hWndMain, IntPtr.Zero, "UserName", "")) != IntPtr.Zero)
// Send the username text to the active window
SendMessage(hWnd, WM_SETTEXT, 0, username);
// Here you would need to find the password text input window
if ((hWnd = FindWindowEx(hWndMain, IntPtr.Zero, "Password", "")) != IntPtr.Zero)
// Send the password text
SendMessage(hWnd, WM_SETTEXT, 0, password);
// Send ENTER key to invoke login
SendKeys.SendWait("{ENTER}");
}
}

Receives a question mark when trying to see active window open in my program

I built Software that grabbing weight and throw into open window where the cursor is. Everything worked out well - I only have one problem annoying
Receives a question mark (?) when I open Word. and Then the software hangs not recognize the window properly.
when i open Word - i see Word? - 123.docx for example. And even if I remove the question mark, the software is still stuck in this case.
my code:
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private string GetActiveWindowTitle()
{
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
IntPtr handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
}
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
public void Start(string NAME)
{
MSG = lblMSG.Text.Trim();
IntPtr zero = IntPtr.Zero;
for (int i = 0; (i < 60) && (zero == IntPtr.Zero); i++)
{
Thread.Sleep(500);
zero = FindWindow(null, NAME);
}
if (zero != IntPtr.Zero)
{
.
.
.
}
}
what is the problem ? how to fix it ?
thanks
By default, strings and StringBuilders are marshalled as Unicode on Windows, so that's not a problem. However, you're calling the ANSI version of the GetWindowText method (and the same with FindWindow) - that simply isn't going to work. Windows tries to translate everything it can from unicode to ANSI, but it can't do anything with characters outside of the current ANSI codepage.
You need to use CharSet.Auto:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
This will use the unicode version of GetWindowText (GetWindowTextW) on unicode systems, and the ANSI version on non-unicode systems.
For comparison, without CharSet.Auto, my Word produces
??? ?? عربي ,عربى‎‎ [Compatibility Mode] - Microsoft Word
With it,
ščř řč عربي ,عربى‎‎ [Compatibility Mode] - Microsoft Word
My system locale is currently set to Arabic, so Arabic works fine even with the ANSI GetWindowText - if I changed back to czech, the ščř řč would work fine in ANSI, while the arabic letter would be replaced with question marks. Changing to english would replace all of those with question marks, since neither the czech nor arabic letters are supported in the english ANSI codepage.

Messaging interop between c# and VB6 mdi applications

I have two mdi applications, both of which retrieve their data from the same database.
The two applications need to be able to send messages to each other to keep in synch.
The messages being passed back and forth only contain a string telling the recieving application which piece of data in the database it should be looking at (a job number, and some additional related info).
Both applications have a message handler, instantiated when each program starts up.
When a message is sent from the VB6 app to the c# app it sees the message, and acts appropriately, but when I send the same type of message from the c# app to the VB6 app, it seems to see the message event, but when unpacking it, only sees part of the data, and then ignores the message.
I'm thinking I may be formatting something wrong on the c# end.
Here is the method that sends the message:
namespace InterProcessMessaging
{
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;//a pointer to a number use this to identify your message
public IntPtr lpData;//a pointer to the address of the data
public IntPtr cbData;//a pointer to the number of bytes to be transferred
}
public class clsMessaging : System.Windows.Forms.NativeWindow, IDisposable
{
//API function to send async. message to target application
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
public static extern IntPtr SendMessageA(IntPtr hwnd, Int32 wMsg, Int32 wParam, COPYDATASTRUCT lParam);
public void SendMessageToVB6(string sendMsg, string WindowsAppTitle)
{
try
{
IntPtr hwndTarget = FindWindow(null, WindowsAppTitle);
IntPtr pDWData = Marshal.AllocHGlobal(sizeof(Int32));//a pointer to a number used this to identify your message
Marshal.StructureToPtr(3, pDWData, true);//place the value 3 at this location
IntPtr pLPData = Marshal.StringToHGlobalAnsi(sendMsg.Trim());//a pointer to the address of the data
IntPtr pCBData = Marshal.AllocHGlobal(sizeof(Int32));//a pointer to the number of bytes to be transferred
Marshal.StructureToPtr(sendMsg.Trim().Length+1, pCBData, true);//place the size of the string at this location
COPYDATASTRUCT cds;//a structure containing the three pointers above
cds.dwData = pDWData;//a pointer to a number used this to identify your message (3)
cds.lpData = pLPData;//a pointer to the address of the data
cds.cbData = pCBData;//a pointer to the number of bytes to be transferred
if (!System.IntPtr.Zero.Equals(hwndTarget))
{
SendMessageA(hwndTarget, 74, 0, cds);
}
}
catch (Exception ex)
{
Debug.Print(ex.InnerException.ToString());
}
}
}
}
I would recommend to look into Named Pipes. In .NET you can use System.IO.Pipes for this purpose. In VB6 you can easily implement it with Win32API. Named Pipes is better way to make IPC than windows messaging. Also IPC via SendMessage has limitations on Vista and Win7.
You got that pretty wrong. Only COPYDATASTRUCT.lpData is a pointer. dwData indicates the message number. You pick your own, use 0 if you have only one. cbData is the size of the pointed-to data.
More problems, you are leaking the memory. The amount of memory you allocate doesn't match the size you pass. The string conversion is lossy and might not produce as many bytes as string.Length(). FindWindow is notoriously unreliable. Use a socket or a named pipe for this so you don't have to guess a name, WCF is best.
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)3;
cds.lpData = Marshal.StringToHGlobalUni(sendMsg);
cds.cbData = 2 * (sendMsg.Length + 1);
SendMessageA(hwndTarget, 74, 0, cds);
Marshal.FreeHGlobal(cds.lpData);
This works:
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public UInt32 cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
IntPtr result;
byte[] sarr = System.Text.Encoding.Default.GetBytes(sendMsg);
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = (IntPtr)3;
cds.lpData = sendMsg;
cds.cbData = (UInt32)len + 1;
result = SendMessage(hwndTarget, WM_COPYDATA, 0, ref cds);
Credit for this solution goes to Jim Kemp...who is not yet a member.
Thanks Jim!
I based the solution off of the example I found here:
http://boycook.wordpress.com/2008/07/29/c-win32-messaging-with-sendmessage-and-wm_copydata/

SetText of textbox in external app. Win32 API

Using Winspector I've found out the ID of the child textbox I want to change is 114. Why isn't this code changing the text of the TextBox?
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int Param, string s);
const int WM_SETTEXT = 0x000c;
private void SetTextt(IntPtr hWnd, string text)
{
IntPtr boxHwnd = GetDlgItem(hWnd, 114);
SendMessage(boxHwnd, WM_SETTEXT, 0, text);
}
The following is what I've used successfully for that purpose w/ my GetLastError error checking removed/disabled:
[DllImport("user32.dll", SetLastError = false)]
public static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, string lParam);
public const uint WM_SETTEXT = 0x000C;
private void InteropSetText(IntPtr iptrHWndDialog, int iControlID, string strTextToSet)
{
IntPtr iptrHWndControl = GetDlgItem(iptrHWndDialog, iControlID);
HandleRef hrefHWndTarget = new HandleRef(null, iptrHWndControl);
SendMessage(hrefHWndTarget, WM_SETTEXT, IntPtr.Zero, strTextToSet);
}
I've tested this code and it works, so if it fails for you, you need to be sure that you are using the right window handle (the handle of the Dialog box itself) and the right control ID. Also try something simple like editing the Find dialog in Notepad.
I can't comment yet in the post regarding using (char *) but it's not necessary. See the second C# overload in p/Invoke SendMessage. You can pass String or StringBuilder directly into SendMessage.
I additionally note that you say that your control ID is 114. Are you certain WinSpector gave you that value in base 10? Because you are feeding it to GetDlgItem as a base 10 number. I use Spy++ for this and it returns control IDs in base 16. In that case you would use:
IntPtr boxHwnd = GetDlgItem(hWnd, 0x0114);
Please convert your control id (obtained from spy ++) from Hexdecimal Number to Decimal Number and pass that value to the GetDlgItem function.With this
you will get the handle of Text box.This worked for me.
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int Param, string s);
const int WM_SETTEXT = 0x000c;
private void SetTextt(IntPtr hWnd, string text)
{
IntPtr boxHwnd = GetDlgItem(hWnd, 114);
SendMessage(boxHwnd, WM_SETTEXT, 0, text);
}
Are you sure you are passing text right? SendMessage last param should be a pointer to char* containing text you want to set.
Look at my "crude hack" of setting text in
How to get selected cells from TDBGrid in Delphi 5
this is done in Delphi 5, where PChar is char* alias, and I simply cast it as int (Integer in Delphi).
You must make sure that "text" is allocated in the external app's memory space. You will not be able to allocate text in the caller app and pass it to another app as each of them will have their own private memory space.

Categories

Resources