Friday, March 30, 2007

Run Application at Time (and Time Change)

MSDN describes CeRunAppAtTime as a function which "prompts the system to start running a specified application at a specified time." This function can be extremely useful for performing scheduled tasks such as snapshots of running processes, the retrieval of device memory statistics, routine checks on security settings, or automatic device synchronization. Conversely, it can also be used to rescind the scheduling of these activities.

The CeRunAppAtTime function takes two parameters: TCHAR* pwszAppName, and SYSTEMTIME* lpTime. The first parameter, pwszAppName, is the name of the application to be run (e.g. \windows\cgacutil.exe). The type of the parameter is a pointer to a TCHAR. One might ask, "What's a TCHAR?" Well, according to MSDN, a TCHAR is a "Win32 character string that can be used to describe ANSI, DBCS, or Unicode strings. [...] For Unicode platforms, TCHAR is defined as synonymous with the WCHAR type." With Windows CE being a Unicode platform, this makes a TCHAR a WCHAR (wide character). Further demonstrating this is that the name of the actual first parameter - pwszAppName, translates into Pointer to a Wide String terminated with a Zero (null character) when broken down. Because the parameter is an input parameter, a standard C# string can be passed in as an equivalent in a P/Invoke function call.

The second parameter, lpTime, is the time at which the specified application should be run. A simple C# type though won't suffice for lpTime (Long Pointer to a time variable). This parameter requires a little bit more work. SYSTEMTIME is a structure composed of WORD representations of 8 items: year, month, day, day of week, hours, minutes, seconds, and milliseconds. A WORD is equivalent to a C# short, i.e. 2 bytes, putting the size of the structure at 16 bytes. A C# equivalent has been provided in Code Listing 1.

Additionally, it is the second parameter which also controls whether CeRunAppAtTime rescinds the scheduling of an application. This is done by passing in a null parameter for lpTime. One note on cancelling an application though is that CeRunAppAtTime will return false if the application specified was not already currently specified, so do not interpret this as the function only failing to remove a scheduled instance.

In the example below, because SYSTEMTIME (represented in C# as SystemTime) is a structure, two P/Invoke calls have been defined (see NativeTimeMethods.cs). The one which takes an array of bytes as the lpTime parameter is the one which can have a null value sent into it (see TimeUtilities.cs). The other one should be used for standard scheduling of applications.

An interesting point requiring further discussion is what happens when the time on the device is changed. The device time could be changed by the user or by a time negotiation on synchronization, or via an NTP (Network Time Protocol) server.

Images 1 through 5 show what happens when the current time on a device (March 28th, 2007) is changed to a future time (March 30th, 2007) to the point where applications would be scheduled to run in the past (March 29th, 2007). Windows CE ends up running those scheduled applications immediately. This is demonstrated within the screenshots of the sample application.

Image 1 - First "Set Date" button tapped. Date changed to March 28th, 2007


Image 2 - Right soft key button tapped. Application soft key menu displayed


Image 3 - Run App menu option selected. Application is run now.


Image 4 - "Set App" button tapped. Application set to run on March 29th, 2007


Image 5 - Second "Set Date" button tapped. Date changed to March 30th, 2007


With the time now changed to March 30th, 2007, the application which was scheduled to run on March 29th, 2007 is now run immediately.

Code Listing 1 - SystemTime.cs



#region Using Directives

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Rhinomobile.ClassLibrary
{
/// <summary>
/// This structure represents a date and time using individual members for the month,
/// day, year, weekday, hour, minute, second, and millisecond.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
#region Fields

private short m_year;
private short m_month;
private short m_dayOfWeek;
private short m_day;
private short m_hour;
private short m_minute;
private short m_second;
private short m_milliseconds;

#endregion

#region Constructors

public SystemTime(DateTime value)
: this()
{
ConvertFromDateTime(value);
}

#endregion

#region Properties

/// <summary>
/// Specifies the current year
/// </summary>
public short Year
{
get { return m_year; }
}

/// <summary>
/// Specifies the current month
/// January = 1 ... December = 12
/// </summary>
public short Month
{
get { return m_month; }
}

/// <summary>
/// Specifies the current day of the week
/// Sunday = 0 ... Saturday = 6
/// </summary>
public short DayOfWeek
{
get { return m_dayOfWeek; }
}

/// <summary>
/// Specifies the current day of the month
/// </summary>
public short Day
{
get { return m_day; }
}

/// <summary>
/// Specifies the current hour
/// </summary>
public short Hour
{
get { return m_hour; }
}

/// <summary>
/// Specifies the current minute
/// </summary>
public short Minute
{
get { return m_minute; }
}

/// <summary>
/// Specifies the current second
/// </summary>
public short Second
{
get { return m_minute; }
}

/// <summary>
/// Specifies the current millisecond
/// </summary>
public short Milliseconds
{
get { return m_milliseconds; }
}

public DateTime DateTime
{
get { return ConvertToDateTime(); }
set { ConvertFromDateTime(value); }
}

/// <summary>
/// Gets the day of the year represented by this instance.
/// </summary>
public int DayOfYear
{
get { return DateTime.DayOfYear; }
}

/// <summary>
/// Gets the number of ticks that represent the date and time of this instance.
/// </summary>
public long Ticks
{
get { return DateTime.Ticks; }
}

/// <summary>
/// Gets the time of day for this instance.
/// </summary>
public TimeSpan TimeOfDay
{
get { return DateTime.TimeOfDay; }
}

#endregion

#region Private Methods

private void ConvertFromDateTime(DateTime value)
{
m_year = (short)value.Year;
m_month = (short)value.Month;
m_dayOfWeek = (short)value.DayOfWeek;
m_day = (short)value.Day;
m_hour = (short)value.Hour;
m_minute = (short)value.Minute;
m_second = (short)value.Second;
m_milliseconds = (short)value.Millisecond;
}

private DateTime ConvertToDateTime()
{
return new DateTime(m_year, m_month, m_day, m_hour, m_minute, m_second);
}

#endregion
}
}



Code Listing 2 - NativeTimeMethods.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Rhinomobile.ClassLibrary
{
/// <summary>
/// P/Invoked time related methods.
/// </summary>
static class NativeTimeMethods
{
#region Internal Static Methods

/// <summary>
/// This function sets the current local time and date.
/// </summary>
/// <param name="lpSystemTime">A pointer to a SystemTime structure that contains
/// the new local date and time. The DayOfWeek member of the SystemTime structure
/// is ignored.</param>
/// <returns>Nonzero indicates success. Zero indicates failure.</returns>
[DllImport("coredll")]
internal static extern bool SetLocalTime(ref SystemTime lpSystemTime);

/// <summary>
/// This function prompts the system to start running a specified application at
/// a specified time.
/// </summary>
/// <param name="pwszAppName">Pointer to a null-terminated string that specifies
/// the name of the application to be run.</param>
/// <param name="lpTime">Long pointer to a SystemTime structure that specifies
/// the time when the given application is to be run. If this parameter is NULL,
/// the existing run request is deleted and no new request is entered. The
/// deleted run request must have been initiated by a call to CeRunAppAtTime.</param>
/// <returns>TRUE indicates success. FALSE indicates failure.</returns>
[DllImport("coredll")]
internal static extern bool CeRunAppAtTime(string pwszAppName, ref SystemTime lpTime);

/// <summary>
/// This function prompts the system to start running a specified application at
/// a specified time.
/// </summary>
/// <param name="pwszAppName">Pointer to a null-terminated string that specifies
/// the name of the application to be run.</param>
/// <param name="lpTime">Long pointer to a SystemTime structure that specifies
/// the time when the given application is to be run. If this parameter is NULL,
/// the existing run request is deleted and no new request is entered. The
/// deleted run request must have been initiated by a call to CeRunAppAtTime.</param>
/// <returns>TRUE indicates success. FALSE indicates failure.</returns>
[DllImport("coredll")]
internal static extern bool CeRunAppAtTime(string pwszAppName, byte[] lpTime);

#endregion
}
}



Code Listing 3 - TimeException.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Rhinomobile.ClassLibrary
{
/// <summary>
/// Represents errors that occur during time methods
/// </summary>
public class TimeException : Exception
{
#region Constructors

/// <summary>
/// Initializes a new instance of the TimeException class.
/// </summary>
public TimeException()
: base()
{
}

/// <summary>
/// Initializes a new instance of the TimeException class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public TimeException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the TimeException class with a specified error
/// message and a reference to the inner exception that is the cause of the exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is
/// the cause of the current exception.</param>
public TimeException(string message, Exception innerException)
: base(message, innerException)
{
}

#endregion
}
}



Code Listing 4 - TimeUtilities.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Rhinomobile.ClassLibrary
{
/// <summary>
/// Methods for performing time based functions
/// </summary>
public static class TimeUtilities
{
#region Static Public Methods

/// <summary>
/// This function sets the current local date and time.
/// </summary>
/// <param name="value">The local date and time to set on the device.</param>
/// <returns>True if date and time are successfully set</returns>
public static bool SetLocalDeviceTime(DateTime value)
{
SystemTime systemTime = new SystemTime(value);
bool result = NativeTimeMethods.SetLocalTime(ref systemTime);
if (!result)
{
int errorCode = Marshal.GetLastWin32Error();
throw new TimeException(StandardConstants.ERROR_CODE + errorCode.ToString());
}
return result;
}

/// <summary>
/// This function prompts the system to start running a specified application
/// at a specified time.
/// </summary>
/// <param name="application">The name of the application to be run</param>
/// <param name="value">The date and time when the given application is to be run</param>
/// <returns>True if the application is properly scheduled</returns>
public static bool RunApplication(string application, DateTime value)
{
SystemTime systemTime = new SystemTime(value);
bool result = NativeTimeMethods.CeRunAppAtTime(application, ref systemTime);
if (!result)
{
int errorCode = Marshal.GetLastWin32Error();
throw new TimeException(StandardConstants.ERROR_CODE + errorCode.ToString());
}
return result;
}

/// <summary>
/// This method deletes an existing run application request
/// </summary>
/// <param name="application">Application which has been requested to run</param>
/// <returns>True if application is found and request is removed</returns>
public static bool CancelRunApplication(string application)
{
return NativeTimeMethods.CeRunAppAtTime(application, null);
}

#endregion
}
}

1 comment:

維塵緣 said...

First, I would like to thank you for sharing this code wrapping native time methods. Good work.

There is a bug that "StandardConstants.ERROR_CODE" is not defined explicitly in four programs. This causes a compiler error. Lucky, that could be resolved by replacing it with a constant string value. If you have time, I would be very appreciate you can fix it.

Good day.