This article describes this new approach. The first part of the article is the same as in the original article so if you are already familiar with that go ahead and skip to the section labeled "The solution".
The problem with developing COM servers
The 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 the 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 or 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 Basic
Let’s start Visual Basic 6 for a quick comparison of how to develop a web application in Visual FoxPro and Visual Basic:
A few big differences with Visual FoxPro are immediately obvious:
Figure 3: A Visual Basic 6 webclass
The problem with Visual FoxPro
Why, 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 were 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:
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.
Visual FoxPro has a GetObject() function that, amongst other things, allows the developer to get an object reference to an existing COM server. For example, try the following in the Visual FoxPro command window:
o = GetObject(, 'Excel.Application')
So how does this function find existing objects? The trick is that Windows has an item called the Running Object Table and that a program can publish objects using the RegisterActiveObject API function and remove them again using the RevokeActiveObject API function.
The solution is divided into two parts:
After the Visual FoxPro object has been created properties like the ASP Request and Response objects are set. Depending on the success or failure of this either the original COM server or the newly created object in the Visual FoxPro development is returned for the client to use.
Figure 4: The ASP Status form
* Listing 1: The GetDebugObject method in the COM server
Procedure GetDebugObject() as Object
* Return an object reference to a debugable copy of this COM server
Local loServer, loStatus, lcName
loServer = Null
lcName = JustFname(Left(_vfp.ServerName, Rat('\', _vfp.ServerName) - 1))
If Type([GetObject(, lcName + '.Debugger')]) = 'O'
* Get a handle on the _VFP object
loStatus = GetObject(, lcName + '.Debugger')
If Type('loStatus.Name') = 'C'
* We got the status form reference, retreive the object
loServer = loStatus.Eval('CreateObject([' + This.Class + '])')
* Set any required properties of the debug server
* Return this server as the object to use if we are unable to reach the VFP design time environment
loServer = Nvl(loServer, This)
If you are familiar with the original version of this function you will have noticed that the code is much shorter and simpler, now most of the complexity has moved from the COM server to the development environment.
Please note that I don’t use Try/Catch in the code partly to keep it backward compatible with Visual FoxPro 7. At fist glance it might be faster to use Try/Catch in Visual FoxPro 8, after all I am doing two separate GetObject() calls, the first just to check if the object is registered and the second to actually get an object reference. As is to be expected a single GetObject() call is faster that a double so this is true then the object actually exists. However things are very different when the object isn’t available. In this case the Try/Catch is about 5 times slower than the current code. As this method never needs to be called outside of a development environment this isn’t much of an issue but if it accidentally is called there could be a significant performance penalty.
The second part of the solution is in the development environment where we need to call the RegisterActiveObject using a pointer to the IUnkown interface of the object and associated class id. Running the main.prg program starts this.
The main program first runs a DebugSetup.prg, use this program to set additional path, procedure and classlib statements not needed in the runtime code. Next a new instance of the status form and the RotManager are created. The RotManager class has the required functionality to publish a Visual FoxPro object in the Windows Running Object Table, for this purpose the Register method is called with the object to publish and the name of the used in the GetObject() function call.
Before I explain how the Register method works first a short explanation about the standard directory structure I use for projects.
In the image you see the directory structure for two projects.
Figure 5: The directory structure I use when developing
Registering the status form in the OLE Running Object Table
As previously mentioned I need two things to call the RotManager’s Register method, the object itself and the name under which to register it. The object to register is the status form which is already created so that leaves us with the name to use. I could use a fixed name here but as each object registered in the Running Object Table requires a different name that would allow me only to debug a single COM server at the time and might result in trying to debug a COM server in a project that has no knowledge of the original COM sever. For that reason I decided to use the original project name with Debugger as the class name, so the COM server ComDebug.ComDebug uses the name ComDebug.Debugger.
Remember that there is no actual class with this name, it is just a name used to register an object with Windows so another process can retrieve it using the same name therefore I could have used any name I wanted.
The first thing the Register method do is to remove any previous registrations. This is just in case the status form was already registered but due to an error of some sort the normal deregistration wasn’t done.
Remember that only a single object can be registered under a given name at any time and the fact that this object no longer exists isn’t taken into account. Removing an old registration is quite straightforward.
The call to RegisterActiveObject returns a registration pointer and removing the registration simply requires a call to RevokeActiveObject with this pointer as the parameter. The RotManager class simply remembers all class names and their related registration pointer.
After making sure any previous registrations are removed the function can go ahead and register the new object. To do this we need a pointer to the object and the associated class id. The class id is exactly the same for every COM server. The main problem here is that when we make a COM server, Visual FoxPro takes care of creating the class id for
us, but now we don’t actually have a COM server with that name, so we have to create it ourselves. The registry entry we need is stored under the HKEY_CLASSES_ROOT in the registry, and what we need is the GUID stored under clsid key. The first time a COM server is debugged, this key doesn’t exist yet, so we need to create it first. This is done using the UuidCreate and the UuidToString Windows API functions. This key needs to be created as the GetObject function uses the same information to turn the class name specified into a GUID, and a class id to call the GetActiveObject Windows API function.
Once we have determined the GUID, we can convert this into the required class id. This leaves us with getting a pointer to the Visual FoxPro object we want to publish. The SYS(3095) function returns a pointer to the IDispatch interface, which is derived from the IUnkown interface of a COM server, but doesn’t work directly on native Visual FoxPro objects. We first need to turn our native Visual FoxPro object into a COM object. Although there is no official way to do this, it turns out that there is a rather simple trick to do this. If you do a _VFP.Eval() with the string containing a variable name of a Visual FoxPro object, it will return a COM wrapper to this object instead of the original. So using _VFP.Eval() and SYS(3095) we now have a pointer to IUnkown interface of the status form.
With all prerequisites taken care of, we are only left with the simple task of calling the RegisterActiveObject API function and saving the resulting pointer so we can revoke the object at a later time. With all the behind the scenes code explained it is time to take a look at two samples of how to use this. First, a simple COM server using an Excel spreadsheet as the client, and secondly, a sample using an ASP web site calling a Visual FoxPro COM server from the ASP code.
Debuging a COM Server called from Excel
For this sample I have created a very simple MS Excel workbook. All the code for this sample is contained in the ComDebug subdirectory with the Excel workbook in the XLS subdirectory. The Excel workbook is very simple. It contains a single button with the text “Click me”. When this is done the following lines of code are executed.
Set oDemo = CreateObject("ComDebug.ComDebug")
Set oDemo = oDemo.GetDebugObject()
The LodaData method in the Visual FoxPro ComDebug class opens the Tastrade database, selects the first two customers, and lists their orders with the related products. Not exactly rocket science there.
Now open the project in Visual FoxPro, build it into a DLL if this is the first time, and type “DO Main” followed by an enter. This should open a status form and launch Excel with the workbook. Go ahead and click the button. Now you should see new worksheet open and being filled with the order data for two customers. If nothing happens you might have your security level set to high, in that case you either need to reset the security to a lower level or sign the code.
If you switch back to Visual FoxPro you will see a single line in the status form for the code you have just executed. Now check the Debug checkbox, switch back to Excel and click the button a second time. This time the Visual FoxPro debugger will open up and you can step through the code and inspect all variables including the toWorkBook parameter passed from Excel.
Now close Excel and close the status form. Open the ComDebug.prg and change some of the code, for example change the first SQL select from a top 2 to a top 10. Without rebuilding the COM server just start the main program again and once in Excel click on the button.
This time the code stops running with a "OLE error code 0x800a03ec: Unknown COM status code" error. Normally this would be hard to debug but now using the debugger we can quickly see that the error is caused by the code trying to set an Excel cell to an empty datetime value, something Excel doesn’t allow.
So what did I do to add this functionality to the COM server? I made sure it was subclassed from the ComBase class that contains the GetDebugObject method. In the client script code I added one line of code:
Set oDemo = oDemo.GetDebugObject()
Debugging a COM server called from an ASP page
Debugging an ASP COM server is somewhat more complex than 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.
Figure 6: The Active Server pages Authentication settings
For example: The directory C:\AspDevelopmentAndDebugging \AspDebug\wwwroot is reached through the browser using the URL http://LocalHost/AspDebug.
The next problem is security related. In ASP the COM server is created under the IUSR_ account instead of the user account you are using to develop with Visual FoxPro. The IUSR account is by default an account with a lot of restrictions.
To get around these restrictions it is necessary to change the Internet Information Server settings for this web site to disallow anonymous access and only permit Integrated Windows authentication. This results in the ASP page and COM components to run under the actual user account that is logged on instead of the ISUR_ account.
The monitoring form checks the authentication setting when it is started and will offer to correct it if this isn’t set properly. This setting is only required for debugging and not required for the production server.
The next issue is that the COM server used under ASP must be build as an out of process server, an EXE, to permit the debug functions to work. Whenever I use an in process server, either a regular or a multi threaded DLL, the GetObject() function would fail. 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 FoxPro in the AspDebug directory. First time you need to do a BUILD EXE AspDebug FROM AspDebug to create the initial COM server. 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.
Set oDemo = Server.CreateObject("AspDebug.AspDebug")
Set oDemo = oDemo.GetDebugObject()
To 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. Note the code in the debugger changing a variable in the ASP Session object.
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: ')
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.
The 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.