Developing and Debugging Visual FoxPro COM serversMaurice de BeijerABL - The Problem Solver Since the release of Visual FoxPro version 5 we have been able to create COM servers. This has created a whole new range of possibilities for Visual FoxPro and has given Visual FoxPro developers a new tool for creating solutions. However COM servers have always had the drawback of being difficult to develop. The problem with developing COM serversThe main problem developing COM servers has always been the fact that the developer had to write the code, compile it into an EXE or DLL and then start the client program to test the COM server. This means that one always executes code that is compiled and under control of the Visual FoxPro runtime meaning that there was no way to step through the code using the debugger or inspect the values of variables used. If there is a bug of some sort in the source code of the COM server the test would either result in a crash or a wrong return value to the client application. Whenever this happens the developer has to change the COM server source code, recompile and execute the client again. ![]() Figure 1: A COM Server failing with the usually not so helpful error message. Checking the values of variables can only be done by adding extra code to the COM server to output the contents, similar to the way we used to write output to the screen or a log file before we had the current debugging facilities. To some extend this problem can be resolved by testing the COM server as a regular Visual FoxPro object and creating it in the Visual FoxPro development environment. However this is less than a perfect solution because Visual FoxPro can’t simulate every type client application equally well. Notably COM servers that are used in Active Server Pages scripts are hard to debug this way as the interaction between the Visual FoxPro code and the native ASP objects is impossible to simulate. Compare COM server development to the power of Visual FoxPro when developing interactive applications. The developer doesn’t even have to build an executable, he can just run the code and start the debugger whenever he wants. If a runtime error occurs, a Cancel Suspend Ignore dialog appears offering the developer the option of suspending the program and inspecting the environment using the debugger. Often you can just change some variables or part of the code and run that part again. ![]() Figure 2: A far more helpful error message with the ability to suspend the program and start the debugger This result is quite a difference in productivity between developing an interactive application and a COM server with the interactive application holding all the ace cards. As far as the COM server developer is concerned the debugger might as well not exist at all as he can’t use it very much anyway. One way to level the playing field for COM and interactive Visual FoxPro development would be to allow the developer of COM components to execute the code in the Visual FoxPro development environment just like it does for interactive development. A quick comparison with Visual BasicLets start Visual Basic 6 for a quick comparison of how to develop a web application in Visual FoxPro and Visual Basic:
![]() Figure 3: A Visual Basic 6 webclass The problem with Visual FoxProWhy, you might wonder, can’t we do the same thing in Visual FoxPro? The main problem in doing so is the inability of creating the COM Server object in the Visual FoxPro designer instead of the Visual FoxPro run time. One way this could be done is by doing a CreateObject(“VisualFoxPro.Application”) instead of the COM server you where originally trying to create and using the Visual FoxPro application object to create the COM server. While this approach works it has two big drawbacks. First, it requires quite a bit of development code in the client application that is completely different to the production code that would be required for the actual COM server. Secondly, this starts a new Visual FoxPro session every time instead of reusing the existing one resulting in frequent compilation errors when an FXP file is still in use in another session. Getting an object handle to an object in the already active Visual FoxPro session just wasn’t possible. Another workaround is to let the sessions communicate through files. This means there is a timer object in the Visual FoxPro development environment checking for request files and stating a request when this is found. This has the drawback that all communication has to be done through the file system and we can’t pass objects, like the native ASP objects, to the Visual FoxPro hosted COM server. The solutionWith the release of Visual FoxPro 7 Microsoft has added support for Active Accessibility to Visual FoxPro. Active Accessibility is originally intended for the support of disabled users. Besides it’s original purpose, it is also very useful for several other purposes like the ability to create graphical user interface test tools. Visual FoxPro 7 comes with a tool, the Active Accessibility Test Harness, which does just that. One of the many API functions of Active Accessibility is AccessibleObjectFromWindow. This API function allows the developer to retrieve an IAccessible object reference to another object if he has the hWnd value for that object. Once we have an object reference to this IAccessible object we can use the other Active Accessibility API functions to get more information. One of these API functions is AccChild() that has the ability to return an object reference to the Visual FoxPro Application object or _VFP. Once this has been done anything you can do in the Visual FoxPro development environment is possible. Listing 1: The method used to create the object in the Visual FoxPro design time environment.
************************************
Procedure GetDebugObject() as Object
************************************
* Return an object reference to a debugable copy of this COM server
Local loServer, loAcc, lnHwnd, lcGuid, loVFP, lcComLink
loServer = Null
#If c_ComDebugging
loAcc = Null
lnHwnd = 0
lcComLink = Addbs(JustPath(_vfp.ServerName)) + 'ComLink.dbf'
If File(lcComLink)
* There is an ComLink file with the required HWnd value of the design time VFP
Use (lcComLink) In 0 Again Shared
* Get the _VFP.hWnd value
lnHwnd = ComLink.iHwnd
* Close the table again
Use In ComLink
EndIf
If lnHwnd <> 0
* We where able to load the required _VFP.hWnd value
* Declare the required Active Accessibility function
Declare Integer AccessibleObjectFromWindow In OleAcc Integer, Integer, String, Object @
* Convert the IAccecssible to a useable format
lcGuid = Str2Guid(c_IAccGuid)
* Try to get a handle on the IAccecssible interface of _VFP
AccessibleObjectFromWindow(lnHWnd, c_ObjIdClient, lcGuid , @loAcc)
If Type('loAcc.AccChild(Int(2^31-1))') = 'O'
* Retreive the _VFP reference from the IAccecssible object
loVFP = loAcc.AccChild(Int(2^31-1))
If Type('loVFP.Name') = 'C'
* We got the _VFP reference, use it to check if the status form exists
If loVFP.Eval('Type([poStatus.chkDebug.Value]) = [L]')
* The test succeeded, retreive the object
loServer = loVFP.Eval('CreateObject([' + This.Class + '])')
* Set any required properties of the debug server
This.ConfigureDebugServer(loServer)
EndIf
EndIf
EndIf
EndIf
#EndIf
* Return this server as the object to use if we are unable to reach the VFP design time environment
loServer = Nvl(loServer, This)
Return loServer
Using the functions described above I have created a technique to retrieve a copy of the original COM server that is actually created and hosted in the Visual FoxPro design time environment. The solution has two parts, one part is a method included in the original COM server and the second part is a monitoring utility for the Visual FoxPro design time environment. For the Visual FoxPro design time environment I developed a status monitoring form that allows the developer to see when a request is started and how long it takes. Before displaying the status form it creates a table named ComLink with a single record and field in the current directory that contains the _VFP.hWnd value of the current Visual FoxPro session. The application then makes sure that all required class libraries and procedure files are loaded so the required objects can be created. The status form also includes an option to start the debugger at the beginning of the next request. ![]() Figure 4: The ASP Status form Because developing ASP components is harder than developing regular COM servers I decided to create a separate monitoring form that not only allows the developer to start the debugger at the start of a request but also has the option to add the current contents of the ASP objects to the end of the response page. On the server side the main method involved is GetDebugObject(). This method first checks if there is a ComLink table in the same location as the COM server executable file. If one is found the hWnd value is retrieved and used with the Active Accessibility functions described above to get an object reference to the Visual FoxPro Application Object. If this is successful the _VFP.Eval() method is then used to create a new object of the same class as the COM server. This new object, or if the attempt failed for some reason the original COM server, is then returned to the client. To use this from a client application the only thing you have to do is add one line right after the CreateObject() statement. For example the following Visual Basic for Applications script in an Excel sheet: Listing 2: The Visual Basic for Applications code use in the Excel workbook.
Dim oDemo
Set oDemo = CreateObject("ComDebug.ComDebug")
Set oDemo = oDemo.GetDebugObject()
oDemo.LoadData ActiveWorkbook
Two additional methods included in the base class are the Init() and Destroy(). Both are used to register the start and end of the request with the monitoring status form in the design time. Debugging Active Server Pages componentsDebugging an ASP COM server is somewhat more complex then debugging a COM server used in an Excel VBA macro. The first major difference is that you can’t just run the ASP application from Visual FoxPro itself, you first need to make sure that the Internet Information Server virtual directory exists and is configured properly. This is nothing new but can quite easily be automated using the Windows ADSI objects. The status form uses these ADSI objects to make sure there is a virtual directory for the specified directory and determines the URL to use to access this virtual directory. If no virtual directory is found the developer is asked if he wants to create a new Internet Information Server virtual directory with the proper settings. I developed this code with the directory structure in mind that I always use for web sites. You might need to change it somewhat for your purposes. I use a directory structure with a main directory, for example AspDebug, for the project. This directory always has one subdirectory named WWWRoot. This directory is shared as the Internet Information Server virtual web directory under the name of the main project directory. For example: The directory C:\AspDebug\WWWRoot is reached through the browser using the URL http://LocalHost/AspDebug. ![]() Figure 5: The directory structure I use when developing. The next problem is security related. In ASP the COM server is created under the IUSR_ ![]() Figure 6: The Active Server pages Authentication settings. The next problem is something I have been unable to pin down the cause of. This is not a big problem as it is only a design time constraint. The COM server used under ASP must be build as an out of process server, an EXE, to permit the Active Accessibility API functions to work. Whenever I use an in process server, either a regular or a multi threaded DLL, the AccessibleObjectFromWindow function would fail with an E_FAIL error. This is a very generic COM error status and no help in tracking down the problem. Once the debugging process is completed the COM server can be build into either a single or a multi threaded DLL as required. To take the debugging for a test drive start Visual FoxPro7 in the AspDebug directory. First you need to either do a BUILD EXE AspDebug FROM AspDebug to create the initial COM server or to /regserver the existing AspDebug.exe using the register.bat file. Next, type DO MAIN in the Visual FoxPro command window and press enter. The first time you do this you will see a message box informing you that the virtual directory for this project isn’t found and ask if you want to create it. Click on the OK button to create the new Internet Information Server virtual directory. Now the status form appears and the default web browser is started. The browser automatically navigates to the default page of the web site. When you change back to Visual FoxPro you will see the first ASP request in the status form. Listing 3: The code in the Active Server Pages default.asp.
<%
Dim oDemo
Set oDemo = Server.CreateObject("AspDebug.AspDebug")
Set oDemo = oDemo.GetDebugObject()
oDemo.Test()
%>
Do debug the server check the Debug checkbox, switch back to the browser and hit F5 to refresh the page. The Visual FoxPro debugger opens up and leave you at the end of the OnStartRequest() method. Use F8 to step through the OnStartPage() and the OnStartAspRequest() methods until you are in the Test() method. This is the method called from the Default.asp page to generate the HTML output. Note that you can do anything you would normally be able to do when debugging a regular Visual FoxPro application. As you can see the ASP Request is available just like it would be in a regular COM server running under ASP. Changing code is just as easy as developing a regular application. Just click the Close button, open up AspDebug.prg in the editor and change some of the code in the Test() method. For example add the following code to the bottom of the Test() method:
This.oResponse.Write('<p>Hi there: ')
This.oResponse.Write(This.oRequest.ServerVariables('auth_user'))
Save the program and run the main program again without building a new COM server. You should now see the new message at the bottom of the page.
As an additional help, because the Visual FoxPro debugger doesn’t show all the ASP variables that are available through the ASP objects, I have added the option to dump part of or all of the ASP information at the end of the response output. To show this just set the ASP dump to basic or full and refresh the page. ConclusionThe solution presented above generates a huge increase in productivity when doing COM development in general and ASP development in particular. The development has become much more of an interactive process again and the debugging possibility is a huge benefit. Download the source code for this article |
| Maurice de Beijer Copyright © 2002 ABL - The Problem Solver. All rights reserved. Revised: 29 augustus, 2006 |
Home |