Howto run an application periodically

Hello

for some reason you might want to rerun an application very day or every hour. In commercial applications, one may want to let the device sync data every 4 hours, So you need an application that runs periodically at specified times or intervals. As this first sounds easy, it is not as simple as you mean.You cannot simply use timers, timers will not continue to run within Suspend mode.

To have an application run at specific times, Windows Mobile supports a notification database. Your device will already wake up periodically to reschedule events and to clean up the notification database. Normally, Windows Mobile Phone devices will awake every night at 0:00.

There are also some other possible notifications that can launch an application, see my old web site.

How can we do a scheduled application ourself? At first run, the scheduler application has to delete all previous notification entries of the notification database. Then it has to create a new timed notification and in our sample, it will then launch another application. The scheduler application itself should be small and only care about the schedules. The worker application can be what you want and will be started by the scheduler application.

The following function shows how to remove all notifications containing the name of the scheduler application:

static HRESULT ClearRunApp(LPCTSTR szExeName)
{
	HRESULT hr = S_OK;

	// hold a notification
	PBYTE pBuff = (PBYTE)LocalAlloc(LPTR, 8192);

	if (!pBuff) {
		return E_OUTOFMEMORY;
	}

	TCHAR mExeName[MAX_PATH];
	wsprintf(mExeName, L"%s", szExeName);

	// at most 256 notification handles
	HANDLE hNotifHandlers[256];
	DWORD nNumHandlers, nNumClearedHandlers = 0;
	DWORD i = 0;
	int rc = CeGetUserNotificationHandles(hNotifHandlers, 255, &nNumHandlers);
	if (!rc) {
		ULONG uErr = GetLastError();
		hr = E_FAIL;
		nclog(L"no more handles? in CeGetUserNotificationHandles()? GetLastError()=%u\n", uErr);
		goto FuncExit;
	}

	// iterate all notifications
	// Notice: We do not care about the status of the notification.
	// Just clear it even if it is not filed??
	nclog(L"RunAtTimes, ClearRunApp(): %s", L"######################\n");
	for (i=0; ipcent;

		nclog(L"RunAtTimes, ClearRunApp(): %s\n", pNotifTrigger->lpszApplication);

		// Notice some events with NULL lpszApplication might be inserted!
		if ( pNotifTrigger && pNotifTrigger->lpszApplication ){
			if(pNotifTrigger->lpszApplication != NULL){
				if (wcsicmp(pNotifTrigger->lpszApplication, mExeName)==0) {
					nclog(L"RunAtTimes, ClearRunApp()-CeClearUserNotification for handle: 0x%0x\n", pnih->hNotification);
					CeClearUserNotification(pnih->hNotification);
				}
			}
		}
	}

	FuncExit:
	nclog(L"##### RunAtTimes, ClearRunApp():FuncExit ############\n");
	if (pBuff) {
		LocalFree(pBuff);
	}

	return hr;
}

Then the scheduler app has to create a new notification entry:

static HRESULT ScheduleRunApp(
  LPCTSTR szExeName,
  LPCTSTR szArgs)
{
	//do not add a schedule if actual date is 21.3.2003
	SYSTEMTIME t;
	memset(&t, 0, sizeof(SYSTEMTIME));
	GetLocalTime(&t);
	//check if the system clock is at factory default, device specific!
	if ( (t.wYear == 2003) && (t.wMonth == 3) && (t.wDay == 21) )
	{
		nclog(L"RunAtTimes: # no next run schedule as date is 21.03.2003!\n");
		return NOERROR;
	}

	HRESULT hr = S_OK;
	HANDLE hNotify = NULL;

	// set a CE_NOTIFICATION_TRIGGER
	CE_NOTIFICATION_TRIGGER notifTrigger;
	memset(&notifTrigger, 0, sizeof(CE_NOTIFICATION_TRIGGER));
	notifTrigger.dwSize = sizeof(CE_NOTIFICATION_TRIGGER);

	// calculate time
	SYSTEMTIME st = {0};
	GetLocalTime(&st);

	st = AddDiff(&st, 5);
	wsprintf(str, L"Next run at: %02i.%02i.%02i %02i:%02i:%02i\n",
										st.wDay, st.wMonth , st.wYear,
										st.wHour , st.wMinute , st.wSecond );
	nclog(L"RunAtTimes: %s\n", str);

	notifTrigger.dwType = CNT_TIME;
	notifTrigger.stStartTime = st;

	// timer: execute an exe at specified time
	notifTrigger.lpszApplication = (LPTSTR)szExeName;
	notifTrigger.lpszArguments = (LPTSTR)szArgs;

	hNotify = CeSetUserNotificationEx(0, &notifTrigger, NULL);
	// NULL because we do not care the action
	if (!hNotify) {
		hr = E_FAIL;
		nclog(L"CeSetUserNotificationEx FAILED...\n");
	} else {
		// close the handle as we do not need to use it further
		CloseHandle(hNotify);
		nclog(L"RunAtTimes: CeSetUserNotificationEx succeeded...\n");
	}
	return hr;
}

In this sample code, the next schedule is created for 5 minutes in advance.
No we take a look at the main function. The scheduler app has no GUI and does not need a GUI. I have created a Windows CE console application:

	//launched without args
	//if launched from scheduler, lpCmdLine will have the value reboot as specified below
	if (wcslen(lpCmdLine) == 0)
	{
		//create a new schedule
		if ( !FAILED(ScheduleRunApp(lpFileName, L"reboot")) )
		{
			MessageBox(NULL, str, lpFileName, MB_TOPMOST | MB_SETFOREGROUND);
		}
		else
			MessageBox(NULL, L"error in RunAppAtTime", lpFileName, MB_TOPMOST | MB_SETFOREGROUND);
	}

	//Quiet install
	if (_wcsicmp(L"quiet", lpCmdLine)==0)
	{
		nclog(L"RunAtTimes: processing CmdLine=quiet...\n");
		//create a new schedule
		if ( !FAILED(ScheduleRunApp(lpFileName, L"reboot")) ){
			iRet=0;
			goto MainExit; //OK
		}
		else{
			iRet=-1;
			goto MainExit; //OK
		}
	}

	//check if launched with 'clear'
	if (_wcsicmp(L"clear", lpCmdLine)==0)
	{
		nclog(L"RunAtTimes: processing CmdLine=clear...\n");
		ClearRunApp(lpFileName);
		MessageBox(NULL, L"Schedule for this exe cleared", lpFileName, MB_TOPMOST | MB_SETFOREGROUND);
		iRet=1;
		goto MainExit; //OK
	}

	//launched from scheduler with 'reboot'
	if (_wcsicmp(L"reboot", lpCmdLine)==0)
	{

		nclog(L"RunAtTimes: processing CmdLine=reboot...\n");
		nclog(L"RunAtTimes: ...will reboot now after reschedule...\n");
		//schedule next run
		if ( !FAILED(ScheduleRunApp(lpFileName, L"reboot")) )
		{
			//the worker application should be launched here!
			nclog(L"RunAtTimes: starting target app %s...\n", szExtApp);
			PROCESS_INFORMATION pi;
			CreateProcess(szExtApp,NULL,NULL,NULL,NULL, 0, NULL,NULL,NULL, π);
			CloseHandle(pi.hThread);
			CloseHandle(pi.hProcess);
			//nclog(L"......REBOOT in 60 seconds.......\n");
			//Sleep(60000);
			//ITCWarmBoot();
			iRet=0;
			goto MainExit; //OK
		}
		else{
			MessageBox(NULL, L"error in ScheduleRunApp", lpFileName, MB_TOPMOST | MB_SETFOREGROUND);
			nclog(L"RunAtTimes: error in ScheduleRunApp\n");
			iRet=-2;
			goto MainExit; //OK
		}
	}

Here you can see, that a new schedule is created, if the app is started without argument. Further more, the scheduler can be launched with ‘clear’ as argument and it will then clear all schedules and does not create a new schedule. If it is launched with ‘reboot’ (sorry about this argument), it assumes it has been launched by the scheduler and will then delete all schedules for itself, create a new schedule and launch the worker application (SyncSim).

The worker application has also some special APIs to use. When the scheduler app is launched by the OS from the notification database, it does not switch the device to full power (unanttended power mode?). So the first thing for the worker app is to request full power mode. When the OS runs an application from a notification entry, there is a special timeout, after that the device will suspend again (see WakeupPowerOff). So the worker application has to ensure, that the device will not fall back to suspend before the ‘work’ is done. You can force the device to remain powered by periodically calling SystemIdleTimerReset().

OK, here is a full power request:

        //Switch Backlight ON
	hPower = SetPowerRequirement(_T("BKL1:"), D0, POWER_NAME, NULL, 0); //POWER_FORCE

	//set to full power
	if ( SetSystemPowerState(NULL, POWER_STATE_ON, POWER_FORCE) == ERROR_SUCCESS ){
		nclog(L"SetSystemPowerState ok\n");
	}
	else{
		nclog(L"SetSystemPowerState FAILED with error 0x%0x\n", GetLastError());
	}

Then a background thread with SystemIdleTimerReset():

DWORD WINAPI ThreadProc(LPVOID lpParameter){
	bThreadEnded=false;
	nclog(L"ThreadProc started...\n");
	while(bRunThread){
		SystemIdleTimerReset();
		Sleep(1000);
	}
	nclog(L"ThreadProc ended normally\n");
	bThreadEnded=true;
	return 0;
}

For the backlight: dont forget to release the power request:

	if(hPower != NULL){
		HRESULT result = ReleasePowerRequirement(hPower);
		nclog(L"SyncSim-OnDestroy: ReleasePowerRequirement() returned 0x%0x\n", result);
	}

UPDATE 03. feb 2012: Download Visual Studio 2005 CPP source code (with some more test code inside): [Download not found] (also removed dependencies on itc50.h/lib)

Download EVC4 projects: [Download not found]


							

5 Comments

  1. […] code of the scheduler called PingAlertScheduler is based on my code How to run an application periodically. The code first clears all existing schedules of themself and then creates a new schedule for xx […]

  2. […] If a timed schedule is reached, the scheduler will launch the specified application, see also here . This will also work if the device is in suspend mode (sleeps). The scheduler can also fire on […]

  3. Laurent says:

    Hello,

    I found this usaefull page and now wakeup is working on my unit.

    Nevertheless I encountered some problem with battery lifetime.

    when we use the full power request the device also power up backlight. That means that backlight is often power up even if the user doesn’t use the device.

    Is there a method to avoid total power up ? (we can keep the backlight OFF).

    best regards,

    Laurent.

  4. josef says:

    Hello Laurent

    modern Windows Mobile devices know different power states. You may try another SetPowerRequirement, like Unattended, for example. But this depends on the use case. Does the device really need to be powered on all the time? etc.

    ~~Josef

Leave a Reply