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

The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »

Price Scanner via MacPrices.net

Apple is offering significant discounts on 16...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more
Apple HomePods on sale for $30-$50 off MSRP t...
Best Buy is offering a $30-$50 discount on Apple HomePods this weekend on their online store. The HomePod mini is on sale for $69.99, $30 off MSRP, while Best Buy has the full-size HomePod on sale... Read more
Limited-time sale: 13-inch M3 MacBook Airs fo...
Amazon has the base 13″ M3 MacBook Air (8GB/256GB) in stock and on sale for a limited time for $989 shipped. That’s $110 off MSRP, and it’s the lowest price we’ve seen so far for an M3-powered... Read more
13-inch M2 MacBook Airs in stock today at App...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Operating Room Assistant - *Apple* Hill Sur...
Operating Room Assistant - Apple Hill Surgical Center - Day Location: WellSpan Health, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Read more
Solutions Engineer - *Apple* - SHI (United...
**Job Summary** An Apple Solution Engineer's primary role is tosupport SHI customers in their efforts to select, deploy, and manage Apple operating systems and Read more
DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.