TweetFollow Us on Twitter

Mac to Mainframe
Volume Number:6
Issue Number:6
Column Tag:C Workshop

Mac to Mainframe With HyperCard

By John R. Powers, III, Monte Sereno, CA

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

[John Powers designs and programs computer software tools for people who are not computer experts. In his 23 years of software development experience, Mr. Powers has developed products ranging from portable computers for business executives to educational games for children. Mr. Powers has been employed by Battelle-Columbus Laboratories, the Authorship Resource Inc., Atari, Convergent, and the Learning Company. In 1988, he formed Easy Street Software. His most recent projects have been developing software for the Mac-to-NonMac connection using C and HyperTalk.]

Trapping the Wild XFCN

Little did I know that the phone call would lead me on an adventure with the biggest and wildest XFCN’s I’ve ever done. It all started when Tri-Data Systems wanted to do something special with their Mac-to-Mainframe connection.

The Mac-to-Mainframe Connection

Mac communications with IBM mainframes has become one of the “hot” market areas for the Macintosh. Large corporate users of IBM mainframes have discovered the Macintosh and want to use the Macintosh with their mainframes. They try the terminal emulators on their Macintosh for the mainframe tasks that they have always done, but they become frustrated that they can’t enjoy the full power and flexibility of the Macintosh interface. They enjoy using HyperCard for their other tasks and want to use HyperCard for their mainframe communication as well. Mainframe users are especially frustrated when they want to customize their interaction with the mainframe and can’t use the capability of HyperCard. They were asking major communication hardware and software suppliers like Tri-Data Systems why they couldn’t use HyperCard.

The HyperCard-to-Mainframe Connection

Tri-Data Systems had completed a Mac-based programmatic interface to IBM mainframes and wanted to extend that capability to HyperCard. Using a HyperCard stack, a user should be able to use or write handlers through which a stack could communicate with an IBM mainframe. Traditional communication methods require a dedicated terminal emulator or a custom application program, but Tri-Data Systems wanted more: a HyperCard interface that would greatly simplify the development of custom interfaces by giving the user a powerful set of HyperCard tools combined with extensions for IBM mainframe communication. Handlers could be written that provide a one-button “logon” and use HyperCard fields to store and retrieve mainframe data. Complete transactions could be automated using HyperCard.

The HyperCard XCMD/XFCN capability was the logical way to extend HyperCard into the arena of IBM mainframe communication. The XCMD/XFCN interface would be the linkage between HyperCard and Tri-Data’s driver. This relationship is shown in figure 1, “The HyperCard-to-Mainframe Connection”.

Figure 1.

A number of HyperCard interfaces to IBM mainframe communication were already on the market, but didn’t have the power that Tri-Data Systems wanted for their implementation. Tri-Data Systems wanted the works:

• a complete user-programmable interface to IBM mainframes;

• the support of up to eight separate and simultaneous interaction sessions in a single stack;

• synchronous and asynchronous operation;

• dynamic memory allocation to minimize memory overhead;

• access to internal or external keyboard mapping tables;

• a complete set of 138 result codes with optional text describing each code;

• full support for HyperCard chunking expressions; and

• a stack demonstrating all 15 commands

The user-programmable interface had to support 15 commands dealing with IBM mainframe communication. Commands include those for setting up and closing down communication with the mainframe, passing data back and forth, and dealing with cursor information. The commands had to be powerful enough to create a terminal emulator in HyperCard alone.

The capability of separate and simultaneous sessions had to allow the user to manage up to eight independent interactions with the mainframe at one time, all operating in one stack. Each session had to operate independently and have its own queues, buffers, and request blocks, all requiring memory. However, Tri-Data Systems did not want to penalize the user that wanted fewer sessions with excessive memory requirements, memory was to be allocated in a dynamic fashion on an “as-needed” basis.

Synchronous and asynchronous operation would allow the user to choose between the operation being completed while they wait or permitting the user to do something else in the stack while the operation was completed. Synchronous operation is the more common of these two modes, you initiate the command and wait for the result. Asynchronous operation allows you to initiate the command and proceed immediately with something else without waiting for the result. The process completes occurs while you are potentially elsewhere in the stack.

Mainframe interaction may require non-standard keyboard mappings. Tri-Data Systems wanted to be sure that any special keyboard mappings could be specified from within the HyperCard stack or with a resource attached to the stack.

A lot of messages in the form of result codes can be passed back to the user. These can be from various levels in the driver or XFCN. Manuals for mainframe communication software usually always have one or more appendices containing pages of result codes. Tri-Data Systems wanted more than just a code, they wanted the user to see a textual description of the code at the user’s option.

In addition to these major features, the HyperCard interface had to support full chunking expressions such as “char 21 to 40 of line 7 of bg fld PS” and provide a large variety of initialization options with defaults.

Finally, the product was to be bundled in a stack which would demonstrate the operation of each of the 15 commands.

It was a big list of requirements and the foremost requirement was the HyperTalk interface.

The HyperTalk Interface

My first step was to design a user interface that would accommodate all the Tri-Data Systems’ requirements. A natural hierarchy occurs in the design, one connection method is used for all sessions and each session has its own set of commands (shown in figure 2, “System Hierarchy”). This hierarchy is followed both in the HyperTalk interface and the internal data structures. As a result, there are three major classes of data:

Figure 2.

Connection method data

• Connection initialization data specifies connection method, translation tables, and the number of sessions desired. This is done only once.

Session data

• Session initialization data is passed to the XFCN.

• Session status data is received from the XFCN and driver.

Command

• The command name and the session is passed. Command-specific options is also passed and command-specific status information is received.

• Container descriptions (chunking expressions) for passing and receiving mainframe are passed.

• Results of the transaction is received. Was the command completed successfully? Is it still pending (for asynchronous operations)? Did it fail and why?

As to the passing of data between stack and XFCN, a combination of HyperCard globals, parameters lists, and function results supports the three classes of data nicely:

Globals Used for connection method and session data.

Parameter list Passes command name, command options, and container descriptions used by the command.

Function result Returns the result code and command-specific data.

In addition, HyperCard fields are the most likely choice for storing and displaying data that the command passes between the mainframe and HyperCard. HyperCard globals can also be used, but if the data is used for display, putting it in a field to begin with is more efficient.

Two types of globals maintain parametric data needed by HyperTalk and the XFCN’s. The method global contains the connection method parameters and the session global contains the session-specific parameters. There is one method global for the stack and one session global for each simultaneous session. The only mandatory data in the method global are the names of the session globals, if no additional data is provided, the XFCN uses its defaults. The session globals are two-way. That is, initialization data is passed to the interface in the global and the interface passes session status information back to the global. The user can optionally inspect the session global to see connection status, the connection id, and so forth.

The parameter lists have the command name as their first parameter and the session global name as the second parameter. The session global name is required to identify which session of multiple simultaneous sessions is being referenced.

The function result for the XFCN returns the result code. If the command returns additional information, such as a cursor location, it is returned in a comma-separated list following the result code.

Operations are separated into three functions: one to launch a command, another to check a command state, and a third to translate the result code into a text string. The resulting XFCN’s are as follows:

TriData3270 Launches command and returns it’s result code.

TriData3270State Returns a result code giving the state of a command that was launched asynchronously.

TriData3270Result Returns the text string corresponding to a result code.

“TriData3270” is the workhorse since it launches all the commands while the other two XFCN’s provide specialized support.

The HyperTalk interface is illustrated in the following handlers:

--1
--
-- STARTUPSESSION function
-- Startup the session by an INIT, OPEN, and CONNECT.
-- Return true if startup was done without error.
-- If any error occurs, terminate and return false.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: TriData3270
-- XCMDs used: none
-- HANDLERS used: setupGlobals, showResult,
 updateDebug
-- FUNCTIONS used: none
--
function startupSession
 setupGlobals
 get TriData3270(“INIT_3270_API”, “myMethod”)
 showResult(it)
 if item 1 of it   0 then return false
 get TriData3270(“OPEN_HOST_CONNECTION”,¬
 “myMethod”)
 showResult(it)
 if item 1 of it   0 then
 get TriData3270(“TERM_3270_API”)
 return false
 end if
 get TriData3270(“CONNECT_TO_PS”,¬
 “mySession”, “sync”, “”)
 showResult(it)
 if item 1 of it   0 then
 get TriData3270(“TERM_3270_API”)
 return false
 end if
 updateDebug
 return true
end startupSession
----------------------------------------------
--
-- GET_UPDATE button
-- Initiate an async Get_Update for PS and   OIA.
-- Handlers  are in the background script.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: TriData3270
-- XCMDs used: none
-- HANDLERS used: showResult, updateStatus
-- FUNCTIONS used: none
--
on mouseUp
 global TD3270temp
 showResult(TriData3270(“GET_UPDATE”,¬
   “mySession”,”async”,”50",¬
 “cd fld PS”,””,””,””,”cd fld OIA”))
 updateStatus
end mouseUp
----------------------------------------------
--
-- CHKUPD button
-- Check the state of Get_Update.
-- This forces an update of the fields passed
-- in the Get_Update command.
-- Handlers  are in the background script.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: TriData3270State
-- XCMDs used: none
-- HANDLERS used: showResult, updateStatus
-- FUNCTIONS used: none
--
on mouseUp
 global TD3270temp
 showResult(TriData3270State(“GET_UPDATE”,¬
 “mySession”))
 updateStatus
end mouseUp
----------------------------------------------
--
-- SHOWRESULT function
-- Posts the return from the TriData3270     function.
-- Separates the result code (always item #1).
-- Gets the result message from the result code
-- and posts both to the display.
-- Also displays the mySession global since
-- TriData3270 may have updated it.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: TriData3270Result
-- XCMDs used: none
-- HANDLERS used: showResult
-- FUNCTIONS used: none
--
on showResult resultInfo
 global mySession
 -- flash to show update
 put empty into cd fld resultCode
 -- flash to show update
 put empty into cd fld resultMessage 
 put resultInfo into cd fld resultCode
 put TriData3270Result(item 1 of resultInfo)¬
 into cd fld resultMessage
 put mySession into cd fld Session
end showResult
----------------------------------------------

Code Requirements

The full-featured capability and flexibility required by Tri-Data Systems provided some challenges for the code design.

Data storage is allocated upon entering the XFCN, but also must be maintained after exiting the XFCN. This is especially important because asynchronous operation requires stable data structures that were accessible by the driver after the XFCN completes execution. Since the XFCN “goes away” when not executing, no globals or other static memory areas could be used. Data structures needed to be preserved between XFCN calls.

The TriData3270 and TriData3270State XFCN’s share a lot of functionality. Duplicating this functionality means duplicate code, wasted memory, and complicated software maintenance. The XFCN’s needed to share common code.

Tri-Data Systems wanted support for complete chunking expressions, not just container names. Users could enter “char 2 to 10 of line 5 of bg fld PSdata” rather than just “PSdata” for container descriptions. Mimicking HyperCard’s powerful chunking expression processing is a big coding job and wasteful. Complete chunking expressions needed to be supported.

Up to 8 simultaneous sessions are possible, each with its own memory allocation consisting of a variety of request blocks, queue blocks, buffers, translation tables, and record blocks. Some of this memory is fixed length and some of it is variable length depending on how much data the user is transferring or what options the user has selected. Allocating all this memory at initialization for all possible sessions would exceed the capacity of most Macintosh computers. Dynamic memory allocation was needed.

Tri-Data Systems required development to be in MPW 3.0. Unfortunately, the SADE debugger does not support debugging of XCMD’s or XFCN’s. In addition, successful debugging of communication software requires the ability to trace data as it passes thru the data structures used by the code being analyzed. It’s not sufficient to just look at the input and output. A new debugging tool was needed.

Preserving Data Structs Between XFCN Calls

A single memory handle is allocated for the entire connection. Additional memory handles, and there are many, are referenced from the single memory handle’s structure (further described in “Dynamically Allocating And Preserving Memory”). To preserve data structures between XFCN calls, the memory handle is stored as a string in a HyperCard global. This is shown in Figure 3, “Preserving Data Between Calls.” The handle could not be saved in a HyperCard global in its native form because of the possibility of a zero byte being misinterpreted by HyperCard as a string terminator. To avoid this possibility, each byte of the memory handle is translated into an ASCII code so that the result can be displayed in HyperCard as a string of hex characters. Storing the handle as a hex string also helps in debugging.

Figure 3.

The following functions are used to convert, save, and retrieve the memory handle:

/* 2 */

int storeHanInGlobal(paramPtr, globalName,   han)
/*
 Convert the handle to HexStr representation.
 Store in the HC global container globalName.
 If handle is zero, store empty in the global.
 This lets the HyperCard script test to see if
 memory has been allocated (not empty ==
 allocated).
 
 Returns result code.
*/
 XCmdPtr  paramPtr;
 char   *globalName;
 Handle han;
{
 Handle tempHan;
 Str255 tempPstr;
 char   hanCstr[kMaxLenLine];
 
 if(han==NIL)
 strcpy(hanCstr, kEmptyStr);
 else
 convertHanToHexStr(hanCstr, han);
 copyCtoPstr(tempPstr, globalName);
 tempHan = CopyStrToHand(hanCstr); 
 SetGlobal(paramPtr, tempPstr, tempHan);
 DisposHandle(tempHan);
 return MemError();
}/*--------------storeHanInGlobal  */

int fetchHanFromGlobal(paramPtr, globalName,
 han)
/*
 Fetch the handle stored in HexStr
 in the HC global container globalName.
 
 Returns result code:
 TRUE   successful, found handle
 FALSE  handle is NIL
*/
 XCmdPtr  paramPtr;
 char   *globalName;/*  name of global */
 Handle *han;    /*pointer to handle */
{
 Handle hGlobalString;/*  string in global*/
 Str255 tempPstr;/*temp Pascal string */
 
 copyCtoPstr(tempPstr, globalName);
 HLock(hGlobalString = GetGlobal(paramPtr, tempPstr));
 convertHexStrtoHan(han, *hGlobalString);
 HUnlock(hGlobalString);
 if(*han==NIL)
 return FALSE; 
 else
 return TRUE;
}/*--------------fetchHanFromGlobal*/

void convertHanToHexStr(hexStr, han)
/*
 Convert the Handle to 8-char (+ terminator) hex string representation.
*/
 char   *hexStr;
 Handle han;
{
 short  digit, shiftCnt = 32;
 
 do
 {
 shiftCnt -= 4;
 digit = ‘0’ + (((short) ( (long) han >>
 shiftCnt)) & 0x000F);
 if(digit > ‘9’)
 digit += 7;/* A-F hex digit*/
 *hexStr++ = digit;
 }
 while(shiftCnt>0);
 *hexStr = ‘\0’; /*add terminator  */
}/*--------------convertHanToHexStr*/

void convertHexStrtoHan(han, hexStr)
/*
 Convert the hex string representation to a Handle.
 The HexStr representation must be a C-string.
 Only the least-significant 8 hex digits are used.
 Assumes that handle is already locked.
 Returns NIL for handle if there are not 8 hex digits. 
*/
 Handle *han;
 char *hexStr;
{
 unsigned long digit, hanVal=0L;
 short  shiftCnt=0, i;
 Str255 tempPstr;
 
 if(strlen(hexStr)!=8)
 {
 *han = NIL;
 return;
 }
 copyCtoPstr(tempPstr, hexStr);
 for(i=tempPstr[0]; i>0 && shiftCnt < 32; i--)
 {
 digit = tempPstr[i] - ‘0’;/*unsigned, pos*/
 if(digit>9L)
 digit -= 7L;    /*A-F hex digit   */
 if(digit>’F’)
 {
 *han = NIL;
 return;
 }
 digit <<= shiftCnt; /* shift left */
 hanVal = hanVal | digit;/* insert value*/
 shiftCnt += 4;
 }
 *han = (Handle) hanVal;
}/*--------------convertHexStrtoHan*/

“GetGlobal” and “SetGlobal” are callbacks supported by HyperCard. They are simple and convenient to use. GetGlobal passes the HyperCard parameter block and the names of the global as a Pascal string. It returns a handle to the content of the global. SetGlobal passes the HyperCard parameter block, the name of the global as a Pascal string, and a handle to the content to be stored in the global. The global contents are passed as null-terminated C-strings.

Sharing Common Code

A variety of approaches are possible for sharing common code. One approach would be to combine the TriData3270 and TriData3270State functions into a single XFCN. This would complicate the parameter list and may confuse the user. A better approach is to have the two XFCN’s call a common code module. TriData3270 and TriData3270State were written as two very small XFCN’s calling a common CODE resource. The XFCN’s pass the HyperCard parameter block and a parameter indicating which XFCN is calling the common CODE resource. The common CODE resource uses the “who called me” parameter to switch between unique and common code.

The common CODE resource is accessed as a named resource by GetNamedResource. It seems less likely that the CODE resource name will be changed than its number. If GetNamedResource results in an error code, then an appropriate result code is returned to the calling stack. Since the convention for this series of XFCN’s is to return the result code in hex, the error code is converted to a hex string and returned. To keep things compact, the “makeHexString” function is duplicated between the two XFCN’s. This small duplication is only required to support the error condition. The CODE resource handle is cast as a ProcPtr and “called” as a function after being moved high and locked. After execution, the handle is unlocked.

The complete XFCN’s and a fragment of the entry into the common CODE resource are as follows:

/* 3 */

/* ----------------------------------------------
 Program: TriData3270 XFCN
 Version: v2
 History: 05/04/89 v1, original
 07/04/89 v2, revised memory model
 Author:John R. Powers, III
 Easy Street Software
 16373 Alexander Avenue
 Monte Sereno, CA 95030
 (408) 395-1158
 (408) 395-3308 msg.

 Computer:Mac II with System v6.0.2
 
 Compiler:MPW-C v3.0 

 Usage:
 get TriData3270(“command”, “session”, SAM,
 params, “container”)
 
 Requires HyperCard version 1.2 or better.
 
 Files:
 TriData3270.c   This source file
 TD3270main.c    CODE called by
 TriData3270State
 TriData3270 Lab HyperCard stack for testing
 TriData3270.h   General header
 
 Full Build:
 # TriData3270 XFCN
 C -b  TriData3270.c -mbg off -sym off
 Link 
 -rt XFCN=16373 
 -m ENTRYPOINT 
 -sg TriData3270 
 TriData3270.c.o 
 “{Libraries}”Interface.o 
 “{CLibraries}”StdCLib.o 
 -o “TriData3270 Lab”
 save -a
---------------------------------------------- */

#include<HyperXCmd.h>
#include<Memory.h>
#include<Types.h>
#include<Resources.h>

#include“TriData3270.h”

/*
 Prototypes.
*/

void  makeHexStr(char *hexStr, short value);
void  pTD3270main (XCmdPtr paramPtr, short opFlag);

/*
 Main program and entry point for XFCN.
 
 Get common code resource and continue with that.
 Use opFlag = kOp3270 (TriData3270 XFCN).
*/

pascal void entryPoint(paramPtr)
 XCmdPtr  paramPtr;/*the HyperCard connection*/
{
 short  opFlag=kOp3270, result;
 char   msg[kMaxLenLine];
 Handle hMsg, hTD3270main;
 ProcPtrpTD3270main;
 
 hTD3270main = GetNamedResource(kRsrcMainType,
 kRsrcMainName);
 result = ResError();
 if(result!=noErr)
 {
 makeHexStr(msg, result);
 hMsg = (Handle) NewHandle((long) (strlen(msg)+1));
 HLock(hMsg);
 strcpy(*hMsg, msg);
 HUnlock(hMsg);
 paramPtr->returnValue = hMsg;
 return;
 }
 MoveHHi(hTD3270main);
 HLock(hTD3270main);
 pTD3270main = (ProcPtr) *hTD3270main;
 pTD3270main (paramPtr, opFlag);
 HUnlock(hTD3270main);
 return;
}/*-------------------- entryPoint */

void makeHexStr(hexStr, value)
/*
 Convert the value to 4-char (+ terminator) hex    string representation.
*/
 char *hexStr;
 short  value;
{
 short  digit, shiftCnt = 16;
 
 while(shiftCnt>0);
 {
 shiftCnt -= 4;
 digit = ‘0’ + ((value>>shiftCnt) & 0x000F);
 if(digit > ‘9’)
 digit += 7;/* A-F hex digit*/
 *hexStr++ = digit;
 }
 *hexStr = ‘\0’; /*add terminator  */
}/*---------------------- makeHexStr */

/* ----------------------------------------------
 Program: TriData3270State XFCN
 Version: v1
 History: 07/04/89 v1, original
 Author:John R. Powers, III
 Easy Street Software
 16373 Alexander Avenue
 Monte Sereno, CA 95030
 (408) 395-1158
 (408) 395-3308 msg.

 Computer:Mac II with System v6.0.2
 Compiler:MPW-C v3.0 

 Usage:
 get TriData3270State(command, session)
 
 Requires HyperCard version 1.2 or better.
 
 Files:
 TriData3270State.cThis source file
 TD3270main.c    CODE called by    TriData3270State
 TriData3270 Lab HyperCard stack for testing
 TriData3270.h   General header
 
 Full Build:
 # TriData3270State XFCN
 C -b  TriData3270State.c -mbg off -sym off
 Link 
 -rt XFCN=16374 
 -m ENTRYPOINT 
 -sg TriData3270State 
 TriData3270State.c.o 
 “{Libraries}”Interface.o 
 “{CLibraries}”StdCLib.o 
 -o “TriData3270 Lab”
 save -a
 
---------------------------------------------- */

#include<HyperXCmd.h>
#include<Memory.h>
#include<Types.h>
#include<Resources.h>

#include“TriData3270.h”

/*
 Prototypes.
*/

void  makeHexStr(char *hexStr, short value);
void  pTD3270main (XCmdPtr paramPtr, short opFlag);

/*
 Main program and entry point for XFCN.
 Get common code resource and continue with that.
 Use opFlag = kOp3270State (TriData3270State XFCN). 
*/

pascal void entryPoint(paramPtr)

 XCmdPtr  paramPtr;/*the HyperCard connection*/
{
 short  opFlag=kOp3270State, result;
 char   msg[kMaxLenLine];
 Handle hMsg, hTD3270main;
 ProcPtrpTD3270main;
 
 hTD3270main = GetNamedResource(kRsrcMainType,
 kRsrcMainName);
 result = ResError();
 if(result!=noErr)
 {
 makeHexStr(msg, result);
 hMsg = (Handle) NewHandle((long) (strlen(msg)+1));
 HLock(hMsg);
 strcpy(*hMsg, msg);
 HUnlock(hMsg);
 paramPtr->returnValue = hMsg;
 return;
 }
 MoveHHi(hTD3270main);
 HLock(hTD3270main);
 pTD3270main = (ProcPtr) *hTD3270main;
 pTD3270main (paramPtr, opFlag);
 HUnlock(hTD3270main);
 return;
}/*-------------------- entryPoint */

void makeHexStr(hexStr, value)
/*
 Convert the value to 4-char (+ terminator) hex    string representation.
*/
 char   *hexStr;
 short  value;
{
 short  digit, shiftCnt = 16;
 
 while(shiftCnt>0);
 {
 shiftCnt -= 4;
 digit = ‘0’ + ((value>>shiftCnt) & 0x000F);
 if(digit > ‘9’)
 digit += 7;/* A-F hex digit*/
 *hexStr++ = digit;
 }
 *hexStr = ‘\0’; /*add terminator  */
}/*---------------------- makeHexStr */

/*
 main program and entry point
 The is intended to be called as a CODE      
 resource, not an XCMD or XFCN.
 Call with opFlag==kOp3270 for TriData3270
 functionality.
 Call with opFlag==kOp3270State for  
 TriData3270State functionality.
 TriData3270State is a subset of TriData3270.
 There is so much common code that this      
 approach eliminates redundant source and 
 object code.
*/

void entryPoint(paramPtr, opFlag)
 XCmdPtr  paramPtr;/*the HyperCard connection*/
 short  opFlag;
{
 masBlk *pMas;   /*master memory pointer     */
 sesBlk *pSes;
 Handle hMas,    /*master block handle */
 hSes;
 char   sessionGlobalName[kMaxLenName],
   command[kMaxLenCmd],/* command string*/
 returnList[kMaxLenRet];/*return strng*/
 short  result,  /*result code for return    */
 ignore,
 debugFlag,/*  debug flag */
 reqResult,/*  result code from API*/
 status,/*verb queue status code*/
 cmdNum,/*command number  */
 blockIndx,/*  queue/request blk indx*/
 numParams,/*  number of XFCN parameters*/
 sessionIndx;
 WORD wIgnore;
 /*Map command index to   */
 /*req and queue indexes  */
 short  blockIndxTable[] =
 {
 kIndxConnPS,  /*ConnPS   */
 kIndxCpyToPS, /*CpyToPS  */
 kIndxDiscPS,  /*DiscPS   */
 kIndxGetCurs, /*GetCurs  */
 kIndxSendKey, /*SendKey  */
 kIndxSetCurs, /*SetCurs  */
 kIndxCpyfBuf, /*CpyfBuf  */
 kIndxGetUpd,  /*GetUpd   */
 kIndxCloseHC, /*CloseHC (no queue)*/
 kIndxOpenHC,  /*OpenHC (no queue) */
 kIndxSendKey, /*SendAID => SendKey*/
 kIndxSendKey, /*SendText => SendKey */
 kIndxInit, /* no queue & no request */
 kIndxTerm/*no queue & no request */
 };
/*
 Setup return string.
 Check for opFlag validity.
*/
 *returnList = kCterm;
 if(opFlag!=kOp3270 && opFlag!=kOp3270State)
 {
 resultForHC(paramPtr, kRcHuhOp, returnList);
 return;
 }
/*

Supporting Complete Chunking Expressions

Containers, particularly fields, are used extensively in the Mac-to-Mainframe connection. I wanted to support complete chunking expressions so that the user could refer to chunks of the container rather than the container in its entirety. It was silly to mimic HyperCard’s powerful chunking capability so the solution was to let HyperCard do it.

When the user passes a container description to the XFCN, a callback is formed which has HyperCard interpret the description and place the data into a simple container. The simple container is then processed by the XFCN. For example, if the user wants to send “char 21 to 40 of line 6 of cd fld PSdata” to the mainframe, a callback is created as follows:

“Put <user’s chunking expression> into <temporary global>”

forming

“Put char 21 to 40 of line 6 of cd fld PSdata into TD3270temp”

The callback lets HyperCard process the statement and perform the data transfer from the user’s container to the temporary global, “TD3270temp”. This allows the XFCN to manipulate the simple container, TD3270temp, rather than try to interpret the more complex chunking expression, “char 21 to 40 of line 6 of cd fld PSdata”.

More specifically, a SendHCMessage callback causes HyperCard to copy the data described by the user into a global called TD3270temp. GetGlobal is then used to fetch the data that HyperCard copied. A pseudo-code form of the process is as follows:

--4

get the user’s container description.
put “put <user’s container description> into TD3270temp” into msg
send msg to HyperCard using SendHCMessage
get the TD3270temp data using GetGlobal
send the TD3270temp data to the mainframe

The reverse process is used for receiving data from the mainframe. The pseudo-code is as follows:

--5

get the user’s container description
put the mainframe data into TD3270temp using SetGlobal
put “put TD3270temp into <user’s container description>” into msg
send msg to HyperCard using SendHCMessage
(HyperCard puts the mainframe data into the user’s container)

The code to get the data from the user’s container is as follows:

/* 6 */

void getContainerData(paramPtr, container,   hGlobalData)
/*
 Get the data from the container described in “container”.
 kTmpGlobalPname is a hypercard global used for
 temporary storage.
 Return a handle to the HyperCard data.
 The HyperCard data is stored as a C string.
 Method:
 Send a message to HC to put the data into a
 global.
 Get the contents of the global.
 Requires that a global by name of globalName
 already be defined.
*/
 XCmdPtr  paramPtr;
 char   *container;
 Handle *hGlobalData;
{
 char   msg[kMaxLenLine],
 globalName[kMaxLenGlobal];
 Str255 globalPname;
 Handle hTemp;
 
 /*create global */
 copyPstr(globalPname, kTmpGlobalPname);
 copyPtoCstr(globalName, globalPname);
 hTemp = putGlobal(paramPtr, globalPname, container);
 /*put container into global*/
 strcpy(msg, “put “);
 strcat(msg, container);
 strcat(msg, “ into “);
 strcat(msg, globalName);
 SendCstrHCMsg(paramPtr, msg);
 /*“SendHCMsg error in get: “*/
 CHK_CALLBACK_ERR(msg);
 /*get data from global   */
 *hGlobalData = GetGlobal(paramPtr, globalPname);
 /*“GetGlobal error (copy) in get:”*/
 CHK_CALLBACK_ERR(globalName);
 DisposHandle(hTemp);
 
}/*----------------getContainerData*/

The code to put the data into the user’s container is as follows:

/* 7 */

int putContainerData(paramPtr, container, hData)
/*
 Put the hData into the container described in
 “container”.
 The HyperCard data is stored as a C string.
 hData is checked for non-NIL before proceeding.
 Returns TRUE if the put was done.
 Method:
 Create a HC global.
 Copy the data to the HC global.
 Send a message to HC to put the global into a                 container.
*/
 XCmdPtr  paramPtr;
 char   *container;
 Handle hData;
{
 char   msg[kMaxLenLine], globalName[kMaxLenGlobal];
 Str255 globalPname;
 Handle hTemp;
 
 /*make sure we have desc and handle */
 if(hData==NIL || strlen(container)<=0)
 return FALSE;
 /*create global */
 copyPstr(globalPname, kTmpGlobalPname);
 copyPtoCstr(globalName, globalPname);
 hTemp = putGlobal(paramPtr, globalPname, container);
 /*have container desc and handle  */
 /*copy handle data to temp HC global*/
 SetGlobal(paramPtr, globalPname, hData);
 /*“SetGlobal error (copy) in put: “ */
 CHK_CALLBACK_ERR(globalName);
 /*Have HC copy from global to container*/
 strcpy(msg, “put “);
 strcat(msg, globalName);
 strcat(msg, “ into “);
 strcat(msg, container);
 SendCstrHCMsg(paramPtr, msg);
 /*“SendHCMsg error in put: “ */
 CHK_CALLBACK_ERR(msg);
 DisposHandle(hTemp);
 return TRUE;  
}/*----------------putContainerData*/

The “CHK_CALLBACK_ERR()” is a macro which tests the result of the callback and generates a call to the debugger if there was an error. These kind of macros were used for conditions which may occur during development, but are highly unlikely during production. The code is as follows:

/* 8 */

#define CHK_CALLBACK_ERR(str) \
 { \
 short  result;  \
 char msg[128];  \
 if((result=paramPtr->result)!=0)  \
 { \
 strcpy(msg, “HC callback error: “); \
 strcat(msg, str); \
 DebugStr(ToPstr(msg));   \
 } \
 }

The “create global” code in each of the routines is intended to create a global that can 
be accessed from within the XFCN without burdening the user with defining it in the HyperTalk 
script.  This is based on Gary Bond’s book, “XCMD’s for HyperCard”, in which he states 
“In HyperCard versions greater than 1.1, SetGlobal will create the global variable and 
then assign the contents of the zero-terminated string to the newly created variable.” (page 
101)  Unfortunately, it does not work that way, the user must define the global in the HyperTalk 
script.  If it is not defined, then HyperCard gets or puts the global name rather than its 
contents.  Apple Computer’s Macintosh Developer Technical Support Team has not acknowledged 
that this is a “bug”.
Dynamically Allocating & Preserving Memory
The requirement for up to eight simultaneous sessions meant that the XFCN had to 
allocate enough memory for all the sessions, but each session needed needed a lot of memory 
- up to 10 request blocks, 8 queue blocks, 6 buffers, 5 translation tables, and 4 record 
blocks.  To keep memory at a minimum, the XFCN allocates memory only when needed and 
deallocates when no longer needed.  In addition, the memory is maintained between XFCN 
calls.
Since a single handle is preserved between XFCN calls, the single handle is the key 
to preserving all the remaining memory structures.  A tree structure grows from the single 
memory handle and points to all the needed data structures.  The handle in the HyperCard 
global references a master block of data, masBlk, which in turn references a session-specific 
block of data, sesBlk.  The tree structure grows and shrinks depending on the number of 
sessions and which commands have been launched.  Figure 4, “Dynamic Memory Allocation”, 
is a schematic of the tree structure.  The definitions of the structures are as follows:

Figure 4.
/* 9 */

typedef struct
{
 short  num_sessions;
 short  reserved;
 char   method_global_name[kMaxLenName];
 Handle hSession[kMaxSessions];
} masBlk;

typedef struct
{
 char   session_global_name[kMaxLenName];
 BYTE   conn_id;
 BYTE   port_id;
 BYTE   ps_id;
 BYTE   flags;
 char   status[kMaxLenStat];
 Handle hAPIvars;
 Handle hReq[kMaxReq];
 Handle hQueue[kMaxQ];
 Handle hTable[kMaxTab];
 short  tableLen[kMaxTab];
 Handle hBuffer[kMaxBuf];
 Handle hRecord[kMaxRec];
} sesBlk;

The structures are preserved between XFCN calls because the driver accesses the structures after the XFCN is exited. Asynchronous operation allows the driver to begin operation when the command was launched from the XFCN and then continue operation at a later time. The data structures are preserved until the driver completes the command.

For example, the “Get_Update” command is frequently used to get information from the host mainframe. This is a good application of asynchronous operation because the update may take a long or short time depending on what the host mainframe is doing. Consequently, the “Get_Update” command is launched asynchronously and control is immediately returned to HyperCard while the update is processed. The “TriData3270State” XFCN is used to see if the command has completed.

When the Get_Update is launched, the XFCN must provide a block of memory to the driver to store driver information and receive data as it comes from the host. In addition, the container descriptions of where to store the update information in the HyperCard stack must be saved until the driver completes the update. The container descriptions are supplied in the XFCN parameter list, but can’t be used until the update is complete. Therefore, they are copied from the parameter list to the queue block until needed. Since multiple containers can be updated with one “Get_Update”, each container description must be saved and a update record buffer must be allocated for each. Once the update is complete and the data is placed in the HyperCard containers, the queue block is cleared and the update records buffers are released. The sequence is as follows:

HyperCard XFCN

TriData3270(“GET_UPDATE”, ...)

Allocate memory.

Save container descriptions.

Launch Get_Update.

Return to HyperCard.

Do something else.

.

.

.

TriData3270State(“GET_UPDATE”, ...)

Check for completion.

Transfer data to containers.

Clear and release memory.

Return to HyperCard.

Use updated data.

The TriData3270State(“GET_UPDATE”, ...) command can be put in an idle handler. When the update is complete, the containers will be updated automatically without holding up any other operations.

Because buffer and record structures are allocated only when they are needed, based on the command, memory requirements expand and contract depending on the specific command and the number of containers being referenced. The net result is that memory is kept down to the bare minimum.

Debugging

I expected that debugging the XFCN’s was going to be difficult. There was no symbolic debugger support for XCMD/XFCN’s and just looking at the XFCN input and output was not sufficient. I needed a way to look at the data structures being used by the driver. Since the data structures are preserved between XFCN calls, I had to devise a tool to display the structure contents on demand. Fortunately, MacsBug 6.0 was available with a little-documented feature called “templates”.

MacsBug templates are groups of fields which describe the name, type, and count of various data types. The templates are resources of type “mxwt” and loaded with MacsBug at boot time. Fields types include Byte, Word, Long, strings, and variations. New field types can be defined which are combinations of other types and both pointer and handle dereferencing are supported. The template capability is a powerful tool for defining and displaying complex data structures with a single MacsBug command.

A sample Rez file supplied with MacsBug, “Templates.r”, gives examples of MacsBug templates and with enough trial and error, it was possible to create templates for all the data structures in this project. The templates aided greatly in display of the data structures because they labelled the structures for easy reading and dereferenced the handles so that the entire data structure tree could be displayed.

Examples of MacsBug templates for two of the data structures which were described previously are as follows:

/* 10 */
          
 “masHan”,{
 “master handle”,“^masBlk”, 1
 },
 
 “masBlk”,{
 “num_sessions”, “Word”,  1,
 “reserved”,“Word”,1,
 “method name”,  “Text”,  32,
 “hSession1”,    “^^sesBlk”,1,
 “hSession2”,    “^^sesBlk”,1,
 “hSession3”,    “^^sesBlk”,1,
 “hSession4”,    “^^sesBlk”,1,
 “hSession5”,    “^^sesBlk”,1,
 “hSession6”,    “^^sesBlk”,1,
 “hSession7”,    “^^sesBlk”,1,
 “hSession8”,    “^^sesBlk”,1
 },
 
 “sesBlk”,{
 “global_name”,  “Text”,  32,
 “conn_id”, “Byte”,1,
 “port_id”, “Byte”,1,
 “ps_id”, “Byte”,1,
 “flags”, “Byte”,1,
 “status”,“Text”,12,
 “hAPIvars”,“Long”,1,
 “hReq0-4”, “Long”,5,
 “hReq5-9”, “Long”,5,
 “hQueue0-3”,    “Long”,  4,
 “hQueue4-7”,    “Long”,  4,
 “hTables0-4”,   “Long”,  5,
 “tableLen0-4”,  “Word”,  5,
 “hBuffer”, “Long”,6,
 “hRecord”, “Long”,4
 },

The “masBlk” and “sesBlk” structures are also shown in conventional C structures in the section on “Dynamically Allocating and Preserving Memory” described above. The structures for the request blocks (hReq), queues (hQueue), tables (hTables), buffers (hBuffer), and record (hRecord) were not dereferenced in the template to keep the data display down to a usable size.

The master handle is stored as a hex string in a HyperCard global. This allows the data structure to be preserved from XFCN to XFCN call. It also has an enormous side benefit - it allows the data structure to be accessed directly from the HyperCard stack. Since the master data handle is stored as a hex string, a call to MacsBug can be implemented as an XCMD passing a command to dump the hex string with the master handle template.

The hex string is stored in a HyperCard global, “TD3270memory”, and is also displayed in a background field. The background field has a mouseUp handler so that simply clicking on the field calls MacsBug and displays the data. If the master data handle is undefined, then the handle background field is empty and no call is made to DebugStr. The MacsBug format to dump memory with a template is

;11

; dm <handle in hex> <template name>

The dump memory command is invoked in the following mouseUp handler:

--12

--------------------------------------------------
--
-- Call DebugStr with the memory handle and template.
-- Intended for use with MacsBug.  The label
-- “masHan” below refers to one of many MacsBug
-- templates that were designed for TriData3270.
-- It won’t do you any good unless you have
-- installed the TriData3270 templates in your MacsBug.
-- MacsBug 6.0 or higher is required for this feature.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: none
-- XCMDs used: DebugStr
-- HANDLERS used: none
-- FUNCTIONS used: none
--
on mouseUp
 global TD3270memory
 if TD3270memory is not empty then
 put “;dm” && TD3270memory && “masHan” into¬
 MacsBugStr
 DebugStr(MacsBugStr)
 else
 answer¬
 “Do INIT_3270_API to get memory handle.”
 end if
end mouseUp

For those of you that have been watching closely, you may have noticed that the MacsBug template only has a single dereference for the master handle but a double dereference for the session handles. This is because the MacsBug display memory command, “dm”, dereferences the master handle to a pointer. The template dereferences the resulting pointer value of the master handle one more time to get the actual data. The session handles are accessed directly by MacsBug and must be dereferenced twice as one might expect.

The debugger XCMD invoked by the mouseUp handler is as follows:

/* 13 */

/* ----------------------------------------------
 Program: DebugStr XCMD
 Version: v1.2
 History:
 05/01/89 v1.0, original
 05/19/89 v1.1, use NewHandle rather than
 “char” for P-string
 05/25/89 v1.2, pass HC Handle directly rather
 than copy
 Author:John R. Powers, III
 Easy Street Software
 16373 Alexander Avenue
 Monte Sereno, CA 95030
 (408) 395-1158
 (408) 395-3308 msg.

 Computer:Mac II with System v6.0.2
 Compiler:MPW-C v3.0 
 Usage:
 DebugStr(string-to-pass-to-MacsBug)
 Requires HyperCard version 1.2 or better.
 Checks for DebugStr trap before attempting to
 call it.
 If not present, doesn’t make call.
 
 Files:
 DebugStr.c This source file
 TriData3270 Lab HyperCard stack for testing
 
 Full Build:
 C -b  DebugStr.c -mbg off -sym off
 Link -w 
 -rt XCMD=16373 
 -m ENTRYPOINT 
 -sg DebugStr 
 DebugStr.c.o 
 “{Libraries}”HyperXLib.o 
 “{Libraries}”Interface.o 
 “{CLibraries}”StdCLib.o 
 “{CLibraries}”CRuntime.o 
 -o “TriData3270 Lab”
 save -a
---------------------------------------------- */

#include<HyperXCmd.h>
#include<Memory.h>
#include<OSUtils.h>
#include<Types.h>

/*
 some trap definitions in case we are not using    MPW 3.0
*/

#ifndef _DebugStr
#define _DebugStr0xABFF
#endif

#ifndef _Unimplemented
#define _Unimplemented  0xA89F
#endif

/*
 other definitions
*/

#define TRUE1
#define FALSE  0

/*
 prototypes
*/

Boolean trapAvailable(short tNumber, short tType);
Boolean isImplemented(short trap);
char *ToCstr(char * str);
char *ToPstr(char * str);

/*
 main program and entry point
*/

pascal void EntryPoint(paramPtr)
 XCmdPtr  paramPtr;/*the HyperCard connection*/
{
 Handle hBugCstr;
 
 if(!isImplemented(_DebugStr))
 return;
 if(paramPtr->paramCount==1)
 {
 /*get first param */
HLock(hBugCstr = paramPtr->params[0]);
 /*convert to P-string */
 DebugStr((Str255) ToPstr(*hBugCstr));
 /*restore to C-string  */
 ToCstr(*hBugCstr);
 HUnlock(hBugCstr);
 }
 /*no string to pass */
 else   
 Debugger();
}/*------------------EntryPoint    
 */

Boolean trapAvailable(tNumber, tType)
/*
 Test to see if the trap is available.
 Adapted from p.3-12 to 3-14 in Programmer’s
 Guide to Multifinder.
 APDA #KMB017
*/
 short inttNumber;
 short inttType;
{
 return(NGetTrapAddress(tNumber, tType) !=
 GetTrapAddress(_Unimplemented));
}/*end trapAvailable -------- */

Boolean isImplemented(trap)
/*
 Check to see if trap is implemented.
 Adapted from p.3-12 to 3-14 in Programmer’s 
 Guide to Multifinder.
 APDA #KMB017
*/
 short inttrap;
{
 SysEnvRectheWorld;
 /*machine support trap tables?  */
 SysEnvirons(1, &theWorld);
 if(theWorld.machineType < 0)
 /*ROM does not trap tables or trap*/
 return(FALSE);
 else
 /* check for trap */
 return(trapAvailable(trap, ToolTrap));
 
}/*end isImplemented -------- */

/*
 Converts a Pascal string to a C string.
 The Pascal string is overwritten in the process.
*/
char *ToCstr(str)
 char   *str;
{
 unsigned char length, i;
 
 length = str[0];
 /*   Shift string 1 byte to the left  */
 for (i = 0; i < length; ++i)
 str[i] = str[i+1];
 /*   Put zero-terminator after string */
 str[length] = 0;
 return(str);
}

/*
 Convert a C string to a Pascal string.
 The C string is overwritten in the process.
*/
char *ToPstr(str)
 char   *str;
{
 unsigned char length, i;
 /* Find end of string    */
 for (i = 0, length = 0; str[i] != 0; ++i)
 ++length;
 /* Shift string 1 byte to right   */
 while (i--)
 str[i+1] = str[i];
 /* Put string length in 1st byte  */
 str[0] = length;
 return(str);
}

In addition to direct calls from the HyperCard stack using the DebugStr XCMD, calls direct to the DebugStr() trap were inserted into the code at a variety of useful places. A small collection of debugging macros execute the DebugStr in a variety of ways. To eliminate the nuisance of constantly having MacsBug interrupt the XFCN’s, I wanted to turn the DebugStr feature on and off with a switch or flag. There were a variety of choices:

• a compile flag using the “-d” option to define a name and/or value to the preprocessor which, in turn, would be used by #ifdef and #ifndef to control compilation of the macros;

• a resource flag which could be accessed every time the XFCN was called; and

• a flag maintained in a HyperCard global and accessed from the XFCN with “GetGlobal”.

Maintaining the flag in a HyperCard global proved to be the easiest way to turn the debug on and off. A check box button was created containing the following script:

--14

--------------------------------------------------
--
-- Set the flag for XFCN internal use of DebugStr.
--
-- Script byJohn R. Powers, III
-- Easy Street Software
--
-- XFCNs used: none
-- XCMDs used: none
-- HANDLERS used: none
-- FUNCTIONS used: none
--
on mouseUp
 global TD3270debug
 answer “Set debug flag.” with “On” or “Off”
 if it is “On” then
 answer “Debug when?” with “Before” or “After”¬
 or “Both”
 if it is “Before” then put 1 into TD3270debug
 else if it is “After” then put 2 into¬
 TD3270debug
 else if it is “Both” then put 3 into¬
 TD3270debug
 set the hilite of target to true
 else
 put 0 into TD3270debug
 set the hilite of target to false
 end if
end mouseUp

To avoid confusion, the check box button is hidden when the master memory handle is undefined (empty).

The TriData3270 XFCN uses “GetGlobal” to access the global, “TD3270debug”, to determine whether or not DebugStr should be called internally. The code to get the debug flag is as follows:

/* 15 */

short fetchDebugFlag(paramPtr, globalPname)
/*
 Fetch the debugFlag stored in globalPname
 
 Returns the debugFlag, 0 if no global found.
*/
 XCmdPtr  paramPtr;/*HyperCard connection    */
 Str255 globalPname;
{
 Handle hGlobalString;
 Str255 globalPstr;
 long   longFlag;
 
 HLock(hGlobalString = GetGlobal(paramPtr,
 globalPname));
 copyCtoPstr(globalPstr, *hGlobalString);
 StringToNum(globalPstr, &longFlag);
 HUnlock(hGlobalString);
 return (longFlag & 0xFFFF);
}/*-------------------- fetchDebugFlag */

Macros are used within the code to determine the flag state and are as follows:

/* 16 */

#define DEBUG_AFTER(flag) \
 { \
 if(flag==kDebugAfter||flag==kDebugBoth)     \
 DebugStr(“\pAfter”);\
 }
#define DEBUG_BEFORE(flag)\
 { \
 if(flag==kDebugBefore||flag==kDebugBoth)    \
 DebugStr(“\pBefore”);    \
 }

Storing flags in HyperCard globals is a powerful way to pass debugging information to and from your XCMD/XFCN’s.

The Wild XFCN is Captured

In the course of this adventure, we devised a set of XCMD and XFCN tools to allow the HyperCard user to communicate with an IBM host mainframe. These tools permit up to eight simultaneous sessions with synchronous and asynchronous operation. A number of problems were overcome, allowing the preservation of data between XFCN calls, eliminating duplicate code, permitting complete chunking expressions, dynamically allocating memory, and simplifying XCMD/XFCN debugging . In addition, a stack was written to allow the user to exercise all the commands individually.

The next adventure is for the users of the TriData 3270 HyperCard Toolkit. Here’s hoping their adventure will be as fun and successful as mine.

Acknowledgements

Keith Odom, Manager of Macintosh Applications for Tri-Data Systems, Inc., is the person who set the challenging goals for the software, taught me a lot about the world of the 3270, and asked a lot of intelligent questions along the way. May all my customers be so good.

Dan Shafer is appreciated for his suggestions, encouragement, and friendship.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.