Quantcast
Channel: CodeProject – Windows CE Programming
Viewing all 36 articles
Browse latest View live

Mobile Development: RDP AutoLogin v5

$
0
0

Based on version-4 here is an more extended version with the option to preset Resource mapping (Device storage and Remote desktop sound).

These two options are controlled by the default.rdp file:

EnableDriveRedirection:i:1
AudioRedirectionMode:i:

with the following options for windows mobile:

EnableDriveRedirection
1=enables access to local files inside the host session
0=disable access to local files

AudioRedirectionMode
0=Redirect sounds to the client,
1=Play sounds at the remote computer,
2=Disable sound redirection; do not play sounds at the server

The settings program has been updated to let you change the corresponding registry settings for RDP_AutoLogin.exe:

REGEDIT4

[HKEY_LOCAL_MACHINE\Software\RDP_autologin]
...
"AudioRedirectionMode"=dword:00000002
"DeviceStorage"=dword:00000001
...

Here are the updated executables:

RDP_Autologin.exe: DOWNLOAD:RDP_AutoLogin v5 - (Hits: 137, size: 22 kB)

RDP_Autologin_Settings.exe: DOWNLOAD:RDP_Autologin_Settings - (Hits: 127, size: 24.5 kB)

RDP_Keepbusy.exe: DOWNLOAD:RDP_Keepbusy - (Hits: 110, size: 9 kB)

Update 21.aug.2012: new binary downloads at code.google.com. Fixed a buf with RDP_AutoLogin (if run twice). Added RDM_KeepBusy settings to settings application (see also changelog at google code).

Source Code


Windows Mobile: CF how to catch F1 and F2 in WEH

$
0
0

Before WEH and Windows Mobile 6 it was easy to catch all keys including the function keys in a Compact Framework (CF) application, you simply had to use Allkeys(true) and Form.Keypreview=true.

Actually, with Windows Embedded Handheld (WEH) or Windows Mobile 6.5.3 the above will not work for the F1 and F2 key. There are simply no KeyDown and KeyUp events reaching your CF application.

Windows Mobile 6.1 demo

with IMessageFilter active, F1 and F2 can be captured

After removing IMessageFilter no F1 and F2 keys are being captured

With WEH Microsoft moved the Start button from the taskbar at top to the menu bar at bottom. Further on, the menu bar is now using graphic tiles to display the top menu, the Close and OK/Done option. The OK/Close button also moved from taskbar to menu bar. Additionally the menu bar is higher than in Windows Mobile 6.1. That leaves less space for your client window.

Due to the above changes in the UI, the window messages are routed in another unknown way and normally a CF application does not get F1 and F2 key messages. Possibly the CF main message queue gets notification messages but these are handled internally by the CF runtime, you only see the menus are working.

Windows message routing

How to overcome this restriction

I tried several approaches to get the F1 and F2 keystrokes in a test CF application.

  • Using a Microsoft.WindowsCE.Forms MessageWindow with a WndProc fails. [CF MessageWindow approach]
  • Using SetWindowLong with a WndProc replacement fails, if used only on the form handle. But you may use that and implement it  for every  child element in the form. It looks like the messages are not routed thru the main fom WndProc (see  native winapi below), or lets say, the F1 and F2 keys do not bubble from child to parent as seen in a Win32 API window application. [Subclassing approach]
  • using OpenNetCF Application2/ApplicationEx and an IMessageFilter works. If you already use or see other needs for   OpenNetCF/SmartDeviceFramework, you may use this approach. [SmartDeviceFramework Application2/IMessageFilter approach]
  • using a global keyboard hook works. That is the one I prefer, as it easy to use and gives all keyboard messages. [KeybdHook approach]

Native Win32 window application

In a native window application you create a new window class, provide a WndProc and a message queue handler and init the
class. If you need a textbox or a button, you create a window insde the main window using a pre-defined windows class (let
me call them stock window classes).

All these windows have there own WndProc. The a stock window classes handles standard messages itself. Messages that are not
handled by a window should call DefWindowProc. So unhandled messages are transfered from a focused child window to its
parent window and it’s WndProc. Stock window classes provide much functionality itself, for example an edit stock window
handles cursor movement, cut copy and paste of text, painting and much more.

If a stock window class does not handle a message like the F1 keydown message, it returns it to its parent window, normally your main window. Now your main window WndProc can react on this message with a WM_KEYDOWN handler and inform the OS that it handled the message. The message is then removed from the global message queue. If your code let windows know, that you dont handled the message (return FALSE), the message is returned and can be handled by the OS. If no window handles the message, the OS discards the message of the message queue.

The above works fine with function keys, if your app uses Allkeys(true). If you do not use Allkeys(true), the OS (the Windows Mobile GWES (Graphic Windowing Event System)) handles the message BEFORE it is dispatched to other windows. As GWES handles the message itself, for example to do Volume Up/Down with F6/F7 key presses, it is NOT placed in the message queue for any other window. When you press F1 or F2 GWES opens the left or right main menu of the foreground app (if it has a menu).

Using Allkeys workes fine with native window apps. The window app’s WndProc gets keyup and keydown messages for the function keys too, even for F1 and F2.

Allkeys works also fine with CF apps before WEH, for example within Windows Mobile 6.1. But in WEH a CF app does not get the F1 and F2 key stroke. Thanks to Microsoft (also for malfunction of SHFullScreen in WEH, but this is another story).

The different approaches

Keytest3AKsdf

This example shows the usage of IMessageFilter of the Smart Device Framework (SDF) and its Application2/Ex class. In your program starter you simply have to change the Run function to:

            //Application.Run(new Keytest3AKsdfForm());
            Application2.Run(new Keytest3AKsdfForm());

You see, instead of starting your app with Application.Run you use the SDF Application2.Run call. Additionally you have to use:

        public Keytest3AKsdfForm()
        {
            InitializeComponent();
            _hwnd = new List<IntPtr>();
            _hwnd.Add(this.Handle);
            _hwnd.Add(this.listView1.Handle);
            Application2.AddMessageFilter(this);
            win32.AllKeys(true);
...
        List<IntPtr> _hwnd;
...
        public bool PreFilterMessage(ref Message m)
        {
            System.Diagnostics.Debug.WriteLine("msghandler1: " + 
                m.HWnd.ToInt32().ToString("X8") +
                ", " + m.Msg.ToString("X8") +
                ", " + m.WParam.ToInt32().ToString("X8") +
                ", " + m.LParam.ToInt32().ToString("X8"));

            //only process msg to our windows
            if (_hwnd.Contains(m.HWnd))
                return MsgHandler(m);
            else
                return false;

        }

        private bool MsgHandler(Message m)
        {
            if (m.Msg == WindowsMessages.WM_KEYDOWN || m.Msg == WindowsMessages.WM_KEYUP)
            {
                //add the msg to our listview
                addItem(m);
                Keys k = (Keys)m.WParam.ToInt32();
                System.Diagnostics.Debug.WriteLine("msghandler2: " + m.HWnd.ToInt32().ToString("X8") +
                    //", " + m.Msg.ToString("X8") +
                    ", " + ((WindowsMessages.WM_MESG)m.Msg).ToString() +
                    ", " + m.WParam.ToInt32().ToString("X8") + 
                    ", " + m.LParam.ToInt32().ToString("X8"));
                if (_bForwardKeys == false)
                    return true;//let windows now that we handled the message
                else
                    return false;
            }
            return false;//let windows now that we DID NOT handled the message
        }

The first lines use a list of handles (IntPtr) to later filter messages by GUI components.

Then the line Application2.AddMessageFilter(this); adds an IMessageFilter for the form. Now, your form’s code has to implement a function “public bool PreFilterMessage(ref Message m)”. This function is called, when a message is sent to your form (mouse movements, clicks, keyboard events etc). In the above code, I first check the provided window handle against the list of handles to filter. Then MsgHandler is called and processes WM_KeyDown/Up messages only.

The line win32.AllKeys(true); prevents the OS to process keyboard messages itself (ie menu (F1/F2), Volume Up/Down (F6F7)). There is an additinol win32 class that implements a set of native windows API calls.

Internally SDF uses a custom message pump (see also the native message pump using while(GetMessage){…). This message pump is invoked before the compact framework form’s message pump and gives access to F1 and F2, whereas the compact framework itself did not.

The keyboard hook approach

This example uses a keyboard hook library. In the form’s code you use it this way:

    public partial class Keytest3AKcs : Form
    {
        //HookKeyboard
        Keyboard.KeyHook.KeyboardHook _keyboardHook;

        public Keytest3AKcs()
        {
            InitializeComponent();
            win32.AllKeys(mnuAllkeys.Checked);
...
            try
            {
                //test with keyhook
                _keyboardHook = new KeyHook.KeyboardHook(this);
                _keyboardHook.HookEvent += new KeyHook.KeyboardHook.HookEventHandler(_kHook_HookEvent);
...
            }
            catch (SystemException ex)
            {
...
                _keyboardHook = null;
            }
        }
        void _kHook_HookEvent(object sender, KeyHook.KeyboardHook.HookEventArgs hookArgs)
        {
            addLog("HookEvent: " + //win32.helpers.hex8(hookArgs.Code) + ", " +
                win32.helpers.hex8(hookArgs.wParam) + ", " +
                win32.helpers.hex8(hookArgs.hookstruct.vkCode) + ", " +
                win32.helpers.hex8(hookArgs.hookstruct.scanCode) 
                );
            addItem(hookArgs);
        }

First we define a new object of type KeyboardHook. Then we need to initialize this class with the form and then add an event handler for the keyboard messages.

The keyboard class is located in a library project of its own and in general uses SetWindowsHookEx. Here is only a small snippet of the libs code:

            public void Start()
            {
                if (_hook != IntPtr.Zero)
                {
                    //Unhook the previouse one
                    this.Stop();
                }
                _hookDeleg = new HookProc(HookProcedure);

                _hook = NativeMethods.SetWindowsHookEx(
                    NativeMethods.WH_KEYBOARD_LL,
                    _hookDeleg,
                    NativeMethods.GetModuleHandle(null),
                    0);
                if (_hook == IntPtr.Zero)
                {
                    throw new SystemException("Failed acquiring of the hook.");
                }
            }

I wrote the code as a lib to be able to make it usable for VB programmers to.

Keytest3AKvb

Here is the code to use the keybd lib in compact framework VB:

Imports KeybdHook

Public Class Form1
    Dim WithEvents _keyhook As KeybdHook.KeyHook.KeyboardHook
    Dim _bForwardKeys As Boolean = False

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        initListView()
        _keyhook = New KeybdHook.KeyHook.KeyboardHook(Me)
        'AddHandler _keyhook.HookEvent, AddressOf hookProc 'do not use or you will get messages twice
        win32.AllKeys(True)
    End Sub

    Public Sub hookProc(ByVal sender As Object, ByVal hookArgs As KeybdHook.KeyHook.KeyboardHook.HookEventArgs) Handles _keyhook.HookEvent
        'System.Diagnostics.Debug.WriteLine("hookProc: " + hookArgs.wParam.ToInt32().ToString("X8"))

        addItem(hookArgs)
        Select Case hookArgs.wParam.ToInt32()
            Case WindowsMessages.WM_KEYDOWN
                System.Diagnostics.Debug.WriteLine("WM_KEYDOWN: " + hookArgs.hookstruct.vkCode.ToString("X8"))
            Case WindowsMessages.WM_KEYUP
                System.Diagnostics.Debug.WriteLine("WM_KEYUP: " + hookArgs.hookstruct.vkCode.ToString("X8"))
        End Select
    End Sub

Here is the screen of Keytest3AKvb:

Downloads

Code and binaries at: CodePlex

Windows Mobile: disable touch input

$
0
0

Hello

sometimes you might want to ‘lock’ the screen, better say: disable touch input. For example, if you put the device in a pocket, to avoid accidentally tapped screen elements.

It took me a long time and deep searches and trials to find out, how that could be done. Beside there is a API function called DisableTouch, you need to reboot to re-enable touch screen input. The corresponding API EnableTouch() needs a function pointer as callback for touch input processing. How could you provide some?

Fortunately I found TouchRegisterWindow and TouchUnregisterWindow. Although I do not know exactly what they are for and there is NO documentation to find anywhere. The only references I found was about transcriber input and at the TouchLockPro code.

I managed to use the functions easily after some digging in the TouchLockPro (sourceforge project) code. TouchLockPro disables touch screen input by creating a window of zero size and registering this with TouchRegisterWindow. The WndProc of this zero size window does nothing and the screen is ‘blocked’ for touch input.

But there is an easier use: you can just register the desktop window handle of Windows Mobile. If you use it with TouchRegisterWindow(), the touch input is no more forwarded to the mobile screen elements. To ‘unlock’ touch screen input, you just have to call TouchUnregisterWindow() with the handle of the desktop window (GetDesktopWindow()).

Looking thru the window list with CE remote Spy you will see two Desktop windows:

   

GetDesktopWindow() will return the non-zero one.

Now, you know an easy way to lock and unlock the touch screen input.

If you are coding a kiosk mode app, it may be easier to just code some ‘ignore input’ function without using TouchRegisterWindow and TouchUnregisterWindow of touch.dll.

BTW: you should use these two functions dynamically.

As some have problems implementing a disableTouch, here is my code:

// disabletouch1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "hooks.h"

int _tmain(int argc, _TCHAR* argv[])
{
    if(argc==2){
        InitializeTouchDll();
        HWND hwnd = GetDesktopWindow();
        DEBUGMSG(1,(L"GetDesktopWindow() = 0x%08x", hwnd));
        long lCmd;
        lCmd = _wtol(argv[1]);

        if(lCmd==1){        //disable touch
            DEBUGMSG(1, (L"+++ TouchRegisterWindow called\n"));
            return m_fpTouchRegisterWindow(hwnd);
        }
        else if (lCmd==0){    //enable touch
            SetLastError(0);
            DEBUGMSG(1, (L"--- TouchUnregisterWindow called\n"));
            m_fpTouchUnregisterWindow(hwnd);
            return GetLastError();
        }
        else
            return -2;//wrong arg
    }
    else
        return -1; //no args no fun
    return 0;
}

with the first function call being:

HMODULE    m_TouchDLL;
TouchRegisterWindow_t    m_fpTouchRegisterWindow;
TouchUnregisterWindow_t    m_fpTouchUnregisterWindow;
...
bool InitializeTouchDll() {
    LOGDEBUG(L"InitializeTouchDll()");
    if (m_TouchDLL == NULL) {
        m_TouchDLL = LoadLibrary(L"touch.dll");
        if(m_TouchDLL != NULL) {
            m_fpTouchPanelPowerHandler = (TouchPanelPowerHandler_t)GetProcAddress(m_TouchDLL, L"TouchPanelPowerHandler");
            m_fpTouchRegisterWindow = (TouchRegisterWindow_t)GetProcAddress(m_TouchDLL, _T("TouchRegisterWindow"));
            m_fpTouchUnregisterWindow = (TouchUnregisterWindow_t)GetProcAddress(m_TouchDLL, _T("TouchUnregisterWindow"));
            if ((m_fpTouchPanelPowerHandler != NULL) && (m_fpTouchRegisterWindow != NULL) && (m_fpTouchUnregisterWindow != NULL)) {
                return true; // everything Ok
            }
        }
    } else {
        LOGERROR(L"Error in initializing Touch dll");
        return false;
    }
    LOGERROR(L"Error in finding Touch dll register/unregister functions");
    return false; // something wrong
}

That is all you need.

Download C source code and VS2008 project file
DOWNLOAD:DisableTouch1 - (Hits: 8, size: 6.04 kB)

Download C source code of DLL to be used from C# or so (just use the exported disableTouch(bool bEnable) function with true or false):
DOWNLOAD:DisableTouchDLL - (Hits: 6, size: 4.82 kB)

A set of source code is available at http://code.google.com/p/win-mobile-code/source/browse/#svn%2Ftrunk%2FdisableTouch

There are  windows, command line, DLL and CSharp example codes.

 

 

Windows Mobile: redirect function keys into Internet Explorer Mobile browser

$
0
0

iHookIE6

this small tool enables you to use Function keys within Internet Explorer Mobile (IEM) web sites.

Normally, most function keys are catched and used by the OS (GWES) to perfom special actions like menu softkeys, phone call, end phone, volume up, volume down and more.

Using a keyboard hook we can catch the function key presses, or better say WM_KEYDOWN and WM_KEYUP messages before the OS can catch them.

One challenge was to find the window that processes normal key presses. The keyboard windows messages are not send to the top level window. Using the Remote Spy Tool I found the Window inside Internet Explorer window that finally processes keyboard messages. Now the tool can hook the keyboard, catch F key presses (F1 to F24) and send them directly to the browser window (class name = “Internet Explorer_Server”). The tool simply uses FindWindow and GetWindow to locate the window handle of this window and then does a PostMessage with WM_KEYDOWN and WM_KEYUP directly to the browser window.

The hook function:

__declspec(dllexport) LRESULT CALLBACK g_LLKeyboardHookCallback(
   int nCode,      // The hook code
   WPARAM wParam,  // The window message (WM_KEYUP, WM_KEYDOWN, etc.)
   LPARAM lParam   // A pointer to a struct with information about the pressed key
) 
{
    static int iActOn = HC_ACTION;
    bool processed_key=false;
    int iResult=0;
    if (nCode == iActOn) 
    { 
        HWND hwndBrowserComponent=getIEMBrowserWindow();    //get the browser window
        if(getIEMWindow(&iResult)==NULL || hwndBrowserComponent==NULL)    // if IE is not loaded or not in foreground or browser window not found
            return CallNextHookEx(g_hInstalledLLKBDhook, nCode, wParam, lParam);

        PKBDLLHOOKSTRUCT pkbhData = (PKBDLLHOOKSTRUCT)lParam;
        //we are only interested in FKey press/release
        if(pkbhData->vkCode >= VK_F1 && pkbhData->vkCode <=VK_F24){
            DEBUGMSG(1,(L"found function key 0x%08x ...\n", pkbhData->vkCode));
            if(processed_key==false){
                if (wParam == WM_KEYUP)
                {
                    //synthesize a WM_KEYUP
                    DEBUGMSG(1,(L"posting WM_KEYUP 0x%08x to 0x%08x, lParam=0x%08x...\n", pkbhData->vkCode, hwndBrowserComponent, g_lparamCodeUp[pkbhData->vkCode - 0x70]));
                    PostMessage(hwndBrowserComponent, WM_KEYUP, pkbhData->vkCode, g_lparamCodeUp[pkbhData->vkCode - 0x70]);
                    processed_key=true;
                }
                else if (wParam == WM_KEYDOWN)
                {
                    //synthesize a WM_KEYDOWN
                    DEBUGMSG(1,(L"posting WM_KEYDOWN 0x%08x to 0x%08x, lParam=0x%08x...\n", pkbhData->vkCode, hwndBrowserComponent, g_lparamCodeDown[pkbhData->vkCode - 0x70]));
                    PostMessage(hwndBrowserComponent, WM_KEYDOWN, pkbhData->vkCode, g_lparamCodeDown[pkbhData->vkCode - 0x70]);
                    processed_key=true;
                }
            }
        }
    }
    else
        DEBUGMSG(1, (L"Got unknwon action code: 0x%08x\n", nCode));
    //shall we forward processed keys?
    if (processed_key)
    {
        processed_key=false; //reset flag
        if (bForwardKey){
            return CallNextHookEx(g_hInstalledLLKBDhook, nCode, wParam, lParam);
        }
        else
            return true;
    }
    else
        return CallNextHookEx(g_hInstalledLLKBDhook, nCode, wParam, lParam);
}

You will see, that the web site code in IEM can now process these function key presses using the body onKeyDown event.

the browser window with a javascript key demo:


(see also onkey.htm in http://www.hjgode.de/wp/2009/05/14/internet-explorer-mobile-handles-key-events/)

The same will not work, if you switched the browser engine from IE6 (or MSHTML) to PocketIE using the HKLM\Security\Internet Explorer\MSHTML=”0″ entry.

Screenshot shows that function keys are sent to the browser window. But you will see no reaction inside the web site.

Only the newer engine, started with Windows Mobile AKU 6.1.4, supports keyboard events inside the javascript object model.

To find the browser window, you normally would use EnumChildWindows(), but this API is not available on Windows CE based devices. So I re-used some code to scan a window for child and sibling windows to find the nested browser window:

//search for a child window with class name
static BOOL bStopScan=FALSE;
BOOL scanWindow(HWND hWndStart, TCHAR* szClass){
    HWND hWnd = NULL;
    HWND hWnd1 = NULL;    
    hWnd = hWndStart;
    TCHAR cszWindowString [MAX_PATH]; // = new TCHAR(MAX_PATH);
    TCHAR cszClassString [MAX_PATH]; //= new TCHAR(MAX_PATH);
    TCHAR cszTemp [MAX_PATH]; //= new TCHAR(MAX_PATH);
    BOOL bRet=FALSE;
    while (hWnd!=NULL && !bStopScan){
        //do some formatting
        GetClassName(hWnd, cszClassString, MAX_PATH);
        GetWindowText(hWnd, cszWindowString, MAX_PATH);
        wsprintf(cszTemp, L"\"%s\"  \"%s\"\t0x%08x\n", cszClassString, cszWindowString, hWnd);//buf);
        //DEBUGMSG(1, (cszTemp));
        if(wcsicmp(cszClassString, szClass)==0){
            DEBUGMSG(1 , (L"\n################### found target window, hwnd=0x%08x\n\n", hWnd));
            //set global hwnd
            g_hWndIEM6=hWnd;    //store in global var
            hWnd=NULL;
            hWndStart=NULL;
            bRet=TRUE;
            bStopScan=TRUE;
            return TRUE;    //exit loop
        }
        // Find next child Window
        hWnd1 = GetWindow(hWnd, GW_CHILD);
        if( hWnd1 != NULL ){ 
            scanWindow(hWnd1, szClass);
        }
        hWnd=GetWindow(hWnd,GW_HWNDNEXT);        // Get Next Window
    }
    return bRet;
}

Thanks to all the code providers in the internet. Keep coding and publishing.

Download (VS2008/WM5 SDK C++ project and binary)

DOWNLOAD:iHookIE6_v20120927.zip - VS2008 source code, WM5 SDK, DEBUG binary (Hits: 45, size: 42.58 kB)

Windows Mobile: CameraCaptureDialog alternative

$
0
0

This is not really an alternative but a different approach if you need the user to take multiple pictures in one session and then return to your application.

The code starts the Camera application in background and waits for the minimise/close of the Camera dialog. If you first launch the camera using “pimg.exe -camerakey” the camera dialog shows a live preview (liveview). If you press the Enter key, you will get a snapshot and the camera dialog shows a preview of the image (imageview).

   

The screenshot for liveview does not show the real preview.

If you click (OK) in liveview, the dialog is hidden. If you click (OK) in imageview the liveview is displayed.

The demo app shows only two buttons and a listbox.

When you click [Start], the pimg.exe is started and comes into front. The showCamera application remains in background and waits for the ‘exit’ of the camera application.

showCamera also uses FileSystemWatcher (opennetcf) to watch for the new photo files taken. When the camera app is ‘closed’, a listbox shows the new photos.

Attached is the full C# source code (VS2008, WM 6.5.3, CF3.5) including a binary.

DOWNLOAD:showCamera (C# VS2008 source, WM 6.5.3 SDK) incl. binary - (Hits: 116, size: 169.23 kB)

 

Windows Mobile: Kiosk mode – Clear Today and Programs

$
0
0

This time I combined a set of functions to clear the Start Menu and the Today/Home Screen:

Before

  

After

  

Empty the Start Menu

The Start Menu is cleared by removing all files below “\Windows\Start Menu” (using SHGetSpecialFolderPath) after the whole bunch has been backed up into a Zip file (using Ionic compact framework library: http://dotnetzip.codeplex.com/). Additionally one registry key has to be changed to remove the Setting icon (HKLM\Security\Shell\StartInfo:HideSettings).

If the Settings icon has been changed, you need to reboot the device to make it visible or not.

Empty the Home Screen

All items in the home screen are enabled via the registry. The tool simply iterates thru all subkeys of HKLM\Software\Microsoft\Today\Items and sets Enabled=0. Before doing so another Backup is created with the current settings (using XML and serialization). Additionally, some other reg keys have to be changed: One is HKLM\Software\Microsoft\Today:Date to hide the Date item. Another is
HKLM\Software\Microsoft\Today:Enabled and, to disable the Home screen will popup periodically, HKLM\Software\Microsoft\Shell\Rai:SessionTimeout.

After changing the Home screen items via an app, the device has to be rebooted.

ClearDevice

The tool enables you to play with the above settings. It does backups before removing all the default stuff, so you can restore the settings later on.

Source code and binary

Attached is the source code (VS2008, WM 6.5.3 DTK, CF3.5, C#) and a binary inside.

DOWNLOAD:ClearDevice (source and bin) - (Hits: 51, size: 363.14 kB)

Windows Mobile – A simple web server with extended features

$
0
0

The attached web server offers some options to control the device or show informations about it. The web server runs on the device and enables direct control or delivers information about the device.

The project enclosed default web page offers all implemented commands. The following is a list of what is currently implemented:

  • SystemInfo: get OS version info
  • BEEP: let the device beep
  • START: starts a process
  • KILL: kills a process
  • SETLED: light/unlight/blink a LED
  • VIBRATE: special LED case, this sets a LED which is a placeholder for the vibrate motor
  • SIP: show/hide the Software Keyboard
  • SHOW: ShowWindow implementation (show normal, hide, minimize etc)
  • ENABLEWINDOW: enable or disable a window

The default web page

  

If you click a function (command) the output is displayed at the bottom of the example default page:

You can also call the functions directly from any web browser that has access to the devices network and get only the answer back.

In example, calling “http://192.168.128.100:8088/ahah.html?SystemInfo” will give you “‘SystemInfo’ ”, ‘Microsoft Windows CE 5.2.29040′ = OK” or so.

Reflection instead of switch() or If/ElseIf

These commands can be easily extended as the code uses reflection to look up the commands you send. This is a better approach than doing a If/ElseIf or Switch() serach for known commands:

        /// <summary>
        /// here the myCommands object is queried for the recognized command
        /// </summary>
        /// <param name="sFunc">name of the command</param>
        /// <param name="sArg">arguments to the command</param>
        /// <param name="sResponse">return string of a command</param>
        /// <returns>True if existing command was found</returns>
        public static bool execCmd(string sFunc, string sArg, ref string sResponse)
        {
            bool bRet = true;
            try
            {
                // Instantiate this class
                myCommands cmbn = new myCommands(sFunc, sArg);

                // Get the desired method by name: DisplayName
                //MethodInfo methodInfo = typeof(CallMethodByName).GetMethod("DisplayName");
                MethodInfo methodInfo = typeof(myCommands).GetMethod(sFunc);

                // Use the instance to call the method without arguments
                methodInfo.Invoke(cmbn, null);
                sResponse = cmbn.response;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception in execCmd for '" + sFunc + "' and '" + sArg + "' " + ex.Message); 
                bRet = false; 
            }
            return bRet;
        }
...

The myCommands class looks like this and is easy to extend:

        /// <summary>
        /// this class holds all know commands
        /// the class is queried using reflection to lookup a command
        /// </summary>
        class myCommands
        {

            string name;
            string arg="";
            public string response="";

            public myCommands(string name, string sArg)
            {
                this.name = name;
                this.arg = sArg;
            }

            /// <summary>
            /// return some System information
            /// </summary>
            public void SystemInfo()
            {
                string sOS = System.Environment.OSVersion.ToString();                
                response = sOS;
            }

            public void BEEP()
            {
                beepType beep = (beepType)Enum.Parse(typeof(beepType), arg, false);
                MessageBeep(beep);
            }
...

So, if you need to add an command, simply add a new function inside myCommands class using arg and response to communicate with the web server.

Code and binary

The Visual Studio 2008 source code is written against  Compact Framwork 2.0. There are two projects, one is the great CompactWeb web server and the other is the webCommand project. The webCommand project includes a directory inetpub, which is deployed below the applications directory.

inetpub Directory

    • default.html
      this is the default web site containg examples for all commands
      Example:

 

<html>
<script language="javascript" type="text/javascript" src="ahah.js"></script>
<script language="javascript" type="text/javascript">
//Calls the library function 'ahah' - defined in 'ahah.js' file.
function load(filename) {
    ahah(filename,"content");
}
</script>
<body>
<p>WebCommands TestPage</p>
<h2>Informations</h2>
<ul id="SystemInfo">
 <li><a href="javascript:load('ahah.html?SystemInfo');">OS Version</a></li>
</ul>
...
  • javascript.js
    simply loads ahah.js and implements the load function (asynchronously call (AJAX) technology).
  • ahah.js
    this javascript library does asynchronously call the ‘web commands’ (AJAX).

The rest of the files are included for testing. The same is valid for the server dir, which contains example files which will work on an external web server (ie XAMPP on windows), if cross-side scripting is allowed. The server files contain fixed IP address for the device and have to be changed if you would like to test cross-side scripting.

I insist on your fantasy, what elese is possible. Some examples: get a screenshot, get extended device informations, get a process list…….

DOWNLOAD:webCommand_src_and_bin - (Hits: 57, size: 336.61 kB)

Mobile Development: Watch the power of your device, think green

$
0
0

This is a very simple application to show the values of GetSystemPowerStatusEx2 in live view.

That enables you to see how much power is drawn of the battery if you switch WiFi on or off for example.

  

On industrial devices you can also check the power consumption of special modules like a barcode scanner.

The power current values shown are negative if the power is drawn off the battery. Do not misunderstand this. A -565mA value says that the battery draw current is 565mA (dis-charging). If you read a positive value, the battery is currently charging.

The app can also been hidden and run in background and log the battery life time and percent to a file.

201211071400    97    17:33:00    1    0    0
201211071401    97    17:21:35    1    0    0
201211071401    97    17:21:35    1    0    0
201211071404    97    17:30:08    1    0    0
201211071404    97    17:10:24    1    0    0
201211071405    97    14:16:30    1    0    0
201211071405    97    12:26:06    1    0    0
201211071405    97    12:26:06    1    0    0
201211071405    97    12:26:06    1    0    1
201211071405    97    11:45:27    1    0    1
201211071405    97    11:45:27    1    0    1
201211071405    97    11:01:41    1    0    1
201211071405    97    11:01:41    1    0    1

The first column shows the date time in an excel friendly form, then the life percent and the estimated remaining life time is listed. The next three columns represent the status of WiFi, WWAN and Bluetooth.

Although there is no special with the code here are some snippets:

The perfChart initialization:

            //setup bar graph
            perfChart1._SymmetricDisplay = true;
            perfChart1._ScaleMode = SpPerfChart.ScaleMode.Relative;

            perfChart1.BackColor = Color.LightGray;
            perfChart1.ForeColor = Color.Black;

            SpPerfChart.PerfChartStyle perfStyle = new SpPerfChart.PerfChartStyle();
            SpPerfChart.ChartPen cpen = new SpPerfChart.ChartPen();
            cpen.Color=Color.Blue;
            cpen.Width=2;
            perfStyle.ChartLinePen = cpen;
            perfStyle.BackgroundColorBottom = Color.LightGray;
            perfStyle.BackgroundColorTop = Color.LightGray;
            perfChart1._PerfChartStyle = perfStyle;

Adding a new value to perfChart:

        void timerStatus_Tick(object sender, EventArgs e)
        {
            if (iStatusTimerCount > 9)
            {
                iStatusTimerCount = 0; progressBar1.Value = iStatusTimerCount;

                txtBatteryCurrent.Text = _batterStatus._BatteryCurrent.ToString();
                perfChart1._AddValue(_batterStatus._BatteryCurrent);
...

If you already know perfChart you see that I added some options. The perfChart can now hold positive and negative values and will still scale automatically.

Source and Binary download:

DOWNLOAD:BatteryLog2 Src and Bin - VS2008 sources (WM6 SDK) and binary (Hits: 47, size: 92.28 kB)

Mobile Development: Move your Form

$
0
0

Although I do not yet know a use case for this, here comes some code to make your smartdevice forms being moveable.

As default, Windows forms on mobile devices are created always as maximized forms. There may be situations, where you need a moveable form. The trick in compact framework is to use SetWindowLong with WS_CAPTION style and apply that to your form.

  

To enable you to experminet more with Window Styles there is another demo enabling to check all known window stlyes with a form. Be warned, setting WS_DISABLED or some other styles will make your form inaccessible.

  

The above changes or Window Styles are well known to every native C Windows programmer. Possibly you are now curious what else can be done with a good background knowledge of Windows API programming.

Another nice piece of code is how to get a list of an enum type. The below needs a list of options to build the checkboxes in the form:

        //build a list of chk options for WSYTLES
        void buildOptions()
        {
            string[] stylesList = winapi.getStyles();

            int iCount=0;
            foreach (string s in stylesList)
            {
                CheckBox chkBox = new CheckBox();
                chkBox.Left = offsetX;
                chkBox.Top = iCount * offsetY;
                chkBox.Size = new Size(widthX, heightX);
                chkBox.Text = s;
                uint uStyle = (uint)Enum.Parse(typeof(winapi.WINSTYLES),s,false);
                if ((uiCurrentStyle & uStyle) == uStyle)
                    chkBox.Checked = true;
                chkBox.CheckStateChanged += new EventHandler(chkBox_CheckStateChanged);
                tabPage1.Controls.Add(chkBox);
                iCount++;
            }
        }

But an enum can not be enumerated ( great wording :-) ). Although you could build the list by hand, I am a lazy programmer. I already have entered all the values as an enum. But there is a solution:

        public static string[] getStyles()
        {
            List<string> list = new List<string>();
            foreach (WINSTYLES ws in GetValues(new WINSTYLES()))
            {
                list.Add(ws.ToString());
            }
            return list.ToArray();
        }

with the special GetValues() function for the enum (WINSTYLES):

        ...
        [Flags]
        public enum WINSTYLES:uint{
            WS_OVERLAPPED =     0x00000000,    //#define WS_OVERLAPPED       WS_BORDER | WS_CAPTION
...
            WS_MAXIMIZE=        0x01000000,
            WS_CAPTION =        0x00C00000,    //#define WS_CAPTION          0x00C00000L     /* WS_BORDER | WS_DLGFRAME  */
 ...
            WS_MAXIMIZEBOX=     0x00010000,
            WS_POPUPWINDOW=     0x80880000,     //     Creates a pop-up window with WS_BORDER, WS_POPUP, and WS_SYSMENU styles. The WS_CAPTION and WS_POPUPWINDOW styles must be combined to make the window menu visible.
        }        
...
        //great stuff by http://ideas.dalezak.ca/2008/11/enumgetvalues-in-compact-framework.html
        public static IEnumerable<Enum> GetValues(Enum enumeration)
        {
            List<Enum> enumerations = new List<Enum>();
            foreach (FieldInfo fieldInfo in enumeration.GetType().GetFields(
                  BindingFlags.Static | BindingFlags.Public))
            {
                enumerations.Add((Enum)fieldInfo.GetValue(enumeration));
            }
            return enumerations;
        }

Have fun.

DOWNLOAD:MoveableForms Source and Binary - VS2008, WM5 SDK, WM6.5.3 DTK (Hits: 38, size: 98.51 kB)

Mobile Development: Analyze QRcode content on Windows Mobile

$
0
0

QRreaderWH6

You possibly know these QRcodes that make your phone open a web site or add a contact. Unfortunately there is no such function on Windows Mobile and I just wrote a demo of such a functionality:
A demo app to show how these nifty QR barcodes are encoded to hold URLs, business cards, appointments or other data. Further on, the application shows how to use QR code reading and import or launch the appropiate application for the QRcode content. The demo uses ZXing.Net to encode text into a QRcode barcode image but ZXing or ThoughtWorks code to decode QRcode images as the latter was easier to use. Then there is a list of sample encodings to show you how to specify a vcard or SMS or others to be encoded into a readable QRcode.

After start of the app and then simply click in the type list at the top and select for example “vcard”. In the sample field you can see how a vcard is to be encoded:

BEGIN:VCARD
N:Heinz-Josef Gode
ORG:ACME GmbH
TITLE:Systems Engineer
TEL:0123456789
URL:www.hjgode.de/wp
EMAIL:heinz-josef.gode@somewhere.com
ADR:Musterstrasse 1
NOTE:Support Engineer
END:VCARD

Now click [decode&encode] and you get the QRcode image and the cleaned content:

  

The ‘decoded’ cleaned content is:

Heinz-Josef Gode
Systems Engineer
ACME GmbH
Musterstrasse 1
0123456789
heinz-josef.gode@somewhere.com
www.hjgode.de/wp
Support Engineer

If you click [process] instead, the information is transferred to the right app (if any) on the device. For a vcard, this is Pocket Outlook or say Contacts. You can check the information has arrived in Contacts:

     

Easy! Just using zxing client code and some mobile code. Here is a calendar entry example:

  

You also have the option to save the QRcode image or to load QRcode images. You even can take a photo of a QRcode print. See the File menu with Save Image, Load Image and the Extras menu with Camera.

The code to parse a QRcode text is:

            if (comboBox1.SelectedIndex == -1)
                return;
            testQRcodes = new TestQRcodes();
            TestQRcodes.TestType tt = (TestQRcodes.TestType)comboBox1.SelectedItem;
            string sTest = tt.content;
            QRdecodeClass qr = new QRdecodeClass(sTest);
            txtResultType.Text = qr.resultTypeString;
            txtResultContent.Text = qr.sResult;
            encode2QRcode(txtResultContent.Text);

whereas encode2QRcode encodes the data into a QRcode image:

        void encode2QRcode(string s)
        {
            try
            {
                var writer = new BarcodeWriter();
                writer.Format = BarcodeFormat.QR_CODE;
                writer.Options.Height = 240;
                writer.Options.Width = 240;
                pictureBox1.Image = writer.Write(s);
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation,MessageBoxDefaultButton.Button1);
            }
        }

The code to decode text from a QRcode image is that:

        private void mnuLoadImage_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                string FileName = ofd.FileName;
                try
                {
                    pictureBox1.Image = new Bitmap(FileName);
                    QRCodeDecoder decoder = new QRCodeDecoder();
                    String decodedString = decoder.decode(new QRCodeBitmapImage(new Bitmap(pictureBox1.Image)));

                    txtResultContent.Text = decodedString;

                    processQRresult.processResult(txtContentSample.Text);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Sorry, could not load image. Exception: " + ex.Message);
                    txtResultContent.Text = ex.Message;
                }
            }
        }

You see the code is simple.

A list of possible standard texts to encode into a barcode: http://archive.is/20120530/http://code.google.com/p/zxing/wiki/BarcodeContents

Warning: the menu extra offers to try to capture an image to be decoded. Most QRCode decoders I have tested fail in decoding these camera pictures. As I am working with and developing for industrial devices, this is no problem for me. Most of these devices have a barcode reader intergrated and decode QRcode images the fastest.

Download source of QRreaderWH6 incl. ZXing.net binary runtimes

DOWNLOAD:QReaderWH6 - (Hits: 43, size: 1.52 MB)

Mobile Development: A remote cpu monitor and cpu usage analysis

$
0
0

cpumon2 and cpumonRcv

Although this project is not yet finished, the main function works very well: viewing cpu usage remotely and capture data to database for later analysis.

Some of the ideas and code is inspired by a cpu usage article at http://www.codeproject.com/Articles/159461/Mobile-Processor-Usage. Further on I got more questions the last days like “Why is app x running slow?” or “When I start that app, the system gets slow and taskmanager shows a high cpu usage.”.

Here are two tools to capture cpu usage of a windows mobile device remotely via a TCP/IP connection.

cpumon2   cpumonRcv

excel-barchart

Before you read on, the code is by far not perfect and there are still many improvements possible. But if you need a working remote cpumon, here we go…

cpumon on device

Why another cpumon or taskmanager like app? Simply as you may test an application without having to switch between the app and taskmanager to see cpu usage. Switching back and for may also interrupt the workflow in your app.

cpumon creates snapshot of all processes and threads periodically including there used user and kernel times. On windows ce, the kernel times are always zero, but that may change in future and so I included that in the statistics info too.

First, cpumon creates a snapshot of all processes with there process ID’s. Then another snapshot of all threads is created with there owner ProcID, thread ID and usage times. There is a delay between two snpashots further called duration. The threads are assigned to the processes and there times (user and kernel) are summarized. This statistics snapshot is put in a queue. This will repeat all 3 seconds. At the end of a statistics set, another thread is released that dequeues the data and sends it using an UDP socket. I decided to use UDP broadcast as this is fast and requires no client/server connection. At the end of a set, one <EOT> packet is sent to let the host know, that a set has been transfered.

The statistic data is also shown in the GUI of cpumon, but this is only for reference.

cpumonRcv

cpumonRcv is a desktop csharp app that listens for UDP broadcasts of cpumon. It converts the received bytes back to a statistic set of processes and threads and en-queues the data until an EOT packet has arrived. Then the data is saved to a local database and can be used for later analyzes. Additionally every new statistic data packet will update a live view.

The code

On the device

A process has an exe name and a list of threads. Every thread is captured with the time it has spend in user and kernel space. The threads and processes are recorded with a timestamp and the duration of two consecutive snapshots. If you sum the user times of all threads of a process and divide that by the duration and multiply with 100 you get the percentage the process has used of the processors time.

The above is reflected in a thread and a process class. Further on, these classes include a conversion from/to byte arrays. These byte arrays build the packet that is send via UDP broadcasts.
The transmitter (cpumon) and receiver share the same code for process and thread class. So the receiver can easily transform byte packets back to processes and threads.

The main class is ProcInfo. It starts the snapshot (usage) thread and the socket thread.

        public ProcInfo()
        {
            statisticsTimes = new Dictionary&lt;string, ProcessStatistics.process_statistics&gt;();
 
            eventEnableCapture = new AutoResetEvent(true);
            eventEnableSend = new AutoResetEvent(false);
 
            //procStatsQueue = new Queue&lt;ProcessStatistics.process_statistics&gt;();
            procStatsQueueBytes = new Queue&lt;byte[]&gt;();
 
            myThreadSocket = new Thread(socketThread);
            myThreadSocket.Start();
 
            myThread = new Thread(usageThread);
            myThread.Start();
        }

The snapshot thread captures a snapshot of processes and threads and adds the data to a dictionary and en-queues it for the socket thread. I use a dictionary for the process stats as this automatically inserts or updates process data. So I do not need to look first, if a process is already known or not and if I have to update existing data or add new data. Another reason is that the code has to look up a the thread list for existing data (TryGetValue) to build the new stats.

        /// &lt;summary&gt;
        /// build thread and process list periodically and fire update event and enqueue results for the socket thread
        /// &lt;/summary&gt;
        void usageThread()
        {
            try
            {
                int interval = 3000;
 
                uint start = Process.GetTickCount();
                Dictionary&lt;uint, thread&gt; old_thread_List;// = Process.GetThreadList();
 
                string exeFile = Process.exefile;
                //read all processes
                Dictionary&lt;uint, process&gt; ProcList = Process.getProcessNameList();
                DateTime dtCurrent = DateTime.Now;
 
                //######### var declarations
                Dictionary&lt;uint, thread&gt; new_ThreadList;
                uint duration;
                long system_total;
                long user_total, kernel_total;      //total process spend in user/kernel
                long thread_user, thread_kernel;    //times the thread spend in user/kernel
                DWORD dwProc;
                float user_percent;
                float kernel_percent;    
                ProcessStatistics.process_usage usage;
                ProcessStatistics.process_statistics stats = null;
 
                string sProcessName = "";
                List&lt;thread&gt; processThreadList = new List&lt;thread&gt;();
 
                //extended list
                List&lt;threadStatistic&gt; processThreadStatsList = new List&lt;threadStatistic&gt;(); //to store thread stats
                while (!bStopMainThread)
                {
                    eventEnableCapture.WaitOne();
                    old_thread_List = Process.GetThreadList();  //build a list of threads with user and kernel times
 
                    System.Threading.Thread.Sleep(interval);
 
                    //get a new thread list
                    new_ThreadList = Process.GetThreadList();   //build another list of threads with user and kernel times, to compare
 
                    duration = Process.GetTickCount() - start;
 
                    ProcList = Process.getProcessNameList();    //update process list
                    dtCurrent = DateTime.Now;
                    system_total = 0;
                    statisticsTimes.Clear();
                    //look thru all processes
                    foreach (KeyValuePair&lt;uint, process&gt; p2 in ProcList)
                    {
                        //empty the process's thread list
                        processThreadList=new List&lt;thread&gt;();
                        processThreadStatsList = new List&lt;threadStatistic&gt;();
 
                        user_total     = 0;  //hold sum of thread user times for a process
                        kernel_total   = 0;  //hold sum of thread kernel times for a process
                        sProcessName = p2.Value.sName;
 
                        //SUM over all threads with that ProcID
                        dwProc = p2.Value.dwProcID;
                        foreach (KeyValuePair&lt;uint, thread&gt; kpNew in new_ThreadList)
                        {
                            thread_user = 0;
                            thread_kernel = 0;
                            //if the thread belongs to the process
                            if (kpNew.Value.dwOwnerProcID == dwProc)
                            {
                                //is there an old thread entry we can use to calc?
                                thread threadOld;
                                if (old_thread_List.TryGetValue(kpNew.Value.dwThreadID, out threadOld))
                                {
                                    thread_user=Process.GetThreadTick(kpNew.Value.thread_times.user) - Process.GetThreadTick(old_thread_List[kpNew.Value.dwThreadID].thread_times.user);
                                    user_total += thread_user;
                                    thread_kernel =Process.GetThreadTick(kpNew.Value.thread_times.kernel) - Process.GetThreadTick(old_thread_List[kpNew.Value.dwThreadID].thread_times.kernel);
                                    kernel_total += thread_kernel;
                                }
                                //simple list
                                thread threadsOfProcess = new thread(kpNew.Value.dwOwnerProcID, kpNew.Value.dwThreadID, kpNew.Value.thread_times);
                                processThreadList.Add(threadsOfProcess);
 
                                //extended list
                                threadStatistic threadStats = 
                                    new threadStatistic(
                                        kpNew.Value.dwOwnerProcID, 
                                        kpNew.Value.dwThreadID, 
                                        new threadtimes(thread_user, thread_kernel), 
                                        duration, 
                                        dtCurrent.Ticks);
                                processThreadStatsList.Add(threadStats);
 
                            }//if dwProcID matches
                        }
                        //end of sum for process
                        user_percent      = (float)user_total / (float)duration * 100f;
                        kernel_percent    = (float)kernel_total / (float)duration * 100f;
                        system_total = user_total + kernel_total;
 
                        // update the statistics with this process' info
                        usage = new ProcessStatistics.process_usage(kernel_total, user_total);
                        // update process statistics
                        stats = new ProcessStatistics.process_statistics(p2.Value.dwProcID, p2.Value.sName, usage, dtCurrent.Ticks, duration, processThreadStatsList);
 
                        //add or update the proc stats
                        if (exeFile != p2.Value.sName || bIncludeMySelf)
                        {
                            statisticsTimes[p2.Value.sName] = stats;
                                procStatsQueueBytes.Enqueue(stats.ToByte());
                        }
 
                        start = Process.GetTickCount();
                    }//foreach process
 
                    onUpdateHandler(new ProcessStatsEventArgs(statisticsTimes, duration));
                    procStatsQueueBytes.Enqueue(ByteHelper.endOfTransferBytes);
                    ((AutoResetEvent)eventEnableSend).Set();
                }//while true
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: usageThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: usageThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("Thread ENDED");
        }

 

The other thread, the socket thread, looks like this:

        /// &lt;summary&gt;
        /// send enqueued objects via UDP broadcast
        /// &lt;/summary&gt;
        void socketThread()
        {
            System.Diagnostics.Debug.WriteLine("Entering socketThread ...");
            try
            {
                const int ProtocolPort = 3001;
                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 32768);
 
                IPAddress sendTo = IPAddress.Broadcast;// IPAddress.Parse("192.168.128.255");  //local broadcast
                EndPoint sendEndPoint = new IPEndPoint(sendTo, ProtocolPort);
 
                //UdpClient udpC = new UdpClient("255.255.255.255", 1111);
                System.Diagnostics.Debug.WriteLine("Socket ready to send");
 
                while (!bStopSocketThread)
                {
                    //block until released by capture
                    eventEnableSend.WaitOne();
                    lock (lockQueue)
                    {
                        //if (procStatsQueue.Count &gt; 0)
                        while (procStatsQueueBytes.Count &gt; 0)
                        {
                            //ProcessStatistics.process_statistics pStat = procStatsQueue.Dequeue();
                            //byte[] buf = pStat.ToByte();
                            byte[] buf = procStatsQueueBytes.Dequeue();
                            if (ByteHelper.isEndOfTransfer(buf))
                                System.Diagnostics.Debug.WriteLine("sending &lt;EOT&gt;");
 
                            sendSocket.SendTo(buf, buf.Length, SocketFlags.None, sendEndPoint);
                            //System.Diagnostics.Debug.WriteLine("Socket send " + buf.Length.ToString() + " bytes");
                            //System.Diagnostics.Debug.WriteLine(pStat.dumpStatistics());
                            System.Threading.Thread.Sleep(2);
                        }
                    }
                    ((AutoResetEvent)eventEnableCapture).Set();
                }
 
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: socketThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: socketThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("socketThread ENDED");
        }

After setting up the socket for port 3001 the thread waits for the event eventEnableSend. Then it starts the transfer of all en-queued packets. The last packet in the queue should be the <EOT> packet, which has been en-queued by the usageThread. The usageThread is waiting during the transfer of the packets and will be released by the socketThread with setting the event eventEnableCapture.

As we can only send bytes thru the socket, the process statistics class include functions to be converted to or from byte arrays. Here is a snippet of the process_statistics class to show you how I did implement this.

            public byte[] ToByte()
            {
                List&lt;byte&gt; buf = new List&lt;byte&gt;();
                //string length
                Int16 bLen = (Int16)System.Text.Encoding.UTF8.GetByteCount(sName);
                buf.AddRange(BitConverter.GetBytes(bLen));
                byte[] bName = System.Text.Encoding.UTF8.GetBytes(sName);
                //string as byte[]
                buf.AddRange(bName);
 
                buf.AddRange(BitConverter.GetBytes(dateTime));
                buf.AddRange(BitConverter.GetBytes(duration));
                buf.AddRange(procUsage.ToByte());
                buf.AddRange(BitConverter.GetBytes(processID));
 
                //list count
                Int16 iCnt = (Int16) ThreadStatList.Count;
                threadStatistic[] threadsArray = ThreadStatList.ToArray();
                buf.AddRange(BitConverter.GetBytes(iCnt));
                //now add the threads of the list
                foreach (threadStatistic th in threadsArray)
                    buf.AddRange(th.ToByte());
 
                return buf.ToArray();
            }

The packet (byte array) can vary in size as a string (the process name) has to be encoded and decoded. So the function first encodes the string length and then the string.

and here a function to get the object back from a byte array.

            public process_statistics FromByte(byte[] buf)
            {
                int offset = 0;
                Int16 bLen = BitConverter.ToInt16(buf, 0); //2 bytes
                offset += sizeof(System.Int16);
                if (bLen &gt; 0)
                    this.sName = System.Text.Encoding.UTF8.GetString(buf, offset, bLen);
                offset += bLen;
                this.dateTime = BitConverter.ToInt64(buf, offset);
                offset += sizeof(System.Int64);
                this.duration = BitConverter.ToUInt32(buf, offset);
                offset += sizeof(System.Int32);
                this.procUsage = new process_usage(ref buf, ref offset);
                //offset = offset; //has been changed by process_usage
 
                this.processID = BitConverter.ToUInt32(buf, offset);
                offset += sizeof(System.UInt32);
 
                //how many thtreads are in the byte stream
                Int16 iCnt = BitConverter.ToInt16(buf, offset);
                offset += sizeof(System.Int16);
                //start reading the threads
                List&lt;threadStatistic&gt; thList = new List&lt;threadStatistic&gt;();
                for (int x = 0; x &lt; iCnt; x++)
                {
                    threadStatistic th = new threadStatistic(buf, ref offset);
                    thList.Add(th);
                }
                this.ThreadStatList = thList;
 
                return this;
            }

Here the string has to be decoded and so first the length has to be read and then the string.

On the PC

cpumonRcv runs on a PC in the same subnet as the device is running cpumon. The receiver code listens for packets coming on UPD port 3001 and then re-assembles the byte to process statistics. These stats are saved to a local database (currently sqlite) and are send to the GUI for a live view.

The main function is the socket server thread code.

    public void StartReceive()
    {
        receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        bindEndPoint = new IPEndPoint(IPAddress.Any, 3001);
        recBuffer = new byte[maxBuffer];
        receiveSocket.Bind(bindEndPoint);
        receiveSocket.BeginReceiveFrom(recBuffer, 0, recBuffer.Length, SocketFlags.None, ref bindEndPoint, new AsyncCallback(MessageReceivedCallback), (object)this);
    }

You see, that is not really a thread. Instead of polling the socket for received bytes within a thread, the code uses the asynchronous BeginReceiveFrom socket call.

The callback will be called whenever there are bytes received. As a packet always holds a complete statistic process data, the GUI can be updated after each received packet. But to be fast, the GUI is only updated when a <EOT> packet has arrived. The other stats data are delivered to the GUI thread, where they are recorded for later use and directly update the GUI.

When a packet has received, we must call BeginReceiveFrom again to be ready for the next packet.

    void MessageReceivedCallback(IAsyncResult result)
    {
        EndPoint remoteEndPoint = new IPEndPoint(0, 0);
        try
        {
            //all data should fit in one package!
            int bytesRead = receiveSocket.EndReceiveFrom(result, ref remoteEndPoint);
            byte[] bData = new byte[bytesRead];
            Array.Copy(recBuffer, bData, bytesRead);
            if (ByteHelper.isEndOfTransfer(bData))
                updateEndOfTransfer();// end of transfer
            else
            {
                ProcessStatistics.process_statistics stats = new ProcessStatistics.process_statistics(bData);
                //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                updateStatus(stats);
            }
        }
        catch (SocketException e)
        {
            System.Diagnostics.Debug.WriteLine(String.Format("MessageReceivedCallback SocketException: {0} {1}", e.ErrorCode, e.Message));
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(String.Format("MessageReceivedCallback Exception: {0}", e.Message));
        }
        try
        {
            //ready to receive next packet
            receiveSocket.BeginReceiveFrom(recBuffer, 0, recBuffer.Length, SocketFlags.None, ref bindEndPoint, new AsyncCallback(MessageReceivedCallback), (object)this);
        }
        catch (Exception) { }
    }

I placed the data capturing inside the GUI thread. The live view will be updated and a queue is filled with the data.

        delegate void addDataCallback(ProcessStatistics.process_statistics procStats);
        void addData(ProcessStatistics.process_statistics procStats)
        {
            if (this.dataGridView1.InvokeRequired)
            {
                addDataCallback d = new addDataCallback(addData);
                this.Invoke(d, new object[] { procStats });
            }
            else
            {
                //enqueue data to be saved to sqlite
                dataQueue.Enqueue(procStats);
 
                //dataAccess.addSqlData(procStats);
 
                dataGridView1.SuspendLayout();
                //dtProcesses.Rows.Clear();
 
                dataAccess.addData(procStats);
 
                //dataGridView1.Refresh();
                dataGridView1.ResumeLayout();
 
                //release queue data
                dataAccess.waitHandle.Set();
 
                //object[] o = new object[7]{ procUsage.procStatistics. .procStatistics. [i].sApp, eventEntries[i].sArg, eventEntries[i].sEvent, 
                //        eventEntries[i].sStartTime, eventEntries[i].sEndTime, eventEntries[i].sType, eventEntries[i].sHandle };
            }
        }

A queue is used for the background task that saves the data to a file. Saving will need some time and should not interfer with the live view or the data receive function.

        Queue&lt;System.Process.ProcessStatistics.process_statistics&gt; dataQueue;
        Thread myDataThread;
 
        public EventWaitHandle waitHandle;

The data is ‘written’ to the local database using a separate thread.

        void dataAddThread()
        {
            try
            {
                while (true)
                {
                    waitHandle.WaitOne();
                    if (dataQueue.Count &gt; 10)
                    {
                        while (dataQueue.Count &gt; 0)
                        {
                            System.Process.ProcessStatistics.process_statistics procStats = dataQueue.Dequeue();
                            addSqlData(procStats);
                        }
                    }
                    Thread.Sleep(10);
                }
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            }
        }

The receiver code has some time (3 seconds) to save the data between the send of two full statistic sets.

The live view shows a list of running processes in a datagridview and a small line plot of the usage times of the selected process.

cpumonRcv

There is another data view that is not yet finished. The detail view shows a list of captured stats with the processes and the timestamp. If you click a line, the threads stats of the process are displayed with there usage. The detail view shows the saved data, where the main view shows live data.

cpumonRcv_detailview

Export and excel analysis

Now I have also finished export functions. From the detail form you can use the menu to export the process or threads table data to CSV files. These can be easily imported into other applications like for example excel or LibreOffice Calc. The below table is the result of a CSV import and adding some calulated fields (the percent ‘of cpu usage’ is calculated by UserTime/Duration*100).

excel01

There is another export which first transforms the data and then exports to a CSV file. This CSV file is much easier to handle for analysis. It contains the recorded times, all processes and the time they spend in user (cpu usage) mode. The export does something like rotating the ‘processes’ table.

        #region Transform
        class PROCESS_USAGE
        {
            public string procname;
            public int user;
            public UInt64 timestamp;
            public PROCESS_USAGE(string sProcessName, int iUserTime, UInt64 iTimeStamp)
            {
                procname = sProcessName;
                user = iUserTime;
                timestamp = iTimeStamp;
            }
        }
 
        public int export2CSV2(string sFileCSV)
        {
            //### setup
            sql_cmd = new SQLiteCommand();
            sql_con = new SQLiteConnection();
            SQLiteDataReader sql_rdr;
            connectDB();
            if (sql_con.State != ConnectionState.Open)
            {
                sql_con.Close();
                sql_con.Open();
            }
            sql_cmd = sql_con.CreateCommand();
            long lCnt = 0;
 
            //### Build a List of known processes
            sql_cmd.CommandText = "Select DISTINCT Process from processes order by Process";
            List&lt;string&gt; lProcesses= new List&lt;string&gt;();
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                lProcesses.Add((string)sql_rdr["Process"]);
            }
            sql_rdr.Close();
            sql_rdr.Dispose();
 
            //create a new table with the process names as fields
            string sProcField = "";
            foreach (string sProc in lProcesses)
            {
                sProcField += "[" + sProc + "] INTEGER,";
            }
            sProcField = sProcField.TrimEnd(new char[] { ',' });
            sProcField = "[Time] INTEGER, " + sProcField;
            //delete existing table            
            lCnt = executeNonQuery("DROP Table IF EXISTS [ProcUsage] ;");
            //create new one
            lCnt = executeNonQuery("Create Table [ProcUsage] (" + sProcField + ");");
 
            //### get all process,user,time data
            List&lt;PROCESS_USAGE&gt; lProcessUsages = new List&lt;PROCESS_USAGE&gt;();
            sql_cmd.CommandText = "Select Process,User,Time from processes order by Time";
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                string sP = (string)sql_rdr["Process"];
                int iUT = Convert.ToInt32(sql_rdr["User"]);
                ulong uTI = Convert.ToUInt64(sql_rdr["Time"]);
                lProcessUsages.Add(new PROCESS_USAGE(sP, iUT, uTI));
            }
            sql_rdr.Close();
 
            //### get all distinct times
            List&lt;ulong&gt; lTimes = new List&lt;ulong&gt;();
            sql_cmd.CommandText = "Select DISTINCT Time from processes order by Time";
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                lTimes.Add(Convert.ToUInt64(sql_rdr["Time"]));
            }
            sql_rdr.Close();
 
            string sUpdateCommand = "";
            //### file the new ProcUsage table
            SQLiteTransaction tr = sql_con.BeginTransaction();
            foreach (ulong uTime in lTimes)
            {
                System.Diagnostics.Debug.WriteLine("Updating for Time=" + uTime.ToString());
                //insert an empty row
                sql_cmd.CommandText = "Insert Into ProcUsage (Time) VALUES(" + uTime.ToString() + ");";
                lCnt = sql_cmd.ExecuteNonQuery();
                foreach (string sPro in lProcesses)
                {
                    //is there already a line?
                    // http://stackoverflow.com/questions/4495698/c-sharp-using-listt-find-with-custom-objects
                    PROCESS_USAGE pu = lProcessUsages.Find(x =&gt; x.procname == sPro &amp;&amp; x.timestamp == uTime);
                    if (pu != null)
                    {
                        System.Diagnostics.Debug.WriteLine("\tUpdating User="+ pu.user +" for Process=" + sPro);
                        //update values
                        sUpdateCommand = "Update [ProcUsage] SET " +
                            "[" + sPro + "]=" + pu.user +
                            " WHERE Time=" + uTime.ToString() + //" AND Process=" + "'" + sPro + "'"+
                            ";";
                        sql_cmd.CommandText = sUpdateCommand;
                        lCnt = sql_cmd.ExecuteNonQuery();
                    }
                }
            }
            tr.Commit();
 
            lCnt = 0;
            SQLiteDataReader rdr = null;
            System.IO.StreamWriter sw = null;
            try
            {
                sw = new System.IO.StreamWriter(sFileCSV);
                string sFields = "";
                List&lt;string&gt; lFields = new List&lt;string&gt;();
                lFields.Add("Time");
                lFields.AddRange(lProcesses);
                foreach (string ft in lFields)
                {
                    sFields += ("'" + ft + "'" + ";");
                }
                sFields.TrimEnd(new char[] { ';' });
                sw.Write(sFields + "\r\n");
 
                sql_cmd.CommandText = "Select * from ProcUsage;";
                rdr = sql_cmd.ExecuteReader(CommandBehavior.CloseConnection);
                while (rdr.Read())
                {
                    lCnt++;
                    sFields = "";
                    //Console.WriteLine(rdr["ProcID"] + " " + rdr["User"]);
                    foreach (string ft in lFields)
                    {
                        sFields += rdr[ft] + ";";
                    }
                    sFields.TrimEnd(new char[] { ';' });
                    sw.Write(sFields + "\r\n");
                    sw.Flush();
                }
            }
            catch (Exception) { }
            finally
            {
                sw.Close();
                rdr.Close();
            }
 
            return 0;
        }
        #endregion

That is the first time that I used a List<>.Find (PROCESS_USAGE pu = lProcessUsages.Find(x => x.procname == sPro && x.timestamp == uTime);). The ‘rotated’ table looks like this:

proc_usage

Using the above data enables you to create very informative 3D bar charts:

proc_usage02

In the chart above you can see the time running from left to right. Each process has its own bar row. The height of the bar shows the relative cpu usage against the measure interval duration of 3000ms (3 seconds).

Possible extensions and planned enhancements

The code is by far not perfect. If you look closer to the exported transformed data, you will recognize empty user time fields. That happens when the receiver thread is not active for example during a time expensive GUI update. So, there are possible improvements.

  • Improve decoupling of receive and display of data
  • Option to log cpu usage locally on the device (if not to time expensive)
  • Integrate better graphics using mschart?

Source Code

Source code at code.google.com

Downloads

Binaries are available inside the google code dirs for cpumon2 and cpumonRcv. For the PC you need the SQLite .Net runtimes installed.

Internet Explorer Mobile – QVGA web site do not scale well to VGA screen

$
0
0

As described in this post, I was looking for a way to get a proper view of pages designed for QVGA screens on devices with higher resolutions like VGA.

1) SAP ITS mobile screen on VGA Internet Explorer Mobile

1) SAP ITS mobile screen on VGA Internet Explorer Mobile

2) SAP ITS mobile screen on VGA display with viewport width=240 and zoom=2

2) SAP ITS mobile screen on VGA display with viewport width=240 and zoom=2

The trick is to use the non-standard viewport META tag. As described here, here and here, you can use meta-viewport to define the initial viewport, the part of the page you want to see and a zoom level.

Fortunately the meta tag for viewport works within Internet Explorer Mobile (Windows Embedded Handheld 6.5.3).

weh-version

The differences between the html code of image 1) and image 2) is just one additional line in the head section of the code for image 2):

<meta name="viewport" content="width=240, initial-scale=2, maximum-scale=2, minimum-scale=2">

This line makes the viewport equal to the PocketPC QVGA width of 240 pixel and specifies a zoom level of 2 to enlarge the viewport onto the VGA screen with a width of 480.

Now, my final goal is to disable the Zoom bar of Internet Explorer Mobile.

See also this post about QVGA and VGA scaling.

Windows Mobile: watch the memory footstep of running processes

$
0
0

Some times ago I posted my remote cpu usage monitor. Now here is a similar tool but for logging the memory. You can now watch the memory usage of processes remotely for example when you test an application.

There are two tools: vmUsage and vmUsageRecvr. You may use the mobile vmUsage alone and just use its logging. The other tool receives the memory status information on a PC and enables long time logging and export to a csv text.

vmusage   memeater-vm   excel-linechart

vmUsage is the mobile application that shows you a list of bars, one bar for each of the 32 possible process slots. It also shows the process name running in a slot and the memory usage. The memory usage is queried using a KernelIOCtl (IOCTL_KLIB_GETPROCMEMINFO). I found that API call at CodeProject. I first tried with the approach made in VirtualMemory at codeproject. But using VirtualQuery for every 4K block inside 32 pieces of 32MB takes a lot of time (256000 blocks!). The following shows a process memEater that is gaining more and more memory:

memeater-vmusage   memeater-vmusage2

You can also see the total physical and available memory in the first bar and you will recognize irregular memory changes too.

The small tool sends all data using UDP to possible receivers. My receiver is called vmUsageRecvr and receives the data and saves every virtual memory status set it to a SQLite database. The data can then be exported and is re-arranged by known processes. The live view of vmUsageRecvr shows the latest receive memory status and a small line graphic showing how the total used memory changed over time.

You can use the exported data in excel and again produce nice graphics.

excel-vmusage

In the above graph you can see memeater is consuming memory in 1MB steps until it crashes. The other memory peek is produced by pimg.exe, the camera dialog, when I made a photo.

Processes may start and go and so there process ID will be zero when they are not running. If a process is gone, vmUsage will not record it in its log:

20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    15175680    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    323395584    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    16228352    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    322342912    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    17281024    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    322342912    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    17281024    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    321282048    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    18337792    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    320163840    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    19456000    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    320163840    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    19456000    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    319111168    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    20508672    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    318054400    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    21561344    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    317001728    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    22614016    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    317001728    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    22614016    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    315949056    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    23666688    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314896384    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    24719360    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314896384    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    24719360    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    313843712    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    25772032    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314822656    vtotal    33554432tvfree    27459584    load    37    
20130221 06:18 pimg    569344    VMusage.exe    1191936    MemEater.exe    25772032    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340561920    vtotal    33554432tvfree    27328512    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1323008    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340430848    vtotal    33554432tvfree    27197440    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1388544    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340365312    vtotal    33554432tvfree    27131904    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1519616    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340234240    vtotal    33554432tvfree    27000832    load    32

 

When a process is gone in vmUsageRecr, the process data is still there. In the following export viewed in excel you can see pimg is first not running. After pimg is started it consumes about 368KB. Then I took a photo and the memory increased to 1.1MB:

excel_process_0

Notes about the code

As said, the code is similar to my cpuMon tool.

A costum panel to simulate a bar graph

vmUsage uses a large panel and places 32 smaller custom panels inside to display a bar graphic. The custom panel’s background is just green and I draw a rectangle on top to visualize the current value. Alternatively I could have used 32  progress bars but I liked to also have text inside the graphic. Here is the OnPaint override code:

        protected override void OnPaint(PaintEventArgs e)
        {
            //draw the background rectangle
            e.Graphics.FillRectangle(new SolidBrush(BackColor), 0, 0, (int)((float)(this.Width / _Maximum) * _Maximum), this.Height);
            //draw foreground rectangle
            if (scaleMode == scaleModeValue.relative)
            {
                e.Graphics.FillRectangle(new SolidBrush(ForeColor), 0, 0, (int)((float)(this.Width / _Maximum) * @Value), this.Height);
            }
            else
            {
                e.Graphics.FillRectangle(new SolidBrush(ForeColor), 0, 0, (int)@Value, this.Height);
            }
            //draw text
            if (Text.Length > 0)
            {
                StringFormat sf = new StringFormat();
                sf.Alignment=StringAlignment.Center;
                RectangleF rect = new RectangleF(0f, 0f, this.Width, this.Height);
                e.Graphics.DrawString(Text, base.Font, new SolidBrush(Color.Black), rect, sf);
            }
            base.OnPaint(e);
        }

When vmUsage is started it starts a background thread that captures the current memory usage data:

            //start the background tasks
            vmiThread = new vmInfoThread();
            vmiThread._iTimeOut = iTimeout*1000;
            vmiThread.updateEvent += new vmInfoThread.updateEventHandler(vmiThread_updateEvent);

In the background thread I am using two events and a queue to sync foreground and background working:

        public vmInfoThread()
        {
            _fileLogger = new Logging.fileLogger(Logging.utils.appPath + "vmusage.log.txt");

            eventEnableCapture = new AutoResetEvent(true);
            eventEnableSend = new AutoResetEvent(false);

            //procStatsQueue = new Queue<ProcessStatistics.process_statistics>();
            procStatsQueueBytes = new Queue<byte[]>();

            myThreadSocket = new Thread(socketThread);
            myThreadSocket.Start();

            myThread = new Thread(usageThread);
            myThread.Start();
        }

The queue is used to decouple the data capture and the data send functions. One thread captures the data into a queue and then releases the socket thread which reads the queued data and releases the data capture thread:

        /// <summary>
        /// build thread and process list periodically and fire update event and enqueue results for the socket thread
        /// </summary>
        void usageThread()
        {
            try
            {
                int interval = 3000;
                //rebuild a new mem usage info
                VMusage.CeGetProcVMusage vmInfo = new CeGetProcVMusage();

                while (!bStopMainThread)
                {
                    eventEnableCapture.WaitOne();
                    List<VMusage.procVMinfo> myList = vmInfo._procVMinfo; //get a list of processes and the VM usage
                    StringBuilder sbLogInfo = new StringBuilder();  //needed to merge infos for log

                    System.Threading.Thread.Sleep(interval);
                    uint _totalMemUse = 0;
                    long lTimeStamp = DateTime.Now.ToFileTimeUtc();

                    //send all data in one block
                    List<byte> buffer = new List<byte>();
                    buffer.AddRange(ByteHelper.LargePacketBytes);
                    foreach (VMusage.procVMinfo pvmi in myList)
                    {
                        pvmi.Time = lTimeStamp;
                        buffer.AddRange(pvmi.toByte());

                        _totalMemUse += pvmi.memusage;

                        if (!pvmi.name.StartsWith("Slot", StringComparison.InvariantCultureIgnoreCase))
                        {
                            //_fileLogger.addLog(pvmi.ToString());    //adds one row for each VM info
                            sbLogInfo.Append(pvmi.name + "\t" + pvmi.memusage.ToString() + "\t");
                        }
                    }                    
                    procStatsQueueBytes.Enqueue(buffer.ToArray());

                    onUpdateHandler(new procVMinfoEventArgs(myList, _totalMemUse));

                    //send MemoryStatusInfo
                    memorystatus.MemoryInfo.MEMORYSTATUS mstat = new memorystatus.MemoryInfo.MEMORYSTATUS();
                    if (memorystatus.MemoryInfo.GetMemoryStatus(ref mstat))
                    {
                        MemoryInfoHelper memoryInfoStat= new MemoryInfoHelper(mstat);

                        //send header
                        procStatsQueueBytes.Enqueue(ByteHelper.meminfostatusBytes);
                        //send data
                        procStatsQueueBytes.Enqueue(memoryInfoStat.toByte());

                        //log global memstatus
                        sbLogInfo.Append(
                            "total\t" + memoryInfoStat.totalPhysical.ToString() +
                            "\tfree\t" + memoryInfoStat.availPhysical.ToString() +
                            "\tvtotal\t" + memoryInfoStat.totalVirtual.ToString() +
                            "tvfree\t" + memoryInfoStat.availVirtual.ToString() +
                            "\tload\t" + memoryInfoStat.memoryLoad + "\t");
                    }

                    //write a log line
                    _fileLogger.addLog(sbLogInfo.ToString()+"\r\n");
                    procStatsQueueBytes.Enqueue(ByteHelper.endOfTransferBytes);
                    ((AutoResetEvent)eventEnableSend).Set();
                }//while true
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: usageThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: usageThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("Thread ENDED");
        }

The call eventEnableCapture.WaitOne(); waits until the event is set by the socket thread. Immediately after this call a new memory usage dataset is loaded. The rest of the code converts the data to bytes and adds the byte buffer to a queue. Then another event is set to release the socketThread.

        /// <summary>
        /// send enqueued objects via UDP broadcast
        /// </summary>
        void socketThread()
        {
            System.Diagnostics.Debug.WriteLine("Entering socketThread ...");
            try
            {
                const int ProtocolPort = 3002;
                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 32768);

                IPAddress sendTo = IPAddress.Broadcast;// IPAddress.Parse("192.168.128.255");  //local broadcast
                EndPoint sendEndPoint = new IPEndPoint(sendTo, ProtocolPort);

                //UdpClient udpC = new UdpClient("255.255.255.255", 1111);
                System.Diagnostics.Debug.WriteLine("Socket ready to send");

                while (!bStopSocketThread)
                {
                    //block until released by capture
                    eventEnableSend.WaitOne();
                    lock (lockQueue)
                    {
                        //if (procStatsQueue.Count > 0)
                        while (procStatsQueueBytes.Count > 0)
                        {
                            byte[] buf = procStatsQueueBytes.Dequeue();
                            if (ByteHelper.isEndOfTransfer(buf))
                                System.Diagnostics.Debug.WriteLine("sending <EOT>");

                            sendSocket.SendTo(buf, buf.Length, SocketFlags.None, sendEndPoint);
                            System.Diagnostics.Debug.WriteLine("Socket send " + buf.Length.ToString() + " bytes");
                            System.Threading.Thread.Sleep(2);
                        }
                    }
                    ((AutoResetEvent)eventEnableCapture).Set();
                }

            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: socketThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: socketThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("socketThread ENDED");
        }

The first call after eventEnableSend.WaitOne(); inside the socket thread locks the queue, so no other code is able to access it. This is not necessary here as we sync the access using the events but I left the lock to show an alternative for async queue access. The rest of the code inside socket thread just sends the bytes of the queue using UDP.

Minor protocol between sender and receiver

During the development of the code I found it better to send a large block of bytes instead of small packets sending each memory info separately. After some time I added some special packets to mark the end-of-transfer. These are usefull to let the receiver know about which packets will come. So I added a memoryStatusInfo packet that informs the receiver when a memory status packet is attached in contrast to the virtual memory information block.

    void MessageReceivedCallback(IAsyncResult result)
    {
        EndPoint remoteEndPoint = new IPEndPoint(0, 0);
        try
        {
            //all data should fit in one package!
            int bytesRead = receiveSocket.EndReceiveFrom(result, ref remoteEndPoint);
            //System.Diagnostics.Debug.WriteLine("Remote IP: " + ((IPEndPoint)(remoteEndPoint)).Address.ToString());

            byte[] bData = new byte[bytesRead];
            Array.Copy(recBuffer, bData, bytesRead);
            if (ByteHelper.isEndOfTransfer(bData))
            {
                System.Diagnostics.Debug.WriteLine("isEndOfTransfer");
                updateEndOfTransfer();// end of transfer
            }
            else if (ByteHelper.isMemInfoPacket(bData))
            {
                System.Diagnostics.Debug.WriteLine("isMemInfoPacket");
                try
                {
                    VMusage.MemoryInfoHelper mstat = new VMusage.MemoryInfoHelper();
                    mstat.fromByte(bData);

                    //System.Diagnostics.Debug.WriteLine(mstat.ToString());

                    updateMem(mstat);
                }
                catch (Exception) { }
            }
            else if(ByteHelper.isLargePacket(bData)){
                System.Diagnostics.Debug.WriteLine("isLargePacket");
                try
                {
                    List<procVMinfo> lStats = new List<procVMinfo>();
                    VMusage.procVMinfo stats = new VMusage.procVMinfo();
                    lStats = stats.getprocVmList(bData, ((IPEndPoint)(remoteEndPoint)).Address.ToString());
                    updateStatusBulk(lStats);
                    //foreach (procVMinfo pvmi in lStats)
                    //{
                    //    pvmi.remoteIP = ((IPEndPoint)(remoteEndPoint)).Address.ToString();
                    //    //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                    //}
                }
                catch (Exception) { }

            }
            else
            {
                System.Diagnostics.Debug.WriteLine("trying vmUsagePacket...");
                try
                {
                    VMusage.procVMinfo stats = new VMusage.procVMinfo(bData);
                    stats.remoteIP = ((IPEndPoint)(remoteEndPoint)).Address.ToString();
                    //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                    if (stats.Time == 0)
                        stats.Time = DateTime.Now.ToFileTimeUtc();
                    updateStatus(stats);
                }
                catch (Exception) { }
            }
        }
        catch (SocketException e)
        ...

In the above vmUsage Recvr code you can see the different branches for different packet types: EndOfTransfer, MemInfoPacket and isLargePacket.

Network only knows bytes

Using TCP/IP you can only transfer bytes and so my memory info classes all contain code to convert from to bytes – a basic serialization and de-serialization. The class files are shared between the Windows Mobile vmUsage and the Windows vmUsageRecvr code. Following is an example of the VMInfo class.

    /// <summary>
    /// holds the VM data of one process
    /// </summary>
    public class procVMinfo
    {
        public string remoteIP;
        public string name;
        public UInt32 memusage;
        public byte slot;
        public UInt32 procID;
        public long Time;
...
        public byte[] toByte()
        {
            List<byte> buf = new List<byte>();
            //slot
            buf.AddRange(BitConverter.GetBytes((Int16)slot));
            //memusage
            buf.AddRange(BitConverter.GetBytes((UInt32)memusage));
            //name length
            Int16 len = (Int16)name.Length;
            buf.AddRange(BitConverter.GetBytes(len));
            //name string
            buf.AddRange(Encoding.UTF8.GetBytes(name));
            //procID
            buf.AddRange(BitConverter.GetBytes((UInt32)procID));
            //timestamp
            buf.AddRange(BitConverter.GetBytes((UInt64)Time));

            return buf.ToArray();
        }
        public procVMinfo fromBytes(byte[] buf)
        {
            int offset = 0;

            //is magic packet?
            if (ByteHelper.isLargePacket(buf))
                offset += sizeof(UInt64);   //cut first bytes

            //read slot
            this.slot = (byte)BitConverter.ToInt16(buf, offset);
            offset += sizeof(System.Int16);

            UInt32 _memuse = BitConverter.ToUInt32(buf, offset);
            memusage = _memuse;
            offset += sizeof(System.UInt32);

            Int16 bLen = BitConverter.ToInt16(buf, offset);
            offset += sizeof(System.Int16);
            if (bLen > 0)
            {
                this.name = System.Text.Encoding.UTF8.GetString(buf, offset, bLen);
            }
            offset += bLen;
            this.procID = BitConverter.ToUInt32(buf, offset);

            offset += sizeof(System.UInt32);
            this.Time = (long) BitConverter.ToUInt64(buf, offset);

            return this;
        }

You see a lot of BitConverter calls. The fromByte function needs to keep track of the offset for reading following data.

vmUsageRecvr

The code uses also a queue to transfer data between background thread (RecvBroadcast) and the GUI.

        public frmMain()
        {
            InitializeComponent();
            //the plot graph
            c2DPushGraph1.AutoAdjustPeek = true;
            c2DPushGraph1.MaxLabel = "32";
            c2DPushGraph1.MaxPeekMagnitude = 32;
            c2DPushGraph1.MinPeekMagnitude = 0;
            c2DPushGraph1.MinLabel = "0";

            dataQueue = new Queue<VMusage.procVMinfo>();

            dataAccess = new DataAccess(this.dataGridView1, ref dataQueue);

            recvr = new RecvBroadcst();
            recvr.onUpdate += new RecvBroadcst.delegateUpdate(recvr_onUpdate);
            recvr.onUpdateBulk += new RecvBroadcst.delegateUpdateBulk(recvr_onUpdateBulk);
            recvr.onEndOfTransfer += new RecvBroadcst.delegateEndOfTransfer(recvr_onEndOfTransfer);

            recvr.onUpdateMem += new RecvBroadcst.delegateUpdateMem(recvr_onUpdateMem);
        }

As we have different packet types for global memory status, single and bulk virtual memory data, I implemented multiple delegates. One handler of is the bulk updater. It gets its data via the custom event arg which is a list of all virtual memory dat for all ‘slots’:

        void recvr_onUpdateBulk(object sender, List<VMusage.procVMinfo> data)
        {
            foreach (VMusage.procVMinfo pvmi in data)
                addData(pvmi);
        }

The data is then feed into the GUI using the addData call:

        delegate void addDataCallback(VMusage.procVMinfo vmdata);
        void addData(VMusage.procVMinfo vmdata)
        {
            if (this.dataGridView1.InvokeRequired)
            {
                addDataCallback d = new addDataCallback(addData);
                this.Invoke(d, new object[] { vmdata });
            }
            else
            {
                dataGridView1.SuspendLayout();
                //enqueue data to be saved to sqlite
                dataQueue.Enqueue(vmdata);

                if (bAllowGUIupdate)
                {
                    dataAccess.addData(vmdata);
                    //release queue data
                    dataAccess.waitHandle.Set();
                }
                dataGridView1.Refresh();
                dataGridView1.ResumeLayout();
            }
        }

You see we have to prepare for cross event calling. Then we suspend the refreshing of the datagrid. The sql data is updated using a queue to decouple the GUI and SQL data saving. dataAccess.addData adds the data to the dataset that is bound to the datagrid.

There is lot more of code inside, just take a look if you like.

Questions?

Leave me a comment if you have any questions.

Source Code

Source code can be loaded from github

Mobile development: show a small information window using WIN32 API

$
0
0

ShowWin

Sometimes you may need to display a small window to inform the user about what is going on. Although the scripting tools MortScript and nScript provide functions to show dialogs they can not show simple information windows.

ShowWin default colors

ShowWin default colors

ShowWin with a progress bar

ShowWin with a progress bar

ShowWin is nothing special but a nearly full configurable window to be used from cmd line tools. It just uses Win32 API calls, constants and structures as FindWindow, PostMessage, SendMessage, WM_COPYDATA, COPYDATASTRUCT, GetSystemMetrics, GetDesktopWindow, GetWindowRect, CreateWindowEx, ShowWindow, UpdateWindow, INITCOMMONCONTROLSEX, GetDeviceCaps, CreateFontIndirect, GetWindowDC, ReleaseDC, PROGRESS_CLASS, InvalidateRect, BeginPaint, CreatePen, SelectObject, Rectangle, SetBkMode, DrawText, EndPaint, SetTextColor, DeleteObject, GetKeyState and PostQuitMessage.

Basic WIN32 programming

Possibly you never wrote a native C windows application. Come on and dive into the basics. It is always good to know the basics even if one writes DotNet or JAVA code.

Supported arguments

 showWin -t "Text zum Anzeigen" -r 90 -g 80 -b 70 -s 8 -w 200 -h 50 -x 0 -y 0 -rt 200 -gt 20 -bt 20 -ti 10 -progr 30 -align left

 ARGS: 
 option/parameter:                meaning:                default:            limitations:
 -t "Text zum Anzeigen"           text to show            "Installing"        255 chars, no " inside, no line breaks, no tabs
 -r 90                            background color RED    255                    0-255
 -g 80                            background color GREEN    207                    0-255
 -b 70                            background color BLUE    0                    0-255
 -s 8                             font size in points        10                    7-24 points
 -w 200                           window width pixels        460                    100-screenwidth
 -h 50                            window height pixels    40                    menu bar height (ie 26pixels)
 -x 60                            window pos X            12                    0 + system window bordersize
 -y 60                            window pos Y            48                    0 + system taskbar bar height. Using 0;0 does not work nice on WM, win may be below taskbar
 -rt 200                          text color RED            0                    0-255
 -gt 20                           text color GREEN        0                    0-255
 -bt 20                           text color BLUE            0                    0-255

 -align center                    text alignment            left                center|left|right

 -ti 10                           timeout to autoclose    0                    no autoclose, min: 1 (second), max: 3600 = one hour

 -progr 10                        show with progress val    0                    no progressbar, max: 100
                                  the progressbar is appended at bottom of textwindow
 -prval                           update progress bar value                    no default, min=1, max=100

 -kill                            kill existing window, exit app

 -m "new message text"            replace text in window                        see -t

Argument parsing

Fortunately I found some code to split arguments supplied to int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow). But i had to adopt the class to be unicode compatible. Windows CE and Windows Mobile uses Unicode for strings.

You know, that the WinMain is the first function called by the OS when you start an application.

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 
    LoadString(hInstance, IDC_SHOWWIN, szWindowClass, MAX_LOADSTRING);

    HWND hwndOld=FindWindow(szWindowClass,NULL);
    // kill request?
    if(hwndOld!=NULL){
        if(wcsicmp(lpCmdLine, L"-kill")==0){
            PostMessage(hwndOld, WM_QUIT, 0, 0);
            return 11;
        }
    }

    DEBUGMSG(1, (L"CmdLine parsing #1: \r\n"));
    //command parsing
    struct cmdList *Liste;
    Liste=NULL;
    CmdLineArgs args;
    for (UINT i = 0; i < args.size(); i++){
        DEBUGMSG(1, (L"%20i: '%s'\r\n", i, args[i]));
        append(&Liste, args[i]);
    }
    getOptions(Liste);
    args.~CmdLineArgs();

First the code uses LoadString to load the title and window class from its resources. Then FindWindow is used to look for a previous instance and checks if the only argument is -kill. If so, the previous instance is sent a quit using PostMessage and then the application exits itself.

Now we define a structure (Liste) to hold the arguments. Then we start command line parsing by creating a chained list of arguments. The list is created by using the class CmdLineArgs. We then walk thru the argument list and append each argument to our Liste structure. Using getOptions(Liste) we scan the list for known optional arguments and apply optional values to global variables.

class CmdLineArgs : public std::vector<TCHAR*>
{
public:
    CmdLineArgs ()
    {
        // Save local copy of the command line string, because
        // ParseCmdLine() modifies this string while parsing it.
        TCHAR* cmdline = GetCommandLine();
        m_cmdline = new TCHAR [_tcslen (cmdline) + 1];
        if (m_cmdline)
        {
            _tcscpy (m_cmdline, cmdline);
            ParseCmdLine(); 
        }
    }
    ~CmdLineArgs()
    {
        delete []m_cmdline;
    }
...

getOptions is a chain of if/else if blocks that tests for known options and applies values to global variables:

void getOptions(struct cmdList *l){
    struct cmdList *liste;
    liste=l;
    if(l==NULL)
        return;
    int iVal;
    do{
        DEBUGMSG(1, (L"%s\r\n", liste->value));
        if(wcsicmp(liste->value, L"-t")==0){        // message text
            if(liste->next != NULL){
                liste=liste->next;
                wsprintf(szMessageText, L"%s", liste->value);
            }
        }
        else if(wcsicmp(liste->value, L"-m")==0){        // message text
            if(liste->next != NULL){
                liste=liste->next;
                wsprintf(szMessageTextNew, L"%s", liste->value);
            }
        }
        else if(wcsicmp(liste->value, L"-r")==0){    // rgb r value
            if(liste->next != NULL){
                liste=liste->next;
                iVal=_wtoi(liste->value);
                if(iVal!=0)
                    backcolorRed=iVal;
            }
        }
...
        liste=liste->next;
    }while(liste != NULL);
}

Inter process communication

Now that we have read all arguments, we can test if we need to update the text or progress value of an existing instance:

    // already running?
    if(hwndOld!=NULL){
        //check if new message text?
        if(wcslen(szMessageTextNew) > 0){
            myMsg _mymsg;
            memset(&_mymsg,0,sizeof(myMsg));
            wsprintf( _mymsg.szText, L"%s", szMessageTextNew );
            _mymsg.iVal=0;    //the text message identifier
            //prepare WM_COPYDATA
            COPYDATASTRUCT copyData;
            copyData.dwData=1234;
            copyData.cbData=sizeof(myMsg);
            copyData.lpData=&_mymsg;
            SendMessage(hwndOld, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&copyData);
        }
        if(iProgressValNew!=-1){
            myMsg _mymsg;
            memset(&_mymsg,0,sizeof(myMsg));
            wsprintf( _mymsg.szText, L"%i", iProgressValNew );
            _mymsg.iVal=1;    //the progress message identifier
            //prepare WM_COPYDATA
            COPYDATASTRUCT copyData;
            copyData.dwData=1234;
            copyData.cbData=sizeof(myMsg);
            copyData.lpData=&_mymsg;
            SendMessage(hwndOld, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&copyData);
        }
        ShowWindow(hwndOld, SW_SHOWNORMAL);
        return -1;
    }

To let the ‘old’ window update its text or progress bar we need to use inter-process communication. The simplest one supporting custom data (text, progress value) I found was using the WM_COPYDATA message. To use that, we have to define a structure that holds our data (here myMsg is used) and then assign the filled data structure to lpData of a COPYDATASTRUCT variable. Then we send the data to the windows handle of the existing instance using SendMessage. The asynchronous PostMessage does not work, the data must be available on the sender side when the message is received by the target window. SendMessage will block until the message has been delivered and so the data can be transfered between sender and target. Finally the previous instance will be shown and the actual launched will quit (return -1;).

On the receiver side (same application but second instance) we have to decode the WM_COPYDATA message.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
    switch (message) 
    {
...
        case WM_COPYDATA:
            copyData=(COPYDATASTRUCT*)lParam;
            myMsg _mymsg;
            if(copyData->dwData==1234)    //that's right
            {
                memcpy(&_mymsg, copyData->lpData, sizeof(myMsg));
            }
            if(_mymsg.iVal==0){        //text message
                if(wcslen(_mymsg.szText)>0)
                    wcscpy(szMessageText, _mymsg.szText);
                GetClientRect(hWnd, &rect);
                InvalidateRect(hWnd, &rect, TRUE);
            }
            else if(_mymsg.iVal==1){        //progress message
                if(wcslen(_mymsg.szText)>0)
                    wcscpy(szTemp, _mymsg.szText);
                iProgressVal=_wtoi(szTemp);
                SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);
            }
            break;

The above code shows how we get the data back from the message lParam parameter. The structure myMsg knows actually two types of data: a progress value or a new text. Depending on the message type we either update the global variable szMessageText or iProgressVal. After changing the text we inform the OS that our window needs to be updated (painted again). If a new progress value has been received we just need to send the progress bar the new value using SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);.

Adopt to available screen size

Back to our application winMain startup code. The next code lines query the device for screen size and xxx

    //client size
    int maxX = GetSystemMetrics(SM_CXSCREEN);
    int maxY = GetSystemMetrics(SM_CYSCREEN);        //640 ??
    int borderSize = GetSystemMetrics(SM_CXBORDER);
    int minSize = GetSystemMetrics(SM_CYMENU);
    RECT rectMax;
    GetWindowRect(GetDesktopWindow(), &rectMax);
    if(xWidth<100 || xWidth>maxX)    // secure width setting
        xWidth=maxX-2*borderSize;
    if(yHeight<minSize)
        yHeight=minSize+2*borderSize;

    if(xPos<borderSize)    //secure x pos
        xPos=borderSize;
    if(yPos<rectMax.top)    //secure y pos
        yPos=rectMax.top;

    //progressBar is attached to bottom of window
    if(bUseProgress){
        //extend window
        xProgressWidth=xWidth;
        yHeight+=yProgressHeight;
        yProgress=yHeight-yProgressHeight;

    }

SM_CXSCREEN and SM_CYSCREEN let us know the width and height of the screen in pixels. As we want to limit the window creation to usual values, I also query the system value of border width (SM_CXBORDER) and the menu height (SM_CYMENU).
Using GetWindowRect we query for the maximum client area of the ‘desktop’ window. Then we adjust the given width and height value to usable values. We do the same for the x and y position of the window.

If a progressbar is to be used (determined by parsing the command line arguments), we need to extend the specified window at the bottom to reserve place for the bar.
The remainder of winMain is standard and initializes the window and starts the message loop.

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow)) 
    {
        return FALSE;
    }

    HACCEL hAccelTable;
    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SHOWWIN));

    // Main message loop:
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;

Inside InitInstance() there is a call to myRegisterClass(). As I like to have a ‘backdoor’ to quit the ShowWin app, I added CS_DBLCLKS. Without that style attribute the window will otherwise not get double click messages!:

ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
    WNDCLASS wc;

    hBackcolor = CreateSolidBrush(RGB(backcolorRed,backcolorGreen,backcolorBlue));

    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SHOWWIN));
    wc.hCursor       = 0;
    wc.hbrBackground = (HBRUSH) hBackcolor;// GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = 0;
    wc.lpszClassName = szWindowClass;

    return RegisterClass(&wc);
}

In InitInstance we apply some special wishes for the window z-order: WS_EX_ABOVESTARTUP and WS_EX_TOPMOST. The code also does not use window defaults for size and position as these would result in a maximized window but we want to show only a small window.

    hWnd = CreateWindowEx( 
        WS_EX_TOPMOST | WS_EX_ABOVESTARTUP,    //exStyle
        szWindowClass,    //wndClass
        NULL, //L"Installer",    //title
        WS_VISIBLE, //dwStyle
        xPos,    // CW_USEDEFAULT,  //x
        yPos,    //CW_USEDEFAULT,  //y    
        xWidth,  //CW_USEDEFAULT,  //width
        yHeight, //CW_USEDEFAULT,  //height
        NULL,    //hParent
        NULL,    //hMenu
        hInstance,
        NULL
        );

After all this stuff the window class is registered and the window will be created using our settings. Now the magic starts and the windows message proc is called by the message loop. The first message we will see is WM_CREATE.

        case WM_CREATE:
            //do font calculation
            hdc=GetWindowDC(hWnd);
            iDevCap=GetDeviceCaps(hdc, LOGPIXELSY);    //pixels per inch
            lfHeight = -((long)fontHeight * (long)iDevCap) / 72L;
            GetObject (GetStockObject (SYSTEM_FONT), sizeof (LOGFONT), (PTSTR) &logfont) ;
            //    HFONT hf = CreateFontIndirect(&logfont);
            logfont.lfHeight=lfHeight;
            hFont=CreateFontIndirect(&logfont);
            ReleaseDC(NULL,hdc);

            DEBUGMSG(1, (L"Create hWnd=%i\r\n", hWnd));
            if(iTimeOut>0)
                startThread(hWnd);

            if(bUseProgress){
                //progressBar
                hProgress = CreateWindowEx(0, PROGRESS_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE,
                                xProgress, yProgress, xProgressWidth, yProgressHeight,
                                hWnd, NULL, g_hInst, NULL);
                SendMessage(hProgress, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
                SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);
            }
            break;

The above tries to calculate the right size for a LOGFONT structure for the text drwan in the window. We need to know the number of pixels per inch of the screen – the dots-per-inch resolution. The font size argument you can use on the command line is to be given in points. A point is a 1/72 of an inch (or given at 72 dpi). We calculate the logical font height by relating the screen dpi and the font size dpi. Then the code gets the LOGFONT structure of the system font and applies the new logical font size. Finally the global variable hFont is initialized with the logfont structure.

If an optional timeout value was supplied via the cmd line, the line startThread() will be executed. That starts a background thread that will post a quit message to the message loop when the timeout value is reached:

DWORD myThread(LPVOID lpParam){
    BOOL bExit=FALSE;
    HWND hwndMain=(HWND)lpParam;
    DWORD dwWaitResult=0;
    int iCountSeconds=0;
    DEBUGMSG(1, (L"myThread hWndMain=%i\r\n", hwndMain));
    do{
        dwWaitResult = WaitForSingleObject(hStopThread, 1000);
        switch(dwWaitResult){
            case WAIT_OBJECT_0:
                bExit=TRUE;
                break;
            case WAIT_TIMEOUT:
                iCountSeconds++;
                if(iCountSeconds>=iTimeOut)
                {
                    PostMessage(hwndMain, WM_QUIT, 99, iTimeOut);
                    bExit=TRUE;
                }
                break;
        }
    }while(!bExit);
    return 0;
}

I am using WaitForSingleObject() here to be able to stop the thread by setting a named event.

Back in WM_CREATE the last lines are to create a progressBar, if the optional argument for a progress bar was used.

The next message of importance is WM_PAINT. All drawing of the window is done within the WM_PAINT handler.

        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);

            // TODO: Add any drawing code here...
            GetClientRect(hWnd, &rect);
            //shrink text area if progressbar is there
            if(bUseProgress && hProgress!=NULL){
                rect.bottom-=yProgressHeight;
            }

the above resized the drawing rectangle for text if a progress bar is used.

Next we draw some black rectangles to give the window a simple drop shadow effect.

            //draw rectangle
            myPen = CreatePen(PS_SOLID, 1, RGB(0,0,0));
            oldPen = (HPEN)SelectObject(hdc,myPen);
            SelectObject(hdc, hBackcolor);
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            //shrinkRect(&rect, 1);
            //Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            //a drop shadow
            rect.right-=1;rect.bottom-=1;
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            rect.right-=1;rect.bottom-=1;
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            SelectObject(hdc, oldPen);

Then we set the background mode to transparent and draw the text and finally restore font and text color.

            SetBkMode(hdc, TRANSPARENT);
            oldTextColor = SetTextColor(hdc, RGB(fontColorRed, fontColorGreen, fontColorBlue));
            hfOld=(HFONT)SelectObject(hdc, hFont);
            DrawText(hdc, 
                szMessageText,    //text to draw
                -1,                //length of text
                &rect, 
                dwTextalign | DT_END_ELLIPSIS | DT_EXTERNALLEADING | DT_VCENTER // | DT_SINGLELINE        //text formatting
                );

            EndPaint(hWnd, &ps);
            SelectObject(hdc, hfOld);
            SetTextColor(hdc, oldTextColor);

            DeleteObject(hFont);
            break;

the backdoor to quit

You can quit ShowWin by calling it with ‘-kill’. You can also end ShowWin by double clicking inside the window with the CAPS Lock key being toggled:

        case WM_LBUTTONDBLCLK:
            vkShift=GetKeyState(VK_CAPITAL);
            if( (vkShift & 0x80) == 0x80 || (vkShift & 0x01) == 0x01 ){
                if(MessageBox(hWnd, L"Exit?", L"showWin", MB_OKCANCEL)==IDOK)
                    DestroyWindow(hWnd);
            }
            break;

MessageBox shows a verification dialog and DestroyWindow exits the application.

The End

As we are running a background thread it is a good idea to stop the thread before the application ends. The below code shows the SetEvent call that releases the background thread’s WaitForSingleObject() call.

void stopThread(){
    if(hStopThread==NULL)
        SetEvent(hStopThread);
}
.......
        case WM_DESTROY:
            stopThread();
            PostQuitMessage(0);
            break;
...

Source code download

Full source code available at GitHub.

 

Windows Mobile 6.5: Changed Screen Geometry

$
0
0

Screen layout changes from Windows Mobile 6.1 to Windows Mobile Embedded Handheld 6.5

The Windows Mobile screen geometry changed from Windows Mobile 6.1 and before to the actual Windows Mobile 6.5 (also called Windows Embedded Handheld). Not only the geometry changed, the layout also changed. The Start icon is now moved to the bottom whereas before WM65 the start icon was on the left in the taskbar.

wm61screen   wm65screen

The taskbar and the menubar was about 26 pixels in height. With WM65 the taskbar is about 18 pixels in height and the menu bar occupies 34 pixels in height.

QVGA screen geometry

Windows Mobile 6.1

Windows Mobile 6.5

taskbar

26

18

menubar

26

34

client size

240;268

240;268

client size height no taskbar

240;294

240;302

client size height no menubar

240;294

240;286

You can see that assuming a fixed client size will give problems with the layout of the application, especially if menubar and taskbar height are assumed as being 26 pixels all the time.

Applications that only use the client size with the taskbar and menubar visible, will show normally, as the resulting client size does not differ between WM61 and WM65.

Application geometry

Applications that use any code to make there window more or less fullscreen have to adopt for the new layout of the screen. Applications that do not use code to adopt there screen to the new layout may look like this:

geometry_wm61_QVGA_sample1   geometry_wm65_QVGA_sample1

The screen moves slightly outside top assuming 26 pixels for taskbar and menubar and so the ‘fullscreen’ app does not not cover the bottom menu bar completely.

If an app has used correct coding it would display better on WM65 (wrong coded Citrix 11.5 here):

geometry_wm65_VGA__sample5    geometry_wm65_VGA__sample5_showfullscreen

High Resolution awareness

Applications

A long time nearly all Windows Mobile device came with a QVGA screen resolution only. Now you may be faced by a Windows Mobile VGA screen resolution, whereas consumer smartphones come with many different screen resolutions.

Windows Mobile 6 will adopt non-hires aware applications automatically to the screen resolution. The OS looks for a resource named HIRES_AWARE in the application executable and for the major subsystem version number being less than 5.

If an app does not have the HIRES_AWARE and the major version number is less than 5, the Windows Mobile OS automatically scales the app to the screen size.

The ‘auto-scale’ uses the dpi values for scaling. The screen sizes (the screen diagonal) is mostly similar. So a VGA screen has more pixels per inch (ie 192dpi, dots-per-inch). Whereas a QVGA screen has about 96 pixels per inch (96dpi).

Dotnet AutoscaleMode

In Visual Studio you can set the FormFactor and Autscale-Mode (auto, none) for a form:

dotnet_dpi_auto  dotnet_formfactor

If AutoScale is set to dpi, everything is OK, the form ‘scales’ automatically. If set to None, the form content is not scaled:

dotnet_form_dpi_auto   dotnet_form_dpi_none

If you start desiging the form with QVGA form factor and switch then to VGA form factor, you can see the change directly:

dotnet_formfactor_vga

Browser applications and VGA screen

Internet browser applications must avoid using fixed pixel count for there screen layout. You can imagine what happens if a element like a table is specified for 240 pixels wide on a VGA screen supporting 480 pixels:

geometry_wm65_VGA__sample3

More Sample screens

Windows Mobile 6.1 QVGA

without and with menubar/taskbar:

geometry_wm61_QVGA_noMenu   geometry_wm61_QVGA_noTaskbar

Windows Mobile 6.5 QVGA

geometry_wm65_QVGA_noMenu   geometry_wm65_QVGA_noTaskbar

Windows Mobile 6.5 VGA

geometry_wm65_VGA__noMenu   geometry_wm65_VGA__noTaskbar

 


Mobile Development: manage the event db, what wakes up your device

$
0
0

The event db (it is my naming) holds all events and actions that can be invoked to launch an app or fire some events.

NotificationList

The tool shows all known notifications on a WM device. You can browse the event db and examine the defined events. Additionally the tool shows power change notifications.

noti_01   noti_02   noti_03

In the mid window above you can see there is a timed event that will occur at 0:00 and start \windows\calupd.exe. This will wake your device all night and update the calendar entries for re-occurring schedules etc.

The right window shows the power notifications on a suspend/resume cycle.

Using the options menu you can save a list of the defined notification events.


Example file:

App Args Event Start End Type
sqmevent.exe ‘AppRunAtTime’ None 14052013 07:33 01010001 00:00 Time
\windows\enrollnot.exe ‘AppRunAtTime’ None 14052013 12:00 01010001 00:00 Time
\Windows\calupd.exe ‘AppRunAtTime’ None 15052013 00:00 01010001 00:00 Time
CALNOT.EXE ‘AppRunAtTime’ None 20052013 00:00 01010001 00:00 Time
ceipui.exe ‘AppRunAtTime’ None 27052013 23:48 01010001 00:00 Time
clocknot.exe ‘AppRunAtTime’ None 03112013 02:00 01010001 00:00 Time
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\MSPPDUGateway_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\WLMScheduler_TimeChangeEvent TimeZoneChange 01010001 00:00 01010001 00:00 Event
\\.\Notifications\NamedEvents\WLMScheduler_TimeChangeEvent TimeChange 01010001 00:00 01010001 00:00 Event
calnot.exe ‘AppRunAfterRestore’ RestoreEnd 01010001 00:00 01010001 00:00 Event
calnot.exe ‘AppRunAfterTimeChange’ TimeChange 01010001 00:00 01010001 00:00 Event
ceipui.exe ‘AppRunAfterTzChange’ TimeZoneChange 01010001 00:00 01010001 00:00 Event
ceipui.exe ‘AppRunAfterTimeChange’ TimeChange 01010001 00:00 01010001 00:00 Event
clocknot.exe ‘AppRunAfterTimeChange’ TimeChange 01010001 00:00 01010001 00:00 Event
clocknot.exe ‘AppRunAfterRestore’ RestoreEnd 01010001 00:00 01010001 00:00 Event
repllog.exe ‘AppRunAfterRndisFnDetected’ RndisFNDetected 01010001 00:00 01010001 00:00 Event
repllog.exe ‘AppRunAfterTimeChange’ TimeChange 01010001 00:00 01010001 00:00 Event
repllog.exe ‘AppRunAtRs232Detect’ RS232Detected 01010001 00:00 01010001 00:00 Event
Tskschedule.exe ‘-Time’ TimeZoneChange 01010001 00:00 01010001 00:00 Event
Tskschedule.exe ‘-Time’ TimeChange 01010001 00:00 01010001 00:00 Event

You can see the applications or named events that are issued. Then there are arguments that are provided to the application. You can also see the event type.

There are different notification types:

        public enum CeNotificationType
        {
            CNT_EVENT = 1,          //@flag CNT_EVENT  | System event notification
            CNT_TIME,               //@flag CNT_TIME   | Time-based notification
            CNT_PERIOD,             //@flag CNT_PERIOD | Time-based notification is active for
                                    // time period between stStart and stEnd
            CNT_CLASSICTIME         //@flag CNT_CLASSICTIME | equivalent to using (obsolete)
            // CeSetUserNotification function - standard command line is
            // supplied. lpszArguments must be NULL
        }

CeSetUserNotification is spared here. This structure is used to display user notification bubbles on the screen.

The notification API enables you to setup notification for different events:

        public enum CeNotificationEvent
        {
            NOTIFICATION_EVENT_NONE,
            NOTIFICATION_EVENT_TIME_CHANGE,
            NOTIFICATION_EVENT_SYNC_END,
            NOTIFICATION_EVENT_ON_AC_POWER,
            NOTIFICATION_EVENT_OFF_AC_POWER,
            NOTIFICATION_EVENT_NET_CONNECT,
            NOTIFICATION_EVENT_NET_DISCONNECT,
            NOTIFICATION_EVENT_DEVICE_CHANGE,
            NOTIFICATION_EVENT_IR_DISCOVERED,
            NOTIFICATION_EVENT_RS232_DETECTED,
            NOTIFICATION_EVENT_RESTORE_END,
            NOTIFICATION_EVENT_WAKEUP,
            NOTIFICATION_EVENT_TZ_CHANGE,
            NOTIFICATION_EVENT_MACHINE_NAME_CHANGE,
            NOTIFICATION_EVENT_RNDIS_FN_DETECTED,
            NOTIFICATION_EVENT_INTERNET_PROXY_CHANGE, 
            NOTIFICATION_EVENT_LAST = NOTIFICATION_EVENT_INTERNET_PROXY_CHANGE
        };

Please note that not all devices support all these types. As the names are more less speaking names I do not explain these event types any further.

If you want to add your own periodic, timed, application, you should look at my Tasker application.

PowerNotifications

Although not directly related to the above events db, it may also good to know how you can subscribe to power notifications. The sample uses the MS PowerNotifications Message queue. When registering for the power messages, you can define a filter to get only power notification messages you are interested in.

        const uint POWER_NOTIFY_ALL = 0xFFFFFFFF;
        const uint PBT_TRANSITION          =  0x00000001;  // broadcast specifying system power state transition        
        const uint PBT_RESUME = 0x00000002;  // broadcast notifying a resume, specifies previous state
        const uint PBT_POWERSTATUSCHANGE = 0x00000004;  // power supply switched to/from AC/DC
        const uint PBT_POWERINFOCHANGE = 0x00000008;

Every message comes with a data structure:

        [StructLayout(LayoutKind.Sequential)]
        struct POWER_BROADCAST
        {
            UInt32 dwMsg;
            UInt32 dwFlags;
            UInt32 dwLength;
            string sSystemPowerState; //WCHAR SystemPowerState[1];
        }

And we have a lot of flags to lookup:
        //
        // System Power (Source/State/Option) Flags
        //
        [Flags]
        enum PowerState:uint
        {
            // upper bytes: common power state bits
            //#define POWER_STATE(f)           ((f) &  0xFFFF0000);        // power state mask
            POWER_STATE_NA              = 0x00,
            POWER_STATE_ON              = 0x00010000,        // on state
            POWER_STATE_OFF             = 0x00020000,        // no power, full off
            POWER_STATE_CRITICAL        = 0x00040000,        // critical off
            POWER_STATE_BOOT            = 0x00080000,        // boot state
            POWER_STATE_IDLE            = 0x00100000,        // idle state
            POWER_STATE_SUSPEND         = 0x00200000,        // suspend state
            POWER_STATE_UNATTENDED      = 0x00400000,        // Unattended state.
            POWER_STATE_RESET           = 0x00800000,        // reset state
            POWER_STATE_USERIDLE        = 0x01000000,        // user idle state
            POWER_STATE_BACKLIGHTON     = 0x02000000,        // device scree backlight on
            POWER_STATE_PASSWORD        = 0x10000000,        // This state is password protected.
        }
        [Flags]
        enum PowerEventType
        {
            PBT_TRANSITION = 0x00000001,
            PBT_RESUME = 0x00000002,
            PBT_POWERSTATUSCHANGE = 0x00000004,
            PBT_POWERINFOCHANGE = 0x00000008,
        }

        [Flags]
        enum PowerState1
        {
            POWER_STATE_ON = (0x00010000),
            POWER_STATE_OFF = (0x00020000),

            POWER_STATE_CRITICAL = (0x00040000),
            POWER_STATE_BOOT = (0x00080000),
            POWER_STATE_IDLE = (0x00100000),
            POWER_STATE_SUSPEND = (0x00200000),
            POWER_STATE_RESET = (0x00800000),
        }

And, of course, we need some P/Invoke:

        #region DllImports
        [DllImport("coredll.dll")]
        private static extern IntPtr RequestPowerNotifications(IntPtr hMsgQ, uint Flags);
        [DllImport("coredll.dll")]
        private static extern uint WaitForSingleObject(IntPtr hHandle, int wait);
        [DllImport("coredll.dll")]
        private static extern IntPtr CreateMsgQueue(string name, ref MsgQOptions options);
        [DllImport("coredll.dll")]
        private static extern bool ReadMsgQueue(IntPtr hMsgQ, byte[] lpBuffer, uint cbBufSize, ref uint lpNumRead, int dwTimeout, ref uint pdwFlags);
        #endregion

The main work is done inside a thread function:

        private void DoWork()
        {
            byte[] buf = new byte[10000];
            uint nRead = 0, flags = 0, res = 0;

            System.Diagnostics.Debug.WriteLine("starting loop");
            try
            {
                while (!done)
                {
                    res = WaitForSingleObject(ptr, 2500);
                    if (res == 0)
                    {
                        ReadMsgQueue(ptr, buf, (uint)buf.Length, ref nRead, -1, ref flags);
                        //System.Diagnostics.Debug.WriteLine("message: " + ConvertByteArray(buf, 0) + " flag: " + ConvertByteArray(buf, 4));
                        uint flag = ConvertByteArray(buf, 4);
                        string msg = "";
                        msg += ((PowerState)flag).ToString();
                        if (msg=="")
                            msg = "Unknown Flag: " + flag.ToString();
                        if (msg=="0")
                            msg = "POWER_STATE_NA";

                        if (msg != "")
                        {
                            if(OnMsg!=null)
                                OnMsg(this, new PwrEventArgs(msg, flag));
                            System.Diagnostics.Debug.WriteLine(msg);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (!done)
                {
                    System.Diagnostics.Debug.WriteLine("Got exception: " + ex);
                }
            }
            System.Diagnostics.Debug.WriteLine("loop ended");
        }

The sample code implements some event and delegates that you can subscribe to to get power notification messages.

Source code available via https://win-mobile-code.googlecode.com/svn/trunk/NotificationList/NotificationsList

Mobile Programming – Cmd line tools for automated tests

$
0
0

Recently I had to perform an automated stress test for a compact framework application using the camera. I remembered MortScript (original web site down) and wrote a script to automate the test.

Now, as there are sometimes more needs to automate tests, for example for battery performance tests, I decided to mimic the motorola emScript environment. emScript offers a lot of functions you may need on other devices too. Instead of putting all functions into one executable, I decided to have extern command line driven tool set. So I started to develop small tools to perform device functions.

scannerCLI

The first tool is scannerCLI. This tool issues a barcode scan on an intermec handheld device:

Just issue the scanner for a max time of supplied arg default timeout is 1 second:
"ScannerCLI.exe x" will issue a barcode scan with a timeout of x seconds.
x must be > 0 and < 10.
"ScannerCLI.exe" will issue a barcode scan for max 1 second. The scan will be stopped immediately if a barcode is read.
The return code is 0 for no barcode read, 1 for one or more read and negative for an error in barcode scan.

iBacklIghtCLI

This tool is to control or query the backlight settings of intermec devices. As you know the backlight is one of the most battery consuming setting.

arguments  function
on         switch backlight on using current brightness
off        switch backlight off
max        switch backlight brightness to max and on
min        switch backlight brightness to min and on

state      return 0 for is OFF, 1 for ON, -1 for error
level      return current brightness level or -1 for error

1 to max level 
           set new brightness level, returns new level or -1 for 
           error (ie val exceeds max level)

More tools will added here…

The source code for all published tools is be available at code.google.com.

http://code.google.com/p/win-mobile-code/source/browse/#svn%2Ftrunk%2Fcli-tools

Mobile development: pocketHosts-Edit Windows Mobile hosts entries

$
0
0

PocketPC and Windows Mobile does not support a hosts file as desktop windows. As I recently had to add an entry for a virtual machine running Mini SAP (Netweaver 7.01 Trial) I stumbled about how to add a host entry to a windows mobile device.

pocketHosts

The platform builder help gives the details about how host entries are organized:

Host Name

The host name can be configured through the HKEY_LOCAL_MACHINE\Comm\Tcpip\Hosts subkey. When an application calls gethostbyname or getaddrinfo, the registry is queried first, before a DNS or WINS request is sent. If the host name is found in the registry, the registry values are returned.

The following table shows the values for the HKEY_LOCAL_MACHINE\Comm\Tcpip\Hosts\<Host Name> subkey.

Value : type Description
Aliases : REG_MULTI_SZ This value stores the aliases by which this host is known.
ExpireTime : REG_BINARY If the current time, obtained by calling GetCurrentFT, exceeds the value in ExpireTime, the entire Host Name subkey is deleted the next time that gethostbyname is called. The length of this value is 8 bytes.
ipaddr : REG_BINARY This value stores the IPv4 addresses associated with this host name. The length of this value is 4 bytes per address.
ipaddr6 : REG_BINARY This value stores the IPv6 addresses associated with this host name. The length of this value is 20 bytes per address (16 bytes for address and 4 bytes for Scope ID).

So, there is no simple hosts file.

Before struggling all the time with these entries I decided to write a small app to manage these entries: pocketHosts.

I created a class hostsentry to hold each hosts details and a class hostsentries to hold all host entries.

...    
    public class hostsentry
    {

        //##################################################################
        public string sHost { get; set; }
        public List<System.Net.IPAddress> ipAddress { get; set; }
        public List<System.Net.IPAddress> ipAddress6 { get; set; }
        public List<string> aliases { get; set; }
        private ulong _expireTime = 0;
        public ulong expireTime { get { return (uint)_expireTime; } set { _expireTime = (ulong)value; } }
...

The hostsentries class:

...
    public class hostsentries:IDisposable
    {
        const string regSubKey = @"Comm\Tcpip\Hosts";
        RegistryKey rKeyTCPIP;
        public Dictionary<string, hostsentry> allHosts;
        public hostsentries()
        {
            allHosts = new Dictionary<string, hostsentry>();
            init();
        }
...

The registry can hold IP4 and IP6 addresses. pocketHosts shows them in one list:

pocketHostsIPentry

Then there are some helper functions to convert byte arrays of ipaddr and ipaddr6 to single ipAddresses. All ipAddresses are saved to the registry as binary:

...
        //convert 20010db8ac10fe010000000000000000
        //to 2001:0db8:ac10:fe01:0000:0000:0000:0000
        private string getIP6StrfromByte(byte[] bIpAddress6)
        {
            if (bIpAddress6.Length != 16)
                return "";
            string sIP6 = "";
            for (int i = 0; i < 16; i++)
            {
                byte b = bIpAddress6[i];
                sIP6 += b.ToString("x02");
                if (i > 0 && i % 2 != 0 && i != 15)
                    sIP6 += ":";
            }
            return sIP6;
        }

        private System.Net.IPAddress getIP6fromByte(byte[] bIpAddress6)
        {
            System.Net.IPAddress ip6 = new System.Net.IPAddress((long)0);
            string sIP = getIP6StrfromByte(bIpAddress6);
            if (sIP != "")
            {
                try{
                    ip6 = System.Net.IPAddress.Parse(sIP);
                }
                catch(Exception){}
            }
            return ip6;
        }
...

These function are called to convert the byte arrays back to IP addresses. The functions to convert the IP addresses back to bytes are much easier:

...
                List<byte> b = new List<byte>();
                foreach (System.Net.IPAddress ip in he.ipAddress)
                {
                    b.AddRange(ip.GetAddressBytes());
                    iRet++;
                }
                if(b.Count>=4)
                    regWork.SetValue("ipaddr", b.ToArray(), RegistryValueKind.Binary);

                b.Clear();
                foreach (System.Net.IPAddress ip6 in he.ipAddress6)
                {
                    b.AddRange(ip6.GetAddressBytes());
                    iRet++;
                }
                if (b.Count >= 4)
                    regWork.SetValue("ipaddr6", b.ToArray(), RegistryValueKind.Binary);
...

Although the registry holds the IP addresses in ipaddr and ipaddr6 entries. The application can treat both IP address types the same. Only when saving, the list has to be diverted into IP4 and IP6 addresses.

The source code (VS2005, WM5 SDK, CF2) and a binary can be found at my code.google.com repository.

There is one bug I could not fix: you cannot remove a hosts entry. The RegDelKey function always returns an error.

Mobile development: Netstat, know your device’s open ports

$
0
0

On desktop PCs you have the nice tool netstat to see which ports are open on the PC. A customer wanted to know, why his devices do not release there internet connection. The only tool I know, that will show open network connections, is called netstat. Unfortunately I did not find such tool for Windows Mobile and so I wrote one myself:

netstat for windows mobile

netstatCF2

NetstatCF2 is written in C#, Compact Framework. After you started the tool it immediately collects the open ports data. It will also log this data periodically (every 3 seconds) to a log file called “\netstat.log”.

The code makes massive calls to the ipHlp API functions. Here are some sample snippets:

...
    public class IPHlpAPI32Wrapper
    {
        public const byte NO_ERROR = 0;
        public const int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
        public const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
        public const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
        public int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS;

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public extern static int GetUdpStatistics(ref MIB_UDPSTATS pStats);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetUdpTable(byte[] UcpTable, out int pdwSize, bool bOrder);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public extern static int GetTcpStatistics(ref MIB_TCPSTATS pStats);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetTcpTable(byte[] pTcpTable, out int pdwSize, bool bOrder);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref int pdwSize, bool bOrder);
        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetIpForwardTable(byte[] pIpForwardTable, ref int pdwSize, bool bOrder);
...

        public void GetTcpConnexions()
        {
            byte[] buffer = new byte[20000]; // Start with 20.000 bytes left for information about tcp table
            int pdwSize = 20000;
            int res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
            if (res != NO_ERROR)
            {
                buffer = new byte[pdwSize];
                res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
                if (res != 0)
                    return;     // Error. You should handle it
            }

            TcpConnexion = new IpHlpApidotnet.MIB_TCPTABLE();

            int nOffset = 0;
            // number of entry in the
            TcpConnexion.dwNumEntries = Convert.ToInt32(buffer[nOffset]);
            nOffset += 4;
            TcpConnexion.table = new MIB_TCPROW[TcpConnexion.dwNumEntries];

            for (int i = 0; i < TcpConnexion.dwNumEntries; i++)
            {
                // state
                int st = Convert.ToInt32(buffer[nOffset]);
                // state in string
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).StrgState = convert_state(st);
                // state  by ID
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).iState = st;
                nOffset += 4;
                // local address
                string LocalAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;
                //local port in decimal
                int LocalPort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                    (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);

                nOffset += 4;
                // store the remote endpoint
                //((MIB_TCPROW)(
                (TcpConnexion.table[i]).Local = new IPEndPoint(IPAddress.Parse(LocalAdrr), LocalPort);

                // remote address
                string RemoteAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;
                // if the remote address = 0 (0.0.0.0) the remote port is always 0
                // else get the remote port in decimal
                int RemotePort;
                //
                if (RemoteAdrr == "0.0.0.0")
                {
                    RemotePort = 0;
                }
                else
                {
                    RemotePort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                        (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);
                }
                nOffset += 4;
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).Remote = new IPEndPoint(IPAddress.Parse(RemoteAdrr), RemotePort);
            }
        }
...
        public void GetUdpConnexions()
        {
            byte[] buffer = new byte[20000]; // Start with 20.000 bytes left for information about tcp table
            int pdwSize = 20000;
            int res = IPHlpAPI32Wrapper.GetUdpTable(buffer, out pdwSize, true);
            if (res != NO_ERROR)
            {
                buffer = new byte[pdwSize];
                res = IPHlpAPI32Wrapper.GetUdpTable(buffer, out pdwSize, true);
                if (res != 0)
                    return;     // Error. You should handle it
            }

            UdpConnexion = new IpHlpApidotnet.MIB_UDPTABLE();

            int nOffset = 0;
            // number of entry in the
            UdpConnexion.dwNumEntries = Convert.ToInt32(buffer[nOffset]);
            nOffset += 4;
            UdpConnexion.table = new MIB_UDPROW[UdpConnexion.dwNumEntries];
            for (int i = 0; i < UdpConnexion.dwNumEntries; i++)
            {
                string LocalAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;

                int LocalPort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                    (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);
                nOffset += 4;
                //((MIB_UDPROW)
                (UdpConnexion.table[i]).Local = new IPEndPoint(IPAddress.Parse(LocalAdrr), LocalPort);
            }
        }
...
    public class IPHelper
    {
        private const int NO_ERROR = 0;
        private const int MIB_TCP_STATE_CLOSED = 1;
        private const int MIB_TCP_STATE_LISTEN = 2;
        private const int MIB_TCP_STATE_SYN_SENT = 3;
        private const int MIB_TCP_STATE_SYN_RCVD = 4;
        private const int MIB_TCP_STATE_ESTAB = 5;
        private const int MIB_TCP_STATE_FIN_WAIT1 = 6;
        private const int MIB_TCP_STATE_FIN_WAIT2 = 7;
        private const int MIB_TCP_STATE_CLOSE_WAIT = 8;
        private const int MIB_TCP_STATE_CLOSING = 9;
        private const int MIB_TCP_STATE_LAST_ACK = 10;
        private const int MIB_TCP_STATE_TIME_WAIT = 11;
        private const int MIB_TCP_STATE_DELETE_TCB = 12;

        private const int ERROR_INSUFFICIENT_BUFFER = 122;
...

But there is nothing special except on how to use C/C++ structures in C#.

Here is a sample of the logged data:

xxxxxxxxxxx AM #################
======= TCP table ========
local                       remote
        0.0.0.0:    21         0.0.0.0:     0 LISTEN
        0.0.0.0:  1004         0.0.0.0:     0 LISTEN
        0.0.0.0:  2188         0.0.0.0:     0 LISTEN
        0.0.0.0:  2189         0.0.0.0:     0 LISTEN
        0.0.0.0:  5655         0.0.0.0:     0 LISTEN
        0.0.0.0: 52241         0.0.0.0:     0 LISTEN
      127.0.0.1:  1032       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1034       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1035       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1036       127.0.0.1: 52241 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1032 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1034 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1035 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1036 ESTAB
 192.168.55.101:  1082  192.168.55.100:  7438 ESTAB
 192.168.55.101:  1083  192.168.55.100:   990 ESTAB
 192.168.55.101:  1086  192.168.55.100:   990 ESTAB
 192.168.55.101:  1087  192.168.55.100:   990 ESTAB
 192.168.55.101:  1092  192.168.55.100:   990 ESTAB
 192.168.55.101:  1102  192.168.55.100:  1004 ESTAB
 192.168.55.101:  1103  192.168.55.100:   990 ESTAB
192.168.128.104:  1033   192.168.128.5: 62241 ESTAB
192.168.128.104:  5655   192.168.128.2: 59534 ESTAB
192.168.128.104:  5655   192.168.128.2: 59535 ESTAB
192.168.128.104:  6510   192.168.128.2: 59536 ESTAB
192.168.128.104:  6510   192.168.128.2: 59537 ESTAB
======= UDP table ========
        0.0.0.0:    53
        0.0.0.0:   137
        0.0.0.0:   138
        0.0.0.0:  1088
        0.0.0.0:  9204
        0.0.0.0: 49111
192.168.128.104:    68
======= TCP statistics ========
Retransmission timeout (min/max): Van Jacobson's Algorithm: 300/120000
                max connnections: -1
                     active open: 69
                    passive open: 196
                 failed attempts: 0
              established resets: 243
             current established: 20
                     segments in: 134380
                    segments out: 130900
          retransmitted segments: 175
                       in errors: 0
                      out resets: 861
                 num connections: 26
======= UDP statistics ========
                    in datagrams: 13771
                       in errors: 0
                       num ports: 3353
                   num addresses: 7
                   out datagrams: 887
======= Adapter infos ==========
131074: BCMCF1, 192.168.128.104
262147: USB Cable:, 192.168.55.101
======= Route entries ==========
Network Destination        Netmask          Gateway          Interface     Metric
0.0.0.0                    0.0.0.0          192.168.55.101                   003    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
0.0.0.0                    0.0.0.0          192.168.128.1                    002    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
127.0.0.0                  255.0.0.0        127.0.0.1                        001    0
104.148.1.0                0.0.0.0          255.255.255.255                  255    0
192.168.55.101             255.255.255.255  127.0.0.1                        001    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
192.168.55.255             255.255.255.255  192.168.55.101                   003    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
192.168.128.0              255.255.255.0    192.168.128.104                  002    0
67.148.1.0                 0.0.0.0          255.255.255.255                  255    0

Hopefully you use this small tool at a special time.

Code and sample bin at code.google.com (VS2008, CF2, WM5SDK)

Windows Mobile 6 – Internet Explorer Mobile modes

$
0
0

Since IE6 comes as 6.1.4 or higher, Internet Explorer Mobile can work in IE6 or the older PIE mode.

IE6 means Internet Explorer 6 desktop compatible mode. Which works more or less with known and unknown restrictions. The biggest advantage is that Internet Explorer (IEM) >=6.1.4 supports keyboard event DOM of javascript.

For some web sites you may downgrade to PIE (Pocket Internet Explorer) mode. You will get scroll bars and a standard menu bar and no on-screen zoom option.

To switch to PIE mode in Windows Mobile 6 or Windows Embedded Handheld 6.5 you need to set the following registry key:

[HKEY_LOCAL_MACHINE\Security\Internet Explorer]
MSHTML=0
; 0=PIE mode
; 1=IE6 mode

To control the PIE view mode directly, you can use the following registry key:

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main]
"MakeFit"=dword:00000002
; tested in PIE mode
; 2=OneColumn
; 1=FitToScreen
; 0=Desktop
IE6 automatic mode

IE6 automatic mode

IE6 mobile view

IE6 mobile view

 

IE6 desktop view

IE6 desktop view

 

PIE desktop view

PIE desktop view

 

PIE Fit To Screen view

PIE Fit To Screen view

 

PIE One Column view

PIE One Column view

Viewing all 36 articles
Browse latest View live