While working on bringing up the latest game in Xcode, I've run into a more than average amount of portability issues with the code. Glenda gave a talk at WWDC about building Universal Binaries, and I believe a chunk of hat was related to issues that weren't so much PPC->x86 as they were CodeWarrior or Visual Studio -> Xcode/gcc.
So allow me to share some Tales from the Front Line of Win32 Ports, and ways you can avoid portability problems in your own apps. I won't promise the pseudo-code below compiles; I've just typed it on the fly from my sometimes-faulty memory. I welcome clarifications in the comments.
1. Don't use "legacy" for-loop scoping in Visual Studio.
Legacy for-loop scoping looks like this:
{
for (int i = 0; i < 10; i ++)
{
printf ("i = %d", i);
}
// Using legacy for-loop scoping, i is in context here outside
// the for-loop and this code compiles.
// Using standards-compliant for-loop scoping, it is
// out of context and an error.
printf ("i = %d", i);
}
Apple's gcc 4 implementation gives off a warning that it's using old for-loop scoping when it encounters this and you might think you're OK, but there's a massive catch: the optimizer (at least under PowerPC) does not work right and will generate bad code. The only solution is to fix it to be standards-compliant, which means moving the "int i" declaration outside and above of the for-loop in this example.
2. Don't pass non-POD data types to things like printf.
POD data types are "Plain Old Data" - things like int, char, etc - basically anything that's part of the C spec and doesn't require a header to use. User-defined classes, the STL and things like that are not POD. The most common example of this abuse is trying to print an STL string with printf:
{
std::string hair_status = "fantastic!";
printf ("My hair looks %s", hair_status); // This will magically work under CodeWarrior and Visual Studio
printf ("My hair looks %s", hair_status.c_str()); // But this is the Right Thing, and works under gcc as well.
}
When gcc sees this, it spits out a warning that it will abort when execution reaches this point, and it's not lying. The app generates a trap exception, which will either a) terminate your app or b) stop it in the debugger.
Since std::string is not a "POD" type, you must fix the code to do the right thing. In this case, that means using the c_str() method to return a char * to the actual string data. char * is a "POD" data type and c_str() is intended for usage with older C routines like printf vs. the new ones in C++.
3. Be careful when using std::transform() and certain functions like tolower and toupper.
std::transform is a neat STL routine that takes input and output iterators on an object and iterates over each member in the object, calling a function subroutine on each one. One common use is to convert a string to all upper or lower case using the C library routines toupper() and tolower(), like so:
{
std::string yelling = "i'm yelling at you.";
std::transform(yelling.begin(), yelling.end(), yelling().begin, toupper);
}
Here's the catch: on many implementations (Apple's included), toupper is not a routine, but a macro! To fix this, you would typically include <cctypes> and specify std::toupper to get the actual function. If the toupper macro is still giving you fits (e.g. through some precompiled header complications), you can force the issue like so:
{
std::string yelling = "i'm yelling at you.";
std::transform(yelling.begin(), yelling.end(), yelling().begin, (int(*)(int))std::toupper); // Explicit cast forces the compiler to choose the function def over the macro
}
4. Use the proper syntax for "pointer-to-member functions"
I recently converted a different project from CodeWarrior to Xcode and in the process had to change many lines of code that attempted to use pointer-to-member functions (ptmf). Essentially, they are exactly what they sound like : pointers to member functions of a given class:
class MacPro
{
public:
MacPro();
~MacPro();
bool Radeon_Card();
bool GeForce_Card();
bool PieceOfJunk_Card();
};
typedef bool (MacPro::*whichVideoCard)();
void main(int argc, char **argv)
{
whichVideoCard videoCardPtr;
int card = 1;
if (card == 1)
{
videoCardPtr = &MacPro::Radeon_Card(); // Good
videoCardPtr = Radeon_Card(); // Bad, but Visual Studio happily accepts this.
}
else if (card == 2)
{
videoCardPtr = &MacPro::GeForce_Card(); // Good
videoCardPtr = GeForce_Card(); // Bad, but Visual Studio happily accepts this.
}
else
{
videoCardPtr = &MacPro:: PieceOfJunk_Card(); // Good
videoCardPtr = PieceOfJunk_Card(); // Bad, but Visual Studio happily accepts this
}
}
5. Use "friend class" instead of "friend" where appropriate
The title pretty much says it all. If you're declaring a class as a friend of another class, the proper syntax to use is "friend class TheOtherClass;" instead of just "friend TheOtherClass;". gcc requires the right syntax whereas Visual Studio lets you get away with the sloppier variant.
One of the frequent sources of grief moving between Visual Studio/CodeWarrior and gcc is template syntax, which I'll save for its own future blog entry.
Comments
Hey, Brad. This is pretty much life among The Sims, too (or should I say 2).
Re item #2 (I call the fix "POD casting"), these should obviously generate hard errors. A warning that your program will unceremoniously abort is incredibly stupid. I forgot about it, or Ida brought it up at one of the WWDC Q&A sessions.
Posted by: David Gish | August 23, 2006 09:18 AM
It's also worth noting in that last example that you have to be careful with pointers to member functions; if they're not class functions (i.e. declared as "static"), they'll probably break when you call them unless you have a way of providing them an appropriate instance context. Of course, since you're writing more about portability than functionality, it's reasonable to assume that the programmer would eventually figure this out when their program didn't work the first time. :-)
Posted by: Dave Riley | October 5, 2006 03:00 PM