måndag 26 oktober 2009

Using the PowerState API

It's been a full week since my last post but I've been a bit busy writing sql scripts for an upcoming project at work.. But I did take a couple of minutes to write a simple Windows Mobile Powerstate API sample. Only two function calls are covered but together they hold the last bits you need to be able to write an application that "wakes" up the device at a set time and after performing various tasks are capable to return the device back to the previous state.

I'll show you how right now!

By now you already know how to use the notification library that comes with Windows Mobile. The problem with this API is that although capable of executing an application at a specific time it will not wake the device. In other words; you can initiate a thread but you won't be able to access peripheral devices such as the GPS receiver, open up an internet connection or send text messages. These are all things you often want to do when beeing able to execute applications on set times.
By applying a bit of PowerState magic this small issue may easily be solved.

First of you want to know the current power state of the device. This can easily be found out be calling GetSystemPowerState.

        [DllImport("coredll.dll", SetLastError = false)]
public static extern uint GetSystemPowerState(string sPowerStateName, uint iNameLen, ref uint Flags);

This function will return both the name of the current state as well as all the power state flags that has been set. Power state flags are a simple 32 bit bitmask. The header file declares the different power state flags as constants but I've choosen to declare them as a enum.

    public enum POWER_STATE
{
ON = 0x10000 // on state
, OFF = 0x20000 // no power, full off
, CRITICAL = 0x40000 // critical off
, BOOT = 0x80000 // boot state
, IDLE = 0x100000 // idle state
, SUSPEND = 0x200000 // suspend state
, UNATTENDED = 0x400000 // unattended state
, RESET = 0x800000 // reset state
, USERIDLE = 0x1000000 // user idle state
, BACKLIGHTON = 0x2000000 // device screen backlight on
, PASSWORD = 0x1000000 // this state is password protected
}


Calling GetSystemPowerState before applying a new power state allows you to save the current state and then re-apply it after you've done whatever it is you wanna do. SetSystemPowerState is just as easy to use as its equivelent Get-function.

        [DllImport("coredll.dll", SetLastError = false)]
public static extern uint SetSystemPowerState(string sPowerStatename, POWER_STATE nFlags, uint nForce);



You can pretty much ignore the PowerStateName parameter. nFlags speaks for itself and nForce can be used to force the device into a specific power state.

When using SetSystemPowerState to wake the device you basically call it passing POWER_STATE.ON as the nFlags parameter. And that's it..

I've written a small sample showing you how to use both function calls as well as how the POWER_STATE bitmask works. Enjoy!


            uint iRet;
uint nFlags = 0;
// This would be an excellent time to get aquinted with the StringBuilder object but
// let's do it the cheap way for now.
string sPowerStateName = " ";
iRet = WinCE.Powerstate.GetSystemPowerState(sPowerStateName, (uint)sPowerStateName.Length, ref nFlags);
if (iRet != 0)
{
MessageBox.Show("Failed to retrieve system power state");
return;
}
MessageBox.Show("Powerstate is: " + sPowerStateName);

bool IsOn = Convert.ToBoolean(nFlags & (uint)POWER_STATE.ON);
bool IsOff = Convert.ToBoolean(nFlags & (uint)POWER_STATE.OFF);
bool IsCritical = Convert.ToBoolean(nFlags & (uint)POWER_STATE.CRITICAL);
bool IsBoot = Convert.ToBoolean(nFlags & (uint)POWER_STATE.BOOT);
bool IsIdle = Convert.ToBoolean(nFlags & (uint)POWER_STATE.IDLE);
bool IsSuspended = Convert.ToBoolean(nFlags & (uint)POWER_STATE.SUSPEND);
bool IsUnattended= Convert.ToBoolean(nFlags & (uint)POWER_STATE.UNATTENDED);
bool IsReset = Convert.ToBoolean(nFlags & (uint)POWER_STATE.RESET);
bool IsUserIdle = Convert.ToBoolean(nFlags & (uint)POWER_STATE.USERIDLE);
bool IsBackLightOn = Convert.ToBoolean(nFlags & (uint)POWER_STATE.BACKLIGHTON);
bool IsPassword = Convert.ToBoolean(nFlags & (uint)POWER_STATE.PASSWORD);



            uint iRet;
iRet = WinCE.Powerstate.SetSystemPowerState(null, POWER_STATE.OFF, 1);
if (iRet == 0)
MessageBox.Show("Successfully set new power state");
else
MessageBox.Show("Failed to set new power state");

måndag 19 oktober 2009

Enumerate flash cards on a Windows Mobile based device

Today we'll have a look at how to enumerate available flash cards on a Windows Mobile based device using the native APIs FindFirstFlashCard and FindNextFlashCard. Code is written in C#.

To successfully enumerate the available flash cards, we will need to implement a couple of APIs using DllImport.


        [DllImport("coredll.dll", SetLastError = false)]
public static extern bool FindClose(IntPtr hFindFile);

[DllImport("note_prj.dll", SetLastError = false)]
public static extern IntPtr FindFirstFlashCard(byte[] bFindFlashData);

[DllImport("note_prj.dll", SetLastError = false)]
public static extern bool FindNextFlashCard(IntPtr hFlashCard, byte[] bFindFlashData);



We also need to enum some of the FILE_ATTRIBUTE_xxx values available in Winbase.h.


        public enum FILE_ATTRIBUTE
{
READONLY = 0x1
, HIDDEN = 0x2
, SYSTEM = 0x4
, DIRECTORY = 0x10
, ARCHIVE = 0x20
, INROM = 0x40
, NORMAL = 0x80
, TEMPORARY = 0x100
, SPARSE_FILE = 0x200
, REPARSE_POINT = 0x400
, COMPRESSED = 0x800
, ROMSTATICREF = 0x1000
, ROMMODULE = 0x2000
, ENCRYPTED = 0x4000
}



Next, we need to implement the WIN32_FIND_DATA structure. As you can see in our import of FindFirstFlashCard we don't actually pass an instance of the win32_find_data structure to the API, but instead we pass a byte buffer that are later converted to a win32_find_data structure. I've added a couple of properties to the structure to make working with file attributes a bit more .Net-ish. As you can see, the byte-to-structure-code is located in the constructur the same way as we did with the notification structures last week.


        [StructLayout(LayoutKind.Sequential)]
public class WIN32_FIND_DATA
{
public uint FileAttributes;
private FILETIME ftCreationTime;
private FILETIME ftLastAccessTime;
private FILETIME ftLastWriteTime;
public DateTime dtCreationTime;
public DateTime dtLastAccessTime;
public DateTime dtLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwOID;
public string FileName;
public bool IsReadOnly;
public bool IsHidden;
public bool IsSystem;
public bool IsDirectory;
public bool IsArchive;
public bool IsInRom;
public bool IsNormal;
public bool IsTemporary;
public bool IsSparseFile;
public bool HasReparsePoint;
public bool IsCompressed;
public bool HasRomStaticRef;
public bool IsRomModule;
public bool IsEncrypted;

public WIN32_FIND_DATA()
{
}
public WIN32_FIND_DATA(byte[] Buf, uint Size)
{
int Index = 0;
int iEndOfString = 0;
System.Text.UnicodeEncoding enc = new UnicodeEncoding();
this.FileAttributes = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftCreationTime.dwLowDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftCreationTime.dwHighDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftLastAccessTime.dwLowDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftLastAccessTime.dwHighDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftLastWriteTime.dwLowDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.ftCreationTime.dwHighDateTime = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.nFileSizeHigh = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.nFileSizeLow = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.dwOID = BitConverter.ToUInt32(Buf, Index);
Index += 4;

iEndOfString = WinCE.findEndOfString(Buf, Index, (int)Size);
this.FileName = enc.GetString(Buf, Index, iEndOfString - Index - 2);
Index = iEndOfString;

this.IsReadOnly = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.READONLY);
this.IsHidden = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.HIDDEN);
this.IsSystem = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.SYSTEM);
this.IsDirectory = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.DIRECTORY);
this.IsArchive = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.ARCHIVE);
this.IsInRom = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.INROM);
this.IsNormal = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.NORMAL);
this.IsTemporary = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.TEMPORARY);
this.IsSparseFile = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.SPARSE_FILE);
this.HasReparsePoint = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.REPARSE_POINT);
this.IsCompressed = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.COMPRESSED);
this.HasRomStaticRef = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.ROMSTATICREF);
this.IsRomModule = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.ROMMODULE);
this.IsEncrypted = Convert.ToBoolean(this.FileAttributes & (uint)FILE_ATTRIBUTE.ENCRYPTED);

this.dtCreationTime = DateTime.FromFileTime(this.ftCreationTime.GetAsLong());
this.dtLastAccessTime = DateTime.FromFileTime(this.ftLastAccessTime.GetAsLong());
this.dtLastWriteTime = DateTime.FromFileTime(this.ftLastWriteTime.GetAsLong());
}
}



Now that's a lot of code!
The win32_find_data structure has a couple of instances of the FILETIME structure. This is a simple structure consisting of just two 32 bit unsigned integer values and a function that returns a 64 bit unsigned integer.


    [StructLayout(LayoutKind.Sequential)]
public struct FILETIME
{
public uint dwLowDateTime;
public uint dwHighDateTime;
public long GetAsLong()
{
long lRet;
lRet = dwHighDateTime;
lRet = lRet << 32;
lRet = dwLowDateTime;
return lRet;
}
}


Returning the filetime structure as a 64 bit integer value makes it possible for us to directly convert it to a .Net DateTime structure using the static DateTime.GetFromFileTime(long lFileTime) function.


this.dtCreationTime = DateTime.FromFileTime(this.ftCreationTime.GetAsLong());


Next up is a simple function that enumerates all flash cards and outputs the path using System.Diagnostics.Trace.WriteLine(string sVal).


            FileSystem.WIN32_FIND_DATA FindData;
Byte[] Buf = new byte[295]; // Struct is 40 bytes + MAX_PATH (255 bytes) = 295 bytes

IntPtr hFlashCard = WinCE.FileSystem.FileAppMgmt.FindFirstFlashCard(Buf);

if (hFlashCard != IntPtr.Zero)
{
FindData = new FileSystem.WIN32_FIND_DATA(Buf, 295);
System.Diagnostics.Trace.WriteLine("Filename:" + FindData.FileName);
Buf = new byte[295];

while (WinCE.FileSystem.FileAppMgmt.FindNextFlashCard(hFlashCard, Buf))
{
FindData = new FileSystem.WIN32_FIND_DATA(Buf, 295);
System.Diagnostics.Trace.WriteLine("Filename:" + FindData.FileName);
}
WinCE.FileSystem.FindClose(hFlashCard);
System.Diagnostics.Trace.WriteLine("Succeeded");
}
else
{
System.Diagnostics.Trace.WriteLine("Failed");
}




And that's about it.. Check back tomorrow when we look closer on a couple other file system related APIs.

torsdag 15 oktober 2009

CeGetUserNotification

The last notification related API that we will look at for this time is CeGetUserNotification. As its name implies this function returns information on a previously created notification. The function takes a notification handle as input and returns a byte array containing an instance of a CE_NOTIFICATION_INFO_HEADER. This structure points to two other structures that are allocated in the same buffer, CE_NOTIFICATION_TRIGGER and CE_USER_NOTIFICATION.

        [DllImport("CoreDLL.dll", SetLastError = false, EntryPoint="CeGetUserNotification")]
public static extern bool GetUserNotification(IntPtr hNotification, uint BufferSize, out uint BytesNeeded, byte[] Buffer);


Calling GetUserNotification passing null values and a valid notification handle will return the bytes needed for the byte buffer. You then call GetUserNotification once more passing both a notification handle as well as a byte buffer.
    GetUserNotification(hNotification, 0, out Size, null);
Buf = new byte[Size];
GetUserNotification(hNotification, Size, out Size, Buf)


Had we been doing this in c++ then casting the byte array into a CE_NOTIFICATION_INFO_HEADER would be as easy as 1-2-3.. Casting this C-style array using CSharp is quite but not quite as easy. Basically we have to read the buffer byte by byte, using the data to set the values of an instance of the structure. I choose to do this using the structures constructor. My implementation of CE_NOTIFICATION_INFO_HEADER is found below.


[StructLayout(LayoutKind.Sequential)]
public class CE_NOTIFICATION_INFO_HEADER
{
[MarshalAs(UnmanagedType.U4)]
public uint hNotification;
[MarshalAs(UnmanagedType.U4)]
public uint dwStatus;
public CE_NOTIFICATION_TRIGGER cent;
public CE_USER_NOTIFICATION ceun;

public CE_NOTIFICATION_INFO_HEADER(byte[] Buf, uint Size)
{
int Index = 0;
try
{
this.hNotification = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.dwStatus = BitConverter.ToUInt32(Buf, Index);
Index += 4;
uint pcent = BitConverter.ToUInt32(Buf, Index);
Index += 4;
uint pceun = BitConverter.ToUInt32(Buf, Index);
Index += 4;
if (pcent > 0 && Buf[Index] == Marshal.SizeOf(new CE_NOTIFICATION_TRIGGER()))
this.cent = new CE_NOTIFICATION_TRIGGER(Buf, Size, ref Index);
if (pceun > 0)
this.ceun = new CE_USER_NOTIFICATION(Buf, Size, ref Index);
}
catch (Exception ex)
{
throw ex;
}
}
}

As you can see I've overloaded the constructors of both CE_NOTIFICATION_TRIGGER and CE_USER_NOTIFICATION to be able to create instances from a byte array. Here are the final implementations of both structures.


[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()
{
}

public CE_NOTIFICATION_TRIGGER(byte[] Buf, uint Size, ref int Index)
{
int iEndOfString;
System.Text.UnicodeEncoding enc = new UnicodeEncoding();
uint lpszApp = 0;
uint lpszArgs = 0;
this.Size = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.Type = (CeNotificationType)BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.Event = (CeNotificationEvent)BitConverter.ToUInt32(Buf, Index);
Index += 4;
lpszApp = BitConverter.ToUInt32(Buf, Index);
Index += 4;
lpszArgs = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.StartTime = new SYSTEMTIME(Buf, Index);
Index += 16;
this.EndTime = new SYSTEMTIME(Buf, Index);
Index += 16;
if (lpszApp > 0)
{
iEndOfString = WinCE.WinCE.findEndOfString(Buf, Index, (int)Size);
this.pApplication = enc.GetString(Buf, Index, iEndOfString - Index - 2);
Index = iEndOfString;
}
if (lpszArgs > 0)
{
iEndOfString = WinCE.WinCE.findEndOfString(Buf, Index, (int)Size);
this.pArgs = enc.GetString(Buf, Index, iEndOfString - Index - 2); // Stupid solution to double null ending..
Index = iEndOfString;
}
}
}

[StructLayout(LayoutKind.Sequential)]
public class CE_USER_NOTIFICATION
{
[MarshalAs(UnmanagedType.U4)]
public PUN_FLAGS ActionFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public string sDialogTitle;
[MarshalAs(UnmanagedType.LPTStr)]
public string sDialogText;
[MarshalAs(UnmanagedType.LPTStr)]
public string sSound;
[MarshalAs(UnmanagedType.U4)]
public uint nMaxSound;
[MarshalAs(UnmanagedType.U4)]
public uint Reserved;
public CE_USER_NOTIFICATION()
{
}

public CE_USER_NOTIFICATION(byte[] Buf, uint Size, ref int Index)
{
int iEndOfString;
System.Text.UnicodeEncoding enc = new UnicodeEncoding();

this.ActionFlags = (PUN_FLAGS)BitConverter.ToUInt32(Buf, Index);
Index += 4;
uint lpszDialogTitle = BitConverter.ToUInt32(Buf, Index);
Index += 4;
uint lpszDialogText = BitConverter.ToUInt32(Buf, Index);
Index += 4;
uint lpszSound = BitConverter.ToUInt32(Buf, Index);
Index += 4;
this.nMaxSound = BitConverter.ToUInt32(Buf, Index);
Index += 4;
// read Reserved
Index += 4;
if (lpszDialogTitle > 0)
{
iEndOfString = WinCE.WinCE.findEndOfString(Buf, Index, (int)Size);
this.sDialogTitle = enc.GetString(Buf, Index, iEndOfString - Index - 2);
Index = iEndOfString;
}
if (lpszDialogText > 0)
{
iEndOfString = WinCE.WinCE.findEndOfString(Buf, Index, (int)Size);
this.sDialogText = enc.GetString(Buf, Index, iEndOfString - Index - 2);
Index = iEndOfString;
}
if (lpszSound > 0)
{
iEndOfString = WinCE.WinCE.findEndOfString(Buf, Index, (int)Size);
this.sSound = enc.GetString(Buf, Index, iEndOfString - Index - 2);
Index = iEndOfString;
}
}
}
Creating an instance of a CE_NOTIFICATION_INFO_HEADER from a byte array can now be accomplished like this:
    CE_NOTIFICATION_INFO_HEADER cenih = new Notify.CE_NOTIFICATION_INFO_HEADER(Buf, Size);

And there you have it.. You now have all the pieces needed to implement notifications and scheduled events in your .Net applications. You also have all the pieces needed to create an application that manages all OS specific and user created notifications.

I wont post anything tomorrow, friday, but I'll be back monday. Have a nice weekend!

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