GPS Programming Tips for Windows Mobile - Part 1
NETCF: Memory leak... now what??
Supporting Kiosk-Applications on Windows Mobile (Technically achievable vs. supported)
Wireless Programming on Windows Mobile: supported or not supported?
Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes
Disable WebBrowser's Context-Menu in NETCF applications
MAPI on Windows Mobile 6: Programmatically retrieve mail BODY (sample code)
Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control
The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)
Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity
Support Boundaries for Windows Mobile Programming (Developing Drivers, for example... Or even WiFi Programming)
Miei post in italiano sul team-blog del Supporto Tecnico agli Sviluppatori
Some time ago I handled a request coming from a developer that wanted to intercept Windows Messages sent to another application, in other terms "subclass" the other application's main window. On Desktop's Win32 you can do that by setting a hook through SetWindowsHookEx (which exists on Windows CE but it's a private API), but due to the specific nature of the Win32 implementation under Windows CE before release 6.0, a process can access the memory space of another one: this has been done in the past due to the very tight limitations about Virtual Memory imposed on a Windows CE platform (32 processes, each of them 32MB of virtual space). You might read this great article written by Doug Boling some years ago, but still valid for Windows Mobile as long as it's based on platforms before Windows Embedded CE 6.0 (the version of the same article for Windows CE 6.0 is here).
Therefore on Windows Mobile 5.0 and 6 (both based on Windows CE 5.0 despite of the name! ) GetWindowLong() API with GWL_WNDPROC flag works even if you call it for a different process (contrarily to a real Win32-based OS, like desktops and Windows CE 6.0). This allows us to intercept messages in this way, in a managed application:
IntPtr hwnd = IntPtr.Zero; hwnd = FindWindow(strClassName, strWindowName); newWndProc = new WndProcDelegate (NewWndProc); oldWndProc = GetWindowLong(hwnd, GWL_WNDPROC); int success = SetWindowLong(hwnd, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc));
public IntPtr NewWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == WM_ACTIVATE) { MessageBox.Show("hook!"); } return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam); }
So, in case you want to subclass NETCF applications and you only know the executable's name, a possible approach is to enumerate active processes to get the ID of the one you’re interested on, so that you can use System.Diagnostic.Process.MainWindowHandle property to retrieve the window handle you want to subclass. Unfortunately NETCF's System.Diagnostic.Process class doesn’t implement all the methods that would have helped, for example .GetProcesses(): however, you can mix up a “Process” class exposing a .GetProcesses() method with the System.Diagnostic.Process to exploit its .MainWindowHandle. Such custom class is provided as a sample by the following MSDN article: Creating a Microsoft .NET Compact Framework-based Process Manager Application (and I imagine it was the internal implementation of SDF v1.4's OpenNetcf.Diagnostics.Process).
The result is the sample code below, as usual provided "AS IS" (this is not "production-code", it's meant to have didactic\testing purposes):
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Reflection; using System.IO; using System.Diagnostics; namespace SubclassDemo { public partial class Form1 : Form { private static IntPtr oldWndProc = IntPtr.Zero; private static WndProcDelegate newWndProc; private static IntPtr hWndTarget = IntPtr.Zero; private string strClassName = string.Empty; private string strWindowName = string.Empty; public Form1() { InitializeComponent(); ////txtTargetExeName is a TextBox where user enters target application's name //LaunchSecondApp(txtTargetExeName.Text); //make sure that our application has the focus SetForegroundWindow(this.Handle); } ////assumption: target application is under the same path of running application //private void LaunchSecondApp(string strExeName) //{ // string strAppPath = Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName; // strAppPath = Path.GetDirectoryName(strAppPath); // strAppPath = strAppPath.Trim(); // if (!strAppPath.EndsWith("\\")) // strAppPath += "\\"; // Process.Start(strAppPath + strExeName, string.Empty); //} // SET if you know strClassName and strWindowName of the window you want to subclass private void btnSetHookKnowingNames_Click(object sender, EventArgs e) { hWndTarget = FindWindow(strClassName, strWindowName); newWndProc = new WndProcDelegate(NewWndProc); oldWndProc = GetWindowLong(hWndTarget, GWL_WNDPROC); int success = SetWindowLong(hWndTarget, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc)); } //RESET (before closing) if you know strClassName and strWindowName of the window you want to subclass private void btnRemoveHookKnowingNames_Click(object sender, EventArgs e) { int success = SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc); } //SUBCLASSING PROCEDURE public IntPtr NewWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { //defined WM_RAFFAEL for testing purposes if (msg == WM_RAFFAEL) { MessageBox.Show("hook!"); return IntPtr.Zero; } return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam); } //TEST private void btnTest_Click(object sender, EventArgs e) { SendMessage(hWndTarget, WM_RAFFAEL, 0, 0); } //private string strTargetAppExeName = "TestMANAGED".ToLower(); //SET if you don't know strClassName and strWindowName: this is true for managed applications private void btnManagedSet_Click(object sender, EventArgs e) { //You know the process's executable name (and path, if required) //of the 2nd managed application. --> strTargetAppExeName ////Now: ////Enumerate all the processes, in order to get their ProcessID ////For each ProcessID, check if its executable is the one we're interested on ////Stop when we find the right Process ID ////For example something similar to: //IntPtr pID = GetProcessIDOfTheManagedApp(strTargetAppExeName); ////Enumerate all the windows and for each window use ////GetWindowThreadProcessID to see if it's associated to the ////ProcessID we found ////stop when finding a window associated to that process ////then use GetWindowLong(PARENT) to verify it's the main window of that process ////For example something similar to: //hWndTarget = GetMainWindowOfTheManagedApp(pID); ////BUT... //Utilities.Process class exposes .GetProcesses() //System.Diagnostic.Process exposes .MainWindowHandle Utilities.Process[] processes = Utilities.Process.GetProcesses(); foreach (Utilities.Process p in processes) { //string name = p.ToString().ToLower(); //strTargetAppExeName = strTargetAppExeName.ToLower(); //if (p.ToString().ToLower() == strTargetAppExeName) //since we're comparing 2 strings, both potentially manually entered by user //use .ToLower() to modify the capital letters //use .TrimEnd(".exe".ToCharArray() to remove, if any, the ".exe" from the application name //this is also because in some cases managed applications are reported as without .exe at the end in the name if (p.ToString().ToLower().TrimEnd(".exe".ToCharArray()) == txtTargetExeName.Text.ToLower().TrimEnd(".exe".ToCharArray())) { System.Diagnostics.Process pp = System.Diagnostics.Process.GetProcessById((int)p.Handle); //Handle and PID are the same in WINCE hWndTarget = pp.MainWindowHandle; } } //as per native apps, associate new WndProc newWndProc = new WndProcDelegate(NewWndProc); oldWndProc = GetWindowLong(hWndTarget, GWL_WNDPROC); int success = SetWindowLong(hWndTarget, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc)); } //private void GetProcessIDOfTheManagedApp(string strTargetAppExeName) //{ //} //private void GetMainWindowOfTheManagedApp(IntPtr processID) //{ //} //REMOVE the hook if you're closing! private void Form1_Closing(object sender, CancelEventArgs e) { int success = SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc); } #region DllImport public const int GWL_WNDPROC = (-4); public const int WM_RAFFAEL = 123456789; public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("coredll", SetLastError = true)] public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("coredll", SetLastError = true)] public static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newWndProc); [DllImport("coredll", SetLastError = true)] public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("coredll.dll", SetLastError = true)] private static extern IntPtr FindWindow(string _ClassName, string _WindowName); [DllImport("coredll.dll", SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); [DllImport("coredll.dll", SetLastError = true)] private static extern bool SetForegroundWindow(IntPtr hWnd); #endregion DllImport } }
"supported"?? An interesting API: GetForegroundKeyboardTarget Sometimes cases arrive to my
A possible approach valid on WM5\6\6.1, no longer using DTM_BROWSERDISPATCH Appendix: sample code to
Wow! Impressive. And I had always been told that it was impossible. Very good article.
I tried this it hangs the Start Menu to death. So I have to soft-rest the phone.
Hi Jayson, you tried to subclass the taskbar window, thus intercepting messages sent to the Start Menu?
hi, i'm trying to intercept messages going to the taskbar but when another application is opened the devices freezes
Hi John, if you're intercepting messages sent to taskbar then you may want to read my other post about risks of a kiosk-mode app: blogs.msdn.com/.../supporting-kiosk-applications-on-windows-mobile-technically-achievable-vs-supported.aspx.
HTH!
~raffaele
Hi Raffaele! I'm trying to intercept window messages of the SIP panel on Windows Mobile 6.5. The window name is "SipWndClass". I added the WS_CAPTION style to the window to let the user to drag&drop the window easily. I'm trying to intercept the WM_WINDOWPOSCHANGED message to modify the size and position of the "SipBackDropWndClass" window as well, otherwise the keyboard panel movement is not correct. I can do all these things, but even if I unsublcass the SIP panel when I close the application the keyboard disappears and only a warm boot solves the problem. Is there something special that I should taking care of when I'm trying to achieve this?
Thank you for your help in advance!
Hi Oliver, you should remember to reset the WndProc: see SetWindowLong(hWndTarget, GWL_WNDPROC, oldWndProc) in the didactic code above.
Thanks,