Basics of Using the VisM Control
This chapter describes the basics of using the VisM control:
Establishing the Caché namespace in which your server-side code will run.
Mirroring VisM properties between the client and server, which gives your code an easy way to pass data between server and client.
Referring to other client user interface properties and methods from the server, via callbacks.
This chapter also discusses constraints on Caché Direct messages that may affect your code on either side. It concludes with a couple of simple examples.
A leading underline indicates a callback reference to the Visual Basic user interface. Note that Caché does not allow this usage in any other situation.
Accessing the VisM Control
When you install the Caché client software, it installs and registers the VisM control so that it is available to any ActiveX host, such as Visual Basic.
If the Caché client is not installed on a given machine, you can manually copy the Caché Direct client files into place and register them. See the section Installing VisM on a New Machine, near the end of this manual.
In the case of Visual Basic, to add this control to your project, click Project —>Components, scroll to VisM, and select the check box. Then you can add instances of the control to your forms as you do any other control.
Connecting and Disconnecting
To connect or disconnect the Caché Direct client from the server, you can use the Server property, the ConnTag property, the SetServer() method, the Connect() method, and the DeleteConnection() method. Each of these tools has specific uses, but there is overlap. The SetServer() and Connect() methods are similar, but have small differences for historical reasons and to preserve backward compatibility. The Server property and the SetServer() method are the same except for the second argument to SetServer().
Also see the appendix Notes for Users of Previous Version.
Connecting to Caché
Connecting is the action of attaching a VisM to a CDConnect (creating the CDConnect if necessary). If the VisM is currently attached to a CDConnect object, it is disconnected from that CDConnect first.
If Not Yet Connected
If the VisM is not yet connected to Caché, you can connect it to Caché by using any of the following techniques, which are listed here in order by how commonly they are used:
Set the Server property equal to a connection string (or equivalent). For example:
Call the SetServer() method with a connection string (or equivalent) as the first argument. This is equivalent to the previous technique. For example:
Call the Connect() method with a connection string (or equivalent) as the first argument. For example:
In each case, Caché Direct creates a CDConnect and creates a connection from the VisM to the CDConnect object. It also starts a server process and creates a channel from the CDConnect to the server process.
If you use the SetServer() or the Connect() method, you can provide a connection tag as the second argument. For example:
If Already Connected
If the VisM is already connected to Caché, that means that a CDConnect has been created, possibly with an associated server process. You can connect the VisM to a different CDConnect, disconnecting from the original CDConnect and creating a server for the new CDConnect. To connect to a different CDConnect, use any of the following techniques.
To create a new CDConnect and connect to it, call the Connect() method with a connection string as the first argument.
To connect to the most recently opened CDConnect, call the Connect() method with an empty string as the first argument.
To connect to an existing CDConnect, do any of the following:
Set the Server property equal to a connection tag (permitted as of Caché 2007.1).
Call the SetServer() method with a connection tag as the first argument (permitted as of Caché 2007.1).
Call the Connect() method with a connection tag as the first argument.
These actions do not destroy the original CDConnect; nor do they affect its server channel, if it has one. The CDConnect exists until it is no longer accessible (when it will be destroyed automatically) or until it is explicitly destroyed, as described in Destroying a CDConnect.
Changing the Channel of a CDConnect
To change the channel of an existing CDConnect, use either of the following equivalent techniques:
Set the Server property equal to a connection string.
Call the SetServer() method with a connection string as the first argument.
In either case, the CDConnect stops the server process to which it is currently connected, disconnects from it, and then starts and connects to a new server process.
Disconnecting from Caché
To disconnect from the Caché server, use either of the following equivalent techniques:
Set the Server property equal to an empty string.
Call the SetServer() method with an empty string as the argument.
These actions do not destroy the CDConnect; nor do they affect its server channel, if it has one. The CDConnect exists until it is no longer accessible (when it will be destroyed automatically) or until it is explicitly destroyed, as described in the next section.
Destroying a CDConnect
To destroy the CDConnect and its channel, call the DeleteConnection() method. This method also stops the server process, of course. For example:
Summary of Techniques
The following table summarizes how to connect and disconnect from Caché. Notice that setting the Server property has the same effect as calling the SetServer() method, in all cases.
|Action||How To Do This|
|Connecting to a new CDConnect, if not yet connected||
Use any of the following techniques:
|Connecting to a new CDConnect, if already connected*||
Call the Connect() method with a connection string as the first argument.
|Connecting to an existing CDConnect (after disconnecting from current CDConnect, if any)*||
Use any of the following techniques:
|Connecting to the last opened CDConnect (after disconnecting from current CDConnect, if any)*||Call the Connect() method with an empty string as the first argument.|
|Changing the channel of the existing CDConnect||
Use either of the following techniques:
Use either of the following techniques:
|Destroying the CDConnect and its channel||Call the DeleteConnection() method.|
Key: *These actions do not destroy the CDConnect; nor do they affect its server channel, if it has one. The CDConnect exists until it is no longer accessible (when it will be destroyed automatically) or until it is explicitly destroyed.
The following figure summarizes the differences between the Connect() and the SetServer() methods.
Establishing the Namespace
When you connect to a server, the namespace is initially set to the current namespace of the superserver, which is generally not a suitable place to execute your application code. There are three equivalent ways to switch the namespace:
Your code can explicitly change the namespace (via the $ZNAME command).
You can use the VisM NameSpace property, which is sent to the server in every execution message. The server examines this property before executing any code, and it uses the following logic: If NameSpace is empty or equals the current namespace of the server, the server remains in this namespace. If NameSpace is different from the current namespace, then the namespace of the server is changed before the code is executed. The server job is then left in the new namespace.
You can use a special kind of indirection. In this case, you specify a formal namespace, which is translated to an actual namespace at runtime by means of a registry entry.
To establish a formal namespace, use syntax of the following form:
Here VisM is the name of the VisM instance, and MyNS is the formal namespace.
The registry must include an entry at Cache/CacheDirect/FormalNamespaces. Each entry is a subkey whose name is the formal namespace (for example MyNS) with a single string value, named translation, whose value is the actual namespace to use on this machine.
With this option, you can easily build and test an application in a test namespace and then deploy it to another namespace when it is ready, without modifying the code. This option also permits you to use the identical application on different machines, with a different namespace on each.
Remember that changing namespace is a relatively expensive operation. In particular, the global and routine buffers are purged. For example, it would be undesirable to have messages alternating between namespaces, which would entirely eliminate the advantages of buffering data in memory. If you need to work alternately in two namespaces, you should instead establish connections to two server jobs, each running in its own namespace – then each job can take advantage of the buffering efficiencies.
There are two general ways to use Caché Direct to execute ObjectScript:
One technique is to use the Execute() method. The argument for the method is a line of code. This is the shortest way to the most common use of the control, executing a line of ObjectScript code on command. This technique is shown in the example later in this section.
The less common technique is to put the code into the Code property and set the ExecFlag property to 1 (execute immediately). As soon as the ExecFlag property has been set to 1, you have effectively pushed a virtual execute button. The VisM immediately assembles a message to the server, sends it off, and receives the reply. When the reply has arrived, ExecFlag is set back to 0 (idle), and the virtual execute button is released.
There are other possible settings of ExecFlag for use in special situations. See the reference section for the VisM.ocx properties.
These techniques have exactly the same net effect. (When you call Execute(), the client stores the string temporarily in the Code property and internally changes the ExecFlag setting to 1.) Choosing one method or the other depends on the application. If the code string is not changing, or is chosen by some separate computation, the ExecFlag technique is slightly more convenient. If you just need to execute a line of code, the Execute() technique is more convenient and more direct.
The line of code to be executed can be any legal line of code or expression.
If you call Execute() and the client fails to connect to a remote Cache instance, Caché Direct will try to connect to the default server. This behavior allows an application, including a non-interactive one, to connect to a default server without having to prompt the user or code the server into the application. If there is no default, then the client will prompt the user with a 'Communication Error' dialog. Clicking the 'Cancel' button causes the client to connect to the local default instance.
Because it is common to evaluate expressions, there is a useful convention: the server looks at the beginning of the line of code and tries to determine if the line has the form of an expression. Specifically, it checks whether the line begins with an equal sign or at least one dollar sign. If so, the server code prepends either "Set VALUE " or "Set VALUE = " to create a command that sets the VALUE property to the result of the expression. For example, "$zv" on the client side would be expanded to "Set VALUE=$zv" on the server side. Except for this special treatment, the VALUE property is no different from any of the other mirrored properties; see the next section.
Using Mirrored Properties
Caché Direct mirrors the values of certain VisM properties between the client and server, as follows.
When the client communicates with the server, it creates a message that contains the values of the mirrored properties (as well as other needed information). When the server receives the message, it creates and sets local variables that have the same names.
On the server side, you can use the local variables in the same way you would use any other local variables. There are no limitations on how you can use them, including, for example, indirection.
When the server replies, it creates and sends another message that contains the values of all the mirrored properties, whether or not they have changed, as well as other information.
The server then destroys (via $KILL) these local variables (but leaves your other local server variables untouched).
Therefore the mirrored variables are not preserved on the server between calls; they exist only while the server is executing a client command.
Using Basic Mirrored Values
The basic VisM properties that are mirrored are string properties named P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, and VALUE. When the server is executing a client command, the server process will have local variables with the same names, with values equal to the properties in VisM. A change on the server is followed by a change to the properties on the client when the response message is received from the server.
For the VALUE property, there is one additional feature. If the value of the Code property is an expression (that is, if it begins with a dollar sign or an equal sign), then the server returns the result of that expression in the VALUE property, as noted in the section on Executing Code.
One other VisM property is mirrored, the PLIST property. Caché Direct uses this property to pass array-like values between the server and client. Because the client and Caché have different representations of arrays, it is important to understand the transformations in both directions. The PLIST property has a different form in these two environments:
On the client, PLIST is a pieced list that contains all the “array” elements, appended to each other with a delimiter. The delimiter is specified by the PDELIM property, which is used only on the client. For example, if PDELIM were equal to "^" then PLIST could equal "first^second^third".
On the server, PLIST is a one-dimensional array with the following form:
PLIST contains a number that indicates how many items were in the list on the client.
PLIST(1) contains the first list element.
PLIST(2) contains the second list element.
And so on.
If the PDELIM property is an empty string, PLIST is considered to be a single piece.
When the server assembles its reply message, it treats PLIST as follows:
If PLIST equals the number of elements that are currently in the list, the server returns the PLIST array as is.
If PLIST is less than the number of elements that are currently in the list, the server adjusts the PLIST array by removing elements from the end of the list.
For example, on the client you may have the following code:
VisM1.PDELIM = "^" VisM1.PLIST = "armadillo^beaver^cobra"
On the server, you will see
Suppose the server then runs this code:
Set PLIST(3)="cat" Set PLIST(4)="donkey"
When you return to the client, the PLIST property will be "armadillo^beaver^cat". The third list element is updated, but the fourth list element has not been returned, because we have not updated the list count.
If PLIST is greater than the number of elements that are currently in the list, the server adjusts the PLIST array by adding empty strings as elements, adding them in the empty list positions.
Consider the previous example, suppose that the server code does the following instead:
Set PLIST=5 Set PLIST(5)="eagle"
Then the client property would become "armadillo^beaver^cobra^^eagle".
If PLIST does not equal a positive integer, the server counts the number of elements in the list — including any elements with nonnumeric subscripts — and sets PLIST equal to that number. (In contrast, in all the previous cases, the server returns only array values with numeric subscripts.)
Note that both the count and the values returned are computed with $Order. This has the effect of eliminating the requirement of sequential, numbered entries in the array. This means that you can make use of the string subscripts and automatic sorting that Caché provides.
Thus, either of the following would produce the same result as the four-piece example above:
Set PLIST="" Set PLIST(10)="donkey"
Set PLIST="" Set PLIST("don")="donkey"
The preceding behavior means that typically if the server changes the number of list elements, it should either update PLIST to the new count or clear PLIST (and let the server count the list elements).
Callbacks to the Visual Basic User Interface
If you use Visual Basic, VisM includes a special feature that allows your ObjectScript code to refer to elements of the client user interface. These property and method references are called callbacks because they cause a message to be sent from the server to the client when the reference is made. The client looks up the form and control, issues an OLE call, and returns the result to the server.
Specifically, you can access properties of any controls on the client user interface, via getter and setter methods. Your ObjectScript can also use the following Visual Basic methods, if your form includes controls that provide these methods:
Requirements to Support Visual Basic Callbacks
In order to make callbacks available for a particular form, there are two requirements:
The form must contain a VisM control.
Before you make the first server reference to a control on that form, make a call like the following (usually in the Form_Load event code):
CreateDispatch Me, VisM1
Here, Me is the Visual Basic reference to the current form, and VisM1 is the name of the VisM on that form. The CreateDispatch function is part of the <cache-install-dir>\dev\cdirect\VBRUN.BAS module, which must be included in your project. This function creates a list of the controls on the form and stores them, for future reference, in a hidden property of the VisM.
Referring to Properties of a Control
To refer to a property of a control, use the following special ObjectScript syntax:
The elements in square brackets are optional, and the brackets are not part of the syntax. The leading underline is required. If you omit formname, it is assumed to be the form whose VisM sent the current message. The controlname and propertyname are required. If the control is one of a collection, controlindex is required. If the property is a collection, propertyindex is required. For example, to get the text property of a text control named txt1 on the current form, use the following code:
To set the text property, use the following code:
Because the underline is also a concatenation operator in ObjectScript, if there is any ambiguity about its meaning, use parentheses.
Executing Methods of a Control
To execute a method of a control, use the following syntax:
For example, to add an item to a list box named list1, use the following code:
Do _list1.AddItem("item data")
Using Windows Functions and Caché Utility Functions
Your ObjectScript code can also use the following general Windows functions:
Also, it can use the following Caché Direct utilities, also available in the %CDSrv routine:
GetClientIP() returns the IP address of the client
GetSvrNode() returns the name of the server machine
GetTCPDevice() returns the identifier of the TCP device that is serving this channel, in Caché format
To execute any of these functions, use syntax like the following:
For example, to call the Windows MsgBox function, use code like the following:
Set reply=$$MsgBox^%CDSrv("Are you finished?",1)
Understanding Message Constraints
All communication between the CDConnect and the server is done as Caché Direct messages. These messages are sent over TCP connections (even when all components are local).
There are certain constraints on messages that in turn impose constraints on how you set the properties whose contents are sent in the messages. These constraints also affect how you write callbacks. While you do not need to know the internal details of the message structure, a general description is useful. Generally, a message consists of a 56-byte header followed by a series of fields for the data. Flags in the header describe the type of message and, by implication, what fields to expect. Types of messages include NewTask, BeginTask, ExecuteCode, EndTask, and other types. In addition to internal information, the message includes the mirrored properties plus a few additional ones noted in the Other VisM Properties section.
Unicode and Locale Issues
For all properties that are sent to the server, the values must have only text characters. This restriction also applies to any Visual Basic properties that are set or retrieved by Caché Direct; see the section Callbacks to the Client User Interface. (There are exceptions to this constraint that work in some environments, but the general requirement still exists.)
Caché servers operate in either 8-bit or 16-bit (Unicode) mode. In the initial communications between the client and server, the server notifies the client of the mode of the server. Then:
If the server is Unicode, all clients convert all strings to Unicode before sending them to the server. This may make the messages larger, but then any client, operating in any locale, can communicate reliably with the server.
If the server is in 8-bit mode, none of the clients convert strings in any way. If any client is not using the same locale as the server, then it is likely that data will be lost or confused when the server writes to the database, because some characters have different meanings in the different locales. Therefore, it becomes the responsibility of your application to either convert or interpret what it receives from the client.
The requirement for properties to be strings arises from the conversions that are performed for Unicode servers. However, there are two situations in which non-text data is preserved: 8–bit servers and control codes with values in the range $c(1) to $c(31).
Because no conversion occurs for 8–bit servers, no corruption can take place. And because the low-range control codes are the same in all 8–bit locales and in Unicode, they also survive for any server.
However, because the client is written in C++ and uses C string conventions, embedded null values ($c(0)) may cause strings to be truncated.
For historical reasons, the limit on the size of a single message in either direction between the client and server is 32Kb. Caché Direct does not split long messages into multiple shorter messages and cannot recover from an attempt to send a message that is too large. As a result, it is the responsibility of your application to make sure that no attempt is made to construct a message that exceeds this limit. To transmit more data than that, you send a series of messages. Tests have shown that there is no speed penalty for this arrangement, because TCP breaks large messages into small segments for transmission. All else being equal, optimum speed – in terms of total bytes per second – seems to be achieved with messages in the 12-20 Kb range.
To estimate message size, consider all characters in the VisM properties that are sent to the server, plus a few hundred bytes of overhead. The properties sent are the mirrored properties, plus a few smaller ones like CODE and NameSpace. A normal message has about a dozen fields plus the pieces of the PLIST property, which are each transmitted as a field. The data fields are one byte per character if the server is 8-bit or if the string is all Latin-1 characters. (Latin-1 characters have no bits on in the high byte of their Unicode representation. Such strings can be sent as 8-bit strings.) If the server is Unicode and the property contains any non-Latin-1 characters, the field contains two bytes per character. The result is that the maximum capacity of a message, instead of being about 30K characters, is less than half of that. A good guideline, which does not sacrifice any efficiency, is to make sure none of your messages are larger than about 12K characters.
Simple Example: A Lightweight Terminal
This is a minimal sample application that shows how to create a single client form in Visual Basic, where the user can enter a single line of ObjectScript and get its result. You will use defaults wherever they are available. There is also almost no error checking.
This sample assumes you have a Caché server running on your local machine and have also installed the Caché Direct components. Make sure Caché is running locally on your machine.
Start Visual Basic. Create a new Standard EXE project with one form. Using the Project/Components menu, add the VisM control to your toolbox. On your form, add a command button (Command1), two text boxes (Text1 and Text2), and a VisM control. Make the first text box wide enough to enter a simple line of ObjectScript and the second big enough to hold a result string. In the Command1_Click event code, enter the following:
VisM.Execute Text1.Text If VisM1.Error <> 0 Then Text2.Text = "Error " & VisM1.Error & ": " & VisM1.ErrorName Else Text2.Text = VisM1.VALUE Endif
In the Form_Unload method, add the following line:
VisM1.Server = ""
Run the project. When the form appears, click the command button. You should get a Choose Server Connection dialog. Choose LOCALTCP and press OK. Leave the Text1 field empty and click the Command1 command button. A syntax error message should appear in Text2, because you did not enter a line of ObjectScript.
Now enter "$H" in Text1. Click the command button again. The current date and time in $H format should appear in Text2.
Then exit the application.
When you click the command button, the contents of Text1 are temporarily put into the VisM Code property and sent to the server to be executed (by means of the VisM Execute() method). On the first click, because “Text1 ”(the default contents of the text box) is not valid ObjectScript, you receive a syntax error in the Error and ErrorName properties. The code in the Command1_Click event routine displays the error in Text2.
In the second case, the server can execute the code you provide, and it sets the VALUE property to the current $H. The code in the Command1_Click event routine again displays the contents of the VALUE property in the Text2 text box.
As you exit, the code in the Form_Unload event routine sets the Server property to the empty string, which disconnects the client from the server gracefully.
On the one hand, this is clearly a very simple example. On the other hand, it is also very powerful. Any line of ObjectScript can be executed on the client and any (small) result can be retrieved and displayed. A value from a global could be retrieved. A computation could be performed and the result returned. You could start a long-running background process via $JOB.
Consider the following example code:
VisM1.P0 = "pig" VisM1.Execute "Set VALUE=$e(P0,2,$l(P0))_$e(P0,1)_""ay""" Print VisM1.VALUE
This would result in the VALUE property being set to "igpay". In slightly more detail, the P0 property is sent to the server and becomes a local server variable named P0. The server executes the line of code, which computes a variable named VALUE, and sends a message back to the client. The client updates the VALUE property correspondingly and then prints it.