Understanding the Source code

I’m a pretty proficient programmer (most of my experience is with C#), but I’m still finding myself stumped by some of the source code. I’m hoping that if I understand the source I can implement new classes as necessary and use proper conventions (especially where error handling is concerned).

First question: Macros.
FRC apparently (and inexplicably) has made extensive use of macros. I’m not extremely familiar with them, but they seem to serve many purposes.
The first one I came across: “START_ROBOT_CLASS(IterativeDemo);” at the bottom of the demo class. This references RobotBase.h, which has:


#define START_ROBOT_CLASS(_ClassName_) \
RobotBase *FRC_userClassFactory(void) \
{ \
	return new _ClassName_(); \
} \
extern "C" { \
	int FRC_UserProgram_StartupLibraryInit(void) \
	{ \
		RobotBase::startRobotTask((FUNCPTR)FRC_userClassFactory); \
		return 0; \
	} \
}

This makes no sense to me. Where is the main() function that would presumably instantiate the robot class? Also, what is this START_ROBOT_CLASS doing exactly?

Second question: Macros. Heh, sorry, I mean Macros that do error handling.
The next major macro confusion is with the "wpi_assert"s and "wpi_fatal"s that are sprinkled across the code. One example in RobotDrive.cpp:


void RobotDrive::SetInvertedMotor(MotorType motor, bool isInverted)
{
	if (motor < 0 || motor > 3)
	{
		wpi_fatal(InvalidMotorIndex);
		return;
	}
	m_invertedMotors[motor] = isInverted ? -1 : 1;
}

I understand that it’s some sort of error code, but how exactly does it work? From the FRC C++ guide, the WPI library doesn’t use C++'s exception handling system (for the rather odd reason that an uncaught exception might be thrown - which might happen anyway if there’s no push to use exception handling). The only thing that has any macro definitions seems to be the WPIStatus.h file. Relevant section:


#ifdef WPI_STATUS_DEFINE_STRINGS
#define S(label, offset, message) const int wpi_v_##label = offset; const char *wpi_s_##label = message ;
#else
#define S(label, offset, message) const int wpi_v_##label = offset; extern const char *wpi_s_##label;
#endif

What does that do?? I confess, I’m not very well versed in preprocessor directives, but that doesn’t even make a bit of sense to me.

Sorry if this was a lot of questions… Basically, I want to know:
-What happened to main() and what is the START_ROBOT_CLASS macro doing
-How the wpi_assert and _demands handle errors

This makes no sense to me. Where is the main() function that would presumably instantiate the robot class? Also, what is this START_ROBOT_CLASS doing exactly?

I can try to explain this, to the best of my knowledge. Here’s how it works as far as I can tell:

The program’s entry point is FRC_UserProgram_StartupLibraryInit. This is what the LabVIEW Run-Time calls when it loads the user program.

The START_ROBOT_CLASS macro sets up a “user class factory”, which is a function that returns a pointer new instance of your robot class. Additionally, it creates the entry point function, FRC_UserProgram_StartupLibraryInit.

This entry point calls RobotBase::startRobotTask() with a pointer to the “factory” function. From here, a new task is spawned to run the user program. This task creates an instance of your Robot class and calls its StartCompetition() function. I’m not sure I quite understand the reason behind spawning off this new task, but I believe it somehow makes it easier to debug.

The next major macro confusion is with the "wpi_assert"s and "wpi_fatal"s that are sprinkled across the code.

The code for these functions is in Utility.cpp. It seems like they handle some output to stdout, breaking, and stack tracing. I’m pretty curious about these too. I haven’t really played around with it yet so I’ll leave it up to someone more knowledgeable than myself to explain.

I’m not sure, but I think this is something called “stringification”.

As for your other complaints – I hear ya brother. Gotta love C++.

As for your other complaints – I hear ya brother. Gotta love C++.

Indeed. What do you guys mostly code in?

Thank you, Matt! With those pointers in the right direction (heh), I think I understand the code now.
So, just to recap:
-START_ROBOT_CLASS, through many convolutions, defines the entry point FRC_UserProgram_StartupLibraryInit, which is called from external code (on the cRIO), which:
-Spawns a new task running an instance of the robot class

Do I have that right?

OK, so, in lieu of using try-catch blocks, I should use wpi_assert around conditionals that try to trap errors like null pointers? From what I can tell, this prints a message to the console (and puts a condition on the stack?). So, if I wanted to flush the error stack, I’d use wpi_assertCleanStatus.
What’s the difference between wpi_assert and wpi_fatal?

Yes.

OK, so, in lieu of using try-catch blocks, I should use wpi_assert around conditionals that try to trap errors like null pointers?

No. On code that you run on a ‘normal’ system, when the condition inside the parentheses of assert() is false, the program immediately prints out a message with whatever is inside the parentheses and a line number and the program ends (strictly speaking, it isn’t always implemented this way, but thats typically the case). However, when NDEBUG is defined, then assert expands to nothing and does nothing.

Additionally, if a debugger is attached, when the assert condition is false then the debugger usually takes over at that point and you can examine the local variables and see what happened that caused the condition to be false.

Now why is that useful? When you’re making something, its useful to put in little checks basically saying “this condition should never happen, if it does then we need to immediately stop execution”. You can also put in more expensive checks in there that only run in the debug build, so that way the code is more optimized in the release build (since, the checks are not done if NDEBUG is defined).


switch (some_val)
{
    case 1:
        // do something
        break;
    case 2:
        // do something
        break;
    default:
        // some_val should never be this
        assert(0 && "this should never have happened");
}

An alternative way of doing this would be:


assert((some_val == 1 || some_val == 2) && "this should never be a different value");

switch (some_val)
{
    // cases here... 
}


However, this usage of assert does not seem to be what is implemented in WPILib by default. In particular, it does not appear to care about the value of NDEBUG. Instead it just prints out the error message and continues (as far as I can see, without actually going back up the stack). However, if you call wpi_suspendOnAssertEnabled(true) then I believe you will get this type of behavior.

Look at Utility.cpp in WPILib for the implementation.

Sounds good, thanks.

I’ve been wondering what the keyword extern does. I see “extern “C”” a lot. None of the C++ references really provide a good explanation in the context of this code.

It basically tells the compiler “This is the name of something that is defined in a separate file. You don’t need to know any more details about it at the present time. Make sure to put the appropriate placeholder information in the object file so that things can be linked together correctly later.”

The extern “C” gives a hint to the compiler that the separate file will be using C conventions, so the linker doesn’t get confused trying to connect C and C++ object files together without the appropriate conversion.

Like the words “static” and “const”, “extern” is somewhat context-dependent in C++. For more: http://en.wikipedia.org/wiki/Extern

Ah, thanks.
Something that my programming teacher asked me and I had no idea: does the cRIO use interrupts to handle input from joysticks (like a Windows environment does)?

I (a fellow C#er) don’t personaly like the unsafe nature of C++, but alas!
Anyhow, in the C Programming guide:

We have chosen to not use the C++ exception handling mechanism, although it is available
to teams for their programs. Our reasoning has been that uncaught exceptions will unwind
the entire call stack and cause the whole robot program to quit. That didn’t seem like a
good idea in a finals match in the Championship when some bad value causes the entire
robot to stop.

I can still cause the program to quit!:smiley:
so no exceptions
look for c complilers operations (google it) for #ifndef and #define, they are compiler constants.

Agreed, C++ is…well, a bit low-level after working with C# for so long.

I can still cause the program to quit!
so no exceptions

Yeah, that’s what I’m worried about. I haven’t thrown any exceptions yet, but it seems stupid, hence my original comment:

From the FRC C++ guide, the WPI library doesn’t use C++'s exception handling system (for the rather odd reason that an uncaught exception might be thrown - which might happen anyway if there’s no push to use exception handling)

How have you been using the preprocessor to handle defines? It’s more conventional (from the C++ standpoint) to use the “const” keyword.

How have you been using the preprocessor to handle defines? It’s more conventional (from the C++ standpoint) to use the “const” keyword.

i use enum { Var1, Var2}; and const float speed=1; but just at the top or near the top #define VAR_NAME 1(Optional value)
also

#ifdef WPI_STATUS_DEFINE_STRINGS
#define S(label, offset, message) const int wpi_v_#label = offset; const char *wpi_s_#label = message ;
#else
#define S(label, offset, message) const int wpi_v_#label = offset; extern const char *wpi_s_#label;
#endif

a file may be included more than once (weird, i know, but that is the way the C++ compiler works) so the compiler sees:

if i have not defined "WPI_STATUS_DEFINE_STRINGS" then
{
  replace the macro "S(label, offset, message)" with
  {
    const int wpi_v_##label = offset;
    const char *wpi_s_##label = message ;
   }
}
else (if i have defined "WPI_STATUS_DEFINE_STRINGS")
{
  replace the macro "S(label, offset, message)" with
  {
    const int wpi_v_##label = offset;
    extern const char *wpi_s_##label;
  }
}

Got it? (I like medium level languages that are safer like C# or (ick!)VB!)

Thanks, I understood what the macro was doing (as far as conditional includes and substituting the values were concerned), my question was why. I didn’t see what the wpi_v_##label did, how it was used, or where ##label was defined.

EDIT: I guess when I made the original post I didn’t know what you were explaining. Sorry for the confusion.

the ## is the preprocessor concat symbol

so in S(label, offset, message) const int wpi_v_##label = offset; const char *wpi_s_##label = message ;

if you say S(MyLabel, 1,“Message of mine”)
it will replace the vars so it looks like this

const int wpi_v_MyLabel = 1;
const char *wpi_s_MyLabel = “Message of mine”;