Scott Scovell on Visual FoxPro

Wednesday, November 23, 2005

BindEvent() and Compile

Recently I have been working on a Fox IDE tool called FoxTabs. FoxTabs utilises my favourite feature of VFP 9.0, binding to Windows message events. The design approach is to hook into window events within the Fox IDE and manage multiple editing windows via tab controls on a toolbar. We, Craig and I, have just posted a beta version for all to check out. However there is one issue preventing me from getting some well earned sleep. (issue posted by Alan Stevens on the forum)
 
When running FoxTabs, if an error occurs during compiling your source code, VFP crashes. I admit, its only a minor issue. I mean, who writes code with bugs in it right? :-P
 
So what's happening? We have event bindings to all WM_CREATE events received by Fox. When a window is created, we interrogate its properties to see if it is an IDE window we want to manage via a toolbar tab. When the compile error occurs, Fox will create a dialog box to display the error. It is when we process the WM_CREATE event of this dialog box we get the fatal crash.
 
Here is some simplified code to demonstrate this:
 

* Window messaging constants

#define GWL_WNDPROC           (-4)

#define WM_CREATE             0x0001

#define WM_DESTROY            0x0002

#define WM_SETTEXT            0x000C

 

* Create an instance of the wnHandler class

Public wmHandler As wmHandler

wmHandler = NewObject("wmHandler")

 

* Bind to all window create notifications

BindEvent(0, WM_CREATE, wmHandler, "wmEventHandler")

 

Return

 

* Define Windows messages event handler class

Define Class wmHandler As Custom

 

      PrevWndFunc       = 0

      IgnoreEvents      = .F.

     

      Function wmEventHandler(hWnd As Integer, Msg As Integer, wParam As Integer, lParam As Integer)

     

            Local lnReturn As Integer

           

            Do Case

                  Case This.IgnoreEvents

                        ? "Ignoring WM events"

 

                  Case Msg = WM_CREATE

                        ? Transform(hWnd, "@0x"), "WM_CREATE", Transform(wParam, "@0x"), Transform(lParam, "@0x")

 

                        * Bind to the WM_SETTEXT messages received by this window

                        BindEvent(hWnd, WM_SETTEXT, wmHandler, "wmEventHandler")

 

                  Case Msg = WM_SETTEXT        

                        ? Transform(hWnd, "@0x"), "WM_SETTEXT", Transform(wParam, "@0x"), Transform(lParam, "@0x")

 

                  Case Msg = WM_DESTROY

                        * Remove all WM event bindings to this window

                        UnBindEvents(hWnd)

 

            EndCase

 

            * Pass the WM event to the intended Window procedure

            lnReturn = CallWindowProc(This.PrevWndFunc, hWnd, Msg, wParam, lParam)

           

            Return lnReturn

           

      EndFunc

 

      Function Init()

           

            * Declare Win32 API functions

            Declare Integer CallWindowProc In Win32API Integer lpPrevWndFunc, Integer hWnd, Integer Msg, Integer wParam, Integer lParam

            Declare Integer GetWindowLong In Win32API Integer hWnd, Integer nIndex

 

            * Store handle for use in CallWindowProc

            This.PrevWndFunc = GetWindowLong(_Vfp.hWnd, GWL_WNDPROC)

 

      EndFunc

 

      Function Destroy()

     

            * Remove all WM event bindings

            UnBindEvents(0)

           

      EndFunc

     

EndDefine

 

Copy the code into a PRG and run. Open some IDE editing windows and observe the text outputted to the desktop. To reproduce the crash, open a new PRG and enter some junk (anything that will generate a compilation error). Compile the PRG...Bugger!

 

Now run the test again, only this time before you compile the PRG, go the command window and type

 

wmHandler.IgnoreEvents = .T.

 

Compile the PRG and notice the sky does not fall in.

 

So it would seem that the solution may be to implement some sort of BeforeCompile() event hook and disable my Windows message event processing. But how can I determine whether Fox is in the middle of compiling some code? I have tried the call stack but no luck there.

 

I have no choice, I am going to have to use the emergency red phone (the Fox phone) and contact Calvin. After all, it was Calvin who coded all this great functionality. If he can not help me, I am truly Wallaby Ted's brother Roo!

 
Powered By Qumana

Sunday, November 20, 2005

COM Servers shutting down

I've been working with COM servers in Fox for some time now and I am still finding new tips and functions that help me solve issues. I thought I might share one issue and its solution with the hope it may save you a few precious hours, perhaps days.
 
The issue arose when a business object I was exposing as web service would suddenly shutdown and stop responding to requests. The object was written in 8.0 and compiled as a multi-threaded dll. I then installed the component into a COM+ application and exposed it as a web service.
 
The business object would query a Fox database, transform the resultant dataset into xml, and pass the xml document string back down the wire to the caller. This solution tested well in development and I deployed to the client's production environment where it ran successfully. Another satisfied client...or so I thought.
 
A few days went by when the client contacted me and reported the web service had just stopped responding. I was able to confirm this from my location and decided to log onto their server and check the logs. After establishing the remote desktop connection, I found no logs reporting component failure or trouble with the IIS service. So I test again locally, and hey it works. Further tests from my remote location are also successful.
 
So what we had was a web service that would suddenly stop responding and the act of logging onto the IIS server seem to fix the problem. It took a number of days, and many shutdowns, to identify the cause of the issue. We found that Fox was responding to the log off event of the console account and shutting down.
 
After much research, I had one of those great Fox moments. I came across an obscure SYS function that completely solved my problem.
SYS(2340 [,0 | 1 ])
This function intercepts Windows logoff messages (WM_QUERYENDSESSION and WM_ENDSESSION) to Visual FoxPro and specifies whether to keep Visual FoxPro COM Server instances running or to shut them down. A Visual FoxPro application will terminate when the current user logs off Windows. If the application is a Windows Service, then that service will terminate. Enabling support for NT service will make it possible for Visual FoxPro applications to continue even if the current user logs off Windows.
I added one line of code to the Init() of my business object

* Enable NT support
SYS(2340, 1)

re-compiled and deployed to the client. Now my Fox component, exposed as a web service via COM+, ignores the Windows log off notifications preventing the service from shutting down.
 
Powered By Qumana

Thursday, November 03, 2005

Class name during Init()

While working on an enhancement to my label base class control Istumbled across a real gotcha with the way VFP instanicates objects

While working on an enhancement to my label base class control I stumbled across a real gotcha with the way VFP instantiates objects.

 

Craig had showed me some great code he had implemented on his label base class to allow user defined captions. On the Init() of the label control, a check would be made for a stored user defined caption in a table. If one is found, the caption property is set to the user defined value otherwise the default caption isused. The user was able to change the caption by right clicking on the control.I used the label object’s hierarchy, SYS(1272), as the key on my lookup table.

 

What I found was, during object instantiation the value returned by SYS(1272) differed to the returned value after the object was instantiated. This can be demonstrated with the following code example:

 

Local oLabel As Label

 

Clear

? "*** On Init ***"

_Screen.NewObject("NewInstance", "SubContainer")

 

? "*** After Init ***"

For Each oLabel In _Screen.NewInstance.Controls

      oLabel.Init()

Next

_Screen.NewInstance.Init()

 

* All done

_Screen.RemoveObject("NewInstance")

 

Return

 

Define Class SubContainer As BaseContainer

 

      * Try commenting out this line

      Name = "SubContainer"

 

      Add Object lblSubLabel As BaseLabel

 

EndDefine

 

Define Class BaseContainer As Container

 

      * Try commenting out this line

      Name = "BaseContainer"

 

      Function Init()

            ? "BaseContainer.Init: " + Sys(1272, This)

      EndFunc

 

      Add Object lblBaseLabel As BaseLabel

     

EndDefine

 

Define Class BaseLabel As Label

 

      Name = "BaseLabel"

 

      Function Init()

            ? "BaseLabel.Init: " + Sys(1272, This)

      EndFunc

     

EndDefine

 

After executing this code, you will notice the object name changes when the object's Init() method executes. Try commenting out the Name properties on each of the class definitions. Notice that the object name remains consistent during instantiation.

 

So the answer is simple, do not explicitly set object names when defining objects.Well, that’s fine in this example, but this is what VFP actually does within the VCX. Try browsing the VCX table and see for yourself what’s going on under the hood.