TweetFollow Us on Twitter

Segment DAs
Volume Number:5
Issue Number:5
Column Tag:C Workshop

Related Info: Segment Loader

Segmenting DA's

By Tom Saxton, Bellevue, WA

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

Segmented DAs: Breaking The 32K Limit

In a variety of contexts on the Macintosh we find ourselves running up against “the 32K limit.” One area that has been particularly limiting and persistent is the 32K limit on resources containing code. The reason for this has to do with the way the MC68000 addresses memory; specifically one can address code within ±32K (and the minus is generally ignored) of some base address very efficiently, so this mode is used for jumping to subroutines and for building jump tables. In particular, Lightspeed C limits the code-containing resources it builds to 32K. This isn’t a big problem with applications, since one can break up the code in an application into multiple segments. This segmentation is actually nice in itself, since it allows programs that are too big to fit entirely in memory to run by swapping code resources in and out as needed.

But Desk Accessories and stand-alone code resources (INITs, FKEYs, etc.) are a different story. The Mac OS has nothing built in to handle segmentation for anything but applications, so we’re stuck with a 32K size limit. Fortunately, this can all be fixed up with a little inline assembler. I have been working with these sorts of things on several different projects, and have developed a fairly painless way of segmenting either DAs or code resources. This is all done with stand-alone code resources, which I will call PROC’s. “Fairly painless” means:

1) One shouldn’t have to write and debug a bunch of assembly code every time a PROC or routine is added to the project.

2) A PROC should be allowed to have multiple entry points. Further, accessible routines should be able to have different numbers of arguments. (A routine with a variable number of arguments is a bit trickier, but not an unreasonable variation on the code given below.)

3) Lightspeed’s ability to check arguments types via function prototypes should be left intact.

3) Calling a procedure in an external PROC should be completely invisible to the calling C routine.

4) It should be possible to have multiple entries into a code resource. That is, it should be possible to jump in and out of a given PROC, even to the point of allowing recursion between two PROCs. In short, there should be no more limitations in calling routines than exist in a normal segmented application.

5) There should be a mechanism for loading and unloading PROCs the same way application code-resources are handled, or at least a reasonable approximation thereto.

The biggest drawback of doing your own segmentation is that you have to handle part of the link yourself. That is, when you make a change to one of the PROCs in a project, you have to make it accessible to the “parent” code either by copying the PROC into the parent’s resource file, or by having the parent explicitly open resource file(s) containing the PROC(s). At the moment, I am working on a ShareWare DA that will make this process considerably less painful.

The LSC documentation (2.01) supplement shows how to write “glue” to handle a single-entry PROC. Their code breaks down if you want to either call different routines within the PROC, or if there is a possibility of the PROC being called from two different places in the same call chain; more on this below. Working with all of this, I found out several interesting side effects of some ToolBox calls. For instance, PrJobDialog() and PrStlDialog() both go to the trouble of redrawing any dialog windows in the window list that need updating. This can really hose things if you have user item-drawing routines in a PROC that isn’t set up to handle being called again before it returns from the current call.

The Big Issues

There are three basic problems to solve. The first is how to handle multiple entry points. This is the easiest. The Mac PACK resources do this by tacking on an routine selector to the arguments for a routine within the PACK. Upon entry into the PACK, a bit of glue pulls the selector off of the stack and jumps to the right place. A variation on this theme will serve our needs.

Next, LSC (and other environments such as Aztec C) allow non-application code resources to have globals by addressing off of register A4 instead of A5 (which is reserved for applications and QuickDraw). This means that upon entry into a PROC (or a DA) the value of A4 has to be set to the “correct” value to address the PROC’s globals. This is relatively easy; the tricky bit is to restore the old value when you exit so that whoever called you can still find their globals. The obvious solution (which LSC describes) is to store the original value of A4 in a fixed location (actually, they use a pc-relative location, which can run afoul of the instruction cache in a 68020, but that’s another story). This fails if the PROC gets re-entered from somewhere having a different value of A4, since we can only store one.

The third problem has to do with handling the first two. The simple schemes for handling the first two problems involve three pieces of glue. The first handles putting the routine selector onto the stack and jumping to the (locked!) PROC. The next chunk is at the head of the PROC, it sets up A4, then uses the routine selector to jump to the desired routine. Finally, when the called routine is done, it can’t just jump back to the caller; we must go through a last bit of glue to restore the callers’s A4. Since, we would like to avoid tacking on this glue at every exit point of every externally accessible routine, we want to doctor the return address to trick the called routine into returning to a third bit of glue which handles the clean-up. This means we have to pull the real return address off of the stack and store it somewhere. Storing it in a fixed place leads to the same problems we had with A4 above.

The Solution

To avoid storing the two values which need to be saved and recovered in fixed locations, we will store them on the stack. We can’t store them below the arguments to the called routine, since then the routine will think it is getting two extra (long word) arguments, and this is supposed to be invisible to the C code. So instead, the first bit of glue shuffles the routine’s arguments down on the stack to make room for the 8 bytes of storage we need. We then replace the old return value with the address to the third bit of glue (which does the clean-up). We then jump to the second piece of glue which lives at the entry point to the PROC. It sets up A4 then pulls the selector off of the stack and jumps to the target routine. When the target routine “exits” the clean-up code retrieves the saved values, restores A4 then jumps back to the original caller.

The details of implementing these ideas mainly have to do with doctoring the stack so that both the original caller and the target routine act exactly as if the usual JSR and RTS instructions were used to call and return from the target routine. (Remember this is all supposed to be invisible to the C code.) Care has to be taken to not attempt to address globals while A4 is holding the “wrong” value. We also cannot molest D0 on the way back since that is where any return value is stored.

There are a pair of routines which load and unload a PROC in the spirit of LoadSegment and UnloadSegment. To count as loaded, the PROC resource must be in memory, locked and marked unpurgeable. Unloaded is unlocked and purgeable (don’t actually ReleaseResource, in case we want it again). If these PROC’s are to be “owned” by a desk accessory, then we have to calculate the resource’s actual ID from the driver’s ID number and the sub-ID of the PROC. This is all handled by FLoadProc() and UnloadProc().

The following example is a stupid little DA which calls three routines in an external PROC with differing numbers of arguments and return values. Before doing so, it calls FLoadProc() which loads the PROC and stores a pointer to the PROC in a location passed to the routine. FLoadProc() calls MoveHHi() on the resource handle to reduce the possibility of causing fragmentation problems. When the DA is closed (which is the only thing that can be done to or with it), it calls UnloadProc() to unlock and free up the space used by the PROC resource. While debugging, it would be wise to set the pointer to the PROC to -1 when it is unloaded, so that if someone tries to use the unloaded PROC, an address error is immediately generated. This is really only an issue if PROCs are to be loaded and unloaded while the program is running to conserve memory (not an unlikely occurance, but remember that under LSC, globals are effectively re-initialized when a PROC is released from memory and read back in from disk).

The second source is for the DA. It contains a bare bones DA with routines needed to load and call three routines in a PROC. There are also defines for the procedure selector numbers. These must begin at zero and occur in the same order as the jump table in the PROC. The glue to call a routine in a PROC consists of a “call” to an assembly language macro that does all of the work. Notice that the macro wants the number of WORDs passed as arguments to the routine, not bytes. Note also that this does not count the return address which also gets passed to the routine; the macro takes that into account.

The first source file is for the PROC resource. It contains the entry glue for the PROC. Every routine that is to be externally callable has to have an entry in the JumpTable; see the note about routine selectors above.

To build the PROC, create a new LSC project, add UselessProc.c, set its project type to “Code Resource,” its resource type to ‘PROC’ and its ID to -16000 (sub ID zero owned by DRVR ID 12). Once created, the resource file is named UselessDAProj.rsrc so that it will be copied automatically into the DA file. In general, the PROC resource would have to be copied into the DA’s resource file. Note that you don’t have to include MacTraps with this project (it does not call any routines defined there).

After building the PROC, and giving its output file the specified name, create a new project called “UselessDAProj”, add the first source file and MacTraps to the project, and build the DA. Now Run the project (or open the DA file with Suitcase), and open the DA. The DA will then put up a window (the first PROC call), write something funny in the window (the second PROC call). It will then wait until you close it, at which time it will get rid of its window (the third call to the PROC) and go away. Note that it does not handle even so much as activate or update events. Like I said, useless.

For the Non-Hackers

If assembly language is not your second language already, you’re likely thoroughly lost. This should not prevent you from using the trickery here. Below is all you need to know to use the segmenting code.

In the project that calls an external PROC, create a source module which contains the macro definition and a routine for each external procedure you wish to call. Pattern them after the routines OpenWindow(), DrawWindow() and KillWindow() in the DA source file. Each just calls the horrid assembler macro. Be sure to count the words of arguments correctly, or things will die horribly. If you’re not sure how to do this, consult your local guru. Next set up a main() in the PROC project patterned after the main() in UselessProc.c. Make one entry in the “JumpTable” for each routine you want to access. Then build a set of defines for the routine selectors (in the same order as they appear in the jump table).

Potential Pitfalls

The easiest things to mess up are counting the words in the argument list. Remember that Pointers, Handles, Points, pointers to structs and arrays, and longs all count 2 words. Ints, shorts, Booleans and chars all count 1 word (a char is only half a word, but gets passed as a word on the stack, this can be tricky and is probably compiler-dependent). Counting words in a struct or union can be tricky since bytes can get packed together, use sizeof() when in doubt. Don’t use sizeof(char), for the reason above. Next is mismatching the order of the routine selectors, check that one twice, too.

Finally, the assembler macro assumes that the glue routine begins with a “LINK” instruction. This is true for any routine that has arguments or local variables. If you want to call an argument-less routine in a PROC, give its glue routine a dummy local variable to force a LINK instruction (LSC talks about LINKs on page 9-2 of the original manual). Alternatively, one could create another version of the macro with the UNLK statement removed for functions with no arguments, but this would leave two chunks of assembler code to maintain.

Debugging Advice

Although I have been using the code for some time and have written it very carefully, if you hit an inexplicable bug while using it, you’re going to have to be convinced that the segmentation glue is not at fault. Besides, there is always a chance that I have made some subtle oversight. So, here is some advice for verifying that it’s working correctly, or finding out why it is not.

If you haven’t already, read LSC’s explanation of MacsBug, and the C calling conventions, print out the source for all of the assembler hacking and dive in. If necessary, turn on “MacsBug Symbols” under the Options and recompile the relevant modules so that you can see routine names. The best place to start watching things is just before the call to the glue routine. Watch the arguments as they are pushed onto the stack, step through the glue (not as messy as it sounds), and into the PROC. Make sure the right entry in the jump table is used. As soon as you hit the LINK statement that begins the target routine, do an “mr” (magic return) in MacsBug. You should then break out in the clean-up glue. Step through it and you’re back to the caller. The Heap Dump command “hd” is useful for verifying the integrity of the application heap and that the PROC (and its caller!) are correctly loaded into memory, locked in place and unpurgable, use it liberally. It’s a good idea to write down the state of the stack, the values for A4 and the return value as the glue is entered for comparison with things before and after the routine in the PROC is called.

The Upshot

This stuff is quite simple and reliable to use once properly set up. By creating multiple PROCs with different sub-IDs, there is virtually no limit to how much code a DA can address (pardon the pun). Routines can even be called directly between PROCs, making it possible to put libraries in PROCs for everyone to use. The only trick is calculating the PROC resource ID, since only the sub ID is known at compile time. The easiest way around that problem is to call an initialization routine for each PROC needing to make inter-PROC procedure calls which stores the base ID in a global.


Listing Useless.c

#include<MacTypes.h>
#include<MemoryMgr.h>
#include<Quickdraw.h>
#include<WindowMgr.h>
#include<EventMgr.h>
#include<OSUtil.h>
#include<ResourceMgr.h>
#include<ToolboxUtil.h>
#include<DeviceMgr.h>

#define NULL(0L)
#define drvrOpen 0
#define drvrPrime1
#define drvrControl2
#define drvrStatus 3
#define drvrClose4
#define subidProc0
enum {
 irtnOpenWindow = 0,
 irtnDrawWindow,
 irtnKillWindow
};

extern shortmain(CntrlParam*,DCtlPtr,short);
extern shortDAOpen(CntrlParam*,DCtlPtr);
extern shortDAClose(CntrlParam*,DCtlPtr);
extern WindowPtr OpenWindow(char*,short,short,short,short);
extern void DrawWindow(WindowPtr,char*);
extern void KillWindow(WindowPtr);
extern Boolean   FLoadProc(short,Ptr*);
extern void UnloadProc(Ptr);

PtrpProc;
short rsidBase;

#define JumpToProc(pPROC,irtn,cwArgs)\
 asm {  \
 unlk   a6/* get rid of LSC’s frame  */\
 movea.lsp,a0  /* address of 1st argument */\
 lea    -8(sp),sp /* make room on the stack */\
 movea.lsp,a1  /* bottom of stack  */\
 move.w #(cwArgs)+1,d0/*(# of words to copy)-1*/\
 /* copy rtn addr and arguments      */\
LCopyAnother:    \
 move.w (a0)+,(a1)+/* move a word down */\
 dbra   d0,@LCopyAnother/* done when --d0 == -1*/\
 move.l a4,(a1)+ /* copy our a4 into storage*/\
 move.l (sp)+,(a1) /* copy our rtn addr just behind it*/\
 \
 pea    @LReturnToCaller/* push return for proc*/\
 move.w #irtn,-(sp)/* push routine selector*/\
 move.l pPROC,-(sp)/* push address of PROC*/\
 beq.s  @LHandleError/* jump if passed a NULL pPROC*/\
LJumpToProc:\
 rts    /* jump to proc(sleazy trick)*/\
 \
LHandleError:    \
 dc.w   0xA9FF   /* let Macsbug “handle” the error*/\
 lea    10(sp),sp/* fix up the stack */\
 \
LReturnToCaller: \
 lea    (2*(cwArgs))(sp),a0/* get add of storage space*/\
 movea.l(a0)+,a4 /* restore our a4 */\
 movea.l(a0),a0  /* get our return address*/\
 lea    8(sp),sp /* fix up the stack */\
 jmp    (a0)/* return to sender (address known)*/\
 }

short main(piopb, pdce, drvrRoutine)
CntrlParam*piopb;
DCtlPtr pdce;
short   drvrRoutine;
{
 GrafPtrpgpSav;
 short  valRtn;
 
 if (pdce->dCtlStorage == NULL) {
 if (drvrRoutine == 0) {  /*  open, but no data  */
 SysBeep(3);
 CloseDriver(pdce->dCtlRefNum);
 }
 return(0);
 }
 
 GetPort ( &pgpSav );

 switch (drvrRoutine) {
 case drvrOpen:
 valRtn = DAOpen(piopb,pdce);
 break;
 case drvrControl:
 case drvrPrime:
 case drvrStatus:
 valRtn = 0;
 break;
 case drvrClose:
 valRtn = DAClose(piopb,pdce);
 break;
 }
 
 SetPort ( pgpSav );
 
 return valRtn;
 
} /* main */

short DAOpen(piopb,pdce)
CntrlParam*piopb;/* pointer to parameter block */
DCtlPtr pdce;  /* the device control entry */
{
 rsidBase = 0xC000 | (~pdce->dCtlRefNum<<5);
 
 if ( !FLoadProc ( 0, &pProc ) ) {
 SysBeep(2);
 CloseDriver(pdce->dCtlRefNum);
 return 0;
 }

 /* Using hard-wired window coordinates? For shame... */
 pdce->dCtlWindow=OpenWindow(“\POur Window”,100,50,300,150);

 ((WindowPeek)pdce->dCtlWindow)->windowKind=pdce->dCtlRefNum;
 
 DrawWindow(pdce->dCtlWindow,”\PSomething funny.”);
 
 return 0;

} /* DAOpen */

short DAClose(piopb,pdce)
CntrlParam*piopb;
DCtlPtr pdce;
{
 if ( pdce->dCtlWindow != NULL )
 KillWindow ( pdce->dCtlWindow );

 if ( pProc != NULL )
 UnloadProc ( pProc );
}

Boolean FLoadProc ( subid, ppProc )
short subid;
Ptr*ppProc;
{
 Handle hProc;

 hProc = GetResource ( ‘PROC’, rsidBase + subid );
 if ( hProc == NULL ) {
 *ppProc = NULL;
 return FALSE;
 }

 /* avoid fragmentation and other evils */
 MoveHHi ( hProc );
 HLock ( hProc );
 HNoPurge ( hProc );
 *ppProc = *hProc;
 return TRUE;
}

void UnloadProc ( pProc )
PtrpProc;
{
 Handle hProc;
 
 if ( pProc == NULL )
 return;
 
 hProc = RecoverHandle ( pProc );
 if ( hProc == NULL )
 return;
 HUnlock ( hProc );
 HPurge ( hProc );
 return;
}

WindowPtr OpenWindow ( stTitle, xLeft, yTop, xRight, yBottom )
char  stTitle[];
short xLeft, yTop, xRight, yBottom;
{
 JumpToProc(pProc,irtnOpenWindow,6);
}

void DrawWindow(pwind,st)
WindowPtr pwind;
char    st[];
{
 JumpToProc(pProc,irtnDrawWindow,4);
}

void KillWindow(pwind)
WindowPtr pwind;
{
 JumpToProc(pProc,irtnKillWindow,2);
}
Listing: UselessProc.c
#include<MacTypes.h>
#include<MemoryMgr.h>
#include<Quickdraw.h>
#include<WindowMgr.h>
#include<EventMgr.h>
#include<OSUtil.h>
#include<ResourceMgr.h>
#include<ToolboxUtil.h>
#include<DeviceMgr.h>

#define NULL(0L)

extern void main ( void );
extern WindowPtr OpenWindow ( char*, short, short, short, short );
extern void DrawWindow ( WindowPtr, char * );
extern void KillWindow ( WindowPtr );

void main()
{
 asm {
 movea.la0,a4  ; get pointer to our globals
 move.w (a7)+,d0 ; pop off routine selector
 add.w  d0,d0    ; double the selector
 add.w  d0,d0    ; double it again
 lea    @JumpTable,a0; get address of jump table
 jmp    0(a0,d0.w) ; jump to our entry
JumpTable:
 jmp    OpenWindow
 jmp    DrawWindow
 jmp    KillWindow
 }
}
WindowPtr OpenWindow ( stTitle, left, top, right, bottom )
char  stTitle[];
short left, top, right, bottom;
{
 Rect rect;
 
 SetRect ( &rect, left, top, right, bottom );
 return NewWindow(NULL,&rect,stTitle,TRUE,documentProc,-1L,TRUE,0L);
}

void DrawWindow ( pwind, stMessage )
WindowPtr pwind;
char    stMessage[];
{
 short  h, v;

 SetPort ( pwind );
 v = (pwind->portRect.top + pwind->portRect.bottom)/2;
 h = (pwind->portRect.left + pwind->portRect.right)/2;
 h -= StringWidth(stMessage)/2;
 MoveTo ( h, v );
 DrawString ( stMessage );
}

void KillWindow ( pwind )
WindowPtr pwind;
{
 DisposeWindow ( pwind );
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Lyn 1.7.2 - Lightweight image browser an...
Lyn is a fast, lightweight image browser and viewer designed for photographers, graphic artists, and Web designers. Featuring an extremely versatile and aesthetically pleasing interface, it delivers... Read more
Tunnelblick 3.6.7beta02 - GUI for OpenVP...
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
jAlbum Pro 13.4 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. You can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly... Read more
calibre 2.65.1 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
jAlbum 13.4 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results - Simply drag and drop photos into groups, choose a design... Read more
Backblaze 4.2.0.966 - Online backup serv...
Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
Backblaze 4.2.0.966 - Online backup serv...
Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
Tunnelblick 3.6.7beta02 - GUI for OpenVP...
Tunnelblick is a free, open source graphic user interface for OpenVPN on OS X. It provides easy control of OpenVPN client and/or server connections. It comes as a ready-to-use application with all... Read more
calibre 2.65.1 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
jAlbum Pro 13.4 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. You can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly... Read more

Clean Text (Productivity)
Clean Text 1.0 Device: iOS Universal Category: Productivity Price: $3.99, Version: 1.0 (iTunes) Description: | Read more »
Gemini - A Journey of Two Stars (Games)
Gemini - A Journey of Two Stars 1.0.1 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.1 (iTunes) Description: *** SPECIAL LAUNCH SALE: $2.99 (25% off) *** "A mesmerizing and unexpectedly emotional journey." -- Los... | Read more »
How to get four NFL superstars for your...
Even though you're probably well on your way to building a top notch squad for the new season in Madden NFL Mobile, let's say you could beef it up by adding Rob Gronkowski, Antonio Brown, Von Miller, and Todd Gurley to your roster. That's... | Read more »
Cartoon Network Superstar Soccer: Goal!!...
Cartoon Network Superstar Soccer: Goal!!! – Multiplayer Sports Game Starring Your Favorite Characters 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Become a soccer superstar with your... | Read more »
NFL Huddle: What's new in Topps NFL...
Can you smell that? It's the scent of pigskin in the air, which either means that cliches be damned, pigs are flying in your neck of the woods, or the new NFL season is right around the corner. [Read more] | Read more »
FarmVille: Tropic Escape tips, tricks, a...
Maybe farming is passé in mobile games now. Ah, but farming -- and doing a lot of a other things too -- in an island paradise might be a little different. At least you can work on your tan and sip some pina coladas while tending to your crops. [... | Read more »
Become the King of Avalon in FunPlus’ la...
King Arthur is dead. Considering the legend dates back to the 5th century, it would be surprising if he wasn’t. But in the context of real-time MMO game King of Avalon: Dragon Warfare, Arthur’s death plunges the kingdom into chaos. Evil sorceress... | Read more »
Nightgate (Games)
Nightgate 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: *** Launch Sale: 25% OFF for a limited time! *** In the year 2398, after a great war, a network of intelligent computers known as... | Read more »
3 best fantasy football apps to get you...
Last season didn't go the way you wanted it to in fantasy football. You were super happy following your drafts or auctions, convinced you had outsmarted everyone. You were all set to hustle on the waiver wire, work out some sweet trades, and make... | Read more »
Pokemon GO update: Take me to your leade...
The Team Leaders in Pokemon GO have had it pretty easy up until now. They show up when players reach level 5, make their cases for joining their respective teams, and that's pretty much it. Light work, as Floyd Mayweather might say. [Read more] | Read more »

Price Scanner via MacPrices.net

Global Tablet Shipments Projected to Increase...
Digitimes’ Jim Hsiao reports that global tablet shipments will increase by 16.3 percent sequentially to reach nearly 47 million units in 2016′s third quarter, but that volume will still be down over... Read more
Apple’s 2016 Back to School promotion: Free B...
Purchase a new Mac or iPad using Apple’s Education Store and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free, and... Read more
Apple refurbished iPad Air 2s available start...
Apple has Certified Refurbished iPad Air 2 available starting at $339. Apple’s one-year warranty is included with each model, and shipping is free: - 128GB Wi-Fi iPad Air 2: $499 - 64GB Wi-Fi iPad... Read more
13-inch 2.5GHz MacBook Pro available for $961...
Overstock has the 13″ 2.5GHz MacBook Pro available for $961.63 including free shipping. Their price is $138 off MSRP. Read more
Clearance 12-inch Retina MacBooks, Apple refu...
Apple has Certified Refurbished 2015 12″ Retina MacBooks available starting at $929. Apple will include a standard one-year warranty with each MacBook, and shipping is free. The following... Read more
BookBook Releases SurfacePad, BookBook &...
BookBook has released three new covers just for iPad Pro: SurfacePad, BookBook and BookBook Rutledge Edition. BookBook for iPad Pro is a gorgeous leather case reminiscent of a vintage sketchbook.... Read more
Clean Text 1.0 for iOS Reduces Text Cleanup a...
Apimac today announced availability of Clean Text for iOS, a tool for webmasters, graphic designers, developers and magazine editors to reduce text cleanup and editing time, and also for any iPhone... Read more
27-inch iMacs on sale for up to $220 off MSRP
B&H Photo has 27″ Apple iMacs on sale for up to $200 off MSRP including free shipping plus NY sales tax only: - 27″ 3.3GHz iMac 5K: $2099 $200 off MSRP - 27″ 3.2GHz/1TB Fusion iMac 5K: $1899 $100... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 2016 and 2015 13″ MacBook Airs now available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 2016 13″ 1.6GHz/8GB/... Read more
Apple refurbished iPad mini 2s available for...
Apple is offering Certified Refurbished iPad mini 2s for up to $80 off the cost of new minis. An Apple one-year warranty is included with each model, and shipping is free: - 16GB iPad mini 2 WiFi: $... Read more

Jobs Board

Hardware Design Validation Engineer - *Apple...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
*Apple* /Mac Support Engineer - GFI Digital,...
FI Digital, Inc. is currently seeking candidates for a full time Apple Support Engineer to add to our Maryland Heights, Missouri IT team. Candidates must be dynamic Read more
*Apple* Solutions Consultant - Apple (United...
Apple Solutions ConsultantJob Number: 51218534Pleasant Hill, California, United StatesPosted: Aug. 18, 2016Weekly Hours: 40.00Job SummaryAs an Apple Solutions Read more
SW Engineer *Apple* TV - Apple Inc. (United...
The Apple TV team is looking for excellent software engineers with experience in hardware, media management, media playback, content delivery and a passion for Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.