Hi Dave,
We use function pointers as well. We have gone one step further in that for much-faked modules, we write generic stubs that allow for swapping in mocks, handwritten stubs, dummies (default) or even the original code at runtime.
Swapping in the original code requires an #include "original.c" (which is slightly ugly ;-). I write the wrapper code in C++, which results in the original function names being mangled, so I can call them from wrapper functions declared as extern "C". I used to declare them static before, but that's tricky and not really clean. The C++ approach is a lot nicer.
Once such stub is written, it never needs to be touched again, well, hardly ever (interface changes). Different stubs can be linked for different tests, if necessary, Dummies and mocks are always the same anyway.
I have taken to writing dummies in C++ as well, because it allows you to omit parameter names when you don't plan on using them. This eliminates the need for UNUSED() macros when compiling with -Werror and -Wall. The C application doesn't care, as long as the dummies are declared extern "C".
Cheers,
Robert