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.