onsdag 14 oktober 2009

CeGetUserNotificationHandles

So far, we've been looking at how to create, modify, handle and delete notifications. When you create a notifiction you are passed a handle to that specific notification by the API and that same handle must be used later on when you either modify or delete the notification. As you know by now - either I've told you or you're about to find out - notifications aren't automatically deleted from the system scheduler after they've been triggered. What this means is that notifications that has served their purpose must be removed using CeClearUserNotification.
This also implies that you must save the handle in between sessions.

Another way to resolve this issue is to enumerate all notifications and find the notification you're looking for based on i.e. the name of the application its set to execute. Enumeration of notifications is accomplished thru the use of CeGetUserNotificationHandles.

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeGetUserNotificationHandles")]
public static extern bool GetUserNotificationHandles(IntPtr[] rghNotifications, uint cHandles, ref uint pcHandlesNeeded);


CeGetUserNotificationHandles returns an array of notification handles. Each of the handles can then be used as input for CeGetUserNotification which in turn will return the notification as a byte array.
To determine the size of rghNotifications needed, call CeGetUserNotificationHandless with NULL parameters.
    uint dwHowMany=0;
GetUserNotificationHandles(null, 0 ref dwHowMany);


You then use the value of dwHowMany to create an IntPtr array of the correct size.
    IntPtr rghNotifications = new IntPtr[dwHowMany];

Calling CeGetUserNotificationHandles one more time will fill the IntPtr array.
    GetUserNotificationHandles(rghNotifications, dwHowMany, ref dwHowMany);

Given that GetUserNotificationHandles returned TRUE you should now have an IntPtr array filled with notification handles that can you can use.

Tomorrow we'll tie the knot on the notification sack and look and the final missing piece; CeGetUserNotification.

tisdag 13 oktober 2009

ClearUserNotification

Yesterday you learnt how to create a notification using SetUserNotificationEx and today you will learn how to remove the notification you just created. The API to use is CeClearUserNotification and to use it you pass a handle to a existing notification.

Here is the declaration of CeClearUserNotification.

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeClearUserNotification")]
public static extern bool ClearUserNotification(IntPtr hNotification);


And using it you do a little something like this.

IntPtr hNotification;
/*
Call CeSetUserNotificationEx and save its output in hNotification
*/

if ( ClearUserNotification(hNotification) )
MessageBox.Show("Successfully cleared the notification");
else
MessageBox.Show("Failed to clear the notification");

måndag 12 oktober 2009

CeSetUserNotificationEx, DateTime.Now and GetLocalTime

It's been known for a while now that DateTime.Now has a few time zone related quirks in it. To overcome these issues I've come to use the native function GetLocalTime as a replacement. GetLocalTime returns SYSTEMTIME and converting it to DateTime is as easy as 1-2-3. Add the following code to the SYSTEMTIME class described earlier.
            public DateTime GetAsDateTime()
  {
      return new DateTime(wYear, wMonth, wDay, wHour, wMinute, wSecond);
  }
Declaration and usage of GetLocalTime is done like this:

        [DllImport("coredll.dll")]
public static extern void GetLocalTime(SYSTEMTIME sysTime);

SYSTEMTIME sysTime = new SYSTEMTIME(DateTime.Now);
GetLocalTime(sysTime);
DateTime dtLocalTime = sysTime.GetAsDateTime();

Now that's that..

What I really wanted to talk about tonight was the rest of the Notification API's. Or some of them at least.
All the API's that we have looked at so far has been marked as obsolete by Microsoft. They are still supported but are so only for back compatibility.
Instead of using CeRunAppAtTime and CeSetUserNotification you are now encouraged to use CeSetUserNotificationEx. The main difference between these two are the use of the CE_NOTIFICATION_TRIGGER struct in the latter. This struct basically holds information on what triggers the notification and what application to run. It's declaration is as follows.

    [StructLayout(LayoutKind.Sequential)]
public class CE_NOTIFICATION_TRIGGER
{
 public UInt32 Size = 0;
 [MarshalAs(UnmanagedType.U4)]
 public CeNotificationType Type;
 [MarshalAs(UnmanagedType.U4)]
 public CeNotificationEvent Event;
 [MarshalAs(UnmanagedType.LPWStr)]
 public string pApplication;
 [MarshalAs(UnmanagedType.LPWStr)]
 public string pArgs;
 public SYSTEMTIME StartTime;
 public SYSTEMTIME EndTime;
 public CE_NOTIFICATION_TRIGGER()
 {
 }
}
The struct is dependent of a couple of ENUMs that specifies the type and event parameter. CeNotificationEvent has already been discussed but CeNotificationType is new. It's values are..

    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
}

Now what it does is that it tells the API what type of trigger it is. The names speak for themselfs so I won't dig any further in that.

As you can see we use SYSTEMTIME but since the API expects the SYSTEMTIME structs to be a part of the CE_NOTIFICATION_TRIGGER struct then we must change our implementation of SYSTEMTIME from beeing a Class to become a Struct. This means that our older code will break unless you add the "ref" keyword in your API declarations and API calls. As follows..

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeRunAppAtTime")]
   public extern static bool RunAppAtTime(string AppName, ref SYSTEMTIME SysTime);

   RunAppAtTime(@"\windows\bubblebreaker.exe", ref sysTime);
As with CeSetUserNotification, CeSetUserNotificationEx alse makes use of the CE_USER_NOTIFICATION struct. Just remember tough that if Type is CNT_EVENT you must pass a NULL-parameter instead of CE_USER_NOTIFICATION! If you don't; CeSetUserNotificationEx will fail!

The actual declaration of CeSetUserNotificationEx are like this:

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeSetUserNotificationEx")]
    public static extern uint SetUserNotificationEx([MarshalAs(UnmanagedType.U4)]uint hNotification, CE_NOTIFICATION_TRIGGER Trigger, CE_USER_NOTIFICATION Notification);
The following code will create a time based trigger and connect that trigger to a notification that will fire up \windows\fexplore.exe.
Note that you need to compile this sample with the /unsafe switch. You could always hard code the size of CE_NOTIFICATION_TRIGGER in which case /unsafe won't be necessary. If hard coded; the value is 52.
Also don't forget to pass NULL instead of ceun if you change the trigger type to CNT_EVENT.

            CE_NOTIFICATION_TRIGGER cnt = new CE_NOTIFICATION_TRIGGER();
         CE_USER_NOTIFICATION ceun = new CE_USER_NOTIFICATION();
         cnt.Type =CeNotificationType.CNT_TIME;
         cnt.StartTime = stStart;
         cnt.EndTime = stEnd;
         cnt.pApplication = @"\windows\fexplore.exe";
         cnt.pArgs = "";
         cnt.Size = (UInt32)Marshal.SizeOf(cnt); // Needs to compile with /unsafe

         ceun.ActionFlags = PUN_FLAGS.PUN_LED;
         ceun.sDialogText = "Dialogtext";
         ceun.sDialogTitle = "Title";
         ceun.nMaxSound = 0;

         hNotificationEx = SetUserNotificationEx(0, cnt, ceun);
         if (hNotificationEx > 0)
             MessageBox.Show("Successfully created a notification. Handle: " + hNotificationEx.ToString());
         else
             MessageBox.Show("Failed to create a notification");

Reviewing it, I don't think I forgot something important.. Tomorrow we'll look at how to remove a previously created notification and later this week we'll take a peek at how to do a system wide enumeration of all existing notifications.

fredag 9 oktober 2009

CeSetUserNotification - Using CSharp

Have you ever wanted to display a notification as is displayed by the calendar application when you are reminded of an event? If so, then CeSetUserNotification might be your saviour.

With CeSetUserNotification we introduce yet another struct. To invoke this API you must also construct an instance of CE_USER_NOTIFICATION. This struct holds some basic information on the notification such as strings and actionflags. It's declared as follows.

Comments on each field are taken from Notify.h which is part of the Windows Mobile 6 SDK.

    [StructLayout(LayoutKind.Sequential)]
public class CE_USER_NOTIFICATION
{
[MarshalAs(UnmanagedType.U4)]
public PUN_FLAGS ActionFlags; //@field Action Flags. Any combination of the
// PUN_* flags. Flags not valid on a given
// hardware platform will be ignored.
[MarshalAs(UnmanagedType.LPTStr)]
public string sDialogTitle; //@field Dialog Title. Required if PUN_DIALOG is set, ignored
// otherwise. Ignored by CeGetUserNotificationPreferences().
[MarshalAs(UnmanagedType.LPTStr)]
public string sDialogText; //@field Dialog Text. Required if PUN_DIALOG is set, ignored
// otherwise. Ignored by CeGetUserNotificationPreferences().
[MarshalAs(UnmanagedType.LPTStr)]
public string sSound; //@field Sound string as supplied to sndPlaySound.
// CeSetUserNotification() ignores it if PUN_SOUND is not set.
[MarshalAs(UnmanagedType.U4)]
public uint nMaxSound; //@field Max Sound string length. Specifies the
// maximum length of the string that can be copied
// into the pwszSound buffer by
// CeGetUserNotificationPreferences().
// Should be at least MAX_PATH * sizeof(TCHAR).
// Ignored by CeSetUserNotification().
[MarshalAs(UnmanagedType.U4)]
public uint Reserved; //@field Reserved. Must be NULL or a pointer to
// platform-defined expansion structure.
// The first dword of the structure
// indicates the size of the structure.
[MarshalAs(UnmanagedType.U4)]
public uint pReserved;
}


You probably notice that we also need the PUN_FLAGS enum for this. PUN_FLAGS are the bit flags that specify how the device will behave when the notification is invoked.

Again; Comments on each field are take from Notify.h.

    public enum PUN_FLAGS
{
PUN_LED = 1 //@flag PUN_LED | LED flag. Set if the LED should be
// flashed when the notification occurs.
,
PUN_VIBRATE = 2 //@flag PUN_VIBRATE | Vibrate flag. Set if the device should
// be vibrated.
,
PUN_DIALOG = 4 //@flag PUN_DIALOG | Dialog flag. Set if a dialog should be
// displayed (the app must provide title and text
// when calling <f CeSetUserNotification>).
,
PUN_SOUND = 8 //@flag PUN_SOUND | Sound flag. Set if the sound specified
// in pwszSound should be played.
,
PUN_REPEAT = 16 //@flag PUN_REPEAT | Sound repeat flag. Set if the sound
// specified in pwszSound should be repeated progressively.
,
PUN_PRIVATE = 32 //@flag PUN_PRIVATE | Dialog box z-order flag. Set if the
// notification dialog box should come up behind the password.
};


Combin any of these flags to specifiy the behaviour you want.
The example below will flash the led and turn on the vibrator when the notification is invoked by Windows.

CE_USER_NOTIFICATION ceun = new CE_USER_NOTIFICATION();
ceun.ActionFlags = PUN_FLAGS.PUN_LED & PUN_FLAGS.PUN_VIBRATE


Enough ranting.. CeSetUserNotification is declared as follows:

        [DllImport("CoreDLL.dll", SetLastError=false, EntryPoint="CeSetUserNotification")]
public static extern uint SetUserNotification([MarshalAs(UnmanagedType.U4)]uint hNotification, string AppName, SYSTEMTIME Time, CE_USER_NOTIFICATION Notification);


As you can see we also pass a SYSTEMTIME instance. Nothing magical here - this works the same way as the call to CeRunAppAtTime did.

If you want to create a new notification, pass hNotification as 0. To modify an existing one pass that notifications handle. (Hint: The handle is the return value from CeSetUserNotification.)
You may also pass a string that identifies your application. This identification can be used later on when calling CeHandleAppNotifications.

below is a complete listing of a function using CeSetUserNotification.

            CE_USER_NOTIFICATION ceun = new CE_USER_NOTIFICATION();
DateTime dtTime = DateTime.Now;
dtTime = dtTime.AddMinutes(1);
SYSTEMTIME sysTimeStart = new SYSTEMTIME(dtTime);

ceun.ActionFlags = PUN_FLAGS.PUN_DIALOG;
ceun.sDialogTitle = "";
ceun.sDialogText = "This is a notfication set by the Notifications Sample Application.";
ceun.Reserved = 0;

uint nHandle = SetUserNotification(0, "MySampleApp", sysTimeStart, ceun);
MessageBox.Show("Handle returned by SetUserNotification:" + nHandle.ToString() + "\r\nNotification is scheduled for:" + dtTime.ToShortTimeString());


Notifications that has already occured can be marked as "Handled" by calling CeHandleAppNotifications. Remember, calling ceHandleAppNotifications doesn't cancel out scheduled notifications but simply dismisses notifications that has already occured.

CeHandleAppNotification is declared as follows:

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeHandleAppNotifications")]
public extern static bool HandleAppNotifications(string AppName);


And that's it! Next week we'll work out why Microsoft has marked these functions as obselete and what alternative APIs there are. We'll also take a look at DateTime.Now() and make sure you understand why you should use the native function GetLocalTime() instead.

Have a great weekend!

torsdag 8 oktober 2009

CeRunAppAtEvent - Using CSharp

Yesterday we looked at how to use CeRunAppAtTime for executing applications at a specific date and time. CeRunAppAtTime has a sibling, CeRunAppAtEvent, that allows for executing applications at specific OS events. The events available to trigger are the following:
    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
};

Just as with CeRunAppAtTime, CeRunAppAtEvent resides in CoreDll.dll and its declaration are as follows:
        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeRunAppAtEvent")]
public extern static bool RunAppAtEvent(string AppName, [MarshalAs(UnmanagedType.U4)]CeNotificationEvent WhichEvent);


To set an application for execution when the system time is changed you call CeRunAppAtEvent like this:
RunAppAtEvent(@"\windows\bubblebreaker.exe", Notify.CeNotificationEvent.NOTIFICATION_EVENT_TIME_CHANGE);

And you cancel the execution of an application like this:
RunAppAtEvent(@"\windows\bubblebreaker.exe", Notify.CeNotificationEvent.NOTIFICATION_EVENT_NONE);

That's it.. Tomorrow we'll look at CeSetUserNotification as well as CeHandleAppNotifications and on Monday I'll explain why you really shouldn't use any of these APIs.

onsdag 7 oktober 2009

CeRunAppAtTime - using CSharp

Some time ago, I needed to run Windows Mobile based application at a specific interval. No sweat - Windows CE offers excellent scheduler API's; CeRunAppAtTime among others.
The catch however was that I had to implement it in CSharp.

CeRunAppAtTime is a simple API that takes two parameters. One string containing that path to the executable and one instance of SYSTEMTIME that specifies the date and time to execute the application.

Here is the CSharp implementation of the SYSTEMTIME struct. Notice that I've implemented it as a class and that the constructor converts a .Net DateTime to the corresponding SYSTEMTIME.

    [StructLayout(LayoutKind.Sequential)]
public class SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;

public SYSTEMTIME(DateTime dtDateTime)
{
this.wYear = (ushort)dtDateTime.Year;
this.wMonth = (ushort)dtDateTime.Month;
this.wDayOfWeek = (ushort)dtDateTime.DayOfWeek;
this.wDay = (ushort)dtDateTime.Day;
this.wHour = (ushort)dtDateTime.Hour;
this.wMinute = (ushort)dtDateTime.Minute;
this.wSecond = (ushort)dtDateTime.Second;
this.wMilliseconds = 0;
}
}


The DllImport declaration of CeRunAppAtTime is as follows:
        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeRunAppAtTime")]
public extern static bool RunAppAtTime(string AppName, SYSTEMTIME SysTime);


To schedule an application for execution simply do the following:
            DateTime dtStart = DateTime.Now;
dtStart = dtStart.AddMinutes(2);
SYSTEMTIME sysTime = new SYSTEMTIME(dtStart);
if (Notify.Notify.RunAppAtTime(@"\windows\bubblebreaker.exe", sysTime))
MessageBox.Show("Successfully scheduled Bubble Breaker for execution at: " + dtStart.ToShortTimeString());
else
MessageBox.Show("Failed to schedule Bubble Breaker for execution.");

Bonus trick:
To cancel the execution of a scheduled application simply call CeRunAppAtTime with the same application path but pass a null-value instead of a SYSTEMTIME struct.
Ex.
        public static bool CancelRunAppAtTime(string AppName)
{
return RunAppAtTime(AppName, null);
}


Tomorrow we'll look at CeRunAppAtEvent.

Enjoy