TweetFollow Us on Twitter

Dec 01 REALbasic

Volume Number: 17 (2001)
Issue Number: 12
Column Tag: Declaring External Functions

by Joe Strout

Declaring External Functions in REALbasic

Harness the power of toolbox calls without resorting to C

Declaring Your Intentions

REALbasic’s extensive library floats on top of the sea of low-level functions that comprise the operating system like a cruise ship, protecting you from the dangers that lurk below. This is a two-sided coin; it frees you to concentrate on what you want to accomplish, rather than the details of how it’s done on this or that platform, but it also places limits on what you can do. When you hit one of those limits, and need to accomplish something not directly supported by the REALbasic library, then you’re going to have to get wet.

But even then, you have a choice. You could write a plug-in in C++, and link this plug-in into your REALbasic application [reference - Erik’s article on plugin dev]. That gives you full power to do pretty much anything, but it also requires quite a bit of learning, especially for developers not already familiar with C++. The other option is to use the Declare statement, which makes a function in REALbasic that links to a function in the operating system or a shared library. This is done entirely within REALbasic; no C++ is involved. To extend the ocean analogy a just bit further: writing a plug-in is like diving into the ocean untethered, while using a Declare is like hanging onto a life preserver on a rope — there are still restrictions on what you can do, but it’s also a lot easier to avoid drowning.

Though the Declare statement is used entirely within REALbasic, it’s still an advanced feature and a source of difficulty for many experienced REALbasic coders. This article will attempt to clarify and illustrate its use. After reading this article, you’ll be able to declare calls to classic MacOS, Carbon, or Win32 system routines, as well as to other shared libraries. This gives you the power to do a wide range of things not otherwise possible in REALbasic.

The Declare Statement: A Close Look

With no further ado, let’s jump right into an example. Listing 1 shows a REALbasic function that toggles the “modified” flag of a window. (Under Mac OS X, this sets or clears the little dot in the red Close widget in the title bar.) Since the functions involved are only available in MacOS 8.5 and higher, it also checks the system version before attempting to call them.

Listing 1: ToggleModified

ToggleModified

Toggle the “Modified” flag of the given REALbasic window.  This subroutine requires MacOS 8.5 or higher,
and does nothing under older versions of MacOS.  It will not compile as-is for 68k (these functions are 
not available to 68k apps).

Sub ToggleModified(win As Window)
   // Toggle the given window’s Modified flag
   Dim sysVersion, err As Integer

   #if TargetCarbon
   Declare Function IsWindowModified Lib “CarbonLib” (window 
         as WindowPtr) as Boolean
   Declare Function SetWindowModified Lib “CarbonLib” (window 
         as WindowPtr, modified as Boolean) as Integer
   #else
   Declare Function IsWindowModified Lib “WindowsLib” (window 
         as WindowPtr) as Boolean
   Declare Function SetWindowModified Lib “WindowsLib” (window 
         as WindowPtr, modified as Boolean) as Integer
   #endif

   // check the system version, since these functions are only available
   // in MacOS 8.5 and higher
   if System.Gestalt(“sysv”, sysVersion) and sysVersion >= 
            &h0850 then
      // system version is OK, so do the toggle
      err = SetWindowModified(win, not IsWindowModified(win))
    end if
  end if
end Sub

There are several things to notice in this example. First, if we want to compile for both “classic” Macintosh and for Carbon, we need two versions of each Declare — one linking against CarbonLib, and the other against the appropriate toolbox library (often InterfaceLib, but WindowsLib in this case). Next, built-in REALbasic functions generally do the right thing for any version of MacOS your code may be running on, but it can’t do so with a Declare; it’s your responsibility to make sure that the function you’re calling will be available. In this case, that means checking that the system version is at least 8.5. But once you’ve done all this, using the function you’ve declared is easy — just call it like any ordinary REALbasic method.

Let’s look at the Declare statements more closely. First, note that the declaration occurs within some other REALbasic method (subroutine or function). Just like a local variable, these declarations are local to where they’re declared; they will not be visible outside of this method. There is currently no way to make such a declaration global, though you could make a global wrapper function (as we’ve essentially done in this example).

The declaration always begins with the keyword “Declare” followed by either “Function” or “Sub”; REALbasic doesn’t actually care which you use, but by convention you use “Sub” when the C return type is void. Next is the name of the function, exactly as it appears in the external library. This is also the name used to invoke the function, unless you specify an “Alias” clause (which we’ll cover a bit later).

The next part of the declaration is the library name. This is the fragment name as it appears to the Code Fragment Manager, not the file name as it appears in the Finder; and unlike most words in REALbasic, this name is case sensitive. Finding this name is not hard, but we’ll come back to it a bit later.

Next come the function parameters, and finally the return type. This is where things get interesting. The documentation or header files on which you’re basing your Declare probably specify the parameter and return types with C syntax, rather than REALbasic. It may say something like “char *” or “Str255” or something unpalatable. What to do?

The Right Type for the Job

Most of the work in creating a Declare statement is in finding the right conversions between the C types you see in the headers and documentation, and the REALbasic types that serve the same purpose. Fortunately you have this article, which contains the very handy Table 1. This shows you some of the most common C types, and the corresponding REALbasic types you should use in a Declare statement.

Table 1: C types and REALbasic
equivalents for Declare statements

C data type Declare type Variable Type
Boolean Boolean Boolean
long Integer, OSType, Color Integer, String, Color
int
UInt32
SInt32
void*
Ptr
(etc.)
OSType OSType (parameters only) String
ResType
DescType
short Short Integer
unsigned short
UInt16
SInt16
float Single Single
double Double Double
Str255 PString String
Str63
Str31
unsigned char *
char * CString (parameters only) String
WindowPtr WindowPtr Window
WindowRef (parameters only)
void* Ptr MemoryBlock
Ptr
(or pointer to data structure)

The first column of this table is the data type the function is expecting, as you might see it in the documentation or the C header file. The middle column shows the corresponding types you could use in the Declare statement. Finally, the last column shows the “natural” REALbasic types which most closely match the declared types; these are what you would pass (or get as a result) when calling the function.

REALbasic uses the type you specify in the Declare to figure out how much data to push onto the call stack when making the call; so the most important thing is to get the size right. If the function is expecting any 4-byte value, you can probably declare this as type Integer. If you declare it as Short instead, REALbasic (when compiling for 68k processors) will push only two bytes, the function will try to grab four, and you’ll almost certainly crash.

Many of the type choices are very straightforward; if the function wants a float, declare it as a Single and pass it a number. If the function is expecting a Pascal string, declare it as type PString, and when you invoke the function, give it any ordinary string. REALbasic will automatically convert its internal string representation into a Str255 when calling the function (but note: if the function expects a shorter string than that, it’s up to you to make sure your string isn’t too long before making the call). Or if the return type is a Pascal string, the returned value will be automatically converted into a normal REALbasic string.

With other types, it’s not so clear cut. Suppose the function expects a pointer. Should you declare this as type Integer, or type Ptr? The answer depends on the details. If it’s a function that allocates some data structure, returns you a pointer to it, and you simply pass it back to other functions without trying to peek at the data, then an Integer will do fine. REALbasic integers are four bytes, and a pointer is four bytes, so treating the pointer as if it were just a number will make life easiest for you. Most modern Apple toolbox calls, such as those in Carbon or QuickDraw 3D, are of this sort, since the underlying data structures are opaque.

If, however, the function is expecting you to allocate storage for some data before calling the function, then a bit more work is required. In this case, declare the parameter as type Ptr. Allocate the memory in the form of a REALbasic MemoryBlock, created with the NewMemoryBlock function (and be sure to make it the right size!). If the function is expecting the data structure to be initialized with some values, you can use the MemoryBlock methods to stuff appropriate values into it. Then, pass this MemoryBlock to the function. From the function’s point of view, it just gets a pointer to some already-allocated memory. It may stuff values into this area, which you can later read by using those MemoryBlock methods again.

Calling a Spade a Spade

Your Declare statement must name the shared library which implements the function. In the case of a Windows DLL, you simply use the file name. In the Mac world, we don’t rely on file names, which are too frequently changed to be reliable. So you must use the fragment name as seen by the Code Fragment Manager (CFM). Most of the MacOS toolbox is in the fragment “InterfaceLib” (or “CarbonLib” for Carbon/OS X apps). But suppose you’re calling some other library — for example, the “Quesa” high-level 3D graphics library. The CFM name of this library is “QuickDraw™ 3D” (because it’s a binary-compatible replacement for Apple’s QuickDraw 3D technology). If you don’t happen to have me around to tell you handy facts like this, how would you know?

If you have ResEdit handy, it can give you the answer. Drop the library onto ResEdit and open the “cfrg” (code fragment) resource. This gives you a list of code fragments — usually there’s only one — and the name you’re looking for is labeled “Member Name”. Copy that value, and paste it into your Declare statement as the library name.

You can also use PEF Viewer, a free little utility from Apple. It also lists the code fragments it finds, and even lets you peek into the code, data, and loader information for each. (The last can be handy when Carbonizing — if the shared library you’re using imports any symbols from anywhere other than CarbonLib, you probably can’t use it in a Carbon app.) The CFM name of the library is given right at the top, next to the disclosure triangle.

If you don’t have either of these tools handy, you can use REALbasic itself. Just launch REALbasic, and drag the shared library file into the project window. It will appear there with its CFM name, rather than its file name. If you’re curious, what you have there is the alternative interface to importing functions from shared libraries — useful if you need a large number of functions from a single shared library, and you want them to be global rather than local. Since you’re using Declares rather than importing the library, you can just make note of the name, then delete the library from your project.

TBFinder to the Rescue

Writing Declares has always been a bit of an arcane art. Even armed with the knowledge in this article, you’ll find yourself wondering if it’s really worth the trouble when you just need a quick Mac toolbox call. Fortunately, there is a tool which can take much of the guess-work out of writing a toolbox-related Declare.

TBFinder is an open-source REALbasic project coordinated by Fabian Lidman. First, you point it to your copy of Universal Headers — i.e., Apple’s toolbox interface files, which you can download from Apple’s Development Kits web site. Then you can just find the toolbox call you need by searching or browsing, and it writes the Declare statement for you. There are some caveats, but in many cases it’s as simple as that.

TBFinder’s search window — what you see when you launch the utility — is shown in Figure 1. If you don’t know the name of the call you’re looking for, click “Browse” to get a nice hierarchical list of the functions defined in all the interface files, then just double-click the one you want. If you do know the name of the function you need, type it in the edit field; if you happen to know the header in which it’s declared, you can check the “Look first in:” checkbox and choose the appropriate header from the pop-up menu. Then click Search.


Figure 1. TBFinder search window.

The result, assuming the function actually exists, is a window like that shown in Figure 2.


Figure 2. TBFinder result window.

TBFinder’s search window — what you see when you launch the utility — is shown in Figure 2. The key bit is the second section, where the REALbasic syntax is shown. You can either use the Copy command to move this text to the clipboard, or drag the little text-clipping icon directly into REALbasic. The other icons in the window are actually buttons that do things when you click on them: the upper left icon by the header file name reveals that header file in the Finder; the text file icon by the original C declaration opens the header file (careful — Apple’s headers are set to open in Macintosh Programmer’s Workshop); and the globe at upper right does a web search for the function name in Apple’s documentation.

At the bottom of the window, you’ll notice a handy note telling you whether or not the function appears to be Carbon-compatible. If it is, you can use it in a Carbon application by simply changing the library name to “CarbonLib”. In the case of a classic application, TBFinder correctly identifies the library for you, using a big internal look-up table which I fervently hope was generated automatically by parsing the various system libraries.

TBFinder also supports one other feature of the Declare that I not previously mentioned: the “Inline68K” block. Many Mac toolbox calls on 68k machines are not shared library calls at all, but rather direct jumps to system traps. The C headers give the necessary machine language glue with the xWORDINLINE macros; TBFinder converts this to RB syntax, and even appends the extra glue needed in some cases by RB itself. (Be sure to get the latest version of TBFinder; version 2.0, included on the REALbasic CD, does not do this correctly.)

Finally, note that while TBFinder is invaluable, it isn’t infallible. In particular, it takes its best guess at the appropriate parameter conversions, but these often require human judgment (see Table 1 again). Always double-check the parameter types TBFinder has chosen, and consider whether there are other choices that make more sense for you.

Some Examples

Now that we’ve covered all the basics, let’s look at some real examples. We already saw one in Listing 1, used to toggle the “modified” flag on a window. Listing 2 shows another example, a call to the toolbox routine ObscureCursor, which hides the cursor until the mouse is moved. As this routine takes no parameters and returns no result, the declaration is very straightforward.

Listing 2: ObscureCursor

ObscureCursor
Hide the cursor until the mouse is moved (MacOS only).

Sub ObscureCursor()
   {
  // Declare it with a “Sys” prefix to avoid a name clash
   #if TargetCarbon
  Declare Sub SysObscureCursor Lib “CarbonLib” Alias 
            “ObscureCursor” ()
  #else
  Declare Sub SysObscureCursor Lib “InterfaceLib” Alias 
            “ObscureCursor” () Inline68K(“A856”)
  #endif
  
  // Call it!
  SysObscureCursor
end Sub

Note that in this example, I’ve chosen to name the REALbasic subroutine exactly like the toolbox routine. That means that when I declare the toolbox call within the subroutine, I’d have a name clash unless I use the “Alias” option. “Alias” allows me to declare the system routine as SysObscureCursor, avoiding thus avoiding the clash, and allowing all other users of this subroutine to simply call “ObscureCursor”.

Naturally, if you needed to use HideCursor/ShowCursor to hide the mouse even when it moves — say, because you’re making a game and have a custom cursor or no need for a cursor at all — then the declarations would be very similar. But let’s suppose you need to hide the cursor only within a rectangular region of the screen, perhaps because you’re doing some animation there, or simply because you want to demonstrate a Declare involving a MemoryBlock. The toolbox call for this is ShieldCursor. TBFinder gives the declaration as:

Declare Sub ShieldCursor Lib “InterfaceLib” (shieldRect as Ptr, offsetPt as Point) Inline68K(“A855”)

Apple’s documentation gives the first parameter as a Rect pointer, which TBFinder has rendered as type Ptr. From Table 1, you know that a MemoryBlock is needed for times like this. We use the MemoryBlock to manually construct a Rect structure, recalling that a Rect is just a set of four short integers representing top, left, bottom, and right

Dim m As MemoryBlock

m = NewMemoryBlock(8) // 8 bytes (four 2-byte ints)
m.Short(0) = r.top
m.Short(2) = r.left
m.Short(4) = r.top + r.height
m.Short(6) = r.left + r.width

We can then pass this as the first parameter to ShieldCursor. What about the second parameter, “offsetPt”? Again referring to Apple’s documentation, we find that this is the offset from the global coordinate system. In other words, it’s the top-left corner of the window containing the rectangle to which we’re referring. TBFinder has left this declared as “Point”, which is not a valid Declare parameter type. But since a Point is the same size as an Integer, we can fairly easily construct an integer that contains the information we need:

Dim point As Integer

// put pt.v into the high word, pt.h into the low word:
point = BitwiseAnd(self.top * 65536, &HFFFF0000)
point = BitwiseOr(point, BitwiseAnd(self.left, &H0000FFFF))

Then, we simply change the Declare statement so that the second parameter is of type Integer, and we’re ready to go. The full listing for the ShieldCursor function is shown in Listing 3.

Listing 3: ShieldCursor

ShieldCursor
Hide the cursor within the given rectangle control.

Sub ShieldCursor(r As RectControl)
     Declare Sub SysShieldCursor Lib “InterfaceLib” Alias “ShieldCursor” (shieldRect as Ptr, offsetPt 
     as Integer) Inline68K(“A855”)
  
  Dim m As MemoryBlock
  Dim point As Integer
  
  // construct the rect within which the cursor will be shielded
  m = NewMemoryBlock(8)  // 8 bytes (four 2-byte ints)
  m.Short(0) = r.top
  m.Short(2) = r.left
  m.Short(4) = r.top + r.height
  m.Short(6) = r.left + r.width
  
  // construct the point that converts global to local coordinates
  point = -self.top * 65536 - self.left   
                           // pt.v in the high word, pt.h in the low word
  
  // make the call
  SysShieldCursor m,point
End Sub

Let’s end with a Windows declaration. I know, this is a Mac programming magazine, but sometimes you need to deploy your app for that Other Platform — and the ability to do so easily is one of REALbasic’s strengths. However, with the Declare statement, you’re making a direct call to a toolbox or shared library routine; this is rarely portable. So the first thing you’ll need to do, is conditionalize your code so that Mac declares are included only in Mac builds, and Windows declares are included only in Windows builds. You can use either “#if TargetMacOS” or “#if TargetWin32” for this purpose.

Next, you’ll need information on the Windows API. Two good references are given at the end of this article.

Finally, you’ll need to construct the Declare statement by hand. TBFinder can’t help you here; but with this article on your shelf, you’re well-equipped for the task. So let’s dive into an example.

Windows applications can take command-line parameters, and these are often used to specify options or initial conditions for the application. REALbasic can get these one at a time via the OpenDocument event, but in many cases it’d be preferable to get the whole command line. A search on the MSDN Online Library quickly leads to the GetCommandLine function, declared in the documentation as:

LPTSTR GetCommandLine(VOID);

This function takes no parameters, making our lives easy in that respect. And the return type, it turns out, is a C string of either ASCII or UniCode characters. This is an important point: many Windows functions come in both ASCII and UniCode versions, as signified under “Requirements” with a note such as “Unicode: Implemented as Unicode and ANSI versions on all platforms.” When you see this, it means that the function name given is not the real function name — you need to append “A” for the ASCII version, or “W” for the UniCode version. So the actual function name, for our purposes, is GetCommandLineA.

The return type in this case is an ordinary C string. But, while REALbasic knows how to automatically convert its String type into a CString for use as a parameter, it can’t do the opposite conversion. So we need another approach. A C string is really just a pointer to some data, so we can declare the return type as “Ptr”, and use a MemoryBlock to get at the data. The result is shown in Listing 4.

Listing 4: GetCommandLine

GetCommandLine
Get the full Win32 command line, including executable name and parameters.

Function GetCommandLine()
  #If TargetWin32
  Dim m as MemoryBlock
  Declare Function GetCommandLineA Lib “KERNEL32.DLL” () as Ptr
  m = GetCommandLineA()
  Return m.cstring(0)   // get the C string to which m points
  #Else
  return “Command line available on Win32 only.”
  #EndIf
End Function

There is one other trick that can sometimes make Windows declares easier. If you can find a web site or other documentation with Declare statements for Visual Basic — such as the last reference given at the end of this article — then you can use those Declare statements in REALbasic almost verbatim. You’ll just need to make a few changes: the “ByVal” keyword can be omitted, and data types may have to be translated according to Table 1 (e.g., use “Integer” where VB says “Long”).

Conclusion

REALbasic tries to abstract away the details of the underlying operating system, so you don’t have to worry about whether you’re running on classic MacOS, OS X, or some flavor of Windows. But sometimes you need more control. You may need to talk directly with the OS, or you may need access to some other shared library (such as OpenGL, for example). In those cases, the Declare statement is invaluable.

The Declare statement allows you to do most of the same things you could do directly in C. There are some limitations; you can’t call a function that requires a callback, and you can’t directly invoke a function pointer (e.g., to call a mach-o entry point loaded via the CFBundle API). For those, you’ll still need to write a plug-in. But for most other needs, you can Declare your external calls, providing a neater, more integrated solution.

While Declare gives you nearly the power of C, it gives you nearly the complexity of C as well. Writing a Declare statement is error-prone, and if done incorrectly, can crash your program (and under Classic MacOS, the system as well). Deciphering the headers and documentation for the calls you’re making requires some understanding of how things work in C or Pascal. But, with the tips gained here and a bit of study and experimentation, there will soon be little you can’t do in REALbasic.

References

REALbasic Home Page
http://www.realsoftware.com/realbasic

TBFinder Home Page
http://homepage.mac.com/fabianl/development/

Apple’s Developer Documentation
http://developer.apple.com/techpubs/

Apple’s Software Development Kits
http://developer.apple.com/sdk/

Microsoft’s Official SDK Documentation
http://msdn.microsoft.com/library/

Unofficial Windows API Guide
http://www.vbapi.com/ref/index.html


Joe Strout is a neuroscientist-turned-software engineer currently working for REAL Software, Inc. You can reach him at joe@strout.net.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Alfred 4.2 - Quick launcher for apps and...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
MenuMeters 2.0.8 - CPU, memory, disk, an...
MenuMeters is a set of CPU, memory, disk, and network monitoring tools for Mac OS X. Although there are numerous other programs which do the same thing, none had quite the feature set I was looking... Read more
ClipGrab 3.8.15 - Download videos from Y...
ClipGrab is a free downloader and converter for YouTube, Vimeo, Facebook and many other online video sites. It converts downloaded videos to MPEG4, MP3 or other formats in just one easy step Version... Read more
Malwarebytes 4.6.11.3824 - Adware remova...
Malwarebytes (was AdwareMedic) helps you get your Mac experience back. Malwarebytes scans for and removes code that degrades system performance or attacks your system. Making your Mac once again your... Read more
Safari Technology Preview 14.1 - The new...
Safari Technology Preview contains the most recent additions and improvements to WebKit and the latest advances in Safari web technologies. And once installed, you will receive notifications of... Read more
Mactracker 7.9.7 - Database of all Mac m...
Mactracker provides detailed information on every Mac computer ever made, including items such as processor speed, memory, optical drives, graphic cards, supported OS X versions, and expansion... Read more
Tor Browser 10.0.2 - Anonymize Web brows...
The Tor Browser Bundle is an easy-to-use portable package of Tor, Vidalia, Torbutton, and a Firefox fork preconfigured to work together out of the box. It contains a modified copy of Firefox that... Read more
iTubeDownloader 6.5.24 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more
TeamViewer 15.11.6 - Establish remote co...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
ffWorks 2.1.18 - Convert multimedia file...
ffWorks, focused on simplicity, brings a fresh approach to the use of FFmpeg, allowing you to create ultra-high-quality movies without the need to write a single line of code on the command-line.... Read more

Latest Forum Discussions

See All

Lamplight City is a detective adventure...
Lamplight City is a steampunk adventure game that's available now for iOS after previously launching on PC. It sees players take on the role of private investigator Miles Fordham who is trying to get justice for his clients whilst tracking down... | Read more »
Genshin Impact Progression Guide - Mid-G...
Once you get up to around Adventure Rank 25, Genshin Impact starts to turn into a different thing. What was once a story-driven affair evolves into something more like a traditional MMO. | Read more »
Pixboy, the hit retro-themed platformer,...
Following its successful debut on PC and Nintendo Switch last summer, retro-themed platformer Pixboy has made the lead to mobile devices. The award-winning indie hit, which was developed by a two-person team at Oaky Games, has drawn comparisons to... | Read more »
Raziel: Dungeon Arena's newest char...
Raziel: Dungeon Arena's October update offers a little bit more than usual Halloween-themed outfits you often get this time of year, though those are here too. But beyond that, there is a new character, mercenary system and much more. In need of... | Read more »
Genshin Impact - Marvelous Merchant Even...
We're month since Genshin Impact's initial release, and we're already here at our second event. The Marvelous Merchant event has already been underway for two days and we have just the info you need to complete this silly scavenger hunt each day... | Read more »
Crash Bandicoot: On The Run! spins onto...
There’s a Crash Bandicoot endless runner coming to mobile sometime next spring. [Read more] | Read more »
Mulled 2 is a refreshingly simple ball-p...
Some of the greatest mobile games are actually the simplest ones, and it’s this philosophy that Mulled 2 was built on. Out now for iOS and Android, the puzzle sequel takes the accessible mechanics of its predecessor and builds on them with enticing... | Read more »
Physics-based tower defense game Goblin...
Indie developer Arif Games has released Goblin Raiders on iOS and Android. [Read more] | Read more »
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 »

Price Scanner via MacPrices.net

Apple now offering $8000 discount on maxed Ma...
Apple’s powerful Mac Pro can be fully optioned to a max of $53,399 by choosing a 2.5GHz 28-core Xeon W CPU, 1.5TB of memory, Two Radeon Pro Vega II Duo video cards with 2x32GB memory each, 8TB of... Read more
How to save $100-$200 on a new MacBook with A...
Are you a teacher, student, or staff member of any educational institution and need a new Apple MacBook? When you purchase a new MacBook using Apple’s Education discount, Apple will take $100-$200... Read more
Amazon offers Apple Watch Series 6 and SE dis...
Amazon is among the first Apple resellers to offer sale prices on new Apple Watch Series 6 and Apple Watch SE models with models available for up to $25 off Apple’s MSRP. These are the same Watches... Read more
3rd generation 12.9″ iPad Pros available toda...
Apple restocked select 3rd generation 12.9″ WiFi iPad Pros starting at only $699 and up to $380 off original MSRP. Each iPad comes with a standard Apple one-year warranty, outer cases are new, and... Read more
Upgrading To The iPhone 12 And Making The Swi...
FEATURE: 10.28.20 – The future of wireless technology is here but before you can jump onto the 5G bandwagon, you’ll need to upgrade to a compatible device like one of the new iPhones Apple... Read more
Gazelle drops prices on Apple’s iPhone 11 mod...
Gazelle is offering a range of discounted pre-owned unlocked Apple iPhone 11 models starting at $475, with some models seeing price reductions of up to $200. iPhones are offered in Fair, Good, and... Read more
B&H Photo has 2020 13″ Dual-Core MacBook...
B&H Photo has 2020 13″ Dual-Core MacBook Airs on sale today for $100 off Apple’s MSRP, only $899. Expedited shipping is free to many addresses in the US. These MacBook Airs are the same models... Read more
New promo at Verizon: New 64GB iPhone 11 Pro...
Verizon is offering the new 64GB iPhone 11 Pro Max for free for customers switching and opening a new line of service. Qualified trade-in required, worth up to $850 credited to your account over a 24... Read more
Apple has 2020 13″ 1.4GHz MacBook Pros in sto...
Apple has Certified Refurbished 2020 13″ 1.4GHz/256GB 4-Core Touch Bar MacBook Pros available for $1099, $200 off MSRP. Apple’s one-year warranty is included, shipping is free, and each MacBook has a... Read more
Amazon offers $50 discount on all new 2020 12...
Amazon has new 2020 Apple 12.9″ iPad Pros on sale today for $50 off MSRP, with prices starting at $949. Shipping is free. These are the same iPad Pros sold by Apple in their retail and online stores... Read more

Jobs Board

Licensed Dental Hygienist - *Apple* Valley...
Park Dental Apple Valley is seeking a compassionate, professional Dental Hygienist to join our team-oriented practice. Clinical Requirements Minnesota Licensure and Read more
Deskside Support Technician - *Apple* , Wind...
Deskside Support Technician - Apple , Windows **Ref No.:** 20-01859 **Location:** Northridge, California As part of a leading IT managed services specialist with more Read more
*Apple* Computing Specialist - Best Buy (Uni...
**789374BR** **Job Title:** Apple Computing Specialist **Job Category:** Store Associates **Store Number or Department:** 000896-Atlanta GA-Store **Job Read more
Geek Squad Advanced Repair *Apple* Professi...
**789256BR** **Job Title:** Geek Squad Advanced Repair Apple Professional **Job Category:** Store Associates **Store Number or Department:** 001135-Gulf Coast-Store 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 1 day ago Requisition # 124800 Sign Up for Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.