|Windows Client Development|
This section describes the typical steps that are necessary to develop applications using the SAS Integration Technologies Client in the Visual Basic environment.
ActiveX components can contain numerous classes, each with one or more programming interfaces. These interfaces can have methods and properties. The components can also have many enumeration constants which are symbolic names for constants that are passed or returned over the interface. For name scoping and management, each application defines its own group of these definitions into a type library. The type library is used by programming languages to check the correctness of calls to the component and it is used by COM when it creates the data packet which conveys a method call from one Windows process to another. (This data packet creation is called marshalling.)
You must reference at least two type libraries to access IOM servers. One is for base SAS software and the other is for the Workspace Manager. These type libraries get installed when you install the SAS System as a whole or when you install the SAS Integration Technologies client. However, before you can write Visual Basic code to call the base SAS IOM interfaces, you must reference these type libraries from within your Visual Basic project.
To reference these type libraries from Visual Basic:
After referencing these libraries, you can use them immediately. Also, if you reopen the references dialog, you will see that these libraries have been moved up to the top of the list with the other referenced libraries.
Each type library has a name, known as the Library Name, that is used in programming to qualify (or scope) all names (such as components, methods, constants, and so on) used within it. (This name is not necessarily the same as the name of the file containing the type library.) Visual Basic will look up the names in your program by going through the referenced type libraries in the order that they are listed in the references dialog. If two type libraries contain the same name, then Visual Basic will get its definition from the first library in the list, unless the name is qualified with a library name.
The base SAS IOM library name is "sas". You can use this to qualify any identifier defined in that library. For example, the base SAS library has a FormatService component. If another library in your application has its own identifier with the same name, then you would reference the SAS component by using sas.FormatService.
After the type library is referenced, its definitions become available to the Visual Basic Object Browser. From the Visual Basic development environment, the Object Browser is typically available via a toolbar icon, a menu selection or the F2 key.
The Object Browser can show all referenced type libraries at one time or can focus on any one of them. To focus on the base SAS IOM library, select "SAS" from the drop-down box in the upper left. The left pane of the Object Browser will then show only the component classes, interfaces and enumerations for SAS components. If you select a component (such as FileService) on the left, its methods and properties will be shown on the right. When you select an item in the right pane, more complete information (such as the parameters to pass to a method and a brief description of the method) are shown at the bottom.
Referencing a type library also activates Visual Basic Intellisense ™ for all programming language names in the library. As you begin to type a name like SAS.FileService, you will see the Visual Basic editor show you the list of possible names to complete your typing. As you code a method call, Visual Basic will show you each parameter that you need to provide.
For VB6 and later or Microsoft Office 2000 VBA or later, the Object Browser and the Visual Basic editor are connected to the Base IOM interface help file. In the Object Browser, if you click on the "?" button or press the F1 function key, you will see the help page for the selected item. In the editor, if you press F1 with your cursor on a name, you will get help for that name.
Your Visual Basic program’s interaction with the SAS System begins by creating a SAS.Workspace object. This object represents a session with the SAS System and is functionally equivalent to a SAS Display Manager session or the execution of base SAS software in a batch job. Using a workspace from Visual Basic requires that you declare an object variable to reference it. An example declaration is:
Dim obWS as SAS.Workspace
Note that this statement only declares a variable to reference a workspace. It does not create a workspace or assign a workspace to the variable. For creating workspaces, the SAS Integration Technologies Client provides the Workspace Manager. The Workspace Manager is an ActiveX component which can be used to create SAS Workspaces, either locally or on any server that runs the SAS Integration Technologies product.
If the server is remote, you can create a workspace using either the server's DNS name (such as "unx03.abccorp.com") or a logical name (like "FinanceDept") given to you by your system administrator. Please see SAS Workspace Manager for more information on creating workspaces on remote servers.
If the SAS System is installed on your local PC and you want to create a workspace that runs locally, it is very easy to use the Workspace Manager to create the workspace and assign it to your object variable. The example below shows you how this is done:
Dim obWSMgr As New SASWorkspaceManager.WorkspaceManager Dim obWS As SAS.Workspace Dim errString As String Set obWS = obWSMgr.Workspaces.CreateWorkspaceByServer("My workspace", VisibilityNone, Nothing, "", "", errString)
By using the keyword new, you instruct COM to create a SAS Workspace Manager and assign a reference to it when the object variable (obWSMgr in this case) is first used.
After you create the instance of the Workspace Manager, you can use it to create a workspace (SAS session) on the local machine. The Workspace Manager has a Workspaces collection which has a CreateWorkspaceByServer method for creating a new workspace.
The first parameter to this method is workspace name of your choice. The name can be useful when your program is creating several workspaces and must later distinguish among them. The next parameter, the visibility, can be used when you need to communicate a workspace to another component. You seldom need to do this except when working with ActiveX Data Objects (ADO). The VisibilityNone value indicates that the workspace will not be shared with other components.
The next parameter is an object to identify the desired server for the Workspace. Since we are creating a workspace locally, we pass the Nothing (a Visual Basic keyword) to indicate that there is no server object. The next two strings are for a user ID and password. Again, we don't need these to create a local workspace. The final parameter is a string which can return error information.
This method returns a reference to the newly created workspace.
Note:Whenever you assign a reference to an object variable, the assignment uses the Set keyword. If you do not use Set, Visual Basic will try to find a default property of the object variable and set that default property. This is very different than assigning an object reference to the variable itself.
You can prove that the above example program works by instructing the server to print its Internet Domain Name System (DNS) name. The following program creates a SAS workspace on the local computer, then prints the DNS name, and finally, closes the newly-created workspace.
Dim obWSMgr As New SASWorkspaceManager.WorkspaceManager Dim obWS As SAS.Workspace Dim errString As String Set obWS = obWSMgr.Workspaces.CreateWorkspaceByServer("My workspace", VisibilityNone, Nothing, "", "", errString) Debug.print obWS.Utilities.HostSystem.DNSName ObWS.Close
As you use other features of the SAS System, you will need additional object variables to hold references to objects created within your SAS Workspace. These are declared similarly. For example, if you want to assign SAS FILEREFs, then you may want an object variable to reference your workspace’s FileService. The declaration would be:
Dim obFS as SAS.FileService
Such declarations never use the keyword new because doing so asks COM to create the object. Object variables for objects within the workspace will always reference objects created by the Workspace, not COM. In fact, when you use new in a declaration, you will see that Intellisense provides a much shorter list of possibilities because it lists only those classes of object that COM knows how to create.
Then, to use the obFS variable to hold a reference to the FileService for the workspace referenced by obWS, the statement would be:
Set obFS = obWS.FileService
Again, note that the assignment of an object variable must use the Set keyword.
After you create an object variable and set it with an object reference, you can make calls against the object and access the object’s properties.
The first example assigns a SAS Libref using the IOM DataService.
' Create a workspace on the local machine using the Workspace Manager Dim obWSMgr As New SASWorkspaceManager.WorkspaceManager Dim obWS As SAS.Workspace Dim errString As String Set obWS = obWSMgr.Workspaces.CreateWorkspaceByServer("My workspace", VisibilityNone, Nothing, "", "", errString) ' Get a reference to the Workspace's DataService. Dim obDS as SAS.DataService Set obDS = obWS.DataService ' Assign a Libref named "mysaslib" within the new workspace ' (This is an example of a method call that returns a value) ' Note: you must have a "c:\mysaslib" directory for this to work Dim obLibref as SAS.Libref Set obLibref = obDS.AssignLibref("mysaslib", "", "c:\mysaslib", "") ' Should print "mysaslib" Debug.Print obLibref.Name ' Deassign the Libref. (An example of a method call that does not return ' a value) obDS.DeassignLibref obLibref.name ' Close the Workspace. (Another example of a method call that does not ' return a value.) ObWS.Close
This example illustrates three method calls and two property accesses.
In Visual Basic, method calls that use a return value are coded differently from those that do not. The AssignLibref call returns a reference to a new Libref object. Since the method returns a value and that value is being used in this call, the method must be called with parentheses around its arguments. The calls to DeassignLibref and Close, on the other hand, do not return a value. (Neither of these methods can return a value.) Thus, no parentheses are used following the method name. This is true even for DeassignLibref which passes a parameter in the method call.
When passing parameters, you must first understand whether the parameter is for input or output (also known as ByVal and ByRef in Visual Basic). Unfortunately, the Visual Basic Object Browser does not provide this information. In many cases, understanding the role of the parameters will make it obvious. For example, the Libname string passed to DeassignLibref is used by that method to know which Libref to deassign but the method does not update the parameter. Thus, it is an input parameter. If you are not sure which type of parameter is required, then you can consult the Help or documentation to find out.
Input parameters can be passed by a constant value (such as mysaslib for a string parameter or 0 for a numeric parameter) or an expression. They may also be passed using a variable whose value is taken as input to the method. This technique is used in the DeassignLibref call in the example. Output parameters require the use of variables that will be updated at the time the call returns.
Understanding the data type of the parameter is just as important as understanding the direction. The data type is listed in the Object Browser. If the provided constant, variable, or expression has the same type as the parameter, then no conversion is necessary. Otherwise, Visual Basic may try to convert the value. If you do pass a different type than the method actually defines for that parameter, make sure that you understand Visual Basic’s data conversion rules.
The parameters in IOM methods have a range of data types. Many are standard types such as String, Long, Short, Boolean and Date. Others are object types defined by a type library (such as SAS.Libref in the example above). Most parameters are designed to accept or return a single type of object. In these cases, it is best to use an object variable of that specific type. It is possible, however, to use variables declared with the generic object type Object. In some case, methods may return more than one type of object, depending on the situation. For example, the GetApplication method for a SAS Workspace may return different types of objects (based on the application name that was requested). It is declared to return the generic object type.
When a parameter can take some fixed set of integer values, its declaration will use an enumeration defined in the type library. For example, the Fileref:OpenBinaryStream method takes a parameter indicating the open mode of the stream. The type of this parameter is SAS.StreamOpenMode. In the current version of Visual Basic, you cannot declare a variable of this type. You must use a Long instead. The declaration is still important because it tells you the set of possible values and provides a constant for each. For example, if you want to open a stream for writing, you would pass a constant of SAS.StreamOpenModeForWriting.
Some Visual Basic procedures and subroutines take optional parameters. When calling such routines, you often pass the parameters by specifying their name (such as "color:="Red"). This feature of Visual Basic is not used by SAS IOM methods because it does not work naturally with other types of client programming environments including Visual C++. Therefore, it is best to simply list parameters in order without showing their names.
Many objects have properties that can be easily obtained simply by naming the property. Libref objects, for example, have a Name property which is used in the example above. Some properties are read-only, but others can also be set. Such properties can be set using a normal assignment statement. Keep in mind, however, that if the property type is an object type, then you will need to use Set with your assignment statement (as described in the previous section).
Because SAS servers will often be located on a platform that is remote from the machine running your Visual Basic program, much better performance is achieved by keeping the number of function calls to a minimum. This means that many IOM methods have a large number of parameters and accept arrays for those parameters. Visual Basic Intellisense technology is very helpful for keeping track of parameters as your code them. The next section describes how to pass arrays in Visual Basic method calls.
When the SAS System generates a listing, it may contain thousands of lines. If the IOM calls to read the listing returned only one line at a time, then many thousands of calls would be needed to fetch all of the output. In order to get better performance in situations like this, IOM methods make heavy use of arrays.
By returning an array of lines, the FlushListLines() method can potentially return all LIST output in one call. Similar situations occur in many other places in IOM including reading and writing files, getting and setting options and listing SAS Librefs and Filerefs. It is important, therefore, to understand when arrays are being used and how to declare and pass them.
Visual Basic arrays can be fixed-size or dynamic. An array dimensioned with a size is fixed size. An array dimensioned with no size (empty parentheses) or declared with the Redim statement is dynamic. For array output parameters you must use dynamic array variables because the size that SAS will return is not known when the array variable is declared.
The following example lists all Librefs in the workspace.
' Create a workspace on the local machine using the Workspace Manager Dim obWSMgr As New SASWorkspaceManager.WorkspaceManager Dim obWS As SAS.Workspace Dim errString As String Set obWS = obWSMgr.Workspaces.CreateWorkspaceByServer("My workspace", VisibilityNone, Nothing, "", "", errString) Dim obDS as SAS.DataService Dim vName as variant ' Declare a dynamic array of strings to hold libnames Dim arLibnames() as string Set obDS = obWS.DataService ' pass the dynamic array variable to "ListLibrefs". Upon return, ' the array variable will be filled in with an array of strings ' one element for each libref in the workspace obDS.ListLibrefs arLibnames ' Print each name in the returned array For Each vName in arLibnames debug.print vName Next vName ' Print the size of the array debug.print "Number of librefs was: " & Ubound(arLibnames)+1 ObWS.Close
In the object browser, you can tell that the ListLibrefs names parameter is an array of strings because it is shown as follows:
names() as string
The empty parenthesis indicate an array. A few of the array parameters in IOM require a two-dimensional array. The Object Browser does not distinguish the number of dimension. You must consult the help or documentation to determine this.
After the ListLibrefs call returns, there will be one array element for each Libref assigned in the Workspace. The For Each statement can be used to iterate through these elements. You must use variant as the type of the loop control variable.
In many cases, you will need to know the size of the returned array. Visual Basic allows arrays to be created with a particular lower and upper bound in each dimension In order to allow better compatibility with other client programming languages, all IOM calls require that the lower bound be zero. Input arrays will not be accepted if their lower bound is not zero. Output arrays will always be returned with a lower bound of zero.
The size of the array can be determined by checking its upper bound. For a one dimensional array, you can get the upper bound by passing the array name to the Ubound function. Since the array is zero-based and Ubound returns the number of the last element, you must add one to get the size of the array.
You can get the Ubound for each dimension of a two dimensional array by passing a dimension index. The following example illustrates this technique:
Dim table() Redim table(5,2) ' 6 rows (0 through 5) and 3 columns (0 through 2) debug.print "Number of rows: " & Ubound(table, 1)+1 debug.print "Number of columns: " & Ubound(table, 2)+1
When passing input arrays, you can pass a fixed size array if you know the size in advance. The following code provides an example:
' Create a workspace on the local machine using the Workspace Manager Dim obWSMgr As New SASWorkspaceManager.WorkspaceManager Dim obWS As SAS.Workspace Dim errString As String Set obWS = obWSMgr.Workspaces.CreateWorkspaceByServer("My workspace", VisibilityNone, Nothing, "", "", errString) Dim obLS as SAS.LanguageService ' Declare a fixed size array of strings to hold input statements Dim arSrc(2) as string ' Declare a dynamic array of strings to hold the list output Dim arList() as string ' These arrays will return line types and carriage control Dim arCC() as long, arLT() as long Dim vOutLine as variant arSrc(0) = "data a; x=1; y=2;" arSrc(1) = "proc print;" arSrc(2) = "run;" Set obLS = obWS.LanguageService ObLS.SubmitLines arSrc ' Get up to 1000 lines of output ObLS.FlushListLines 1000, arCC, arLT, arList ' Print each name in the returned array For Each vOutLine in arList debug.print vOutLine Next vOutLine ObWS.Close
The number of input statements are known in advance, so the arSrc array variable can be declared as a fixed size. (A dynamic array could also have been used.)
For some methods, you may sometimes want to pass an input array with no elements. Unfortunately, Visual Basic does not have syntax for creating either a fixed size array or a dynamic array with zero elements.
To work around this deficiency in Visual Basic, IOM methods allow you to pass an uninitialized dynamic array variable. When you do this, it is the same as if you had created an array with no elements.
Visual Basic does handle output arrays with no elements properly. When an array has no elements, the Ubound() function will return –1 and the For Each statement will not execute the body of the loop.
Older versions of Visual Basic (such as VBA in Office 97) do not support defining variables as enumeration types. For these older versions, you have to define them as type long. From the example above, consider the statement:
Dim arCC() as long, arLT() as long
Here, the variables arCC and arLT are being used as arrays of LanguageServiceCarriageControl and LanguageServiceLineType, respectively. However, since this program was written to execute in a Visual Basic for Applications (MS Office 97) environment, the variables are defined as arrays of long integers.
For newer versions (such as Visual Basic 6) you need to use enumeration types. To execute this program in a VB6 environment, the line would be changed to:
Dim arCC() as LanguageServiceCarriageControl Dim arLT() as LanguageServiceLineType
Note that the enumeration constants themselves are available for use in both environments. Given that arCC and arLT are defined appropriately for the environment, the following statements are valid in both the old and new versions.
arCC(0) = LanguageServiceCarriageControlNormal arLT(0) = LanguageServiceLineTypeSource
You should use the Workspace.Close method to close a workspace when you are done with it. This will also delete all objects within the workspace. For some types of objects, such as streams, you can be done with the object long before you are done with the workspace. These objects typically have their own Close method which removes the object and performs other termination processing (such as closing the file against which the stream is open). The lifetime of objects that do not have a Close (or similar) method, is managed by the Workspace in which the object is created.
In Visual Basic, you can release the reference held by an object variable by assigning the keyword Nothing to that variable. The syntax is simply:
ObStream = Nothing
Using this technique, it is possible to make Visual Basic release its references to an object. Doing so does not, however, delete the object because the Workspace still references the object.
As a special case, if your program releases its references to all of the objects in a SAS server, then the server will delete the workspace and all of the objects in it. This behavior is implemented to prevent excess SAS processes from being left on a machine when clients fail to close their workspaces properly. If a client program terminates abruptly or is killed by the user from the Task Manager, then COM notifies SAS of this at a later time. In current versions of COM this takes about six minutes.
Method calls sometimes fail. When they do, Visual Basic raises an error condition. If you want to write code that responds to error conditions, then you should code On Error Goto Next after each call. If you want to centralize the code that responds to error conditions, then code an On Error Goto with a label and place your error handling code at the label.
When an error occurs, COM and Visual Basic store the error in the err predefined variable of the type VBA.ErrObject. The two most important fields in this variables are Err.Number and Err.Description. Err.Number can be used by your program logic to determine what kind of error occurred.
Err.Description provides a text description of the error. It can also provide important details about the error. For example, in the case of an error in opening a file, the description will provide the name of the file that could not be opened.
Different errors are assigned different codes. IOM method calls can return many different codes. The code that they return are listed in various enumerations whose names end in "Errors". For example, the IOM DataService defines an enumeration called DataServiceERRORS in which the constants for all of the possible errors returned by the DataService are defined. The documentation and on-line help show the errors that a particular call is most likely to raise. In addition to those listed explicitly for the method, SAS.GenericError is always possible.
Some IOM objects can generate events. For example, The LanguageService generates the events DatastepStart, DatastepComplete, ProcStart, ProcComplete, SubmitComplete and StepError. In the Object Browser, you can recognize objects that generate events because the event procedures are marked using a lightning bolt icon.
Your Visual Basic program can implement procedures which receive these events if you declare the interface using the WithEvents keyword.
Dim WithEvents obLS As SAS.LanguageService
This declaration must be outside any procedure.
Once you have done this, the Visual Basic development environment will provide empty definitions for the event procedures. The following code segment is a definition that is provided for the ProcStart event, along with a line that has been added to print a debug message tracing the start of the procedure.
Private Sub obLS_ProcStart(ByVal Procname As String) Debug.Print "Starting PROC: " & Procname End Sub
Note that the event procedure name is of the form Object_Event.
In your routine which initializes your workspace variable, you should initialize the LanguageService variable using a statement such as:
set obLS = obWS.LanguageService
Once this initialization has occurred, your events procedures will be called whenever the SAS System fires an event.
|Windows Client Development|