Saturday, November 10, 2012

Run a VFP app like a Service


Basic definition of a Windows Service is a program with no (or very little) user interface which provides programmatic services on the computer on which it is running. Real services though is registered in Windows' Service Control Manager Database and can be started, stopped, restarted, configured via   the Services mode of the Computer Management Console.

However, if your need is just like mine, i.e., a VFP app continuously running in the background just to do some things for my other app, then this simple demo which does not really put the exe among the Windows Services is enough. What it will do is create a system tray icon to tell you it is running, then run continuously and on a specific time (which I control via passing the time as parameter to make it more flexible) will perform something for you (whatever you want that is possible).

That need on my end to create this service arises because our Group Financial Controller wanted me to create a way to track down stocks daily.  And for that, I decided that instead of maintaining a table where I will transfer every day's stock to that for historical purposes which will eventually bloat that table very fast, it is better for me to create a snapshot via a CSV format of our stocks daily; saved inside a folder.

Brief History

That need was mentioned to me before my SWFox DevCon trip.  What I have done then is I rushed a simple module to be performed at every day's end by our Warehouse Manager.  Told him, "before going home click that, then click Perform EOD button.  What it will do is it will create a snapshot of your stocks, both in CSV format and Pivot Report (via ssExcelPivot class), which the top management needs!".  Showed him and he said ecstatic, "That's great!  That will fix our need, don't worry I will do that daily!".

This morning though, I remembered about that snapshot thing and so I checked the folder where those go and to my dismay, it contains only the last snapshot during my demo to him.  So I called him and said, "Hey!  I checked, you never performed that EOD!".  And he told me he is sorry but it always escapes his mind because of so many things to do and think of.

And so the thought of this service this morning.

Bernard Bout's ReduceMemory() Function

Observing, while the RAM consumption is low, it is slowly creeping up over time so I decided to incorporate WinAPI calls I have seen from Bernard Bout's ReduceMemory() function. The result is very satisfying as the memory usage of the service in process is always returning back to just around 840K.  This is really among my favorite simple things, thanks Bernard!

What this service can do?

It creates a system tray icon of your choosing so you will be aware of the process.  Plus it gives you a way out to kill the service via double-clicking on it.

It can run background processes you may need on your end in a specific time.  In my end I need to create a snapshot of our daily stocks without user intervention.  Maybe next week I will also loop on all tables and create a snapshot of the records via COPY TO < filename > TYPE CSV as an additional precaution against data loss.  And maybe some more.  Right now, I am running the service on our server so it will continue doing creating snapshots unimpeded.

Anyway, here are my codes so you can get an idea of how I use this on my end.  You can create your own process in the Timer's Timer Event:




*************
* Service.prg
*************
Lparameters cTime

* For reducing memory eaten by VFP as seen from Bernard Bout's ReduceMemory() function
Declare Integer SetProcessWorkingSetSize In kernel32 As SetProcessWorkingSetSize  ;
      Integer hProcess , ;
      Integer dwMinimumWorkingSetSize , ;
      Integer dwMaximumWorkingSetSize
Declare Integer GetCurrentProcess In kernel32 As GetCurrentProcess

On Error Wait Window '' Nowait

* Set it to where your systray class is
Set Classlib To 'c:\ssclasses\systray.vcx'
Local loSysTray, loTimer
loSysTray = Createobject('myTray', 'systray.vcx')
loSysTray.TipText  = "Snapshots set to run at "+Alltrim(m.cTime)+'!  Double-click to Exit service..'
loSysTray.AddIconToSystray()


*** Add the needed _screen properties

* for detecting of new day
_Screen.AddProperty('OldDate',Date())
* switch to ensure it will be processed only once per day
_Screen.AddProperty('IsProcessing',.F.)
* to specify what hour to process
_Screen.AddProperty('TargetHour',Int(Val(Getwordnum(m.cTime,1,':'))))
*to specify what minute of the hour to process
_Screen.AddProperty('TargetMins',Int(Val(Getwordnum(m.cTime,2,':'))))

loTimer = Createobject( "myTimer" )
Read Events

Define Class myTray As systray
      IconFile  = Addbs(Justpath(Sys(16,0)))+"bcservice.ico"

      * Use Double-click to exit service
      Function iconDblClickEvent
            If Messagebox('Do you really want to stop this service?',4+32,'Oooppppssss!') = 6
                  Clear Events
            Endif
      Endfunc
Enddefine

Define Class myTimer As Timer
      Interval = 1000

      Function Timer
            Local lcMain, lcPath, lcFile
            lcMain = Addbs(Justpath(Sys(16,0)))
            lnHour = Hour(Datetime())
            lnMins = Minute(Datetime())

            * Check if it is now time to perform the service
            If m.lnHour >= _Screen.TargetHour And m.lnMins >= _Screen.TargetMins And _Screen.IsProcessing = .F.

                  * Switch to .T. so it won't be repeated until next day
                  _Screen.IsProcessing = .T.

                  **** Place your codes here like creating a compressed backup, cleaning temporary files used, etc. ****

            Endif

            * If new date, reset isProcessing back to false
            If Date() > _Screen.OldDate
                  _Screen.OldDate = Date()
                  _Screen.IsProcessing = .F.
            Endif

            * Reduce Memory
            SetProcessWorkingSetSize(GetCurrentProcess(),-1,-1)
      Endfunc
Enddefine

*******  End Of Program


Additional Files Needed

You have to add the class systray found in VFP's Samples\Solution\Toledo sub-folder if you want to have a  system tray icon.  The files are:  systray.vcx and systray.vct.

Create a new project and add that prg plus that class is all that is needed to make the exe running as a service later.  Also, choose an icon to your liking so it can be displayed in the system tray.

If you use the codes above and just changed the insides of the timer event, then when you run it, pass a parameter for time like this:

C:\BizCore\bcservice.exe 17:00

Where 17:00 means run the event at 5PM of each day.

How to run it automatically with the unit?

Read this: http://sandstorm36.blogspot.com.au/2011/10/while-we-are-at-it-your-exe-and-nothing.html

As a Real Service?

If you are interested in creating a real service where you can NET START/STOP it, then read this:
http://blogs.msdn.com/b/calvin_hsia/archive/2004/12/13/282351.aspx

Benefits of having a Service

Your app is doing its normal things via user interactions like adding, editing, saving transactions, and generating reports.  But there are things that needs to be done regularly like cleaning temporary files used, creating compressed backup of data, and other more things.  However, if you leave the management of those I call end of the day processes to users, chances are, they may forget about it or simply not do those things.

A small service-like app such as this will fix that need for you because it can do those things on a scheduled time of your choosing not requiring any user interaction.  Click it then leave it!

5 comments: