Pablo van Diest is a software developer with more than 15 years of experience, whose first contact with FoxPro was Foxbase+ for Unix. Nowadays Pablo is a highly skilled expert in n-tier development with Visual FoxPro and .NET and leads his own consulting company: APV Systems
All of us had to adjust to new technologies. A pretty interesting one, which involves several small challenges, is the three-tier applications development. Though the concept is easy to explain, when you try to work out the required symbiosis between the business rules and the GUI, you find that it is not a simple issue and when you thing about the deployment and distribution, things get even more complex.
It is quite logic to think that if you develop a three-tier application, you intend to publish the component with the business rules in a COM+ server to enable the access to its functionality from every terminal in the intranet, which requires the creation of a proxy in order to make the distribution among the client machines.
This proxy is generated from the very component service of the operative system. Right click on the name of the project that has published the business component, select Export option and then select Application Proxy in the dialog box.
This proxy, accomplish the task to redirect all requests made to the local machine towards the server. Thus, when you issue CreateObject("MiProject.Usuarios") in the local machine (when there is not such component) this request is redirected to the COM+ server and solved there. What the proxy does is basically to keep a key in the operative system registry redirecting requests to the server, but part of the information recorded in the Windows registry we find the ClassID for each one of the services (methods) that the component publishes.
Discovering the problem
This implies that provided you do not change the ClassID of the business component or do not add, delete or modify the names of the published services, the proxy will work right, but if you add, modify or eliminate a property or service (method), you will need to perform the following steps:
To eliminate the business components of the application published in the COM+ server.
To publish the new component (DLL).
To create a new proxy, based on the new component.
Uninstall the proxy’s obsolete version from the terminals.
Install the new proxy’s version on each terminal.
Although it doesn’t seem a very tedious routine, when you have a small intranet with 15-20 terminals, it is a minor problem, but when the infrastructure is a bigger one, where after the modifications pass the quality control you need to perform the distribution of the change not only in the server but in each one of the terminals of the LAN-WAN with 200, 500 or 1000 machines scattered all over the country, the impact turns definitely devastating.
Solving the problem
Apparently, the only option is not to use the CreateObject() function and look for a different way that allows to create a remote object without the need to distribute a proxy all over the net.
For this purpose, we will use the CreateObjectEx() function, which allows to create remote objects indicating in which machine you want to do it, but you need to perform some changes to your application since it is necessary to have an xml configuration document, a public array with the xml info and a routine able to create objects in a centralized way, meaning you need to replace every CreateObject() in the code with this routine in order to have total control over the object instances.
To get independent of the Proxy
The CreateObjectEx() function has three parameters, but only the first and the second will be used, in this way:
The first one indicates the ClassID of the remote component, meaning the internal classification number that identifies the componente in the operative system registry; this ClassID is unique and is conformed by 38 alphanumeric digits.
The second parameter is the location of the COM+ server, it accepts Esther the server name or its IP address. If you have installed VFP in the machine containing the COM+ and you are testing the instantiation of a local component with CreateObjectEx(), this parameter must be empty (blank).
To use this technique, you must consider two basic elements:
At the first place, an XML document containing the name of the business class (ProgID) and its corresponding ClassID, i.e.:
It is also required a centralized schema for object creation, meaning that you must to have a routine that receives the request for the desired service and makes the call to that service; for example:
Function CrearObjeto( tcObjeto as String ) as Variant
*tcObjeto = 'Usuarios'
loObjeto = CreateObject( "MiProyecto." + tcObjeto )
Thus, you have total control over all the objects to be created with the (still) CreateObject() function, it is also mandatory that EVERY object of the component be created through this routine, since you will need to modify it soon in order to work with the CreateObjectEx() function.
Although to implement this methodology in an already working project is kind of embarrassing, you must understand that this effort is completely paid when you compile a new component with several changes and you just need to update the XML file with the ClassID's.
You need to load data from this XML file into a public bi-dimensional array that contains in the first column the ProgID or class name and in the second column the corresponding ClassID. This array must be loaded an the system startup and, provided it couldn’t be loaded or parsed the XML into a cursor due to any reason, the user will must be informed about the problem and the system should exit, for if this array does not get loaded with the XML file data the program will not run. To enable an easy access to the XML file, it may reside in a public folder so that every terminal can share it. Such a folder and file should be read only.
We will call the public bi-dimensional array gaClassIDs and it will be declared in the same routine that reads and parses the XML, for example:
Function LeerXMLClassID(tcArchivo as String) as Boolean
Public Array gaClassIDs
If Not File( tcArchivo )
Error 'Configuration XML file not found'
XmlToCursor( tcArchivo, 'Cur_Temporal' )
Select * From Cur_Temporal Into Array gaClassIDs
Use In Cur_Temporal
You need to write a routine that searches the ProgID name in the public array
gaClassIDs and returns the corresponding ClassID, like this:
Function ObtenerClassID(tcObjeto as String) as String
Local lcClassID as String, niCount as Integer
LcClassID = ''
For niCount = 1 To Alen(gaClassIDs,1)
if Lower(gaClassIDs[niCount,1]) == Lower(tcObjeto)
lcClassID == gaClassIDs[niCount,2]
Now we just require a small modification in our routine CrearObjeto() so that it searches the ClassID related to the service name:
Function CrearObjeto(tcObjeto as String) as Variant
Local loObjeto, lcClassID
lcClassID = ObtenerClassID(tcObjeto)
Error 'No se encuentra el class id del objeto ' + tcObjeto
loObjeto = CreateObjectEX(lcClassID,"192.168.0.1")
Now, with these changes, the system can access the business component from the terminals with no need to install the component proxy, but still the CrearObjeto() routine is hard-coded with the component server’s IP address.
Although a small net may have only one components server hence, to write an IP address in the code may not produce a big trouble, if you thing of an enterprise with many terminals that may have several component servers, things get less permissive.
This issue of the server name or IP address, may be seen from two different points of view.
Configuration by Terminal:
The system has for sure a proper configuration file that belongs to the exe file of the proyect. This .INI file is the right place to locate the server name or IP address relate to that terminal. Thus, each terminal instantiates the required component in the server indicated by his .INI file.
Configuration by Service:
or in case you have several COM+ servers and you want to balance the load of the work according to the features of each server, the logic choice should be to add a third column containing the component server’s name or IP address to the configuration XML file. Thus, every terminal would instantiate different components on different servers, according to the balancing set in the XML document.