Previous | Contents | Index |
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:
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 |
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" |
The desktop client program uses the following TP Desktop Connector client services to receive information about outstanding task requests initiated by the DBExec service:
The DBState service returns the success statuses shown in Table 8-4.
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. |
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.
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 |