Compaq TP Desktop Connector
for ACMS
Client Application Programming Guide


Previous Contents Index

8.2.2.1 Sharing Sessions Across Client Programs and HyperCard Applications

You can share a session between one or more client programs on the Macintosh. However, to do so, the program that is adopting the session must issue three special initialization calls to the Communications Toolbox. These calls are:

You must issue these calls before you issue any DAM calls for the adopted session. Failure to do so could cause your Macintosh to crash. The calls must be issued in the same order as listed above. The following code sample illustrates the use of these calls in C:


#include <CRMIntf.h> 
#include <CTBUtils.h> 
#include <CMIntf.h> 
 
OSErr InitToolbox() 
    { 
    CRMErr CRMstatus; 
    CTBUErr CTBUstatus; 
    CMErr CMstatus; 
 
    CRMstatus = InitCRM(); 
    if (CRMstatus != noErr) 
        return CRMstatus; 
    CTBUstatus = InitCTBUtilities(); 
    if (CTBUstatus != noErr) 
        return CTBUstatus; 
    CMstatus = InitCM(); 
    return CMstatus; 
    } 

The uses of these calls are explained in PATHWORKS for Macintosh Communications Toolbox Programmer's Reference.

You can also share sessions between HyperCard applications and programs written in some other language. A program written in some other language can adopt a session started by HyperCard.

If a HyperCard application wants to adopt a session written in some other language or from an earlier execution of HyperCard, the application must issue an XFCN call, ACMSInitComm(). Include the ACMSInitComm XFCN as an XFCN resource in your HyperCard stack. Look in the XFCN_resources program in the HyperCard Externals folder for this XFCN. The folder also contains the source code for the ACMSInitComm XFCN.

Any HyperCard stack can adopt a session started by another HyperCard stack in the same execution of HyperCard. Therefore, you do not need to issue an ACMSInitComm call in this case.

8.2.2.2 Macintosh Handling of Password Expiring Messages

During DBInit processing, the TP Desktop Connector gateway can return a message warning users that their OpenVMS password is expiring. This is simply a warning message. The sign-in attempt succeeds. Your application can display this message to the user, perhaps with a dialog box requesting acknowledgment, and continue processing.

The message identifier is ACMSDI_PWDEXPIRING (--3100). As a convention, all messages from the gateway with identifiers of --3100 and lower (that is, --3101, --3102, and so on) are considered warning messages.

When the front end receives the ACMSDI_PWDEXPIRING message, the Data Access Manager returns a status of rcDBError (--802). The number of hours until password expiration is passed from the gateway with this message. The DDEV appends this number to the message literal when it is retrieved by a DBGetErr call. DBGetErr returns the following:

The sample program, acmsdi_mac_test.C, located in the Example Folder, has an error-handling routine named my_exit, which illustrates one method of handling warning messages from the gateway.

8.2.2.3 Starting a Session Following Program Termination

If your client program terminates leaving a session open (that is, without calling DBEnd for the session) the Communications Toolbox does not allow that program to start a new session until the old session is ended. This is true even though it is possible for a given program to start multiple sessions.

When the program is restarted and calls DBInit, one of the calls made by the DDEV to the Communications Toolbox is CMNew to acquire a communications record. The Communications Toolbox senses that the program has terminated, leaving one or more sessions open, and returns a null pointer to the communications record with no error status. The program receives a rcDBError (--802) status from DAM with an item1 status of rcDBANewConFai (--4018) and an item2 status of cmNoErr (0).

To fix this problem, you can acquire the old session with a different program and end it. There are two programs on the TP Desktop Connector for ACMS kit that allow you to do this. One is the HyperCard stack Search and Destroy. The other is a program written in C called end_session. Both programs terminate active sessions. However, end_session terminates all active sessions, whereas Search and Destroy allows the user to choose whether or not the session is to be terminated.

Another way to fix the problem is to reboot your Macintosh.

8.2.3 Calling a Task

After the desktop client program successfully signs a user in to an ACMS system with the DBInit service, it can call ACMS tasks. In the task call, the desktop client program uses the unique session identification provided by DBInit.

To call tasks, the desktop client program does the following:

  1. Gets needed information, for example, the task to call and the data to be passed to that task.
  2. Performs any necessary data conversion.
  3. Calls DBSend or DBSendItem for each message item to build the message to be sent to the ACMS system.
  4. Calls DBExec to request the execution of that task.

The multiple calls to DBSend or DBSendItem must be done in a fixed order to place the following parameters in the message:

You need to issue one DBSend or DBSendItem for each workspace to be sent to the ACMS system. The workspaces exchanged with the ACMS system must correspond in format and length to the workspace definitions in the task that the desktop client program is calling. If workspace data contains multiple fields of differing data types, convert nontext elements to an appropriate OpenVMS data type before calling DBSend or DBSendItem.

Example 8-5 shows the coding.

Example 8-5 Calling DBSend and DBExec

short           status;                         /* Status Variable       */ 
long            sessid1;                        /* Session Id            */ 
#define keyword         "CALLTASK"              /* Calltask              */ 
#define taskname        "YOUR_TASK_NAME"        /* ACMS Task Name        */ 
#define appname         "YOUR_APPLICATION_NAME" /* ACMS Application Name */ 
#define selstring       ""                      /* ACMS Selection String */ 
#define wscount         "1"                     /* Number of Workspaces  */ 
 
unsigned short  int     wksp_len = 1024;        /* Workspace Length */ 
 
struct wksp                                     /* Workspace Data */ 
{ 
    char data [1024];                           /* text */ 
 
} wksp; 
 
 
 
    /* 
     *      KeyWord 
     */                                         
                                
    status = DBSend (sessid1, keyword, (sizeof keyword)-1, NULL); 
    if (status != noErr) 
            my_exit (status, sessid1, "DBSend Failed at keyword"); 
    /* 
     *  ACMS Taskname 
     */ 
 
    status = DBSend (sessid1, taskname, (sizeof taskname)-1, NULL); 
    if (status != noErr) 
            my_exit (status, sessid1, "DBSend Failed at taskname"); 
    /* 
     *  ACMS application Name 
     */ 
 
    status = DBSend (sessid1, appname, (sizeof appname)-1, NULL); 
    if (status != noErr) 
                    my_exit (status, sessid1, "DBSend Failed at appname"); 
 
    /* 
     * ACMS Selection string 
     */ 
 
    status = DBSend (sessid1, selstring, 0, NULL); 
    if (status != noErr) 
            my_exit (status, sessid1, "DBSend Failed at selstring"); 
 
    /* 
     *Number of workspaces 
     */ 
 
    status = DBSend (sessid1, wscount, (sizeof wscount)-1, NULL); 
    if (status != noErr) 
            my_exit (status, sessid1, "DBSend Failed at wscount"); 
 
    /* 
     *Workspace Length and Workspace 
     */ 
 
    status = DBSend (sessid1, wskp.data, wksp_len, NULL); 
    if (status != noErr) 
            my_exit (status, sessid1, "DBSend Failed workspace"); 
 
    /* 
     *      Initiate execution of the message you just 
     *      built above on the remote ACMS system. 
     */ 
 
    status = DBExec (sessid1, NULL);    
    if (status != noErr) 
            my_exit (status, sessid1, "DBExec Failed"); 

The DBSend and DBSendItem services prepare a request for transmission to the ACMS system. They do not entail any interaction with the network or with an ACMS application. Therefore, if errors are returned on the DBSend or DBSendItem service, they are probably a result of programming errors in the desktop client program.

The DBExec service initiates transmission of the request to the ACMS system and returns control to the desktop client program. While waiting for the requested task to complete, the desktop client program can continue with local processing for other sessions or return control to the Macintosh operating system. If errors are returned on the DBExec service, they are probably a result of programming errors, such as providing an insufficient number of workspaces. Errors occurring during execution of the ACMS task are not returned on the DBExec service call.

DBSendItem is called by the ACMSSendItem and ACMSSendWorkspace HyperCard XFCNs. DBExec is called by the ACMSExec XFCN. Example 8-6 shows the calls to these XFCNs.

Example 8-6 Calling ACMSSendItem, ACMSSendWorkspace, and ACMSExec

on resdetails fnction 
  
  global currentsession 
   .
   .
   .
  -- Call the task VR_RESERVE_RES_DETAILS_TASK in application 
  -- VR_DA_APPL 
  
  ------------------------------------------------- 
  -- Create  the CALLTASK header. 
  ------------------------------------------------- 
  set cursor to busy 
  put "VR_RESERVE_RES_DETAILS_TASK" into tskname 
  put "VR_DA_APPL" into apname 
  put "9" into wscnt 
  put empty into selstr 
  put SendHeader (currentsession,tskname, apname, wscnt, selstr) into status 
  if status <> 0 then 
    put GetErr (currentsession,"SendHeader") into stat 
    dispsenderr 
    exit resdetails 
  end if 
   .
   .
   .
  ---------------------------------- 
  -- Build and send the control wksp 
  ---------------------------------- 
  set cursor to busy 
  put SendControlWksp (currentsession) into status 
  if status <> 0 then 
    put GetErr (currentsession,"Send  control workspace") into stat 
    dispsenderr 
    exit resdetails 
  end if 
end resdetails 
function sendheader currentsession, taskname, applname, wscount, selstring 
  
  put empty into selstring 
  put ACMSSendItem (currentsession,0,8,0,0,"CALLTASK") into laststatus 
  if laststatus <> 0 then 
    return laststatus 
  end if 
  put ACMSSendItem (currentsession,0,length(taskname),0,0,taskname) 
  into laststatus 
  if laststatus <> 0 then 
    return laststatus 
  end if 
  put ACMSSendItem (currentsession,0,length(applname),0,0,applname) 
  into laststatus 
  if laststatus <> 0 then 
    return laststatus 
  end if 
  put ACMSSendItem (currentsession,0,length(selstring),0,0,selstring) 
  into laststatus 
  if laststatus <> 0 then 
    return laststatus 
  end if 
  put ACMSSendItem (currentsession,0,length(wscount),0,0,wscount) into 
  laststatus 
  if laststatus <> 0 then 
    return laststatus 
  end if 
  return laststatus 
end sendheader 
function SendControlWksp currentsession, ctrlkey 
  global ctrl_key 
  --     ctrl_key, text, 5 
  --     current_entry,unsigned word,short 
  --     vr_status, long 
  --     messagepanel, text, 80 
  --     taskname, text, 31 
  --     rs_stat_flag, text, 1 
  put ctrlkey into ctrl_key 
  put "TEXT,5,0,G,ctrl_key"&return into reschunk 
  put "0,6,0,X,0"&return after reschunk 
  put "TEXT,112,0,X, " after reschunk 
  put ACMSSendWorkspace (currentsession,0,123,0,reschunk) into laststatus 
  return laststatus 
end SendControlWksp 
  ----------------------------------------- 
  --    Call ACMSExec 
  ----------------------------------------- 
  set cursor to busy 
  put ACMSExec (currentsession) into status 
  if status <> 0 then 
    put  GetErr (currentsession,"ACMSExec") into stat 
    dispsenderr 
    exit resdetails 
  end if 

8.2.3.1 Using Unidirectional Workspaces

The TP Desktop Connector gateway uses the flags passed from DBSendItem in the client program to decide whether a workspace is read-only, write-only, or modify.

To use unidirectional workspaces, include the ACMSDI_MAC.H file in your C program. This file contains flag settings for DBSendItem as follows:


#define kDBReadOnly   0x8000  /* identifies read only data */ 
#define kDBWriteOnly  0x4000  /* identifies write only data */ 

You specify the type of workspace in your application by specifying the flags parameter in the DBSendItem function. The flags parameter on DBSendItem is a short integer in DDEV.H; therefore, include a short integer in your code as follows:


unsigned short flags; 

To set a READ ONLY flag, use the following statement:


flags = (0 | kDBReadOnly); 

To set the WRITE ONLY flag, use the following statement:


flags = (0 | kDBWriteOnly); 

Both statements set the specified flag on and all other flags off. To set just the read-only flag on without affecting any other flag setting, use the following setting:


flags |= kDBReadOnly 

Because the READ ONLY flag and the WRITE ONLY flag are mutually exclusive, be sure to set the other flag off. Use the following statement to set the WRITE ONLY flag to off without affecting any other flag:


flags &= (0xffff - kDBWriteOnly) 

To send a modify workspace, use the DBSendItem function with both flags set off. Set both flags off by using the following statement:


flags = 0; 

Once you have set the flags parameter properly, use a DBSendItem to send the workspace to the DDEV. The following example sends a read-only workspace:


flags = (0 | kDBReadOnly); 
status = DBSendItem (sessID,       /* session ID */ 
                     NULL, 
                     wksp_len,     /* workspace length */ 
                     NULL 
                     flags,        /* flags */ 
                     wksp_data,    /* workspace buffer */ 
                     NULL); 

To send a write-only workspace to the DDEV, specify the expected length of the workspace without the workspace address. The following example sends a write-only workspace:


flags = (0 | kDBWriteOnly); 
status = DBSendItem (sessID,      /* session ID */ 
                     NULL 
                     wksp_len,    /* workspace length */ 
                     NULL, 
                     flags,       /* flags */ 
                     NULL,        /* no buffer pointer */ 
                     NULL); 

To send a modify workspace, use the DBSend function as illustrated in the following example:


status = DBSend (sessID, buffer, wksp_len, NULL); 

Example 8-7 demonstrates how to implement unidirectional workspaces using HyperCard XFCNs.

Example 8-7 Unidirectional Workspaces with HyperCard XFCNs

global flag 
 
-- 
--      Sets flag for a modify workspace 
-- 
put "0" into flag 
 
-- 
--      Sets flag for a read-only workspace 
-- 
put "1" into flag 
 
-- 
--      Sets flag for a write-only workspace 
-- 
put "2" into flag 

Once you have set the flags properly, use ACMSSendItem, ACMSSendWorkspace, or ACMSSendResWorkspace to send the workspace to the DDEV. The following example sends a read-only workspace from the client to the back-end gateway:


put "1" into flag 
put ACMSSendItem( gSessID, 0, wksp_len, 0, flag, wksp_data ) into bg fld 
"error" 

To send a write-only workspace to the DDEV, specify the expected length of the workspace without the workspace address. The following example sends a write-only workspace:


put "2" into flag 
put ACMSSendItem( gSessID, 0, wksp_len, 0, flag, Nil ) into bg fld 
"error" 

8.2.4 Receiving Information from a Task

The desktop client program uses the following TP Desktop Connector client services to receive information about outstanding task requests initiated by the DBExec service:

8.2.4.1 Checking for a Message

The DBState service returns the success statuses shown in Table 8-4.

Table 8-4 DBState Success Statuses
Status 1 Meaning
rcDBValue ACMS task completed successfully and data can be retrieved with the DBGetItem service.
rcDBNull ACMS task completed successfully and no data (no workspaces) is returned.
rcDBExec ACMS task is still executing.


1DBState does not return the NoErr status.

If the rcDBError status is returned, call the DBGetErr service to determine the cause of the error and whether it is recoverable (see Section 8.2.6). Any other status returned on the DBState service is probably a result of programming errors and is not recoverable. Example 8-8 shows how to use the DBState service to wait for task completion.

Example 8-8 Handling DBState Status on Task Completion

short       status;                     /* Status Variable */ 
long        sessid1;                    /* Session Id   */ 
Boolean     gotEvent;                   /* Event Flag */ 
EventRecord myEvent;                    /* My Event Record */ 
long        everyEvent=0;               /* Every Event */ 
/* 
 *      Check State to see if remote database server 
 *      has successfully executed your query and whether 
 *      it has data available for you to retrieve 
 */ 
while (status != rcDBValue) 
{ 
    status = DBState (sessid1, NULL); 
    switch (status) 
    { 
          case rcDBNull:                 /* No data returned - This is an error */ 
                  my_exit (status, sessid1, "DBState Null Data Returned"); 
                  
          case rcDBExec:                  /* Data Not Ready Lets Wait */ 
                  gotEvent = WaitNextEvent( everyEvent, &myEvent, 3, nil); 
                  break; 
          case rcDBValue:                  /* Date available */ 
                  break; 
          default:                        /* Error */ 
                  my_exit (status, sessid1, "DBState Failed"); 
                 
    } 
} 

Example 8-8 releases control using the WaitNextEvent function while the task is still executing. This example assumes that no workspaces are retrieved.

Note

If you do not use the WaitNextEvent function, you defeat the nonblocking nature of the Macintosh.

In Example 8-8 an error condition exists if the task completes without returning workspace data to the desktop application. Therefore, if DBState returns rcDBNull, the program routines and error routine (my_exit) to display the error to the user and to terminate the connection without returning.

DBState is called by the ACMSState XFCN. Example 8-9 shows the call to the ACMSState XFCN.

Example 8-9 Calling ACMSState

  ----------------------------------------- 
  --    Call ACMSExec and ACMSState 
  ----------------------------------------- 
  set cursor to busy 
  put ACMSExec (currentsession) into status 
  if status <> 0 then 
    put  GetErr (currentsession,"ACMSExec") into stat 
    dispsenderr 
    exit resdetails 
  end if 
  set cursor to busy 
  put ACMSState (currentsession) into status 
  if status < -801 then 
    put  GetErr (currentsession,"ACMSState") into stat 
    dispsenderr 
    exit resdetails 
  end if 

The WaitNextEvent is performed in the XFCN.

8.2.4.2 Receiving Task Results

When the DBState service returns the rcDBValue return status, the desktop client program uses the DBGetItem service to retrieve the workspaces from the task. Example 8-10 shows the coding for receiving the workspace.

Example 8-10 Calling DBGetItem

short           status;                     /* Status Variable */ 
long            sessid1;                    /* Session Id   */ 
unsigned short  int     wksp_len = 1024;    /* Workspace Length */ 
 
struct wksp                                 /* Workspace Data */ 
{ 
    char data [1024];                       /* text */ 
 
} wksp; 
 
   .
   .
   .
while (status != rcDBValue) 
{ 
   .
   .
   .
} 
status = DBGetItem (sessid1, 
                    15, 
                    NULL, 
                    (short *) &wksp_len, 
                    NULL, 
                    NULL, 
                    (Ptr) &wksp.data, 
                    NULL); 
 
if (status == rcDBValue) 
     printf (" Data Length is %d Data Value is %s\n", wksp_len, &wksp.data); 
else 
     my_exit (status, sessid1, "DBGetItem Failed"); 

The DBGetItem service retrieves data from workspaces. Any workspace specified as read-only is not returned. The amount of data retrieved is controlled by the length parameter. DBGetItem returns data sequentially from the workspace buffer. This allows the desktop client program to retrieve an entire workspace, multiple workspaces, or individual fields of a workspace. The next call to the DBSend or DBSendItem service discards any unretrieved data. Data conversion is the responsibility of the desktop client program.

With the DBGetItem service, you can bypass retrieval of all or part of the data associated with a workspace by passing a NULL workspace data pointer. The current location within the workspace that the DDEV maintains is bumped by the length you specify on the DBGetItem call without returning any data. The next DBGetItem retrieves data starting at the new position.

Use the second parameter of the DBGetItem call to specify a time-out value in seconds. In Example 8-10, the time out value is 15 seconds. This value tells the DDEV that if it is not able to retrieve the requested data in 15 seconds, abort the call and return an error status. If you specify NULL or 0, the time-out value defaults to 60 seconds.

DBGetItem is called by the ACMSGetWorkspace and ACMSGetResWorkspace XFCNs. Example 8-11 shows the call to the ACMSGetWorkspace XFCN.

Example 8-11 Calling ACMSGetWorkspace

  ---------------------------------- 
  --      Get back the control wksp 
  ---------------------------------- 
  set cursor to busy 
  put GetControlWksp (currentsession) into status 
  if status < -801 then 
    put  GetErr (currentsession,"Get Control Workspace") into stat 
    dispsenderr 
    exit resdetails 
  end if 
   .
   .
   .
  end resdetails 
on dispsenderr 
  answer "Attempt to verify reservation details and retrieve"& 
  " customer information failed." 
end dispsenderr 
 
function GetControlWksp currentsession 
  global messagepanel,temp,ctrlkey,vr_status,avertzlogfile 
  put empty into reschunk 
  --     ctrl_key, text, 5 
  --     current_entry,unsigned word,short 
  --     vr_status, long 
  --     messagepanel, text, 80 
  --     taskname, text, 31 
  --     rs_stat_flag, text, 1 
  
  put "TEXT,5,0,G,ctrlkey"&return into reschunk 
  put "0,2,0,G,temp"&return after reschunk 
  put "long,4,0,G,vr_status"&return after reschunk 
  put "TEXT,80,0,G,messagepanel"&return after reschunk 
  put "TEXT,32,0,G,temp" after reschunk 
  
  put ACMSGetWorkspace (currentsession,0,TEXT,123,0,0,reschunk) into 
  laststatus 
  return laststatus 
end GetControlWksp 


Previous Next Contents Index