TweetFollow Us on Twitter

Calling CFM Libraries From 68K

Volume Number: 15 (1999)
Issue Number: 3
Column Tag: Programming Techniques

Calling CFM Shared Libraries From Classic 68K Code

by Miro Jurisic

Things they don't tell you in Technote 1077

Introduction

One of the great things about Macintosh software is that older 68K application software tends to work on the newer PowerPC machines and that user of the older machines can get modern software. One example of this is Apple's support for the Code Fragment Manager on 68K ("CFM68K") beginning with System 7.1. With this, 68K code can use the Code Fragment Manager to implement libraries and plug-ins, or can call PowerPC code when running emulated on a PowerPC system.

George Wagner first described how to call CFM shared libraries (whether PowerPC or 68K) from 68K code; you can find this as Apple Developer Technical Support Technote 1077 (TN 1077).

This article contains improvements to the code provided in TN1077, making the code more robust and provides examples of additional problems and techniques. In particular, this article describes:

  • calling CFM functions that return pointers,
  • calling CFM functions that cannot be directly called via the Mixed Mode Manager because of their prototypes, and
  • generating CFM glue code automatically from a Perl script.

This article assumes that you are familiar with the Code Fragment Manager and shared libraries (as described in [IM:PPC] and [IM:RTA]), and that you have read TN 1077 [TN1077].

Correctly Dealing with System 7

The code in TN 1077 does an incorrect check to determine the runtime architecture of the machine it's running on.

The problem with the TN 1077 code is that it returns an error when the gestaltSystemArchitecture Gestalt selector is not installed, i.e., on all versions of Mac OS before 7.1.2. However, CFM-68K is backwards compatible to system 7.1; as a result, TN 1077 glue code fails to load CFM-68K shared libraries on Mac OS 7.1 and 7.1.1.

The correct version of the code is:

GetSystemArchitecture
GetSystemArchitecture determines system architecture it is running
in (68K or PPC).

static pascal OSErr GetSystemArchitecture (
   OSType *outArchType)
{
   // static so we only Gestalt once
   static long sSysArchitecture = 0;
   OSErr tOSErr = noErr;

   // assume wild architecture
   *outArchType = kAnyCFragArch;

   // If we don't know the system architecture yet...
   // Ask Gestalt what kind of machine we are running on.
   if (sSysArchitecture == 0)
      tOSErr = Gestalt(gestaltSysArchitecture,
         &sSysArchitecture);

   if (tOSErr == noErr) { // if no errors
      if (sSysArchitecture == gestalt68k) // 68k?
            *outArchType = kMotorola68KCFragArch;
      else if (sSysArchitecture == gestaltPowerPC) // PPC?
            *outArchType = kPowerPCCFragArch;
      else // who knows what might be next?
            tOSErr = gestaltUnknownErr;
   } else if (tOSErr == gestaltUndefSelectorErr) {
      // No system architecture gestalt
      // We must be running on a system older than 7.1.2, so
      // we are on a 68K machine.
      *outArchType = kMotorola68KCFragArch;
      tOSErr = noErr;
   }
   return tOSErr;
}

Checking for CFM-68K

The TN 1077 code does not check for presence of the Code Fragment Manager before calling it to load the library. This causes the glue code to crash 68K machines with an Illegal A-Trap when CFM-68K is not present. To fix this error, we add code to the Find_Symbol function to check for presence of CFM:

Find_Symbol
Addition to the Find_Symbol function that correctly checks for presence
of CFM before calling it.

static pascal OSErr Find_Symbol (
   Ptr* pSymAddr,
   Str255 pSymName,
   ProcInfoType pProcInfo)
{
   /* Code to determine the runtime architecture goes here*/
   if (!HaveCFM()) {
      // If we don't have CFM68K
      // return a reasonable-looking error.
      sOSErr = cfragLibConnErr;
      return sOSErr;
   }
   /* Code to load the library and find the symbol goes here */
}

Clearly, we also need to add the HaveCFM() function:

HaveCFM
Checking for presence of the Code Fragment Manager
static pascal Boolean HaveCFM(void)
{
   OSErr theError = noErr;
   // static so we only call Gestalt once
   static Boolean sHaveCFM = false;
   static Boolean sCalledGestalt = false;
   long response;

   if (!sCalledGestalt) {
      theError = Gestalt(gestaltCFMAttr, &response);
      sCalledGestalt = true;
      sHaveCFM = 
         (theError == noErr) &&
         (((response >> gestaltCFMPresent) & 1) != 0);
   }
   return sHaveCFM;
}

Prototype Restrictions

Restrictions in the Mixed Mode Manager limit which functions in a CFM shared library can be accessed from classic 68K code. Only functions

  • which have a fixed number of arguments
  • which have no more than 13 arguments
  • whose arguments and the return value (if the function returns a non-void value) all have size of 1, 2, or 4 bytes can be called from classic 68K code.

This means that you cannot access any functions that take or return structs or some floating-point types; however, you can use functions that take or return pointers to structs or pointers to floating-point types.

Thus, if you control the library, you may want to consider changing the library interface so that your functions satisfy the above conditions. If you do not control the library, or you cannot change the library interface, you can write a wrapper library which provides an interface that can be called from classic 68K, and just calls the real library to do the work (with arguments appropriately rearranged).

For example, if you have a function in a shared library with the following prototype:

myFunction
Example of a function that cannot be called via the Mixed Mode Manager
struct myStruct {
   UInt32 a;
   UInt32 b;
}

// The following function can't be called from classic 68K
// using Mixed Mode Manager because the size of its
// arguments and the return value is not 1, 2, or 4 bytes.
myStruct myFunction (myStruct param1, myStruct param2);

then you can write the wrapper function like this:

myFunctionWrapper
Example of a wrapper function that can be called via the Mixed Mode Manager
// The following function can be called from classic 68K
// code using the Mixed Mode Manager.
// Note how the wrapper function avoids having to deal
// with memory allocation by changing the return value
// into a non-const pointer argument
void myFunctionWrapper (myStruct* result, const myStruct* param1, const myStruct* param2) {
   *result = myFunction (*param1, *param2);
}

If you put this wrapper function in a shared library, then you can call the wrapper function from your 68K code (because the wrapper function does not violate the Mixed Mode Manager restrictions), and the wrapper function can call the real function (because that is just a cross-fragment call that does not have those restrictions).

It is not possible to write a simple wrapper for a shared library function which takes a variable number of arguments, such as the C "printf" function. First of all, even if you could write a wrapper function, it would still be limited to no more than 13 arguments. However, the real problem is that the procedure information you would have to pass to the Mixed Mode Manager depends on the number and the types of the arguments, and this information is lost once the arguments are put on the stack. Either you would have to pass the number and sizes of arguments into the glue function, or the glue function would have to parse the arguments to determine how many there are and how big they are.

We can use printf as an example,

 printf("Hello, my name is %s\n", "Miro");

The printf function has to take the first argument and use the information provided there to extract the remaining parameters from the stack. To write a wrapper for this function, you would have to write a parser to extract the arguments, build the appropriate routine descriptor, and push them back onto the stack in the correct form before calling the function.

In any case, the work involved in writing the wrapper function is probably better spent rewriting the library function to take a fixed number of arguments.

Correctly Handling Library Functions that Return Pointers

Suppose your classic 68K code makes a call into a CFM shared library via glue code. The call is executed like this:

  • Your classic 68K code calls the classic 68K glue function, passing arguments according to a 68K calling convention. The particular calling convention used depends on the compiler options used to compile the glue functions.
  • The glue calls the CFM library function using a Universal Procedure Pointer (UPP).
  • The UPP invokes the Mixed Mode Manager, which examines the information in the UPP to determine how to rearrange the arguments into the CFM calling convention.
  • The Mixed Mode Manager calls the CFM function.
  • The CFM function executes, taking its arguments according to the CFM calling convention (68K or PPC, depending on which architecture the code is running on).
  • The CFM function places its return value in the appropriate place, again according to the CFM calling convention.
  • The Mixed Mode Manager returns control to the classic 68K code in the glue function, leaving the return value of the CFM function in the D0 register.
  • The glue function takes the return value and returns it to its caller.

To find arguments passed by the glue function and the value returned to the glue function, the Mixed Mode Manager uses procedure information in the UPP. This means that you have to make sure that compiler options you are using to compile the glue functions match the procedure information in the UPPs for the shared library functions. Otherwise, either your shared library function will get garbage arguments, or the glue function will get a garbage return value.

If your glue functions follow Pascal calling conventions, then you should use kPascalStackBased as the calling convention in procedure information.

If your glue functions follow C calling convention, and are compiled with an MPW compiler, or with another MPW-compatible compiler (such as Metrowerks compiler with "MPW Calling Conventions" turned on), then you should use kCStackBased in procedure information.

If you are compiling with the default settings for Metrowerks compilers, then your glue functions will follow the ThinkC calling convention, and you should use kThinkCStackBased in the procedure information. There is one twist, however: if the return value from a UPP call is a pointer, the glue function will expect the result to be returned in the A0 register instead of the D0 register. However, the MixedModeManager will always put the return value in D0 if UPP has kThinkCStackBased for its calling convention.

Therefore, if you start with the default settings for Metrowerks compilers, you have to compile your glue functions so that they expect the results to always be returned in D0. This is done by surrounding your glue code with the following pragmas:

#pragma d0_pointers on
// Prototypes for glue functions go here
#pragma d0_pointers reset
Generating Glue Code Programatically

As I was working with CFM glue for several shared libraries, it occurred to me that writing the glue was a fairly mechanical process; there is a section of code that's invariant, and a secion of code that contains glue functions, but the glue functions follow a fairly straightforward pattern. Thus I thought generating the glue with a script might be a good idea. Looking at my options, I decided that Perl was a good tool for the job, so I summoned my local Perl deity.

She consed up1 a script that takes as input a list of C function prototypes, such as:

Sample prototype list
UInt16 sampleFun1 (void* arg1, UInt32 arg2);
void sampleFun2 (UInt32* arg1, UInt16 arg[]);
void sampleFun3 (void);

Although it may seem annoying that you need to take your existing C headers and strip them down to the form accepted by the Perl script, we decided that it would be slightly impractical to implement enough of a C parser and semantic analyzer in Perl to extract prototypes from a regular header file.

The script uses MacPerl and its MPW tool; before you can use the script, you will have to install MacPerl and the tool (see [MacPerl] for download and installation instructions).

The output of the script for the above input is:

Sample glue generated by the script
/**** sampleFun1 ****/
/* UInt16 sampleFun1 (void* arg1, UInt32 arg2); */

enum {
   sampleFun1_ProcInfo = kThinkCStackBased
   | RESULT_SIZE(SIZE_CODE(sizeof(UInt16)))
   | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(void *)))
   | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(UInt32)))
};

typedef UInt16 (*sampleFun1_ProcPtrType)(void *, UInt32);
UInt16 sampleFun1 (
   void * arg1,
   UInt32 arg2)
{
   static sampleFun1_ProcPtrType sampleFun1_ProcPtr =
      kUnresolvedCFragSymbolAddress;

   // if this symbol has not been setup yet...
   if ((Ptr) sampleFun1_ProcPtr == (Ptr)
                                          kUnresolvedCFragSymbolAddress)
      FindLibrarySymbol ((Ptr *) &sampleFun1_ProcPtr,
         "\psampleFun1",
         sampleFun1_ProcInfo);
   if ((Ptr) sampleFun1_ProcPtr !=
         (Ptr) kUnresolvedCFragSymbolAddress)
      return sampleFun1_ProcPtr (arg1, arg2);
}

/**** sampleFun2 ****/
/* void sampleFun2 (UInt32* arg1, UInt16 arg[]); */

enum {
   sampleFun2_ProcInfo = kThinkCStackBased
   | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(UInt32 *)))
   | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(UInt16 *)))
};

typedef void (*sampleFun2_ProcPtrType)(UInt32 *, UInt16 *);
void sampleFun2 (
   UInt32 * arg1,
   UInt16 * arg)
{
   static sampleFun2_ProcPtrType sampleFun2_ProcPtr =
      kUnresolvedCFragSymbolAddress;

   // if this symbol has not been setup yet...
   if ((Ptr) sampleFun2_ProcPtr ==
         (Ptr) kUnresolvedCFragSymbolAddress)
      FindLibrarySymbol((Ptr *) &sampleFun2_ProcPtr,
         "\psampleFun2",
         sampleFun2_ProcInfo);
   if ((Ptr) sampleFun2_ProcPtr !=
         (Ptr) kUnresolvedCFragSymbolAddress)
      sampleFun2_ProcPtr(arg1, arg);
}

/**** sampleFun3 ****/
/* void sampleFun3 (void) */

enum {
   sampleFun3_ProcInfo = kThinkCStackBased
};

typedef void (*sampleFun3_ProcPtrType)(void);
void sampleFun3 (
   void)
{
   static sampleFun3_ProcPtrType sampleFun3_ProcPtr =
      kUnresolvedCFragSymbolAddress;

   // if this symbol has not been setup yet...
   if((Ptr) sampleFun3_ProcPtr ==
      (Ptr) kUnresolvedCFragSymbolAddress)
      FindLibrarySymbol((Ptr *) &sampleFun3_ProcPtr,
         "\psampleFun3",
         sampleFun3_ProcInfo);
   if ((Ptr) sampleFun3_ProcPtr !=
         (Ptr) kUnresolvedCFragSymbolAddress)
      sampleFun3_ProcPtr();
}

Once we produce this output, we need to assemble it into a final glue file consisting of:

  • a preamble, containing a #define for the library fragment name, and #includes for all the header files needed by the glue functions (which is at least the header files containing glue functions' declarations and some system headers), followed by
  • the general glue code, containing HasCFM(), GetSystemArchitecture(), and FindLibrarySymbol() functions, followed by
  • the auto-generated glue functions from above, followed by
  • a postamble, containing any other code that we may want (and that requires any of the code in the general glue); typically, this is a function that tests for presence of the shared library.

We generate the final glue file with the following MPW commands:

   perl MakeCFMGlue.pl < :GlueExample.proto.h > ∂
      :GlueExample.Glue.c
   Catenate :GlueExample.Glue.pre.c :Glue.c ∂
      :GlueExample.Glue.c :GlueExample.Glue.post.c | ∂
      Catenate > :GlueExample.Glue.c

Gotchas

As noted before, the Perl script is nowhere near as smart as a C compiler. Because of this, the script cannot detect some things that you cannot or should not put in your glue code, such as:

  • functions whose arguments or the return value have size other than 1, 2, or 4 bytes;
  • functions that take arguments or return values whose size, when passed or returned, is 1, 2, or 4 bytes, but whose sizeof() is not.

The script also does not detect the following conditions which will generate incorrect glue code:

  • functions whose prototypes are not legal C
  • functions whose prototypes contain C preprocessor macros

The script will detect the following problems in your prototypes:

  • functions with no arguments and no dummy void argument
  • functions with a variable argument list (either ... or va_list)

You should be especially careful about types whose sizeof()is not the same as their size when passed as argument; this is somewhat tricky, as it is usually not obvious. Consider the following code:

typedef UInt32 TwoWordType[2];
void myFunction (TwoWordType arg);

Script-generated glue for such a function would contain the following procedure information:

enum {
   myFunction_ProcInfo = kThinkCStackBased
   | STACK_ROUTINE_PARAMETER(1,
      SIZE_CODE(sizeof(TwoWordType)))
};

The problem is that sizeof(TwoWordType) is 8, but a TwoWordType is passed on the stack as a pointer. So, the size that we really want to use here is that of a UInt32[]. Using sizeof(TwoWordType) causes the procedure information to indicate the size is 0 (see the definition for SIZE_CODE in MixedMode.h), i.e., no argument is passed, so that the CFM library function gets garbage in. ("Nasty." "Yeah.")

A typedef to an array type is the only case that we could find where a bizarre thing like this happens, but it took us a while to figure it out, so be careful with your typedefs when CFM glue is in town.

These weaknesses of the Perl script are the main reason why we consider it a feature that you need to create the prototype list (mostly) by hand, because you have an opportunity to look at all the prototypes at that time and inspect them for possible problems.

A Complete Example

Complete source for CFM glue functions is provided with the article; the code consists of the following:

  • GlueExample library: a sample CFM library (includes functions that cannot be called from classic 68K without a wrapper).
  • GlueExample.h: header file containing function declarations for the shared library.
  • GlueExample.c: shared library source.
  • GlueExample.mcp: CodeWarrior Pro 2 project for the shared library.
  • GlueExample.Glue.c: source file containing glue code for GlueExample library.
  • GlueTest test application: sample application that uses GlueExample glue.
  • GlueTest.c: the application source.
  • GlueTest.mcp: CodeWarrior Pro 2 project for the application.
  • WrapperExample library: a sample CFM shared library which provides wrappers for functions in GlueExample that cannot be called from classic 68K code.
  • WrapperExample.h: wrapper function prototypes.
  • WrapperExample.c: wrapper function implementations.
  • WrapperExample.mcp: CodeWarrior Pro 2 project for the wrapper library.
  • WrapperExample.Glue.c: source file containing glue code for the WrapperExample library
  • WrapperTest test application: an application that calls functions in GlueExample that cannot be called directly via the Mixed Mode Manager, using WrapperExample library.
  • WrapperTest.c: application source.
  • WrapperTest.mcp: CodeWarrior Pro 2 project for the application.
  • GlueExample and WrapperExample glue autogeneration: sources and script that autogenerate GlueExample glue.
  • Glue.c: the invariant portion of CFM glue.
  • GlueExample.Glue.mpw: MPW script with commands that generate GlueExample glue
  • GlueExample.Glue.pre.c: preamble for GlueExample glue autogeneration.
  • GlueExample.Glue.post.c: postamble for GlueExample glue autogeneration.
  • GlueExample.proto.h: prototype list for the GlueExample library, input to the glue generation script.
  • WrapperExample.Glue.mpw: MPW script with commands that generate glue for WrapperExample
  • WrapperExample.Glue.pre.c: preamble for WrapperExample glue autogeneration.
  • WrapperExample.Glue.post.c: postamble for WrapperExample glue autogeneration.
  • WrapperExample.proto.h: prototype list for the WrapperExample library, input to the glue generation script.
  • MakeCFMGlue.pl: Perl glue generation script.
  • CFM.InitTerm.h: declarations for standard Metrowerks __initialize and __terminate routines, called from Glue library initialization and termination routines.

Conclusion and Debugging Tips

Writing classic 68K glue for CFM library functions can be tricky. There are plenty of ways to make a mistake, and most of them result in moderately baffling failures. Here are some things to try when your glue code doesn't work:

Check whether your library is getting loaded at all, by either:

  • stepping through the code that loads the library and inspecting the return value from GetSharedLibrary() or
  • stepping over the code that loads the library and using the MacsBug 'frags' dcmd to see if the library has been loaded.

If the library is not getting loaded, make sure that:

  • CFM is installed (either step through HaveCFM(), or look at the 'cfrg' Gestalt selector in MacsBug);
  • your library is in the CFM search path (application file, application folder, Extensions folder). See [IM:RTA] for details on the search order for CFM fragments;
  • the fragment name in the glue code is correct (you can see the names of the fragments in a shared library by opening it in ResEdit or Resorcerer and looking at the 'cfrg' 0 resource);
  • there is enough room in the heap to load the fragment;
  • all the fragments your library depends on are available (if the error returned by GetSharedLibrary() contains fragment name other than your library's fragment name, then that fragment is unavailable and caused your library to fail to load);

If the library is getting loaded, but it is misbehaving:

  • If you seem to be calling a function other than the one you expected to call, check that there isn't a typo in the glue code for the function you are trying to call. Since glue code is a copy-and-paste job, forgetting to change variable or symbol names is not uncommon.
  • If you seem to be calling the right shared library function, but its arguments are garbage (it is very helpful at this point if you have the SYM file for the library), there is probably an error in the procedure information for that function
  • If the shared library function arguments are correct, and the function executes fine, but returns garbage, then most likely either procedure information for the function is wrong or the glue function was not compiled with "pointers in D0" calling convention when it should be.

If all of this fails, then I sincerely hope you are on friendly terms with MacsBug...

Acknowledgements

I would like to thank George Wagner of DTS for writing the original "develop" article and the Technote (without him, I would never have been able to get started on this); Alexandra Ellwood, who wrote the glue code generation Perl script (without her, I would have had to learn Perl); Marshall Vale, for asking "I wonder if we could..."; and Scott McGuire, who is not a writing major.

References


Miro Jurisic is a wacky European working for Massachusetts Institute of Technology Information Systems. In his free time, he is a full-time student at MIT, working on his Master's degree in Computer Science and Bachelor's degree in Mathematics. His only artistic ability is ASCII art. He can be reached at meeroh@mit.edu.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Audio Hijack 3.7.3 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
CleanMyMac X 4.6.15 - Delete files that...
CleanMyMac makes space for the things you love. Sporting a range of ingenious new features, CleanMyMac lets you safely and intelligently scan and clean your entire system, delete large, unused files... Read more
Suitcase Fusion 21.2.1 - Font management...
Suitcase Fusion is the creative professional's font manager. Every professional font manager should deliver the basics: spectacular previews, powerful search tools, and efficient font organization.... Read more
Civilization VI 1.3.6 - Next iteration o...
Civilization® VI is the award-winning experience. Expand your empire across the map, advance your culture, and compete against history’s greatest leaders to build a civilization that will stand the... Read more
Dashlane 6.2042.0 - Password manager and...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more
Airfoil 5.9.2 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
VirtualBox 6.1.16 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
Xcode 12.1 - Integrated development envi...
Xcode includes everything developers need to create great applications for Mac, iPhone, iPad, and Apple Watch. Xcode provides developers a unified workflow for user interface design, coding, testing... Read more
FileZilla 3.51.0 - Fast and reliable FTP...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface. Version 3.51.0: Bugfixes and minor changes: Fixed import of... Read more
KeyCue 9.8 - Displays all menu shortcut...
KeyCue has always been a handy tool for learning and remembering keyboard shortcuts. With a simple keystroke or click, KeyCue displays a table with all available keyboard shortcuts, system-wide... Read more

Latest Forum Discussions

See All

PUBG Mobile has provided yet another upd...
PUBG Mobile has been making a point of publicly mentioning all of their ongoing efforts to vanquish cheating from the popular battle royale. Today two teams within the company have provided updates on their progress. [Read more] | Read more »
Zombieland: AFK Survival is celebrating...
Zombieland: AFK Survival is currently celebrating its one-year anniversary. If you don't quite recognise the name that's because it initially launched as Zombieland: Double Tapper. Anyway, the game is celebrating turning one with two Halloween-... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
Genshin Impact Guide - Gacha Strategy: W...
If you're playing Genshin Impact without spending money, you'll always need to be looking for ways to optimize your play to maximize rewards without getting stuck in a position where you're tempted to spend. The most obvious trap here is the game'... | Read more »
Genshin Impact Adventurer's Guide
Hello and well met, fellow adventurers of Teyvat! Check out our all-in-one resource for all things Genshin Impact. We'll be sure to add more as we keep playing the game, so be sure to come back here to check for updates! [Read more] | Read more »
Genshin Impact Currency Guide - What...
Genshin Impact is great fun, but make no mistake: this is a gacha game. It is designed specifically to suck away time and money from you, and one of the ways the game does this is by offering a drip-feed of currencies you will feel compelled to... | Read more »
XCOM 2 Collection on iOS now available f...
The XCOM 2 Collection, which was recently announced to be coming to iOS in November, is now available to pre-order on the App Store. [Read more] | Read more »
Presidents Run has returned for the 2020...
IKIN's popular endless runner Presidents Run has returned to iOS and Android just in time for the 2020 election season. It will see players choosing their favourite candidate and guiding them on a literal run for presidency to gather as many votes... | Read more »
New update for Cookies Must Die adds new...
A new update for Rebel Twins’ platformer shooter Cookies Must Die is coming out this week. The update adds quite a bit to the game, including new levels and characters to play around with. [Read more] | Read more »
Genshin Impact Guide - How to Beat Pyro...
The end game of Genshin Impact largely revolves around spending resin to take on world bosses and clear domain challenges. These fights grant amazing rewards like rare artifacts and ascension materials for weapons and adventurers, but obviously... | Read more »

Price Scanner via MacPrices.net

Use our exclusive iPhone Price Trackers to fi...
Looking for a new Apple iPhone 12 or 12 Pro? Perhaps a deal on last year’s iPhone 11? Check out our iPhone Price Tracker here at MacPrices.net. We track new and clearance iPhone prices from Apple as... Read more
Weekend deal: $100 off 13″ MacBook Airs at Am...
Amazon has new 2020 13″ MacBook Airs on sale for $100 off Apple’s MSRP, starting at only $899. Their prices are the lowest available for new MacBooks from any Apple resellers. These are the same 13″... Read more
New 10.9″ 64GB Apple iPad Air on sale for $55...
Amazon has Apple’s new 2020 10.9″ 64GB WiFi iPad Air on sale today for $549.99 shipped. That’s $40 off MSRP. Pre-orders are available today at this discounted price, and Amazon states that the iPad... Read more
Get a clearance 2019 27″ 5K iMac for up to $5...
Apple has Certified Refurbished 2019 27″ 5K iMacs available starting at $1439 and up to $520 off their original MSRP. Apple’s one-year warranty is standard and shipping is free. The following... Read more
AT&T offers the Apple iPhone 11 for $10/m...
AT&T is offering Apple’s 64GB iPhone 11 for $10 per month, for customers opening a new line of service, no trade-in required. Discount is applied via monthly bill credits over a 30 month period.... Read more
Apple’s 2020 11″ iPad Pros on sale today for...
Apple reseller Expercom has new 2020 11″ Apple iPad Pros on sale for $50-$75 off MSRP, with prices starting at $749. These are the same iPad Pros sold by Apple in their retail and online stores: – 11... Read more
Did Apple Drop The Ball By Not Branding Its C...
EDITORIAL: 10.21.20 – In the branding game, your marketing strategy can either be a hit or a miss and the latter is the case for Apple when it missed out on an opportunity to brand its “SE” series of... Read more
27″ 6-core and 8-core iMacs on sale for up to...
Adorama has Apple’s 2020 27″ 6-core and 8-core iMacs on sale today for $50-$100 off MSRP, with prices starting at $1749. Shipping is free: – 27″ 3.1GHz 6-core iMac: $1749, save $50 – 27″ 3.3GHz 6-... Read more
Apple’s 16″ MacBook Pros are on sale for $300...
B&H Photo has 16″ MacBook Pros on sale today for $300-$350 off Apple’s MSRP, starting at $2099. Expedited shipping is free to many addresses in the US. Their prices are among the lowest available... Read more
Apple has 2020 13″ MacBook Airs available sta...
Apple has a full line of Certified Refurbished 2020 13″ MacBook Airs available starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a... Read more

Jobs Board

Dental Receptionist - *Apple* Valley Clinic...
Dental Receptionist - Apple Valley Clinic + Job ID: 57314 + Department: Apple Valley Dental + City: Apple Valley, MN + Location: HP - Apple Valley Clinic Read more
*Apple* Mobility Specialist - Best Buy (Unit...
**788165BR** **Job Title:** Apple Mobility Specialist **Job Category:** Store Associates **Store Number or Department:** 001013-Virginia Commons-Store **Job Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley Retail Post Date Oct 08, 2020 Requisition # 124800 Sign Up for Read more
*Apple* Mobility Specialist - Best Buy (Unit...
**784631BR** **Job Title:** Apple Mobility Specialist **Job Category:** Store Associates **Store Number or Department:** 000522-Baxter-Store **Job Description:** The Read more
Senior Data Engineer - *Apple* - Theorem, L...
Job Summary Apple is seeking an experienced, detail-minded data engineeringconsultant to join our worldwide business development and strategy team. If you are Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.