FoxIs, a Visual FoxPro Internet Server
FoxIs, located in the Visual FoxPro ...\Samples\Servers\Foxisapi\FoxIs directory, illustrates creating an out-of-process .exe or in-process .dll with ISAPI functionality that can be accessed from within Visual FoxPro as a stand-alone program, from Automation clients, and from a Web browser. Changes you make to its classes can enhance the Automation server, no matter how it is run.
To open the FoxIs sample project
Type the following in the Command window:
MODIFY PROJECT (HOME(2) + 'servers\foxisapi\foxis\foxis')
Running the FoxIs Sample
You can run the FoxIs sample four different ways. When you are trying out the code, it's a good idea to go run the sample in this order:
Running from Within Visual FoxPro
To run the FoxIs sample from within Visual FoxPro, run the following code in the Command window.
SET DEFAULT TO (HOME(2) + 'servers\foxisapi\foxis\')
SET CLASSLIB TO employee
ox = CREATEOBJECT('employee')
ox.show
Running as an Independent Executable
You can build the sample into an executable file with the following line of code:
BUILD EXE foxis FROM foxis
The compiled file, FOXIS.EXE, is a Windows program that can be added to the Windows Start menu, started from the Windows Explorer, and so on.
Running as an Automation server
Once the FoxIs sample has been compiled into a .exe or a .dll, it is registered as an Automation server in the Windows registry. You can create an object based on the employee class from any OLE controller, for example Excel, Visual Basic, and Visual FoxPro:
ox = CREATEOBJECT('foxis.employee')
ox.SHOW
Running from a Web Browser
You can even run the FoxIs sample from a Web browser, which could be on another machine such as a 286 running MS-DOS, a Unix machine, a Macintosh, or a Personal Digital Assistant.
System Requirements for Internet Use
To run the FoxIs sample from a Web browser, you must be running:
- Windows 98 or Windows NT 4.0 or later.
- An ISAPI-compatible Web server, such as the Microsoft Personal Web Server for Windows 98 or Microsoft Internet Information Services (IIS). IIS comes with Windows NT 4.0 and later and can be downloaded from www.microsoft.com; Personal Web Server also can be downloaded from www.microsoft.com.
If you're using Windows NT 4.0 or later, you need to run the DCOMCNFG utility to configure DCOM to give rights to the IIS service to instantiate OLE objects.
To configure Windows NT 4.0 or later DCOM
At the Command prompt, type DCOMCNFG and press Enter.
In the Applications tab, select the name of the Automation server. When you build the server, the application is "employee" by default.
In the Default Security tab of the Distributed COM Configuration Properties dialog box, choose Edit Default for each area: Default Access Permissions, Default Launch Permissions, and Default Configuration Permissions.
In the Registry Value Permissions dialog box for each area, choose Add.
In the Add Names box of the Add Users and Groups dialog box, type your WWW server's name and your login user name.
You can see the server's name in the Microsoft Internet Service Manager Properties window. If your machine name is MINE, the following line in the Add Names box sets up a default user:
\MINE\IUSR_MINE
If your machine name is MINE and you log in as HOMER, the following line in the Add Names box sets up permissions for you when you login:
\MINE\HOMER
Setting Up the FoxIs Sample for Internet Access
To set up the FoxIs sample, you need to create a .exe or .dll from the FoxIs project, and then copy the file to your Inetsvr\Scripts directory.
To set up the FoxIs Sample
- Open the FOXIS project.
- Choose the Build button, and then choose Build COM DLL.
- Copy Foxisapi.dll to your Inetsrv\Scripts folder.
Preliminary Testing
You can test the FoxIs sample application at various levels to see if you are configured correctly.
To see if the code works
- From the Program menu choose Do.
- Select Main.prg in the Visual FoxPro Samples\Servers\Foxisapi folder.
- Choose Do.
To see if the .EXE works
From the Visual FoxPro Command window, issue the following command:
BUILD EXE Foxis.exe FROM foxis
Double-click Foxis.exe in the Windows Explorer.
To see if the FoxIs Automation server works
Run the following commands:
OX= CREATEOBJECT('FOXIS.EMPLOYEE') OX.Show && See if it works as a ISAPI Automation server ?OX.Startup( ) && See if it returns html
It is much easier to debug Automation servers from an Automation controller (such as Visual FoxPro) before instantiating it in FoxIs.
Set up the HTML page
To get things started, you need an HTML page that contains a reference to a URL. For example, take the following code and put it in the Wwwroot\default.htm file.
<a HREF="/scripts/foxisapi.dll/FoxIS.employee.startup"> <i>VFP ISAPI AUTOMATION SERVER DEMO PAGE</i> </a>
Then use a browser (which can be on the same machine) and connect to YourMachineName. For example, if your machine name was "myMachine," then type "myMachine" as the URL to go to. This would bring up the Default.htm page on the server named "myMachine"
A CreateObject("foxis.employee") is initiated and the Startup method is invoked on the object, which returns a generated HTML page. If the ISAPI Automation server hasn't been built yet, then the Foxisapi.dll returns an HTML error page.
Then use your Web browser to access the href above. If you get an error HTML page that says "Foxisapi error", then you know the DLL is being loaded and is working.
<FORM ACTION = "/scripts/foxisapi.dll/foxis.employee.cmd">
<INPUT NAME="Cmd" VALUE = "Reset">
<INPUT TYPE="submit" VALUE="Dos Command">
</FORM>
You can actually put in any valid MS-DOS command and it will be executed on the server machine. If the command is "Reset" (default), then it will make the ISAPI Automation server release the first instance as well as it's own, thus releasing the ISAPI Automation server completely.
In addition, you can evaluate any Visual FoxPro expression. However, if the Visual FoxPro expression displays a modal interface, as the MESSAGEBOX( ) function does, the Automation server will hang waiting for a response that cannot be provided. However, for an in-process .dll, the modal interface is automatically managed and the Automation server won't hang.
<FORM ACTION = "/scripts/foxisapi.dll/foxis.employee.cmd?FOXCMD">
<INPUT NAME="Cmd">
<INPUT TYPE="submit" VALUE="Fox Expression">
like "today is "+ cdow(date()) or 45 * 3 or SYS(2004)
</FORM>
Debugging the Server
A Windows NT service has no Desktop, therefore no user interface will appear on the server machine. This means you should debug your server applications before deploying them.
To trace through Foxisapi.dll with Visual C++ 5.0
Open Foxisapi.mak.
Remove the comment mark from the following line in HttpExtensionProc:
// _asm int 3
Rebuild the project.
Start MSDEV with the process id (PID) of Inetinfo.exe. You can get the process id in the Windows Task Manager.
This process applies to Windows NT 4.0 or later.
Tip When debugging, you don't have to shut down the Web server to change the Automation server. You can just terminate the out-of-process component by sending a reset value to the cmd method, as described above, or by using the Win32 SDK tools TLIST, KILL, PVIEW, or the Task Manager in Windows NT 4.0 or later.
The ISForm Class
The "engine" of the sample is the ISForm class in Isapi.vcx.
Entry Points into the ISForm Class
The following methods can be called from the Web browser, by way of Foxisapi.dll, to return HTML pages.
Method | Returns |
---|---|
Cmd | Evaluation of a Visual FoxPro expression or the results of a MS-DOS command. |
DoSave | Saves the user changes to the data and returns the employee HTML page. |
Skipit | The employee information for the specified employee in the table. Skipit takes cookie information passed in as a parameter from the Web browser, checks the cookies table to find the previous record number, and moves the record pointer forward or backward relative to the record number stored in the cookies table. The new record number is written back to the cookies table and the GenHTML method is called. |
Startup | The employee information for the first employee in the table. Startup creates a new cookie id for the user and sends it back as a hidden input area in the HTML. |
Keeping the Server Active
Normally, for each request from a Web client, the Automation server is instantiated, generates an HTML page, and is released with the Release( ) call in CallObject( ) in Foxisapi.cpp. This means the entire Visual FoxPro runtime will start up and shut down for each request.
If the ISAPI Automation server is registered as Multi-Use, and the Release( ) is not called, then the first request will start up the server but subsequent requests will use the same instance of the server, making performance much better. Code in Foxisapi.dll and in the Load, Cmd, DoSave, Startup, and Skipit methods of the ISForm manage keeping the same instance of the server active.
Variables in the DLL
Two variables are declared: pdispObj and pdispDoRelease. When the server is initially created, pdispObj is the dispatch handle to the OLE object. The pdispDoRelease variable is set to the same value as pdispObj and passed by reference as a parameter to the method of the ISForm server that was requested by the Web browser. Code in the Visual FoxPro ISForm Automation server can change the value of pdispDoRelease.
Tip For more information about this sample, see the comments in the code in ISForm and Foxisapi.cpp.
The Form Load Event
When the ISForm is created, code in the Load event creates two public variables, gpInstance and gpDisp. The first time the ISForm server is loaded, the gpInstance variable is set to 1. Subsequent instances increment this variable. When an instance is released, gpInstance is decremented.
The Entry Point Methods
When a method (Cmd, DoSave, Skipit, or Startup) of the ISForm is invoked through Foxisapi.dll, the .dll passes a dispatch pointer by reference as a parameter to the method.
The first time the server is run, the instance count is set to 1 and the dispatch pointer is stored to the global variable. Then value 0 is stored to the dispatch pointer.
IF m.gnInstance = 1
IF TYPE('pDisp') $ 'NI'
gpDisp = m.pDisp
pDisp = 0
ENDIF
ENDIF
At this point, two variables point to the same dispatch pointer value: gpDisp in the ISForm Automation server and pdispObj in Foxisapi.dll. The value of pdispDoRelease in the .dll is 0, as changed in the ISForm Automation server.
Subsequent times the server is called the instance count in incremented, a new pdispObj is generated in the .dll, stored to pdispDoRelease, and passed by reference to the requested method. Because the gnInstance value is not 1, the gpDisp variable is not changed. From this point on, gpDisp in the Automation server holds a different value than pdispObj and pdispDoRelease in the .dll.
Code in the DLL
The following C++ code in Foxisapi.cpp manages releasing the server. After the first instance is created, subsequent instances are released normally with the Release( ) call in CallObject( ) in Foxisapi.cpp because pdispObj and pdispDoRelease are set to the same non-zero value.
if (pdispDoRelease != 0) {
pdispObj->Release(); //nonzero, so release the current object
if (pdispObj != pdispDoRelease) {
__try {
(pdispDoRelease)->Release();
} __except (EXCEPTION_EXECUTE_HANDLER) {
}
}
}
The Destroy event of the ISForm decrements the instance count. When there is only one instance, pdispDoRelease has been set to 0 and the release code is not called. if the pdispObject and pdispDoRelease are the same, subsequent instances are released, but the original instance is not released.
Releasing the Server
To force a release, pdispDoRelease cannot be 0 and must be a different value than pdispObject. The following HTML text sends a value to the server that affects a release:
<FORM ACTION = "/scripts/foxisapi.dll/foxis.employee.cmd">
<INPUT NAME="Cmd" VALUE = "Reset">
<INPUT TYPE="submit" VALUE="Dos Command">
</FORM>
The following code in the cmd method sets the pdispDoRelease value to the dispatch value of the first instance.
CASE 'RESET'$upper(m.p1)
m.pDisp= m.gpDisp
Code in the .dll will now release the current instance and the original instance that has been kept in existence to prevent the Visual FoxPro runtime from having to be reloaded each time the server was called.
Sending HTML Back to the Client
The GenHTML method of the ISForm class is called from each of the entry point methods. The HTML returned from the GenHTML method is returned to the Web browser through Internet Information Services.
If the mode parameter passed to the GenHTML method is not "FORM," the GenHTML method simply looks up the value in a table and sends back preformatted HTML.
IF m.mode != 'FORM'
=SEEK(m.mode,'html')
rv = html.html
RETURN m.rv
ENDIF
If the mode parameter is "FORM," code in GenHTML identifies each of the labels and textboxes on the form, sorts them in top to bottom and left-to-right order, evaluates the Captions and ControlSources of the controls, and uses Visual FoxPro's text merge capabilities to construct the appropriate HTML text to approximate the display of the form.
If you add additional labels and text boxes to the form, they are automatically displayed in the generated HTML.
Creating and Using Cookies
As a Web server, this application can be hit dozens of times by various clients, and we need to keep track of the user state. In this case, we only track the current record number for that user. We could present the user with a login screen, and use the username as a key for the cookie, but instead we generate a cookie value in the MakeCookie method and pass it as a hidden value in the HTML sent back to the user. Each time the user chooses to go to a different record, we can read the cookie value from the HTTP string sent to the .dll, locate the cookie in the Cookies table, find the current record number and move the record number relative to this number.
The following property and methods are used in the cookie manipulation process:
- Cookie property
- GetCookie method
- MakeCookie method
- WriteCookieInfo method
Error Handling
If an error occurs, code in the Error event of the ISForm class calls the GenHTML method with a parameter of "ERROR." GenHTML reads the pre-formatted HTML text for errors and returns it to the Error event code. The Error event code substitutes error information for placeholders in the HTML:
LOCAL rv
rv = THIS.GenHTML('ERROR')
rv = strtran(m.rv,'%METHOD%',m.cMethod)
rv = strtran(m.rv,'%ERRORNO%',STR(m.nError,4))
rv = strtran(m.rv,'%ERRORMSG%',Message(1))
rv = strtran(m.rv,'%LINENO%',STR(m.nLine,4))
THIS.ErrorHTML = m.rv
When the ErrorHTML property isn't empty, GenHTML sends the value of ErrorHTML back to the client.
Tables Used in the FoxIs Sample
In addition to the employee table used for data entry and display, the FoxIs sample uses the following tables.
Table | Description |
---|---|
HTML | Holds HTML text to be sent back to the Web browser as a header for FoxCMD and DosCMD evaluations or in case of an error. |
Cookies | Keeps track of record numbers for various Web browsers. The unique cookie field value is passed as a hidden value in HTML text sent to a particular user. |
See Also
Solutions Samples | FoxISAPI Automation Server Samples | FoxWeb, a Simplified Visual FoxPro Internet Automation Server