How to find message only window in C# by using FindWindowEx? - c#

I think I've searched all of related topics on this planet by using Chinese and English but cannot find the solution.
I've created one message only window to receive and process the data from WM_COPYDATA, but I cannot find the window in send side, below is demo (WPF of C#):
Receive:
public partial class MainWindow : Window
{
private readonly IntPtr sourceHandle;
private const int WM_COPYDATA = 0x004A;
[StructLayout(LayoutKind.Sequential)]
public struct CopyDataStruct
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
public MainWindow()
{
InitializeComponent();
sourceHandle = this.CreateMessageOnlyWindow();
this.btnReceive.Content = sourceHandle;
}
private IntPtr CreateMessageOnlyWindow()
{
IntPtr HWND_MESSAGE = new IntPtr(-3);
HwndSourceParameters sourceParam = new HwndSourceParameters() { ParentWindow = HWND_MESSAGE };
var source = new HwndSource(sourceParam);
source.AddHook(WndProc);
return source.Handle;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if (msg == WM_COPYDATA)
{
MessageBox.Show(lparam.ToInt32().ToString());
handled = true;
return new IntPtr(20);
}
return IntPtr.Zero;
}
}
Send:
public partial class MainWindow : Window
{
IntPtr WM_COPYDATA = new IntPtr(0x004A);
IntPtr HWND_MESSAGE = new IntPtr(-3);
[DllImport("User32.dll")]
public static extern IntPtr SendMessage(IntPtr hwnd, IntPtr msg, IntPtr wParam, ref COPYDATASTRUCT IParam);
[DllImport("User32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
public MainWindow()
{
InitializeComponent();
}
private void BtnSend_Click(object sender, RoutedEventArgs e)
{
//Here cannot find the target message only window in receiving
IntPtr WINDOW_HANDLE = FindWindowEx(HWND_MESSAGE, IntPtr.Zero, null, null);
if (WINDOW_HANDLE != IntPtr.Zero)
{
byte[] arr = System.Text.Encoding.Default.GetBytes(txtMessage.Text);
int len = arr.Length;
COPYDATASTRUCT cdata;
cdata.dwData = (IntPtr)100;
cdata.lpData = txtMessage.Text;
cdata.cData = len + 1;
SendMessage(WINDOW_HANDLE, WM_COPYDATA, IntPtr.Zero, ref cdata);
}
}
}
BtnSend_Click method in Send, cannot find the right window handle here, could someone in this pallet can help?
PS: I should describe my requirement first: I want to create a windows service in C#, which is receiver and deal with the data from WM_COPYDATA, so I think message only window is necessary because there is NO window in windows service.
So in Sender, I need to find this message only window to pass the window handle into SendMessage at first, here is the point.
Thanks guys

Related

Correct passing struct to FindText function using PInvoke

I am using this code to call FindText function (https://msdn.microsoft.com/en-us/library/ms646918.aspx) from Comdlg32.dll:
delegate IntPtr FRHookProc(IntPtr hdlg, uint uiMsg, IntPtr wParam,
IntPtr lParam);
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct FINDREPLACE
{
public uint lStructSize;
public IntPtr hwndOwner;
public IntPtr hInstance;
public uint Flags;
public IntPtr lpstrFindWhat;
public IntPtr lpstrReplaceWith;
public ushort wFindWhatLen;
public ushort wReplaceWithLen;
public IntPtr lCustData;
public FRHookProc lpfnHook;
public IntPtr lpTemplateName;
}
[DllImport("comdlg32.dll", CharSet=CharSet.Auto)]
static extern IntPtr FindText(ref FINDREPLACE lpfr);
public void SearchText(string text)
{
try
{
_lpfrStruct.hwndOwner = _windowHandle;
_lpfrStruct.lpstrFindWhat = Marshal.StringToHGlobalAnsi(text);
_lpfrStruct.wFindWhatLen = (ushort)text.Length;
//_lpfrStruct.lStructSize = (uint)Marshal.SizeOf(_lpfrStruct);
_ptrFindTextHandle = FindText(ref _lpfrStruct);
if(_ptrFindTextHandle == IntPtr.Zero)
{
int error = CommDlgExtendedError();
Debug.WriteLine("[VI] Cannot create FindText dialog, error: " + error.ToString());
}
}
catch (Exception)
{
}
}
I suppose the line //_lpfrStruct.lStructSize = (uint)Marshal.SizeOf(_lpfrStruct); is an issue
If commented out the CommDlgExtendedError returns 1 (CDERR_STRUCTSIZE = 0x0001 - The lStructSize member of the initialization structure for the corresponding common dialog box is invalid.) when I keep it I get an Exception (writing to protected memory).

Hook up click event after AppendMenu of 3rd party Application

i'm trying to add a new MenuItem using DLL Fucntions imported of the user32.dll using DLLImort to a third party application out of my WPF app.
No I'd like to get the click event of the newly generated MenuItem. Any ideas?
Here's the code so far. I know there are functions of SetWindowHookEx or something else, but I'm stuck.
It's some test code and not bulletproofed..
public partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern IntPtr GetMenu(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("user32.dll")]
private static extern bool InsertMenuItem(IntPtr hMenu, uint uItem, bool
fByPosition, [In] ref MENUITEMINFO lpmii);
[DllImport("user32.dll")]
private static extern bool DrawMenuBar(IntPtr hWnd);
internal const UInt32 MIIM_FTYPE = 0x00000100;
internal const UInt32 MF_STRING = 0x00000000;
internal const UInt32 MF_OWNERDRAW = 0x00000100;
const uint MF_POPUP = 0x00000010;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool AppendMenu(IntPtr hMenu, MenuFlags uFlags, uint uIDNewItem, string lpNewItem);
[DllImport("user32.dll")]
static extern IntPtr CreatePopupMenu();
[Flags]
public enum MenuFlags : uint
{
MF_STRING = 0,
MF_BYPOSITION = 0x400,
MF_SEPARATOR = 0x800,
MF_REMOVE = 0x1000,
MF_POPUP = 0x00000010,
}
[StructLayout(LayoutKind.Sequential)]
struct MENUITEMINFO
{
public uint cbSize;
public uint fMask;
public uint fType;
public uint fState;
public uint wID;
public IntPtr hSubMenu;
public IntPtr hbmpChecked;
public IntPtr hbmpUnchecked;
public IntPtr dwItemData;
public string dwTypeData;
public uint cch;
public IntPtr hbmpItem;
// return the size of the structure
public static uint sizeOf
{
get { return (uint)Marshal.SizeOf(typeof(MENUITEMINFO)); }
}
}
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
createMenuEntry();
}
private void createMenuEntry()
{
Process[] proceses = Process.GetProcessesByName("spotify");
Process process = proceses.Where(e => e.MainWindowTitle == "Spotify").First();
IntPtr handle = process.MainWindowHandle;
IntPtr mainMenu = GetMenu(handle);
int mainMenuItemCount = GetMenuItemCount(mainMenu);
AppendMenu(mainMenu, MenuFlags.MF_STRING, 555, "TestEntry");
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
//HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
//source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...
Debug.WriteLine((int)wParam);
if (((int)wParam == 555))
{
MessageBox.Show("Click");
}
return IntPtr.Zero;
}
}
Thanks for any ideas or suggestions in advance.
Your first step is to put down the C# and understand how the native menu API works. Start here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms647553.aspx
I strongly recommend that you create a new C++ project and write a simple program to add a menu and respond to clicks.
The key information is found in the documentation I linked to, with my emphasis:
When the user chooses a command item, the system sends a command message to the window that owns the menu. If the command item is on the window menu, the system sends the WM_SYSCOMMAND message. Otherwise, it sends the WM_COMMAND message.
You need to intercept that message. I suspect that means to need to use a global WH_CALLWNDPROC hook. That's going to need an unmanaged DLL to implement the hook.

AccessViolation when calling Marshal.Copy using user32 SendMessage

I'm writing a new tool for my organisation that has to talk via SendMessage to a legacy tool.
I just made a test app using code from here:
http://www.c-sharpcorner.com/Blogs/6444/
I have edited the send code to match my purpose. But I'm having some problems receiving messages in my "GetMessage" form. The message does go through but the program breaks when trying to convert the data into a string.
Here's my code:
Send:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, ref COPYDATASTRUCT lParam);
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
private void button1_Click(object sender, EventArgs e)
{
IntPtr hwnd = FindWindow(null, "GetMessage");
if (hwnd != null)
{
string message = textBox1.Text + "-" + System.DateTime.Now.ToString();
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.lpData = (int)Marshal.StringToHGlobalAnsi(message);
cds.cbData = message.Length;
SendMessage(hwnd, (int)WM_COPYDATA, 0, ref cds);
}
}
Recieve:
private const int WM_COPYDATA = 0x4A;
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public int dwData;
public int cbData;
public int lpData;
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_COPYDATA:
COPYDATASTRUCT CD = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
byte[] B = new byte[CD.cbData];
IntPtr lpData = new IntPtr(CD.lpData);
//string test = Marshal.PtrToStringAuto(lpData, CD.lpData); // this doesn't work either
Marshal.Copy(lpData, B, 0, CD.cbData); // access violation here
string strData = Encoding.Default.GetString(B);
listBox1.Items.Add(strData);
break;
default:
base.WndProc(ref m);
break;
}
}
The error I'm getting:
Attempted to read or write protected memory. This is often an
indication that other memory is corrupt.
I've tried setting the message string in send to a field (so it doesn't go out of scope), setting the "int wParam" argument of Send Message to an "IntPtr" and using IntPtr.Zero instead of 0 in my SendMessage call, and null terminating the string as below:
cds.lpData = (int)Marshal.StringToHGlobalAnsi(message + '\0');
cds.cbData = (message+'\0').Length;
Still getting the same problem.
I fixed the issue, I believe that the problem was that I was using int instead of IntPtr for my SendMessage and COPYDATASTRUCT.
I got this information from http://pinvoke.net/default.aspx/user32.SendMessage and http://pinvoke.net/default.aspx/Structures/COPYDATASTRUCT.html
Here's the working code:
private const int WM_COPYDATA = 0x004A;
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
Send:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
//static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); // original
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref COPYDATASTRUCT cds); // override
private void SendMsg()
{
if (_hwnd != null)
{
COPYDATASTRUCT cds;
cds.dwData = IntPtr.Zero;
cds.lpData = Marshal.StringToHGlobalAnsi(stockCode);
cds.cbData = stockCode.Length;
SendMessage(_hwnd, (int) WM_COPYDATA, IntPtr.Zero, ref cds);
}
}
Recieve:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_COPYDATA:
COPYDATASTRUCT CD = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
byte[] B = new byte[CD.cbData];
IntPtr lpData = CD.lpData;
Marshal.Copy(lpData, B, 0, CD.cbData);
string strData = Encoding.Default.GetString(B);
listBox1.Items.Add(strData);
break;
default:
base.WndProc(ref m);
break;
}
}
I believe this is working on 64bit windows.

Message loop is blocking application

I need to close a download popup in web browser control (disallow user to downloading file).
How i can achieve this?
I found this:
How to block downloads in .NET WebBrowser control?
And i used second answer. It's working but i have problem with it. It's seems that calling GetText of the created object is blocking a whole thread. I don't have any solution for it.
private static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (idObject == 0 && idChild == 0)
{
if(eventType == EVENT_OBJECT_CREATE)
{
string text = GetText(hwnd);
if (text.Contains("File Download"))
SendMessage(hwnd, 0x0010, IntPtr.Zero, IntPtr.Zero); //close window
}
}
}
public static string GetText(IntPtr hWnd)
{
int length = GetWindowTextLength(hWnd); //my app is freezing here - i think it's because i'm calling it from message loop.
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
//edit
Ok, thanks #sgorozco for suggestion. Now i'm using SetWindowsHookEx and WH_CBT. Then in message loop i'm catching HCBT_CREATEWND events. But i have problem with getting CBT_CREATEWND from lparm. I'm getting "Managed Debugging Assistant 'FatalExecutionEngineError'" exception.
Here is my current code:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CREATESTRUCT
{
public IntPtr lpCreateParams;
public IntPtr hInstance;
public IntPtr hMenu;
public IntPtr hwndParent;
public int cy;
public int cx;
public int y;
public int x;
public int style;
public string lpszName;
public string lpszClass;
public int dwExStyle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CBT_CREATEWND
{
public IntPtr lpcs;
public IntPtr hwndInsertAfter;
}
private static IntPtr MessageLoopFuctnion(int code, IntPtr wParam, IntPtr lParam)
{
if (code < 0)
{
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
if(code == 3)
{
CBT_CREATEWND info;
info = (CBT_CREATEWND)Marshal.PtrToStructure(lParam, typeof(CBT_CREATEWND));
CREATESTRUCT info1;
info1 = (CREATESTRUCT)Marshal.PtrToStructure(info.lpcs, typeof(CREATESTRUCT)); //here exception is throwing
if (info1.lpszName != null && info1.lpszName.Contains("File Download")))
SendMessage(wParam, 0x0010, IntPtr.Zero, IntPtr.Zero); //close popup
//Marshal.FreeHGlobal(info.lpcs); //error, why?
//Marshal.FreeHGlobal((IntPtr)lParam.ToUInt64());
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
//set hook
IntPtr hWinEventHook = SetWindowsHookEx(5, myCallbackDelegate, user32DLL, 0);
//edit 2
Here are my definitions:
private delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
private static HookProc myCallbackDelegate = new HookProc(MessageLoopFuctnion);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
My myCallbackDelegate is a static field so it's not GC collected for sure.
Temporary i'm enumerating all windows every 500ms and look for dialog which contains text "File Download". But it is an ugly solution.
The reason for the FatalExecutionEngineError are the strings in CREATESTRUCT. Replace these with a IntPtr and you will get no exception.
[StructLayout(LayoutKind.Sequential)]
public struct CREATESTRUCT {
public IntPtr lpCreateParams;
public IntPtr hInstance;
public IntPtr hMenu;
public IntPtr hwndParent;
public int cy;
public int cx;
public int y;
public int x;
public int style;
public IntPtr lpszName;
public IntPtr lpszClass;
public int dwExStyle;
}
(Maybe you can use GetClassName and GetWindowText instead as workaround. not tested)

Get tooltips text from C# with PInvoke

I'm using PInvoke in C#, trying to read tooltips visible in a window with a known handler, but the apps who's windows I try to inspect in this manner crash with memory access violation errors, or simply don't reveal the tooltip text in the lpszText TOOLINFO member.
I'm calling EnumWindows with a callback and then sending a message to the tooltip window in that function:
public delegate bool CallBackPtr(IntPtr hwnd, IntPtr lParam);
static void Main(string[] args)
{
callBackPtr = new CallBackPtr(Report);
IntPtr hWnd = WindowFromPoint(<mouse coordinates point>);
if (hWnd != IntPtr.Zero)
{
Console.Out.WriteLine("Window with handle " + hWnd +
" and class name " +
getWindowClassName(hWnd));
EnumWindows(callBackPtr, hWnd);
Console.Out.WriteLine();
}
public static bool Report(IntPtr hWnd, IntPtr lParam)
{
String windowClassName = getWindowClassName(hWnd);
if (windowClassName.Contains("tool") &&
GetParent(hWnd) == lParam)
{
string szToolText = new string(' ', 250);
TOOLINFO ti = new TOOLINFO();
ti.cbSize = Marshal.SizeOf(typeof(TOOLINFO));
ti.hwnd = GetParent(hWnd);
ti.uId = hWnd;
ti.lpszText = szToolText;
SendMessage(hWnd, TTM_GETTEXT, (IntPtr)250, ref ti);
Console.WriteLine("Child window handle is " + hWnd + " and class name " + getWindowClassName(hWnd) + " and value " + ti.lpszText);
}
return true;
}
Here's how I defined the TOOLINFO structure:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
private int _Left;
private int _Top;
private int _Right;
private int _Bottom;
}
struct TOOLINFO
{
public int cbSize;
public int uFlags;
public IntPtr hwnd;
public IntPtr uId;
public RECT rect;
public IntPtr hinst;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
public IntPtr lParam;
}
the TTM_GETTEXT value
private static UInt32 WM_USER = 0x0400;
private static UInt32 TTM_GETTEXT = (WM_USER + 56);
and the SendMessage overload
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref TOOLINFO lParam);
So, is there any obvious error that I'm missing in my code, what should I change so that this situation is resolved?
Edit: Here is the whole code, so you could test.
You are sending a private message across processes, which requires manual marshaling. Here's another stackoverflow question on the same topic. Better would be to change direction entirely and use Active Accessibility and/or UI Automation, which are designed for this sort of thing.
I ended up using UI Automation, as Raymond suggested. AutomationElement, who's Name property value contains the text in case of tooltips, proved to be exactly what the code required. I'm cycling through all the Desktop's child windows, where all the tooltips reside and I only display those that belong to the process that owns the window under the mouse:
public static bool Report(IntPtr hWnd, IntPtr lParam)
{
if (getWindowClassName(hWnd).Contains("tool"))
{
AutomationElement element = AutomationElement.FromHandle(hWnd);
string value = element.Current.Name;
if (value.Length > 0)
{
uint currentWindowProcessId = 0;
GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);
if (element.Current.ProcessId == currentWindowProcessId)
Console.WriteLine(value);
}
}
return true;
}
static void Main(string[] args)
{
callBackPtr = new CallBackPtr(Report);
do
{
System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF
currentWindowHWnd = WindowFromPoint(mouse);
if (currentWindowHWnd != IntPtr.Zero)
EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);
Thread.Sleep(1000);
}
while (true);
}

Categories