C++ | how to use Brain in a class that is not in main.cpp

Hi, I’m quite new to vex but have a little bit of experience with C++. I am trying to move my call-back functions for the controller inputs into their own class and C++ file so that I can clean up my main file. However, when I do so I am left stumped as to how to pass the Brain object (is it an object?) to the brain.Screen.printAt() function?

My class looks like this :
image

My creation for the object of the class and main loop looks like this :
screen shot 2 vex v5

Any help would be greatly appreciated and thank you for your time :slight_smile:

I believe you would want your callback to look like

void print(){
Brain.Screen.printAt(Xoffset, Yoffset, "A button pressed !!!");
}

then

Controller.ButtonA.pressed(print);

(this is written for VexCode Pro so I do not know how well it will work in whatever you are using) The format, however, is how it should be.

Brain is a global object of type vex::brain. In a typical VexCode project, it is declared in robot-config.h and instantiated (defined) as a global in the generated robot-config.cpp
You could thus use it anywhere, as long as you #include <robot-config.h>

Like this?
Separate header file but ik I could put it in vex.h
image

Initializing it in the main.cpp
image

Using it in my second file
image

I think I have come up with a solution however I am unable to test with an actual robot rn so will update whether it works or not but it compiles without errors :slightly_smiling_face:

I create an object from a class that is located in the second file above my main loop and then I call a function from the class from the callback. with ClassName.MemberFunctionName (I also #include “SecondFileName” at the top)
image

My class in a separate file
image

I have also added another header file so that Brain can be assessed from both .cpp files as mentioned in my other reply on this thread. (this header is also #included at the top of both .cpp files)

If you run into issues I also had to define my function as static as depicted in the second image as the callback functions must be static. I’m not sure why but I had to.
( credit to theol0403 from this post How Do I Call UserControl From Another File? - #2 by Peter_977Z)

Yes, having static functions is a “solution”. I use quotes, since with that solution, you loose the context (a.k.a. this) and access to all non-static members (variables, functions), likely the main reason you wanted to have the handler implemented inside a class.
There is no easy good way around that, the VEX V5 APIs generally only allow to register C-style callbacks and mostly w/o providing a means of passing the context through. The problem boils down to the information loss. Whenever you want to call an instance member function on an object, you need two pieces, the object identity and the function to call.

Say you have a class like this:

class MySubsystem {
    int id;
public:
    MySubsystem(int _id) : id(_id) {}
    void handleEvent() { printf("Event handled by %d\n", id); }
};

to call the function from your code, you’d have an instance somewhere (or a few of them) and use it to call the function, like:

MySubsystem subA(1);
MySubsystem subB(2);

subA.handleEvent();
subB.handleEvent();

If you need something else to call your member function, you’d need to give it both pieces. But the vex::controller::button::pressed function only supports a simple C-style callback. Or rather half thereof. So let’s take a bit of history detour here.

In the pre-C++ times, the C programmers didn’t have objects, but still had the need to register callbacks and have context available to those callbacks. Objects were emulated by structures, member functions by functions having explicit context argument. Like:

typedef struct {
    int id;
} MySubsystem;

void registerCallback( void (*callback)(void*), void *ctx);

void handleEvent(void *ctx) {
    MySubsystem *self = (MySubsystem*)ctx;
    printf("Event handled by %d\n", self->id);
}
[...]
MySubsystem subA = {1};
registerCallback(handleEvent, &subA);

As you see, the code provides both pieces needed to the registration function, so that thingy can remember and provide the context later on while calling the handler.

C++ solves this need differently. It can use different idioms:

  • A callback interface with virtual functions, such as:
class ButtonHandler { public: virtual void handleEvent() = 0; }
// callback registration signature
void registerCallback(ButtonHandler*);
// your implementation:
class MySubsystem : public ButtonHandler {
[...]
    virtual void handleEvent() { printf("Event handled by %d\n", id); }
};
// your code registering your handler
MySubsystem subA(1);
registerCallback(&subA);
  • Generalized callback using std::function, perhaps even paired with lambdas:
// callback registration signature
void registerCallback(std::function<void()> handler);
// registering your callback
MySubsystem subA(1);
registerCallback(std::bind(&MySubsystem::handleEvent, &subA);
  • Of course C++, being a superset of C, would still allow full C-style callback registration (as seen in vex::thread class)

But for whatever reason, VEX decided to produce a C++ platform, but implement broken, half-baked C-style callbacks. So if you need to use them, you need to implement workarounds to restore the context It won’t be pretty, it would promote bad design, but it would get stuff done.

For your ControllerInputClass use case, it might look like this:

class ControllerInputClass {
    static ControllerInputClass* s_self;
public:
    ControllerInputClass() {
        // assert s_self == nullptr; // only one instance of this singleton allowed
        s_self = this;
    }
    void handleButton(V5_ControllerIndex button) { ... }
    static void buttonAPressed() {         // trampoline for Button A
        if (s_self) s_self->handleButton(ButtonA);
    }
    static void buttonbPressed() {         // trampoline for Button B
        if (s_self) s_self->handleButton(ButtonB);
    }
    // [.... etc ...]
};

… which is a lot to get a sane design, so the general VEX expectation is that you’d just use globals everywhere (like the generated robot-config.cpp), use no context in the handlers and directly access the global objects).
Simple? Yes.
Promoting bad coding practices? Sure!

2 Likes