============================================================================
 VX-REXX Tech Note #6
                                      Application Integration Using VX-REXX

                                                            August 09, 1994
    
                                            Applies to version 2.1+ VX-REXX
                     
----------------------------------------------------------------------------

                                                       Paul Prescod
                                                       WATCOM International

============================================================================

It is sometimes the case that another application has some functionality
that would be useful in your VX-REXX program.  Instead of recreating it,
you can often use the abilities of your applications as if they were
extensions of your VX-REXX program.  This technical note will explain the
facilities that VX-REXX provides for starting applications and controlling
them via DDE and keystroke pushing.

Launching Applications Using "Start"
-----------------------------------

It is possible to issue any OS/2 command from VX-REXX, just by enclosing
it in quotes at the beginning of a line.  This is analogous to typing 
the command on the command line.  This allows you to use the OS/2
start command to launch applications without pausing your VX-REXX
program.  

Example:
"start e.exe"

Start can also be used to run non-Presentation Manager programs, such as
DOS,  Windows, or OS/2 text mode programs.  For more information on
starting OS/2 text mode programs, see VX-REXX Technical Note 3, 
vxtech03.txt (vxt03.txt on CompuServe).

Start has many useful options.  For instance, you can run programs in
the foreground or background, and maximized or minimized.  For
information on these options, use the command "help start" on an OS/2
command line (without quotes).

    Things to Watch Out For
    
It is not strictly required that you put quotes around the entire statement
that you are executing.  Nevertheless, put as much in possible in quotes.
Otherwise you will cause hard to track down errors where REXX interprets 
your code in a manner different from what you expected.  The quotes instruct
REXX not to interpret the text.

Occasionally, you need more control than start provides.  You might, for
instance, need to start a Windows program that requires a particular
WinOS2 session setting, like "DOS_LASTDRIVE=X".  If you need that kind
of fine tuning, you can use the Workplace Shell object functions.


Launching Applications Using the Workplace Shell
-----------------------------------------------
The OS/2 Workplace Shell provides a set of functions for object
maintenance and manipulation.  These functions are provided in the REXX
Utility Functions DLL, (REXXUTIL.DLL).  One of things you can use it for
is to create a program object and invoke it.  This gives you access to
many of the settings in the OS/2 settings notebooks.  To get access to
the functions, you must first register REXXUTIL.DLL.  These two
lines do that:

call RXFuncAdd "SysLoadFuncs", "RexxUtil", "SysLoadFuncs"
call SysLoadFuncs

RexxUtil provides many other useful functions to REXX programmers.  For
more information, read the section "REXX Utility Functions (RexxUtil)"
in the "REXX Reference" in the VX-REXX folder.

SysCreateObject is one of the RexxUtil functions.  It creates a
Workplace Shell (WPS) object with the settings that you specify.  Before
going into SysCreateObject's parameters, we need to briefly explain the
terminology of the Workplace Shell.


    WPS Objectids

WPS objects like your OS/2 System folder and the Desktop have unique
identifiers, known as objectids.  These are analagous to VX-REXX object
handles.   Objectids are of the form <WP_??????>.


    WPS Object Locations

All WPS Objects have a location.  For instance, many of your objects are
located in the Desktop (<WP_DESKTOP>) folder.  Other objects  are located 
in the System Setup (<WP_CONFIG>) folder.


    WPS Object Classes

You cannot create an object without specifying its class.  The class
defines what kind of object we are dealing with, like a shredder
(WPShredder), a shadow (WPShadow), or a program object (WPProgram).  We
are primarily interested in objects of the class WPProgram.


    Transience and Persistence

In general, WPS objects are "Persistent."  That means that if you do a
proper shutdown, the next time you start your machine they will still be
there.  Other objects are destroyed when the system is shutdown.  These
non-persistent objects are called "transient."  If you want to make a
transient object, one way that you can do so is by placing it in the
<WP_Nowhere> folder.

    Settings

Object settings are specified using key value pairs, like this:

"Title=MyObject;Template=Yes;objectid=<MyObject>"

The most comprehensive list of key/value pairs currently available is in
Dick Goran's "REXX Reference Summary Handbook." DOS and WinOS2 settings
are specified a little differently, like this:

"SET DPMI_DOS_API=disabled;SET DOS_FILES=45;"

These two setting formats can be intermixed.



    Back to SysCreateObject

Now that we have the required background, here is the syntax for
SysCreateObject:

ok = SysCreateObject( class_name, title, location, setup_string, option)

For our purposes the class_name will always be "WPProgram".  The title
can be any string you like.  Location will usually be either
<WP_DESKTOP> if you want a persistent object, or <WP_NOWHERE> if you
want a transient one.

Option is either "fail", "replace" or "update".  It specifies what
happens if an object with the same objectid already exists.  Only one 
object can exist with a given objectid. If two objects have the same 
objectid, and you specify "Replace", the old object is destroyed.  If you 
specify "Update", the old object remains, and the properties you specified 
are updated to the new values.  For permanent objects, try to make your
objectids unique to avoid this problem.  For transient objects, there is
no need to specify an objectid, unless you are going to want to refer to
them later.


Here is a typical SysCreateObject invocation:

objectid= "<__Temporary__>"
setup = "EXENAME=d:\os2\mdos\winos2\calc.exe;" ||,
        "PROGTYPE=WINDOWEDWIN;" ||,
        "OPEN=DEFAULT;"
        "OBJECTID="objectid

ok = SysCreateObject( "WPProgram", "My Object", "<WP_NOWHERE>", setup, "r"  )

If you would rather see the object being created, you can do so by
changing its location to <WP_DESKTOP>.  Do not forget to specify a unique
objectid though, because objects on the desktop are persistent.  For more
information on WPS object classes, locations, objectids and setup strings,
refer to the "REXX Reference Summary Handbook."

There is also a SysSetObjectData function that can be used to change the
setup strings for existing object, or activate it.  You need to know the
objectid for the object to use SysSetObjectData.  Once you know that, you
can send any of the object's setting stringslike this:

ok = SysSetObjectData( "<My_AmiProObject>", "Open=Default" )

This particular example is analogous to double clicking on the object on the
Workplace Shell. For more information on SysSetObjectData, look in the 
online REXX Reference in the VX-REXX folder.

    Things to Watch Out For
    
Objectids are very important.  If you have an object without an objectid
you cannot manipulate it from REXX, except in the SysCreatObject where it
was created.  So if you are going to want to use an object more than once 
(in the SysCreateObject call), then you should assign it an objectid.  If 
you have an object that was created without an objectid (i.e. from a 
template object), then you must get a third party product to retrieve the 
objectid before working with it.  Contact WATCOM for a current list of 
products that can do this for you.

    Sending Keystrokes to Applications
    ==================================

Once an object has been started, it opens a Presentation Manager window.  
Often VX-REXX applications can interact with other applications through this 
window.  Our goal in this section is to completely emulate a human
interaction entirely in VX-REXX code.  We will learn how to

#1.Find the window we want.
#2.Activate it.
#3.Type into it.


    Presentation Manager Windows

When a program opens a window on the desktop, that window is a Presentation 
Manager window.  The resizing borders make up what is known as the frame 
window.  The other objects are known as its child windows.  Frame windows
are analogous to VX-REXX window objects. Note the slightly different 
terminology:

VX-REXX                PM
=======                ==
Object                 Window
Window                 Frame Window
Other Objects          Child Windows

PM programs often have many child windows, like entry fields, menus,
title bars etc.  VX-REXX programmers can work directly with these
child windows, filling in entry fields, scrolling listboxes etc., but you
must have the window's handle.

WinOS2 programs do not have child windows.  Therefore you can only
manipulate the frame window.


    HWNDs
    
Windows have handles just as WPS objects have objectids and VX-REXX
objects have object handles.  These window handles (hwnds) are unique
strings that represent an object.  You must have this string to
manipulate the object.  You can get the hwnd from the ListWindows or
FindWindow methods.  Usually it is as
easy as this:

hwnd = VRMethod( "Screen", "FindWindow", "Lotus Ami Pro", "Desktop" )

There is more information on FindWindow and ListWindows in the VX-REXX
Reference.  Note that we have accomplished our first goal, finding the
window in question.

At this point, you have the handle for the frame window.  You can do
many of the same things with PM frame windows as you can with VX-REXX
window objects.  You can minimize, maximize, restore and activate them,
for example.  There is more information in the VX-REXX Reference under
the heading "the hwnd property".


    An Introduction to Sending Keys

You can also send keystrokes to the frame window.  Frame windows only
respond to the keys in their SystemMenu, the menu that drops down from
the icon in the top left corner.  These keys include {Alt}{F4} and
{Ctrl}{Esc}.

So to close a window we could send:

hwnd = VRMethod( "Screen", "FindWindow", "Lotus Ami Pro", "Desktop" )
call VRMethod "Application", "SendKeyString", hwnd, "{Alt}{F4}"

To do more interesting things, we want to send keystrokes directly to the 
child windows, entry fields, menus, MLEs etc.  

We could find these objects by using the FirstChild and Sibling
properties,  but we do not have to. Ideally, we would emulate the human
interaction where you just bring a window to the forefront and start
typing.  The cursor is usually already in a logical place (an MLE or
entry field), and we can use the {tab} key to move around.  We can in
fact do that using the "Activate" and "SendKeyString" methods, like
this:

hwnd = VRMethod( "Screen", "FindWindow", "Lotus Ami Pro", "Desktop" )
call VRMethod hwnd, "Activate" 
call VRMethod "Application", "SendKeyString",, "Hello World"

Notice that we left out the "Hwnd" parameter in the last method
call.  If you leave out that parameter, VX-REXX sends the
keystrokes to the focus object, just as if you had typed it in
yourself.  Plus you can send the {tab} key to move to the next
field if applicable.  You could also send cursor keys to move in
different directions. 

Recapping, the FindWindow method finds a window, the Activate method 
brings it to the front, and the SendKeyString method simulates the user 
typing.

Because seamless WinOS2 windows do not have PM children, you cannot send
keystrokes to the child windows.  You can only send keystrokes to the frame
window, which is in fact, a PM window. As noted above, the frame window
only responds to a few keystrokes directly related to window manipulation.

The Window Controller example program is a good way to familiarize yourself 
with PM windows, window handles, and sending keystrokes.

VX-REXX Technical Note #2 has information on sending keystrokes to DOS and 
OS/2 text mode programs.  This can be found on the Watcom BBS as 
vxtech02.txt and on CompuServe as VXT02.TXT


    Things To Watch Out For
    
Sometimes it takes time for a dialog to appear.  You may have to insert 
delays to make sure that your keystrokes go to the right window.  It is a
good policy to make certain that the window you want is activated before
sending keystrokes to it.  The code above does that, but it does not check
to make certain that FindWindow returned a legitimate handle.  You should
always check this in your code.  

If FindWindow could not find the window it will return the null string 
("").  When it fails you probably want to pause for a second using SysSleep 
and try again in a loop, waiting for it to appear.  It is a good policy to 
have this loop "timeout" at some point, and alert the user that the required 
window never appeared.

Bear in mind that you are working on a multitasking operating system.  If
someone else changes the focus just before you send keystrokes, they will
go to the wrong window.  "Some else" could be the user or another program.
There is no real way to programmatically prevent this.  The user should not
use the computer while it the program is executing.

Also remember that captions do not have to be unique.  If you have two 
windows with the same caption, you must find some way to differentiate 
between them, because FindWindow will not.  It is often a good idea to 
deliberately manipulate the caption of the window so that it is easier to 
differentiate.


DDE
===

While keystroke pushing is a powerful tool, it is a fairly indirect way to 
get what we want.  Basically we are making the REXX program pretend to 
be a user to talk to the application. DDE provides a way that programs can 
communicate directly.  VX-REXX programs can also use DDE to manipulate
Windows programs.

Using DDE is outlined in the Using Objects section of the Programmer's
Guide.  DDE objects can only be involved in one conversation at a time.
You use the Initiate method to initiate conversations with different
servers and on different topics.  Then you choose the one that interests
you and accept it.  Usually, you will start out knowing both the server
and the topic that interests you.  You can get that information from the
application vendor.  Here is the code for a typical connection:

    call VRMethod "DDEC_1", "Initiate", "convs.", "AmiPro", "System"
    call VRMethod "DDEC_1", "Accept"

The last two arguments for "Initiate" are optional.  If you do not know
who you want to speak to, or what topic you want to speak on, you can
leave one or both blank.  In this case we knew that we wanted to talk to
AmiPro's System topic.  All applications have a System topic.

Often the topic will be the file name that you want to work with.  If it
is not loaded, you could have a conversation on the System topic, and
instruct it to load the file.  The instruction to load the file varies
from application to application.  Usually it is the same as the
instruction in the application's macro language.  For instance, for Ami
Pro:

ok = VRMethod( "DDEC_1", "Execute", '[FileOpen("D:\amipro\docs\mercury.sam",,
              1,"")]' )  /* extra comma for continuation */
              
ok = VRMethod( "DDEC_1", "Execute", '[type("hello from DDE[Enter]")]' )

Note how we use single quotes to surround the command, so that we can
embed double quotes.  Also notice the square brackets ("[]" ) Many DDE
servers require them, but not all.

To end our conversation we use the Terminate method.

    call VRMethod "DDEC_1", "Terminate"

Some applications can be shut down via DDE (before you terminate) and others
must be shut down using keystrokes.


    Finding Out More

The DDE protocol only defines the mechanism for sending commands and
setting up links.  For the most part, you will have to consult with the
documentation for the application for the actual commands to send and the
format for linking.  It is often the case that this information is only
available in the online help or directly from the vendor.  Every
application behaves slightly differently, so it may take some trial and
error to get it to work.  The DDE Explorer sample program is provided
for that purpose.

We have also provided the EmpList example that shows a few of the DDE
features of some of the applications on the market.  To find out
about working with wordprocessors, you should look at the Reports file.
There are functions there to launch, insert text, print and close four
popular wordprocessors.  The Chart file to spreadsheets.  There are 
functions to launch them, manipulate data, chart it, and close the 
spreadsheet.

    Things to Watch Out For
    
Some DDE servers require brackets and others do not.  Check with the vendor
for more information. 

Some servers do not provide all of the functionality that you need to 
accomplish your goal in their DDE command set.  You can use keystroke 
pushing to augment the DDE facilities.

DDE to windows programs usually works fine, however you must WIN_DDE set to 
"on" in your WIN-OS/2 settings.

The DDEWaitT function in the emplist example shows how to simulate a 
"timeout".  Use this in cases where you have an uncooperative DDE server 
(for instance one that is prone to crashing).


REXX Aware Applications
=======================

If you have a REXX aware application, you can work with it just by calling
the provided functions.  The functions that are available to you should
be in the vendor documentation.  Vendors who are interested in making
their applications REXX aware can contact Watcom for information.

Lotus Ami Pro is REXX aware.  A separate document outlines the use of 
VX-REXX to make graphical applications on top of Ami Pro.  This can be 
downloaded from Compuserve or the Watcom BBS as vxami.zip.
