advanced visual foxpro automation servers -...

40
Advanced Visual FoxPro Automation Servers Presented by: Randy Brown Randy Brown is a Program Manager for the Visual FoxPro team at Microsoft. Phone: (206)882-8080 Fax: (206)936-7329 Email: [email protected] Introduction In this session you will learn about ActiveX Automation servers (also known as ActiveX Server Components). You will see not only how to use ActiveX Automation servers from within Visual FoxPro, but also how to use Visual FoxPro as an Automation server and how to build your own customer Automation servers with Visual FoxPro. As you probably know, ActiveX used to be called OLE and ActiveX Automation used to be called OLE Automation. In this paper, we use the term ActiveX except where quoting from documentation that was written before the name change. Also, we use OLE where it is part of the language, for instance “OLE container control” or “OLE Public”. Hopefully, this will not confuse you! Automation Concepts What is ActiveX Automation? ActiveX Objects? Let's start out with the Visual FoxPro Help which defines it as: The ability to control another application's OLE objects programmatically. Technically speaking, this is a very concise definition but is rather ambiguous by its nature and assumes the reader already knows about ActiveX Automation and ActiveX Objects. ActiveX Automation really describes the concept of interapplication communications (IAC) in which one application communicates with another application. I think the usage of the word "Object" tends to add some confusion here, especially for folks unfamiliar with OOP (object-oriented programming). When using the term ActiveX Object keep in mind that it can refer either to the application itself or a component of the application (i.e., a cell in a spreadsheet). Here is what the Excel help file says about it: OLE Automation is an industry standard that applications use to expose their OLE objects to development tools, macro languages, and other applications that support OLE Automation. For example, a spreadsheet application may expose a worksheet, chart, cell, or range of cells as different types of objects. A word processor might expose objects such as application, document, paragraph, sentence, bookmark, or selection. When an application supports OLE Automation, the objects it exposes can be accessed by Visual Basic. Use Visual Basic to manipulate these objects by invoking methods on the object or by getting and setting the object's properties. For example, if you created an OLE Automation object named MyObj, you might write code such as this to manipulate the object:

Upload: lydieu

Post on 11-Oct-2018

218 views

Category:

Documents


1 download

TRANSCRIPT

Advanced Visual FoxPro Automation Servers

Presented by: Randy Brown

Randy Brown is a Program Manager for the Visual FoxPro team at Microsoft. Phone: (206)882-8080 Fax: (206)936-7329 Email: [email protected]

Introduction In this session you will learn about ActiveX Automation servers (also known as ActiveX Server Components). You will see not only how to use ActiveX Automation servers from within Visual FoxPro, but also how to use Visual FoxPro as an Automation server and how to build your own customer Automation servers with Visual FoxPro. As you probably know, ActiveX used to be called OLE and ActiveX Automation used to be called OLE Automation. In this paper, we use the term ActiveX except where quoting from documentation that was written before the name change. Also, we use OLE where it is part of the language, for instance “OLE container control” or “OLE Public”. Hopefully, this will not confuse you!

Automation Concepts

What is ActiveX Automation? ActiveX Objects? Let's start out with the Visual FoxPro Help which defines it as: The ability to control another application's OLE objects programmatically. Technically speaking, this is a very concise definition but is rather ambiguous by its nature and assumes the reader already knows about ActiveX Automation and ActiveX Objects. ActiveX Automation really describes the concept of interapplication communications (IAC) in which one application communicates with another application. I think the usage of the word "Object" tends to add some confusion here, especially for folks unfamiliar with OOP (object-oriented programming). When using the term ActiveX Object keep in mind that it can refer either to the application itself or a component of the application (i.e., a cell in a spreadsheet). Here is what the Excel help file says about it: OLE Automation is an industry standard that applications use to expose their OLE objects to development tools, macro languages, and other applications that support OLE Automation. For example, a spreadsheet application may expose a worksheet, chart, cell, or range of cells as different types of objects. A word processor might expose objects such as application, document, paragraph, sentence, bookmark, or selection.  When an application supports OLE Automation, the objects it exposes can be accessed by Visual Basic. Use Visual Basic to manipulate these objects by invoking methods on the object or by getting and setting the object's properties. For example, if you created an OLE Automation object named MyObj, you might write code such as this to manipulate the object: 

MyObj.Insert "Hello, world." ' Place text. MyObj.Bold = True ' Format text. MyObj.SaveAs "C:\WORDPROC\DOCS\TESTOBJ.DOC" ' Save the object.

ActiveX Automation is an IAC strategy and standard implemented by Microsoft in OLE 2.0. It is not the first IAC strategy attempted by Microsoft for their Windows architecture. That would be Dynamic Data Exchange (DDE).

Is DDE dead? DDE was established to allow applications to communicate with each other. From its name, the primary intent of DDE was to allow data to be passed back and forth between the applications. And Visual FoxPro still supports DDE. You can establish a DDE communications channel using DDEInitiate() and send data back and forth via DDEPoke() and DDERequest() commands as long as the data was in a format compatible with both applications. The ability to control an application was handled through the DDEExecute() command. In order to use this command, however, the target application must have a native language or scripting mechanism whose command can be passed as a parameter in the DDEExecute() statement. In order to use DDE, a target application must have some sort of programmable language which controls its objects. So many applications were left high and dry here since they had no true language. So with OLE 1.0, Microsoft provided a common strategy for the piping and protocols to pass data back and forth, but fell short in providing the necessary common medium for interapplication communications. I write this section as if DDE is dead, and for most purposes it is. However, Microsoft left support for it in Visual FoxPro not simply for backward compatibility, but also for functionality not currently provided through ActiveX Automation. I need to reemphasize that DDE's primary intent is establishing a communications channel for passing data back and forth. And this is where DDE offers functionality not supported yet by ActiveX Automation. DDE allows you to create links between data on the target machine. In FoxPro, this is done via the DDEAdvise() command. There are three basic DDE links: Manual, Notify, and Automatic. When a notify link is created, the target application tells FoxPro that the item name has changed. If an automatic link is created, the target application notifies FoxPro that the item name has been modified and passes the new data back to FoxPro. These "warm" and "hot" links are not yet supported by ActiveX Automation. So keep this in mind if your IAC requirements involve using a data link. Otherwise, it is advantageous to use ActiveX Automation if at all possible (meaning the applications both support it). Since ActiveX Automation now includes a common language (VBA), an array created in Excel can be used by FoxPro. since the common language incorporates all the various data structures too.

VBA VBA or Visual Basic for Applications is the IAC standard language established in OLE 2.0 by Microsoft. It is based on Microsoft's Visual Basic software. VBA uses an object-oriented strategy in which objects are created and eventually released. Once created, an ActiveX Object can have its attributes (properties) altered or functions (methods) called. Microsoft has attempted to establish certain standards for objects, methods and properties which is why you will see much in common with the VBA for MS Graph 5.0 and Excel 5.0 charting features. As we all know, application software can differ greatly, so don't expect to see a Cell object in Word even though it exists in Excel. Since VBA is actually controlled through the Windows operating system, a common repository is needed to store vital information about the applications which support ActiveX Automation.

ActiveX Controllers vs. Servers I won't dwell on this topic much now, but just mention that Client-Server is alive and well in ActiveX Automation. As with OLE 1.0, we generally refer to the controlling (current) application as the ActiveX Client and the controlled application as the ActiveX Server. It is important to also remember that ActiveX Servers are not necessarily applications. MS Graph is not a stand-alone application but rather an applet. The ActiveX Client must be a stand-alone application. The ActiveX Client is often referred to as the "Container Application" since it in essence creates and contains an object reference to the server app. As shared components play a greater role in the future of applications, expect

ActiveX Automation to be more prevalent too. Visual FoxPro 3.0 was only an ActiveX Automation Controller. Visual FoxPro 5.0 extends this to being an Automation Server. In addition, VFP5 allows you to create your own custom automation servers.

Sessions One of the distinctions between Windows and the Mac is that Windows allows you to run multiple sessions of the same application. This means that you can run two distinct sessions of Word in separate windows. If you double-click on Word for the Mac in your Finder, the currently running Word application is activated. In Windows, another session is launched. When you are working with ActiveX Automation, it is important to understand that ActiveX Automation sessions will often behave much differently than a normal user-started session. Consider for example, how Word and Excel handle ActiveX Automation sessions. Excel will launch multiple ActiveX Automation sessions each time a new Excel ActiveX Object is created. Word, on the other hand, only allows one ActiveX session running at a time. Therefore, the current session is merely reactivated if one tries to create a new ActiveX Object. From a memory consumption standpoint, you should be conscious of applications such as Excel which have the potential to launch multiple sessions of itself. And you can easily trap and test for this in your code. More on sessions later.

System Requirements Using ActiveX Automation with Visual FoxPro does not come without a price. While FoxPro's memory requirements may only require 8 MB of RAM, do not fool yourself into thinking that this is sufficient for ActiveX Automation. You need to also incorporate the RAM requirements of the ActiveX Server. Running Excel via ActiveX Automation requires more RAM than MS Graph. You should think about having a minimum of 12-16MB RAM for any stand-alone apps such as Excel.

VFP as ActiveX Controller

Establishing communications / Creating ActiveX objects ActiveX Objects are created in FoxPro in the same fashion as a native FoxPro object. An existing Class definition serves as the blueprint for the instantiation of the object. The CreateObject() command creates a new instance of the object. There is a slight twist, however, which differentiates FoxPro and ActiveX objects. Since an ActiveX Object is supported by its ActiveX Server, there needs to be a mechanism to control launching new instances of the Server each time a new object is created. This is where the GetObject() command comes into play. When an object is created with CreateObject( ) or GetObject( ), Visual FoxPro searches for its class definition in the following order:

1. The Visual FoxPro base classes. 2. Class definitions in memory in the order they are loaded. 3. Class definitions in the current program. 4. Class definitions in the .VCX class libraries opened with SET CLASSLIB. 5. Class definitions in procedure files opened with SET PROCEDURE. 6. Class definitions in the Visual FoxPro program execution chain. 7. The Windows Registry (controlled by SET OLEOBJECT).

It is extremely important that you are familiar with the ActiveX Server you are using for ActiveX Automation. There are distinct differences between how certain servers respond to CreateObject() and GetObject(). This is most evident when you compare Word and Excel (discussed below).

Visual FoxPro Language There are basically three ways to setup ActiveX Automation in Visual FoxPro. The first is to establish it directly with the ActiveX Server using the CreateObject() or GetObject() command. Once the object reference is created, you can pass ActiveX Automation to your ActiveX Server. The second way is by setting up a link though an OleControl object on a form. You can do this through the Form Designer or via the ADDOBJECT() method. Finally, you can establish the link through a General field. The best way to setup the link is by using an OleBoundControl object on a form with its ControlSource set to the General field.

CREATEOBJECT()

The CreateObject() command has its roots in Visual Basic and represents the standard way of instantiating an object whether that object is a native Visual FoxPro Baseclass, a user Custom Class, or an ActiveX Class. All ActiveX Classes must be stored in the Windows Registry so that Visual FoxPro can reference them. If that Class is an ActiveX Class, then the ActiveX Server is also activated. Depending on the Server itself, it may or may not make itself visible (Word and Excel differ on this). Syntax 

CREATEOBJECT(ClassName [, eParameter1, eParameter2, ...]) oExcel = CREATEOBJECT("Excel.Application")oWord = CREATE("Word.Basic")

You will want to use the CreateObject() function when working directly with your ActiveX Servers. The ADDOBJECT() is used when adding OleControls and OleBoundControls to forms. You will have to determine if your ActiveX Server supports multiple session as to whether you should use CreateObject() or GetObject(). When you are finished using the object, simply RELEASE it or set its value to "".

GETOBJECT()

This function is used to create an object reference to an existing running ActiveX Server. In addition, it also allows you to specify a specific ActiveX Server document to use when that Server is activated. Syntax 

GETOBJECT([FileName [, ClassName]]) 

The following example starts Excel, opens a file named BUDGET.XLS, and creates a reference through an object memory variable named oXLSheet.

oXLSheet = GetObject('C:\EXCEL\\BUDGET.XLS')

In the preceding example, the application does not need to be specified, because the ActiveX dynamic link libraries determine the application to start based on the file-extension association stored in the Registry. On the other hand, you may need to specify the class if you want to specify that document opened using a specific application.

oXLDoc = GetObject("C:\AUTOEXEC.BAT","EXCEL.SHEET")oXLApp = oXLDoc.ApplicationoXLApp.Visible = .T.FOR m.i = 1 TO oXLApp.Windows.Count      IF ATC("AUTOEXEC.BAT",oXLApp.Windows[m.i].Caption) # 0            oXLApp.Windows[m.i].Visible = .T.            EXIT      ENDIF

ENDFOR

** Important - Word does not support GetObject() with document. See the Word section below.

For some server applications such as Excel, each time you issue GetObject( ), an additional instance of the application is started. This eats up additional memory. If the application is already running, you can prevent additional instances of the application from starting by leaving out the first parameter. This establishes an ActiveX Automation link with the existing running instance of that application.

oXLApp = GetObject(, "Excel.Application")

Unfortunately, if that application is not already running then an error is generated. So it is best to trap for this situation before you even issue the command. As previously noted, DDE is not dead and here is a great use for it. The following example represents the best way to handle creating ActiveX Automation objects from applications such as Excel.

lOldSetOpt = DDESetOption("SAFETY")= DDESetOption("SAFETY",.F.)iChannel = DDEInitiate("Excel","System")IF m.iChannel = -1 &&failed if Excel not running      oXLapp = CreateObject("Excel.Application")ELSE      = DDETerminate(m.iChannel)      oXLapp = GetObject(,("Excel.Application")ENDIF

ADDOBJECT()

This method is called by an existing object which is a container to add another object. The added object is initially included with its Visible property set to .F. This allows you to make changes to that object before actually displaying it. Syntax  Object.AddObject(cName, cClass [, cOLEClass] [, aInit1, aInit2 ...])  In terms of ActiveX, the most common use of ADDOBJECT() is for a form, formset, toolbar, or page to add an OleControl or OleBoundControl. The following example shows two ActiveX Controls being added to a new form.

oForm = CREATE("form")oForm.AddObject("OLE1","OleControl","MSGraph.Chart")oForm.Ole1.Width = 100oForm.Ole1.Height = 100oForm.Ole1.Visible = .T.oForm.AddObject("OLE2","OleControl","MSOutl.Outline")oForm.Ole2.Width = 100oForm.Ole2.Height = 100oForm.Ole2.Left = 100oForm.Ole2.Visible = .T.oForm.Ole2.AddItem("Dogs")oForm.Ole2.AddItem("Cats")oForm.Show()

Note that these two examples represent what are "Unbound" OLE controls. Their values are not tied directly to any data source such as a Visual FoxPro table. Visual FoxPro's

"Bound" OLE Control is represented by the OleBoundControl object which uses a General field as its ControlSource.

SET OLEOBJECT ON/OFF

This setting determines if Visual FoxPro searches the Windows Registry when an object cannot be located during object instantiation with CreateObject(), GetObject() or ADDOBJECT(). You should SET OLEOBJECT OFF if you are developing an application that does not require ActiveX support .This prevents Visual FoxPro from searching the Windows Registry when that object cannot be located.

SYS(3004), SYS(3005), SYS(3006)

These three functions are absolutely necessary if you are developing applications which require localization for use with international versions. While Visual FoxPro's native baseclasses, properties and methods are not localized when you run a localized version, this is not the case with ActiveX Automation servers such as Excel. What this means is that if you are running the German version of Visual FoxPro and you intend to setup an ActiveX Automation session with the US version of Excel, Excel will see and expect ActiveX Automation calls translated into German (i.e., the Locale ID of the controlling application). And if your ActiveX Automation routines are in English, then the program will cause errors.

oXLApp1 = CreateObject('Excel.Application')=SYS(3005, 1033) && Setup English Locale IDoXLApp1.Quit && Closes Excel with English command oXLApp2 = CreateObject('Excel.Application')=SYS(3005, 1031) && Setup as German Locale IDoXLApp2.Beenden && Closes Excel with German command

The best way to handle a common code base for localization efforts is to set Visual FoxPro's Locale ID to 1033 which represents the US version. Any ActiveX Automation done afterwards will be seen by the ActiveX Server as being native English commands. Once you are finished, you can restore the Locale ID to its original setting.

nOldLocaleID = VAL(SYS(3004)) && Save Locale IDoXLApp = CreateObject('Excel.Application')=SYS(3005,1033) && Setup English Locale ID* Insert ActiveX Automation calls in English here=SYS(3005,m.nOldLocaleID) && Reset old Locale ID

Arrays and Collections Most of you are probably familiar with arrays in Visual FoxPro. The nice thing about ActiveX Automation is that you can pass normal FoxPro arrays to your ActiveX Servers. If you pass an array by reference (@) to an object's method, then you can receive them back. While Visual FoxPro natively supports arrays with dimensions greater than 2, you are limited to only 2 dimensions when using ActiveX Automation. It is also important to distinguish how FoxPro arrays differ from how other applications use them. In the Xbase world, arrays can contain elements of various data types. For example, the first element could be a character type, while the second a numeric type. Many languages refer to these types of arrays as records. Therefore, you should be careful when you populate your FoxPro arrays, so that the ActiveX Server can properly handle the data contained within. Collections are similar to arrays in that they contain a list of data referenced by a single data structure. However, they differ in that they cannot be programmatically altered.

They are in essence read-only properties. They tend to represent objects which can be manipulated by the user. For example, Visual FoxPro's _SCREEN object has a Forms collection representing all of the instantiated forms opened in the FoxPro workspace. Usually associated with the Collection property is some sort of Count property. The _SCREEN object has a FormCount property returning the number of opened forms so that you can enumerate through the Forms collection. Here is what the Visual FoxPro help says about Collections: In code, a collection is an unordered list in which the position of an object can change whenever objects are added to or removed from the collection. You access an object in a collection by iterating through the collection, using the Count property of the collection. The Count property returns the number of items in the collection. Additionally, you can use the Item method to return an item in a collection.  For example, to display the names of worksheets in an Excel workbook, use the following code:

oleApp = CreateObject("Excel.Application")oleApp.Workbooks.AddFOR nIndex = 1 to oleApp.Workbooks.Item(1).Sheets.Count      ? oleApp.Workbooks.Item(1).Sheets.Item(nIndex).NameENDFOR

Parameters and Named Arguments Certain applications such as Word and Excel support Named Arguments in their ActiveX Automation. In Visual FoxPro, when you pass parameters, they must be placed in a specific order.

DO (_GENGRAPH) WITH 'AUTOGRAPH',1,1,'OFFICES',.F.,.T.,.F.

In Visual FoxPro, things have been simplified a little so that you are no longer required to pass every parameter if you desire to skip parameters preceding others (note: that by default a .F. is passed to non-passed parameters).

DO (_GENGRAPH) WITH 'AUTOGRAPH',,,'OFFICES',,,.F.

Named arguments get around some of these problems. So for example, hypothetically speaking, if Visual FoxPro supported named arguments, it would something like this:

=foo1(color:='red',name:='Jack',age:=33)PROCEDURE foo1      PARAMETERS name,date,age,colorENDPROC

Named arguments eliminate the need for ordering of parameters or complete inclusion of all parameters. Since Visual FoxPro does not support this, you need to include every parameter when making a call that objects method. Using the above example, if Foo1 represents a method of an ActiveX object that supports named arguments, then you would need to make a call something like this (note: the .NULL. may not be necessary):

object.foo1('Jack',.NULL.,33,'red')

Enhanced General field support Remember the days of FoxPro 2.x. What a PITA it was working with General fields. The short of it was that you couldn't manipulate them at a field level, but rather needed to do so at a row level. This meant that if you had an input entry form in which you allowed the

user to edit a General field, you needed to have additional code to handle the case when the user wanted to discard his/her edits. Usually, the record was saved out to a temporary table before the edits were made. And if the user canceled out, the current record was deleted and the temp table appended back. Visual FoxPro has enhanced commands for REPLACE, INSERT-SQL, and UPDATE-SQL which allow you to manipulate the contents of a General field with those of another General field. This means that you must have two tables around, each with a General field.

Error handling with AERROR() When you are working with ActiveX Automation, there are obvious risks which are not controllable through your Visual FoxPro applications. You will want to set up adequate error checking and handling so that any ActiveX Automation call can be performed safely without the risk of crashing your application. Visual FoxPro now contains a new function called AERROR(<array>) which populates a passed array with information related to the most recent ActiveX or ODBC error. Actually, information for any FoxPro error is stored. In general, ActiveX errors fall between in the 1400 range. You should get in the habit of checking to see whether a particular object was actually instantiated by using the TYPE() function:

oXLApp = CreateObject("Excel.Applicaiton")IF TYPE("oXLApp") # "O"      =AERROR(aErrArray)      * Fail code hereENDIF

If you have contained all of you code into a single class method, then error handling can be simplified if you setup your classes as follows:

DEFINE CLASS oleclass AS custom  PROCEDURE error      PARAMETERS p1,p2,p3      LOCAL aErrs      DIMENSION aErrs[1]      IF AERROR(aErrs) > 0 AND BETWEEN(aErrs[1],1420,1460)            * Return if an ActiveX error occurred            =MESSAGEBOX("An ActiveX Error has occurred.")            RETURN TO OleHandler      ENDIF  ENDPROC  PROCEDURE OleHandler      THIS.MakeOleCalls()  ENDPROC  PROCEDURE MakeOleCalls      * This is where you create your ActiveX objects      * and setup any ActiveX Automation calls.  ENDPROCENDDEFINE

Working MS Graph 5.0

Back in the days of FoxPro 2.x, MS Graph certainly presented challenges for many developers wishing to incorporate graphs into their applications. Many of use relied on some magical routines contained in GENGRAPH.APP which was the Graph Wizard in FoxPro 2.x. These days are over. Visual FoxPro and the new MS Graph 5.0 offer a better story in terms of ease of use. As we will see, creating a chart in Visual FoxPro is really a two-step process. The first step involves passing data to Graph, and the second uses ActiveX Automation to set various attributes of the chart. It is probably a good idea to look at the Microsoft Graph Visual Basic Reference help file which ships with VFP5. It is also available up on CompuServe in the MSL library or through the Office SDK. This help file contains all of the objects, properties and methods use by MS Graph VBA. If you have Excel, you will notice that Chart PEMs are very similar to those of Graph and in fact, you could probably get by using those. Before you begin, it is probably a good idea to see if MS Graph 5.0 is installed. The above Registry routines show how you can do this. Make sure you also check to see if they have the proper version of MS Graph installed. Remember, versions prior to 5.0 do not support ActiveX Automation. Once you have done this, then its off to the races.

MS Graph and sessions Like Excel, you can have multiple ActiveX Automation sessions of MS Graph running concurrently. For example:

oGraph1 = CreateObject('msgraph.application')oGraph2 = CreateObject('msgraph.application')oGraph3 = CreateObject('msgraph.chart')oGraph1.visible = .T.oGraph2.visible = .T.oGraph3.application.visible = .T.

When you add a chart to a form using the Controls Toolbar, you will see the OleClass set to 'MSGraph.Chart.5". You may or may not have already tried this, but it will become quite evident that there is no way to programmatically control the data in this chart. Instead you will want to use OleBoundControls.

Creating a new Graph The bottom line with MS Graph is that you do not create new graphs. Instead, you merely use an existing one, pass it data, manipulate its attributes. As I already said, MS Graph supports ActiveX Automation, however, it does not have any ActiveX Automation methods which allow you to pass it data. That is, you can't manipulate the MS Graph Datasheet. Why is this? Who knows, but hopefully a future version will remedy this serious limitation. In the meantime, Visual FoxPro offers a solution which requires passing a data structure to an MS Graph already stored in a General field. So what does all of this mean. Well, it means that you must use General fields for creating Graphs. There is no programmatic way to create an OleControl on a form and pass it data! You must use the OleBoundControl object. Another serious programmatic ActiveX Automation limitation in the creation of your graph is controlling how the data series are laid out (series by row/series by column). We'll cover how to handle this situation shortly.

Passing data with APPEND GENERAL ... DATA Let's say your boss comes to you and says she wants a monthly report charting company sales. Where do you start. The recommended strategy is to start out with a preset template DBF containing a General field embedded with an MS Graph. As previously noted, the only way to pass FoxPro data to a chart is through a General field. The reason

you wouldn't use an APPEND GENERAL command to add a new graph is that MS Graph is an applet and not a stand-alone ActiveX Server so there are no files created. While having a preset chart in a template DBF may seem like some sort of Kludge, it actually turns out to be a good strategy if only for the time saved by not having to create a new graph from scratch. In addition, with Visual FoxPro's enhanced support for General fields, it becomes a moot issue. So we have a preset graph in some General field. The next step is to add the data. Visual FoxPro has a new command keyword with the APPEND GENERAL command which allows you to do this. It is called DATA and works as follows: APPEND GENERAL GeneralFieldName [DATA cExpression]  The DATA expression takes the form of the Clipboards Cliptxt format in which data in columns is separated by Tabs (CHR(9)) and rows by Carriage Returns/Line Feeds (CHR(13)+CHR(10)). The following table can be passed to a General field graph as follows:

  1st Qtr 2nd Qtr 3rd Qtr 4th Qtr

East 20.4 27.4 90 20.4

West 30.6 38.6 34.6 31.6

North 45.9 46.9 45 43.9

#DEFINE TAB CHR(9)#DEFINE CRLF CHR(13)+CHR(10)cGraphDataExpr = ''+TAB+'1st Qtr'+TAB+'2nd Qtr'+TAB+;      '3rd Qtr'+TAB+'4th Qtr'+CRLF+;      'East'+TAB+'20.4'+TAB+'27.4'+TAB+'90'+TAB+'20.4'+CRLF+;      'West'+TAB+'30.6'+TAB+'38.6'+TAB+'34.6'+TAB+'31.6'+CRLF+;      'North'+TAB+'45.9'+TAB+'46.9'+TAB+'45'+TAB+'43.9'APPEND GENERAL Graphfld DATA m.cGraphDataExpr

Setting Data Series by Row/Column By default, a new MS Graph is created with data Series in Rows. There is no programmatic way to change this, and depending on the chart type and number of series being graphed, it may be preferable to have data Series in Columns. If we go back to the previous section in which you setup a predefined DBF with a single General field, you need to modify this just a little. My suggestion is to have a single DBF containing two General fields and a single record. One field representing a template graph in Series by Rows, and the other with Series by Columns. Another consideration here is that MS Graph is limited to 256 series, so if you have more than 256 records which are being charted, then you should use Series By Column. And while MS Graph allows a maximum of 4000 records in its datasheet, the number of series capable of being plotted is limited.

Changing Chart attributes using ActiveX Automation Finally, we can get to the fun part. We already have our data in the template General field chart. Now it's time to change its chart type, set the title, and add a legend. The first step

is to get an object reference to this chart in your General field. The easiest way to do this is by using an OleBoundControl.

cGraphField = "graphdbf.graphfld" &&name of General fieldoGraphform =CreateObject("form")oGraphform.ADDOBJECT("ole1","oleboundcontrol")oGraphRef = m.oGraphform.ole1oGraphRef.ControlSource = m.cGraphField

The ActiveX Automation session is established between Visual FoxPro and MS Graph through the OleBoundControl. And the link to the MS Graph session representing the chart in the General field is activated when the OleBoundControl's ControlSource property is set to the specific General field. Once a connection is established, the ActiveX Automation is quite easy. Here are some example's of what you can do.

* Add legend to chartoGraphRef.HasLegend = .T.* Add a title to a chartoGraphRef.HasTitle = .T.oGraphRef.ChartTitle.Caption = "Graph Title"* Set the graph's chart type to 3D AreaoGraphRef.type = 9* Set the graph's chart using AutoFormat to 3D Pie.oGraphRef.autoformat(14,6)

Here are some of my recommendations for common charts:

Type Series in Chart Type AutoFormat

Area Columns 1  

3D Area Columns 9  

Bar Rows 2  

3D Bar Rows 10  

Column Rows 3  

3D Column Rows 11  

Pie Columns  

5,6

3D Pie Columns  

14,6

Line Columns 4  

3D Line Columns 12  

Wrapping things up We're almost done now. There are still a few issues you need to worry about. The first is to save out your graph to another table. You probably want to keep the template DBF intact and simply save out your newly created charts to existing tables. Fortunately, Visual FoxPro's enhanced General field commands such as INSERT INTO or REPLACE make life much simpler these days. From the previous example, you might create a new table called Mygraphs and save the new General field as follows:

CREATE TABLE mygraphs FREE (olechart g)INSERT INTO (DBF()) VALUE(graphdbf.graphfld)

When you do this you may be surprised to see that things do not appear as you expect when you do a MODIFY GENERAL on this new field. What is happening here is that the ActiveX Automation is working, however, the Presentation Information representing the visual display of the MODIFY GENERAL window is not being properly updated. So prior to copying the field out, you need to force the Presentation Information to update. This is done simply by releasing ActiveX Automation session and its object references.

oGraphRef = ""oGraphform = ""

Visual FoxPro's Graph Wizard There is a short-cut to the hassle of working with MS Graph. You can use the power of the Graph Wizard as shown below. The graph wizard will use the columns of the selected DBF/Cursor and make intelligent decisions on how the data is input.

DO (_GENGRAPH) WITH 'AUTOGRAPH',1,1,'OFFICES',.F.,.T.,.F.* parm1 - "AUTOGRAPH" &&required* parm2 - chart type (number)* parm3 - chart subtype (number)* parm4 - title (if not empty)* parm5 - series by row (.T.), by column (.F.)* parm6 - has legend (.T.)* parm7 - use autoformat (.F.)* parm8 - name of output DBF for graph* parm9 - don't open graph when done* parm10 - show nulls

Automation with Excel Microsoft Excel is perhaps the best use of ActiveX Automation and VBA on the market today. And because of this, it is an Excellent tool from which to learn. In fact, after spending a great deal of time learning Excel's AppleScript on the Macintosh, it was rather easy migrating to Excel's ActiveX Automation since both share similar objects.

Excel ships with a separate help file which details VBA support in Excel. It is called VBA_XL.HLP and I recommend creating a separate Windows Program Item just for this since you can expect to spend a great deal of time in here. The first thing you should look at is the Object Model. The help file contains a topic which diagrams all of the objects. After looking at this diagram, consider that Word only has one object in its model.

Where to start After reviewing out the entire object model, you might think that you can create and automate any of these object's from within Visual FoxPro. Unfortunately, this is not the case. No. you need to start somewhere. And knowing that every Excel object is a child of the overall Application object container tells us where to start. In fact, let's jump into the Registry again to see what classes Excel has to offer:

Excel.Application Excel.Sheet Excel.Chart

So from within Visual FoxPro, any of the following commands are perfectly valid:

oXLApp = CreateObject("excel.application")oXLApp.Visible = .T.oXLSheet = CreateObject("excel.sheet")oXLSheet.Application.Visible = .T. oXLChart = CreateObject("excel.chart")oXLChart.Application.Visible = .T.

As mentioned earlier, Excel supports multiple ActiveX Automation sessions which means that you can run the following code and have two separate sessions of Excel running. This differs from how Word handles things.

oXLApp1 = CreateObject("excel.application")oXLApp1.Visible = .T.oXLApp2 = CreateObject("excel.application")oXLApp2.Visible = .T.

Opening, Closing and Accessing Documents Excel has a rich set of VBA properties and methods for performing any action. Here are some you can use for manipulating documents.

* Adds and opens a new workbookoXLApp1 = CreateObject("excel.application")oXLApp1.WorkBooks.Add * Opens an existing WorkbookoXLApp1.WorkBooks.Open('\office\excel\examples\sales.xls')* Activates the first windowoXLApp1.Windows(1).Activate* Quits ExceloXLApp1.Quit()

Referencing Objects in Excel

At first it might seem like a madness in the way Excel uses its objects, but after taking a closer look, things actually start to make sense and the order to all of this becomes quite apparent. The best place to start out is with the top level Application object. All other objects are childs of this one, however, there are Shortcut Methods which allow access to certain objects without having to preface them with the Application object (For example, Cells(1,1) returns a Range consisting of cell A1 in the currently active sheet). Now, let's look at how most objects are handled (and for that matter categorized in the Help file). A common child object of the Application one is the WorkBook. If you look inside the Help file, you will see both WorkBook and WorkBooks objects. The WorkBooks object refers to all of the WorkBooks collectively (i.e., the collection). So you would have Add and Open methods associated with them since they don't refer to a specific object. If you want to refer to a specific WorkBook, then use the WorkBook object, not WorkBooks. The WorkBook object in the Help file really refers to a specific WorkBook such as shown below.

oXLApp1 = GetObject(,"excel.application")oXLApp1.WorkBooks.Add? oXLApp1.WorkBooks(1).FileFormat? oXLApp1.WorkBooks(1).Windows(1).Caption? oXLApp1.Windows(1).Caption

Notice that the WorkBook object is not actually used even though it is shown in the Help file. What it really refers to is a specific WorkBook such as WorkBooks(1) or ActiveWorkBook. For each object such as WorkBook, WorkSheet, Cell, Chart, Window, etc., there is an ActiveWorkBook, ActiveSheet, ActiveCell, ActiveChart, and ActiveWindow object which refers the current object of that specific collection.

Automation with Word It could probably be said that Microsoft Word 6.0 is in a transition state when it comes to ActiveX Automation support. You can probably expect significant enhancements in future versions, but there is still great power available with the current version. The folks at Word have done a good job of mapping the macro language so that controlling applications which support ActiveX Automation can communicate seamlessly.

Accessing Word Objects MS Word 6.0 has limited ActiveX Automation support and is not as extensive as Excel. There is only one type of object which is exposed and is called the "Basic" object. The Basic object is actually a mapping of Word's WordBasic macro language.

oWordRef = CreateObject('Word.Basic')

Since Word supports only the Basic object, you cannot use ActiveX Automation to directly access a Word document as an object (unless the document is embedded in the container application). This means that you cannot use the GetObject() function to access a Word document. The following command is not available.o

WordDoc1 = GetObject('letter1.doc','Word.Document')

Since Word is not a fully supported ActiveX Automation application it does not support VBA properties or methods for its Basic object. Instead, you must use WordBasic routines to access Word and to act on Word documents. The primary difference here is that WordBasic is not an OOP language with methods and properties like those of VBA.

Word Sessions

If Word is not already running when another application needs to access it, ActiveX Automation attempts to start it. If ActiveX Automation starts a Word session, ActiveX Automation will close Word when the object variable that references the Basic object expires. In Visual FoxPro, you can clear the object variable by setting it to "" or NULL. Word's Basic object does not support a method to close itself. If Word is running when ActiveX Automation starts, you cannot close Word through ActiveX Automation; you can only close a Word session if you also used ActiveX Automation to start it. So the following code will not work.

oWordRef = CreateObject('word.basic')oWordRef.FileExit()

Word only allows a single session at a time. So, the following code creates two object references to the same Word session. The is vastly different from Excel which allows multiple sessions running simultaneously.

oWordRef1 = CreateObject('word.basic')oWordRef2 = CreateObject('word.basic')

In addition, you can use the GetObject() function as shown below to create another reference to the current Word session. The GetObject() syntax here is equivalent to that of a simple CreateObject() call.

oWordRef3 = GetObject('','word.basic')

Since Word does not support embedded document objects, you cannot use the following calls.

oWordRef = GetObject(,'word.basic')oWordRef = GetObject(.null.,'word.basic')

One of the problems with using Word 6.0 as an ActiveX Server is that when the WordBasic object is created, Word is made visible and activated on top of Visual FoxPro. This may not be the desired operation if you wish to perform some behind the scenes tasks with Word. You can use the following Word command to bring Visual FoxPro back to the forefront.

oWordRef = CreateObject('word.basic')oWordRef.AppActivate(_screen.caption)

If you are using Word 7.0 or later, then the server is not automatically made visible. You can make it visible using the following code:

oWordRef = CreateObject('word.basic')oWordRef.AppShow

Opening, Closing and Accessing Documents Without going into a lot of detail, here are a few ActiveX calls which you most likely will need when working with Word. They all relate to accessing documents.

oWordRef = CreateObject('word.basic')oWordRef.FileOpen('accident.doc')oWordRef.FileClose()

oWordRef = CreateObject('word.basic')nWordWindows = oWordRef.CountWindows()? "Currently open windows:"FOR j = 1 TO m.nWordWindows && lists all open windows      ? oWordRef.WindowName(m.j)ENDFOR? "Active window:"? oWordRef.WindowName(oWordRef.Window())* activates first windowoWordRef.Activate(oWordRef.WindowName(1))

Working with Word I'll let you guys come up with creative ideas for using ActiveX Automation with Word. The most common example cited is for use in Mail Merge. Mail Merge basically brings data from an outside source, such as your Visual FoxPro query, and merges it into a nicely laid out word processing or desktop publishing document. The Visual FoxPro Mail Merge Wizard is an example of an application using ActiveX Automation to bring FoxPro data into a Word document. Each application is doing what it does best. Another common use today is cataloging. It seems like every day or so I receive another catalog in the mail from some mail order company. The catalogs seem to change every few weeks even though its the same junk inside. What are these companies doing? Many mail order companies store all their product information in large databases capable of imaging and handling high resolution graphics. The data can be gathered separately from the layout of the catalog. When the design layout of the catalog is complete, the product information is pumped right in. By using an application such as Word, the entire process can be automated through ActiveX Automation.

Working with Word Embedded Objects You can also embed Word objects into forms using the OLE Container Control. The following snippet is from the Solutions sample showing ActiveX Automation and Word. Notice the trick you can use to activate the object. Without this trick, the automation calls will fail.

#DEFINE CRLF CHR(13)+CHR(10)#DEFINE C_MESS1_LOC "Is this a rainy day?"nMouseRow = MROW()nMouseCol = MCOL()oForm = THISFORMoForm.AddObject('oWordDoc','OleControl','WordDocument')oForm.oWordDoc.Height = THISFORM.txtFrame.heightoForm.oWordDoc.Width = THISFORM.txtFrame.widthoForm.oWordDoc.Top = THISFORM.txtFrame.topoForm.oWordDoc.Left = THISFORM.txtFrame.leftoForm.oWordDoc.Visible = .t.oForm.ShowoForm.oWordDoc.DoVerb(0)oWordRef = GetObject('','word.basic') MOUSE CLICK AT 0,0MOUSE AT m.nMouseRow,m.nMouseCol oWordRef.Insert(C_MESS1_LOC+CRLF)oWordRef.editselectall

oWordRef.Font("Arial",18)oWordRef.BoldoWordRef.EditGoTo("\EndofDoc")oWordRef.WordLeft(4)oWordRef.SelectCurWordoWordRef.CharColor(2)

ActiveX Controls Many people probably cringe when they think of using an ActiveX Control on their form. Actually, it is not much different from using a normal Visual FoxPro control such as a ListBox. There may be a few extra properties and methods, but they are generally accessed and called similarly to a native control. As with any ActiveX Server, an ActiveX Control stores its information also in the Registry. They are simply classes and can be viewed in the HKEY_CLASSES_ROOT key along with other registered servers. ActiveX Controls (OCX files) are not stand-alone applications, but rather controls which can be dropped onto your Visual FoxPro forms. Visual FoxPro stores registered ActiveX Controls for use specifically with FoxPro also in the Registry. You can get at these via the Options dialog. When you register certain ones, they then appear as options in the Controls Toolbar. You then need to select the ActiveX Controls popup item from the library icon. Here are a few examples of ActiveX Controls and their class names.

ActiveX Control Class

Outline MSOutl.Outline

MAPI Session MSMAPI.MAPISession

MAPI Messages MSMAPI.MAPIMessages

Treeview Comctl.TreeCtl.1

Internet Explorer Shell.Explorer.1

When you drop an ActiveX Control onto a form, you can view its class name in the property sheet as the OleClass property. You cannot change this once added to a form. Once an ActiveX Control is added to a form, you can edit the OleControl's properties via the property sheet. These properties, however, are those which are generic to all ActiveX Controls. If you need to edit a property specific to the control itself, then select the Properties menu option when you right-click on that object. Probably the more common ActiveX Controls is the Treeview control. The Class Browser which ships with Visual FoxPro uses it, and it is not that hard incorporating into your forms.

VFP as an ActiveX Server Visual FoxPro 5.0 now supports being an ActiveX Server. An ActiveX Server acts as a host for ActiveX Controllers to control. Servers can reside on the same machine or a local

machine. Here is a list of some common ones. As you can see, developer languages tend to support ActiveX Controllers more so that desktop applications.

Product ActiveX Controller ActiveX Server

Visual FoxPro 3.0 X  

Visual FoxPro 5.0 X X

Visual Basic 4.0 X X

Visual Basic 5.0 X X

Access 95 X  

Access 95 X X

Excel 95 X X

Excel 95 X X

Word 95 X X

Word 95 X X

Project 95 X  

Project 95 X X

Outlook 97 X X

Graph 95/97  

X

Internet Explorer 3.0  

X

Visio 4.0 X  

Visio 4.5 X X

There are several flavors you can choose in implementing an ActiveX Server using Visual FoxPro.

1. VisualFoxPro.Application - any ActiveX Controller can create an instance of VFP's own application object. This application object is

2. Custom ActiveX Server (LocalServer EXE) 3. Custom ActiveX Server (In-Proc DLL)

Visual is currently the only product available which has both an automatable Application object and also separate ability to create custom servers. Most desktop applications employ an Application object (e.g., EXCEL.APPLICATION). Other developer tools like VB and VC++ do allow for creation of Custom ActiveX Servers. The following chart illustrates Visual FoxPro's object model. As you can see, we now have a new Application object at the top of the object hierarchy. This differs from VFP3 which did not have an object model (you should not confuse the _SCREEN global with the Application object because they are different). An ActiveX Controller which creates an instance of VisualFoxPro.Application actually goes through the top Application object and therefore has access to all of its properties and methods. Custom ActiveX Servers, on the other hand, are instantiated at a level beneath the Application object (they would hang off the Application and be part of the Objects collection). Depending on how you setup your Custom Server, you could allow an ActiveX Controller access to the Application object or not depending on whether you Protected these properties. Custom ActiveX Servers come in 2 separate flavors. The first is a LocalServer (EXE). The EXE server runs in its own process space and is the only type you can use for remote automation. In-Proc servers (DLLs) run in the address space of the ActiveX Controller. Since they do not require an extra instance, they are generally faster. In-Proc servers, are not currently supported for remote automation.

Application Object Visual FoxPro now has a single application object which can be referenced by a global called _VFP or Application. In terms of ActiveX Automation, the application object is equivalent to the object reference returned by making a call like -- CREATEOBJECT("VisualFoxPro.Application"). This application object is automatically instantiated whenever Visual FoxPro is launched either directly, through DDE or via ActiveX Automation. Within Visual FoxPro, developers can access this same object either by using the _VFP global or the Application keyword. The Application keyword is also consistent with other VBA application. The following table shows the properties associated with the new Application Object (Note: many are from the recommended set as described in the Inside OLE 2.0 book). The VFP Help has extensive details on these PEMs.

Properties Methods Collections

ActiveForm DataToClip Forms

StartMode DoCmd Objects

Name Eval  

FullName Quit  

Application Help  

Parent SetVar  

Version RequestData  

Visible    

Caption    

DefaultFilePath    

StatusBar    

Left    

Top    

Width    

Height    

OLEServerBusyTimeout    

OLERequestPendingTimeout    

OLEServerBusyRaiseError    

AutoYield    

ActiveX Collections

Visual FoxPro 3.0 added array collections for certain groups of objects. These collections did not follow the same guidelines for true ActiveX collections that VBA implements for use with ActiveX automation and were not exposed through Idispatch (the ActiveX Automation interface). These guidelines also include several properties and methods that are required. VFP 3.0 implemented the following array collections: Forms, Pages, Controls, Buttons and Columns Associated with each of these VFP Collections was a count property (e.g., FormCount, ButtonCount, etc.). VFP3 deviated from the standard ActiveX Collections methodology for a number of reasons. One being that some of the count properties were read/write. Traditionally, ActiveX Collections Count properties are read-only. Additionally, VFP3

objects were not true ActiveX objects, hence were not accessible externally (since VFP3 was not an ActiveX Server). VFP5 now has true ActiveX objects and collections which other ActiveX Controllers can readily access and affect. The following example shows the two different collections. Both lines of code yield the same results.

? _VFP.forms.item[1].controlcount? _VFP.forms[1].controls.count

It is important to understand that the ControlCount property is specific to the VFP Form object whereas the Count property is specific to the Controls collection. The Controls collection is a property of the Form object.

General ActiveX Collections Properties

VFP 5.0 has the following ActiveX Collections associated with the Application object. Forms, Objects, Controls, Pages, Buttons and Columns It is important to understand that these ActiveX Collections are only accessible when you go through the Application object. You cannot access these collections directly from an object variable reference. You must use the Application property as shown below.

x=create('form')? x.controls.count && fails?x.application.forms[1].controls.count &&works

One needs to understand that there is a performance hit going through ActiveX, so if your application is entirely contained within VFP, then you can and should use the VFP array collections. If you have ActiveX Controllers accessing the objects, then you need to use the ActiveX Collections. ActiveX Collections are fairly generic and expose the same PEMs regardless of the specific type of collection.

Properties Methods

Count Item

  _NewEnum *

* not public The Count property is read-only like it is with all ActiveX Collections. This differs from some of the VFP collections (PageCount, ColumnCount, ButtonCount) that are both Read/Write. Collections are referenced by their keyword (method) which is always the plural of the object. For example, you can use the following syntax:

nTotalForms = _VFP.Forms.CountnTotalForm1Controls = _ VFP.Form1.Controls.Count

Collections compose a set of objects, which can be referenced by several scenarios.

? _VFP.Forms.Item[1].NameApplication.Forms.Item["Invoice Form"].Caption = "New Invoice"Form1.Controls[2].Caption = "Cancel"

Form1.Controls["Label"].Caption = "Hello World"

FOR EACH control statement

Enumerating through collections is important, because unlike arrays, the ordering of objects in a collection is dynamic based on these objects being added and removed at will. Support for proper collection enumeration is handled internally by the _NewEnum Collections method. VFP5 adds enhanced support to the FOR/ENDFOR control structure to accommodate ActiveX Collections (and arrays). Within the FOR/ENFOR loop, one treats the singular reference to the particular member of a collection as not requiring an index. This new control statement is consistent in syntax and functionality with VFP's FOR, SCAN and WHILE loops. Syntax: 

FOR EACH MemVarName IN GroupCommands [EXIT] [LOOP]ENDFOR | NEXT MemVarName 

MemVarName: Variable used to iterate through the elements of the group.Group: Name of a VFP array or collection, ActiveX array or collection. Commands: One or more statements that are executed on each item in group. Here are several examples:

For EACH Form of _APP.Forms      Form.Visible = .T.NEXTFor EACH x of Projects("Foo").Forms(1).Controls      x.Enabled = .F.ENDFOR

Collection indexing

All of the VFP collections are indexed based on a starting value of 1. Currently, some VBA apps have 0 based indexing which has created problems for developers trying to code based on a standard. New collections implemented by VBA teams at MSFT will be 1 based. However, there are some applications which use a 0 based starting value (e.g., the Outline Control). You should be aware of the specifics of an application/component's collections when using them.

_SCREEN vs. Application

So why all the fuss over a new Application object when we already have _SCREEN. The _SCREEN object really refers to the VFP desktop. Internally for VFP applications, this has great significance, however, from an ActiveX Controller point of view, it is much less relevant. VPF5 also added the capability to create Top Level forms which can reside outside the VFP desktop. Updating _SCREEN to support these does not represent a clean model. In fact, with Custom ActiveX Servers, it is quite often the case where your server is a non-visual business object. Using an _SCREEN object as a top level object is confusing to an ActiveX Controller.

Custom ActiveX Servers As mentioned above, Visual FoxPro 5.0 now supports creation of Custom ActiveX Servers, which hang off of the application object.

In order to support Custom Servers, a new keyword was added to the DEFINE CLASS command to allow the Project Manager to detect upon project build which classes were indeed ActiveX Servers. If the PM detects an OLEPUBLIC, it will create and register the class as an ActiveX Server which any ActiveX Controller can access.

DEFINE CLASS ClassName1 AS ParentClass OLEPUBLIC      [[PROTECTED|HIDDEN] PropertyName1, PropertyName2 ...]            PropertyName = eExpression ...]      [ADD OBJECT [PROTECTED|HIDDEN] ObjectName AS ClassName2 [NOINIT]            [WITH cPropertylist]]...      [[PROTECTED|HIDDEN] FUNCTION | PROCEDURE Name            [NODEFAULT]            cStatements      [ENDFUNC|ENDPROC]]...ENDDEFINE

There is also an OLE Public checkbox in the Class Info dialog for classes contained within VCX files. This can be accessed from the Class Designer. Since the only specific information a class has that pertains to it being a Custom ActiveX Server is the OLEPUBLIC flag, there must be some mechanism to control various properties related to the server itself. These are all stored in the Project. Let's take a look at what happens during the build process.

LocalServer (EXE) vs. In-Proc Server (DLL)

VFP5 allows you to create two types of servers (EXEs and DLLs). Both servers require and use the VFP runtime libraries, however, they do differ significantly in how they use and consume memory. A LocalServer (EXE) runs in its own address space, so when instantiated will launch an entire new instance of VFP (note: see instancing options below for more details on running multiple servers simultaneously). An In-Proc DLL uses the address space of the Client instantiating it. Therefore, it loads and runs faster because there is no cross-process marshaling of calls. Because of performance gains, one would think that DLLs are always the way to go. There are, however, advantages and disadvantages to using each type of server. An EXE is the only type that can be used in a remote situation. You cannot have DLLs as remote automation servers. EXEs can also serve dual roles. They can be both ActiveX Servers and normal VFP runtime applications. DLLs can only be automation servers. So, you may have a need to distribute a single EXE serving both roles. In terms of speed and performance, if you design your Servers so that the Client does not need to make a lot of calls back and forth, but instead call single methods on the server which handle all the calls locally (not via automation), you will see performance gains. While most ActiveX Servers tend to be non-visual, some may have UI. In-Proc DLLs do not support an event-loop so there is no way to interact with a form object within an In-Proc Server.

Build Process

When a project is built for the first time, or a new class is added that is marked as OLEPublic, the PM updates the header record of the PJX file for this class.. The header record is also be updated with general typelib information that is stored in the Project Information dialog (Servers Tab). Here is a summary of the build process as it pertains to Custom ActiveX Servers:

Detect any OLE Public classes (either in VCX or PRG) Gather typeinfo for each OLEPublic server class including members, properties and

methods. This will be stored in the Typelib (TLB) file. Update header record (Reserved2 field) with Typelib info for these classes if

needed.

Generate new Typelib GUIDs, CLSIDs, IIDs if needed. Create EXE/DLL file with entry point resource for each server class. Register ActiveX server(s) in Registry (ProgID, Typelib and CLSID data). Remove

any old Registry entries if necessary. Generate Typelib (TLB) file. Generate Remote Server Support File (VBR). The VBR file is used by a utility called

CLIREG32.EXE (used by our Setup Wizard) to register a remote automation server. Specify Network Address, Protocol and Access Rights are required.. This is discussed in more detail later.

Project Information dialog (Servers Tab)

Specific settings for a Custom ActiveX Server can be set in the Project Info dialog of a Project. The VFP help describes the various options quite well. The most critical one you need to understand is the Instancing setting. Instancing: 

Setting Description

0 Not Creatable. You can create instances of the class inside VFP only.

1 (Default) Single Use. You can create instances of the class both inside VFP and outside via ActiveX automation. Each request for an instance of the class by an ActiveX client outside the project causes a separate copy of the ActiveX server to be started.

2 Multi Use. You can create instances of the class both inside VFP and outside via ActiveX automation. Requests for an instance of the class by an ActiveX client outside the project will be supplied by an already running copy of the ActiveX server, if any. If there are no copies of the ActiveX server running, a copy is started to supply the class.

Registering a Custom ActiveX Server

Even though the PM registers the server automatically, users can manually register an ActiveX Server using several methods.

1. If the server is a DLL, you can use RegSrv32 to register it (local only). 2. An EXE ActiveX Server supports several command line switches to automatically

self-register and unregister the server in the Local Registry.

/RegServer - registers an ActiveX Server. /UnRegServer - unregisters an ActiveX Server.

1. The Remote Connection Manager (RacMan) allows you to register a server (EXE) remotely and requires a remote network address and protocol. This utility requires that you have the server already registered locally.

2. CLIREG32.EXE can be used for registering a remote server (EXE) which is not on your machine.

Distributing a Custom ActiveX Server

The best way to distribute a Custom ActiveX Server, both for local and/or remote automation usage, is to let the Setup Wizard handles it for you. The Wizard will create a disk image containing all the necessary files and the appropriate script to register the ActiveX Server (either locally or remotely). The Setup Wizard also contains options for

specifying remote locations and whether to also include Remote Automation components for making changes to your Server settings. More on this later.

More on Custom Servers

For more details on creating Custom ActiveX Servers, refer to the Session ACX04 Notes by Calvin Hsia. There are specific examples of how to create servers and various uses for them.

Remote Automation The last topic of this paper is on Remote Automation (RA). RA is just an extension of ActiveX Automation Servers, except that the physical server resides on another machine across the network. One of the primary reason for remote automation servers is to allow for integration of Business Objects into a 3-Tier architecture. The 3-Tier architecture differs significantly from the Traditional Client-Server architecture. Client-Server is generally thought of as a physical layered model where the physical client resides in one location and the physical server in another. The 3-Tier model (often called the Services model) is a logical model. 3-Tier breaks down its logical services of the following:

User Services - presentation of information and functionality, navigation, protection of user interface consistency and integrity.

Business Services - shared business policies, generationof business information from data, protection of business integrity.

Data Services - definition of data, storage and retrieval of persistent data, protection of data integrity.

You will often hear people talk about N-Tier models. You should avoid using the phrase "N-Tier" even though advocates will say how it encompasses 3-Tier and beyond. Unfortunately, these folks are no longer distinguishing between a Layered vs. Services based model. There reference to N-Tier is in the physical architecture. 3-Tier is a logical model not physical one! And as far as I am aware, there has not been a 4th logical tier defined. In order to understand how remote automation works, we need to take a closer look at Proxies and Stubs.

Proxies and Stubs

These are the components which allow for ActiveX Controllers to communicate with ActiveX Servers. In order to make calls between physical machines and separate processes, there needs to be a way to consistently pass information back and forth through a protocol that both client and server recognize.

o PROXY An object that packages parameters in preparation for a remote method call. A proxy runs in the address space of the sender and communicates with a corresponding stub in the receiver's address space.

o STUB An object that unpackages the parameters after they are marshaled across the process boundary, and makes the requested method call. A stub runs in the address space of the receiver and communicates with a corresponding proxy in the sender's address space.

Local ActiveX Automation:  The illustration below shows how ActiveX Automation works on a single local machine. The Proxy and the Stub are contained within the actual processes of the Controller and Server. Local ActiveX Automation is managed through a common system component called OLEAUT32.DLL which performs both ActiveX proxy and stub services. Since these reside on the same machine, all the marshaling (packaging of calls, parameters and return values) are handles transparently behind the scenes. Remote Automation: 

As seen from the picture below, Remote Automation uses the exact same process as local automation, except that there is an intermediary component handling the physical cross-boundary processing. This component is the Automation Manager (AUTMGR32.EXE) and resides on the remote machine (note; for ActiveX Callbacks, Automation Manager must also be installed on the local machine - see below for more details). The ActiveX Controller still uses an ActiveX Proxy, however, in this case a special one called AUTPRX32.DLL is used (note: the VFP Setup Wizard handles installation of all the extra files needed). The remote machine's Automation Manager servers as both a Stub to receive the packet from the local machine and as a Proxy to simulate being the ActiveX Controller on the remote machine. As you can see, the ActiveX Server on the remote machine has no idea that it is being remoted since the ActiveX Proxy resides on the same machine. The bottom line here is that neither the ActiveX Controller (local machine) nor the ActiveX Server (remote machine) know that each other lives in a different location.

Remote Automation Components

If you plan to deploy remote automation servers, you should acquaint yourself with the various components used for managing remote automation. The Automation Manager and Remote Automation Connection Manager components were developed by the Visual Basic group for their 4.0 version. The Visual FoxPro team decided to adopt their components in an effort to avoid duplicate efforts as new technologies affecting remote automation become available. There is nothing specific that these components support in VB4 that is not support in VFP5. A case in point happened recently when NT 4.0 shipped. With support for Distributed COM newly added as an alternative to using Remote Automation (via Automation Manager), it was easy for the Visual FoxPro team to pick up the new Remote Automation Connection Manager piece the VB team updated. Let's take a look at each of these components.

Automation Manager

The Automation Manager runs in the background because it does nothing more than act as the process for handling remote automation. Specifically, the Automation Manager serves as both an ActiveX Proxy and ActiveX Stub to process RPCs (remote procedural calls). Without it running you cannot create a remote server. The Automation Manager resides on the remote machine and relays marshaled (packaged) requests from the Remote Automation proxy on the local machine to the corresponding Remote Automation stub object on the server. The return values are marshaled by the Automation Manager, and returned to the ActiveX client through the Remote Automation proxy. Neither the client nor the server knows that the connection has been remoted. Remote Automation makes the transaction "look like" local ActiveX Automation to both the ActiveX client and the ActiveX server. The Automation Manager component was developed by the VB team, but is actually written in C and handles all the RPC calls and marshaling for remote automation. The Automation Manager program (AUTMGR32.EXE) must be installed and running on the Server machine in order for the Controller to access the remote server. If an ActiveX Callback (see below) is made from an object referenced passed by the client to the remote server, then the Automation Manager must also be installed on the client machine. Note: if the Automation Manager is not running, it will automatically be launched as long as it is properly registered. If the Automation Manager does not automatically launch with an ActiveX Callback, then it is most likely not registered properly. The VFP installation process will do this automatically for you, but if the Registry somehow becomes corrupt, you can do this yourself from VFP as follows:

run /n c:\vfp\autmgr32.exe /regserver

Automation Manager registry settings are stored in the following location:

HKEY_LOCAL_MACHINE\Software\Microsoft\Automation Manager

Remote Automation Connection Manager

The Remote Automation Connection Manager (RacMan) is a component written in Visual Basic 4.0, which is why you need the VB runtime libraries. In addition it uses some ActiveX Controls. RacMan's sole purpose is to handle Registry settings which control remote connectivity on the client side and client access on the server side. In terms of registering servers remotely, RacMan requires that the server be registered locally. The Setup Wizard uses a tool called CLIREG32.EXE that allows you to register a server remotely without first actually having it installed or registered locally. Specifically, RacMan serves two purposes.

1. Remote connectivity on client side - the client can change a server that's already registered locally to being registered remotely.

2. Client access on server side - the server can determine access for clients either at machine or automation server level. With NT, ACL security policies can be enforced.

Registry settings for remote servers are stored with the specific server itself in the CLSID key in HKEY_CLASSES_ROOT

CLIREG32.EXE

The CLIREG32 component allows you to register an ActiveX Server (EXE) remotely. It differs from the Remote Automation Connection Manager (RacMan) in that the server itself does not need to be physically located on the machine registering it. The Setup Wizard handles the maintenance of registering remote servers for you (note: remote servers and CLIREG files are stored in the \Windows\OleSrv\ directory similarly to Visual Basic if installed via the Setup Wizard). The CLIREG32.EXE program takes a number of parameters. The only required one is the name of the VBR file (generated with server during build). ex. CLIREG32 myexesvr.vbr In order to fully register a remote server, you must provide Network Name, Network Protocol and Security Access. CLIREG32 will prompt you will a dialog if these options are not passed. For a list of all parameters, simply execute CLIREG32.EXE by itself.

DCOM

Microsoft's latest release of NT 4.0 includes support for Distributed COM (previously referred to as Network OLE). You can use DCOM to handle remote automation and is generally preferred to the RA employed via the Automation Manager for performance gains. The nice thing about DCOM is that ActiveX Servers written using Remote Automation will continue to work exactly as before. DCOM also enhances many capabilities of today's ActiveX, including remote activation, centralized registry information, and new security capabilities. Additionally, DCOM provides full impersonation services on remote ActiveX servers, so that threads executing remotely can operate against system resources as if they were the client.

ActiveX Callbacks Visual FoxPro 5.0 supports ActiveX Callbacks with its Custom ActiveX Automation Servers. You can setup a method on the server which receives an object reference from the ActiveX Controller for one of its parameters. The advantages of this is that you can implement asynchronous notifications where you don't tie up the client while the server is processing a long job. Note: in order for VFP to do ActiveX Callbacks (in remote

automation), the local machine must be running the Automation Manager. This is only in a remote situation, not local automation. Here is a very simplistic example where you have an exposed class on the ActiveX Automation Server called Processor (in Registry, it is MyServer.Processor). It is a Custom Server. The client creates the server and passes it an object reference. At some point (maybe handled by a timer based pool manager), when the job is done being processed (referenced by the token object reference), the DoCallBack method is called. When the DoCallBack method is called, the object's Notify method on the Controller (which can be remote) is then called. Note: if the Controller and Server exist on separate machines, then the Automation Manager would be running on both machines.

ActiveX Automation Server: DEFINE CLASS Processor AS Custom OLEPUBLIC      oObjRef = ""      PROCEDURE SetupRef (oRef)            THIS.oObjRef = oRef      ENDPROC      PROCEDURE DoCallBack            THIS.oObjRef.Notify()      ENDPROCENDDEFINE

ActiveX Automation Controller:

X=CreateObject("Job")Y=CreateObject("MyServer.Processor")Y.SetUpRef(X) DEFINE CLASS Job AS Custom      PROCEDURE Notify            =MESSAGEBOX("Job is done.")      ENDPROCENDDEFINE

There are a number of ways to successfully handle ActiveX Callbacks. One is with the use of Timers (see the Pool Manager sample in Samples\Servers).

Debugging Remote Automation Servers Unlike servers deployed locally, which can either be EXE or DLL (in-proc), remote servers must be EXEs. One of the advantages with EXE type servers is that they do have their own event loop so you can process events, however, in a typical remote situation, your server will be non-visual (typically a business object in a 3-tier situation). Debugging your EXE servers should be straight forward. For the most part, you should be able to test your servers locally before deploying them remotely. Most of the servers will run the same in both situations. The approach one should take in creating and debugging a remote server is as follows:

1. Create your server as an internal VFP class. Assuming you have a project called Books which has a custom class called taxes which calculates the state/province sales tax for your mail order book sales business, you might have your class loosely structured as follows:

   * TaxLib.PRG   DEFINE CLASS taxes AS custom   PROCEDURE gettaxes      LPARAMETER cState

      DO CASE      CASE m.cState = "CA"        RETURN .08      CASE m.cState = "WA"        RETURN .07      CASE m.cState = "NY"        RETURN .10      OTHERWISE        RETURN 0      ENDCASE    ENDPROC    ENDCLASS

Your application can test this class within VFP internally with code similar to the following:

  cState = "CA"  nBookPrice = 19.95  SET PROCEDURE TO TaxLib &&can also use SET CLASS  oTaxes = CreateObject("Taxes")  nBookTax = oTaxes.GetTaxes(m.cState) * m.nBookPrice  ? "Book total: "+ALLTRIM(STR(m.nBookPrice+m.nBookTax))

The advantage here is that you are working within the Visual FoxPro development environment so you can take advantage of the debugger for common errors such as "Syntax Error", etc.

2. Change you class definitions to OLEPUBLIC and test locally. Theoretically, we have already debugged for most of the common errors that developers are prone to doing. Now, we want to test the class as an ActiveX Server. In order to do this, update the class definition to make it OLE Public and rebuilt the project selecting the Build EXE option. The following sample shows this with a class defined in a PRG. Change:

      DEFINE CLASS taxes AS custom

To:

      DEFINE CLASS taxes AS custom OLEPUBLIC

Note: if you are using a visual class created in a VCX, you would go into the Class Info dialog to check the OLE Public checkbox. Once you have create your ActiveX Automation Server, you can update your test code as follows (notice that we no longer need a SET CLASSLIB or SET PROC statement -- the class is automatically registered in the Registry as an ActiveX class):

cState = "CA"nBookPrice = 19.95oTaxes = CreateObject("Books.Taxes") nBookTax = oTaxes.GetTaxes(m.cState) * m.nBookPrice? "Book total: "+ALLTRIM(STR(m.nBookPrice+m.nBookTax))

You should not encounter many bugs beyond step 1, however, there are a few things to look out for. Since the ActiveX Server uses the runtime version, not all the language is supported (especially some of the UI and designers). Make sure you avoid commands that do not work with the runtime version (these are documented).

3. Deploy the ActiveX Server remotely. You have already created your ActiveX server. Now, we need to register it remotely. The easiest way to do this is using the Remote Automation Connection Manager (RacMan). RacMan allows you to switch an existing ActiveX Server that you already have registered locally to being registered remotely. In order to do this, make sure your remote machine has that server installed and registered properly and access rights are properly made available to the client. Finally, to create an instance of that server, you must be running DCOM (Distributed COM or Network OLE) between your machines (NT 4.0 or later), or be running the Automation Manager program that ships with VFP on the server. See VFP documentation for more details on setting up for remote automation and using RacMan. Many of the problems associated with using remote automation servers are not with the servers themselves but rather configuring them properly with valid network address, protocol and access rights. The biggest concern at this stage is checking for wait states that might stop the client. For example, a MessageBox() call which brings up a dialog on your server can only be closed when someone manually goes over to the server and closes it. Meanwhile, your application is locked in a busy loop waiting for the Server to complete the command.

Error Handling Strategies

Error handling is probably one of the more common problems with remote servers. It is imperative that your error handlers avoid using dialogs that can place your server in a modal state. Your error handlers should return an error code (possibly message) to the client for proper handling. Depending on how conservative you are with your code, you can either check after each automation call to the server for an error, or let your error routine handle it. Avoid Wait states. In general, your remote servers should be non-visual. There may be extraneous wait state code such as BROWSE, MODIFY MEMO, WAIT WINDOW, etc. which relies on user input. Make sure to avoid this type of code which can place the client in an endless loop. Use Application object properties. There are several properties you can use in your client applications to more effectively handle problems with ActiveX Servers. These include:

OLEServerBusyTimeout OLERequestPendingTimeout OLEServerBusyRaiseError StartMode

Watch out for blocking with ActiveX Callbacks. Visual FoxPro supports the ability for remote servers to make ActiveX Callbacks into your clients. An object reference on the client can be passed to remote server. The remote server can then call a method or set a property on this object. When this happens in a remote situation, the Automation Manager is automatically launched on the client machine. A common problem with ActiveX Callbacks is having the client pass an object reference to a method on the server. This method then tries to make a callback on that object. A blocking problem occurs (Error: Call was rejected by callee) because the client is waiting for the method to complete execution even though the server is trying to call back into the client. Here is a simple example showing this.

* Client code

x=create("myproj.myserver")y=create("form")x.test(y) &&generates error because of blocking

* Server code in project Myproj

DEFINE CLASS myserver AS custom OLEPUBLIC  PROCEDURE test

    PARAMETER oForm    oForm.Caption = "Hello World"  ENDPROCENDDEFINE

Common Errors with Remote Automation

Error: OLE error code 0x800706d9: There are no more endpoints available from the endpoint mapper.

Possible Causes: Automation Manager/DCOM not started (running) on server.

Remedy: If using Automation Manager, make sure to launch the program. With DCOM, check DCOM documentation for possible problems configuring it. If a successful connection is made, the Automation Manager window will indicate so.

Error: OLE error code 0x800706a7: The RPC protocol sequence is not supported.

Possible Causes: The Network Protocol specified on the client machine for the remote server is not supported.

Remedy: On the client machine, launch the Remote Automation Connection Manager and go into the Server Connection tab. Select a Network Protocol that is supported such as TCP/IP.

Error: OLE error code 0x80070005: Access is denied.

Possible Causes: This is often due to improper client access set on the server.

Remedy: On the server machine, launch the Remote Automation Connection Manager and choose the Client Access tab. Make sure the System Security Policy is set correctly. If this does not work, try using one of the other Policy options such as Allow All Remote Creates.

Error: OLE error code 0x800706e4: The requested operation is not supported.

Possible Causes: This is often due to improper client access set on the server when the System Security Policy is set to Allow Remote Creates by ACL.

Remedy: On the server machine, launch the Remote Automation Connection Manager and choose the Client Access tab. The 3rd option should already be selected (Allow Remote Creates by ACL). Make sure The ACL (Access Control List) privileges are set correct to allow for client access via the Edit ACL button. If this does not work, try using one of the other Policy options such as Allow All Remote Creates.

Error: OLE error code 0x80010001: Call was rejected by callee.

Possible Causes: Block in OLE Callback

Remedy: Make sure that callbacks are made independent of the client's actions. Use a timer as in the Pool Manager example.

Error: Server busy or mousepointer endlessly busy

Possible Causes: This is often due to some wait state in the server such as an error dialog, messagebox, browse, etc.

Remedy: Avoid using code which require user input.

Error: Feature not available.

Possible Causes: Command or function is not available in runtime version.

Remedy: Do not use these commands.