Thread: Callback into a class member function

  1. #1
    Registered User
    Join Date
    May 2009
    Posts
    9

    Callback into a class member function

    Hi,

    I know C pretty well, but now am learning C++.

    I am writing a maze program that wants to have a Windows (SDL) timer call back to a
    member function in the Maze class.

    The compiler is complaining, and I can't quite figure it out. It has to do with the SDL_AddTimer() function which needs a function pointer to Maze::Maze_timer_update() (see red in maze.cpp below).
    Code:
    C:\Prj\codeblocks\Amazing\maze.cpp||In constructor `Maze::Maze(unsigned int, unsigned int, unsigned int)':|
    C:\Prj\codeblocks\Amazing\maze.cpp|24|error: cannot convert `Uint32 (Maze::*)(Uint32, void*)' to `Uint32 (*)(Uint32, void*)' for argument `2' to `_SDL_TimerID* SDL_AddTimer(Uint32, Uint32 (*)(Uint32, void*), void*)'|
    C:\Prj\codeblocks\Amazing\maze.cpp|20|warning: unused variable 'Maze_array'|
    C:\Prj\codeblocks\Amazing\maze.cpp|24|warning: unused variable 'Maze_timer'|
    ||=== Build finished: 1 errors, 2 warnings ===|
    Also, I'm not clear on how the member declaration should work to allow access to
    Maze::Maze_timer_update(). It shouldn't be public, but private doesn't seem right
    either. It should probably be a friend, but I don't see how to give just SDL access to it.

    Any help is appreciated.

    Maze.h:
    Code:
    #ifdef __cplusplus
        #include <cstdlib>
    #else
        #include <stdlib.h>
    #endif
    #ifdef __APPLE__
    #include <SDL/SDL.h>
    #else
    #include <SDL.h>
    #endif
    
    #ifndef maze_h
    #define maze_h
    
    #include <stack>
    #include <time.h>
    
    
    typedef struct int_vector_s {
        int x;
        int y;
    } int_vector_t;
    
    typedef enum MazeDirs_e {
        up=0,
        dn,
        lf,
        rt,
        dirs_last
    } MazeDirs_t;
    
    
    class Maze {
    
    
    private:
    
                    bool   *WallLocations;
             SDL_Surface   *screen;
    
              MazeDirs_t    MazeOptsMoves[dirs_last];
                     int    MazeOptsMovesAvail;
    
                    bool    MazeDirsOK[dirs_last];
    
    
    std::stack <int_vector_t>    MazeTravelStack;
    
                uint16_t    MazeTravelDist;
              MazeDirs_t    MazeTravelDir;
                uint16_t    MazeTravelXpos;
                uint16_t    MazeTravelYpos;
    
                    bool    *Maze_array;
             SDL_Surface    *Maze_screen;
             SDL_TimerID    Maze_timer;
    
    public:
    
               Uint8    WallThickness;              // Thickness of wall in pizxels
              Uint16    Width;                      // Width  of maze in wall thicknesses (must be odd)
              Uint16    Height;                     // Height of maze in wall thicknesses (must be odd)
    
                  Uint32    Maze_timer_update(Uint32 interval, void *UserData);
    
        Maze::Maze( unsigned int Width, unsigned int Height, unsigned int Wallthickness );
        Maze::~Maze();
        void Maze_iterate(class Maze* const);
        void Maze::Maze_draw(class Maze* const);
    
    
    
    };
    
    
    
    #endif
    maze.cpp:
    Code:
    #ifdef __cplusplus
        #include <cstdlib>
    #else
        #include <stdlib.h>
    #endif
    #ifdef __APPLE__
    #include <SDL/SDL.h>
    #else
    #include <SDL.h>
    #endif
    
    #include "maze.h"
    
    #define MAZE_WALL_ELEMENT(MAZE,X,Y) (*((MAZE)->WallLocations+(MAZE)->Width*(Y)+(X)))
    
    Maze::Maze( unsigned int Width, unsigned int Height, unsigned int Wallthickness ) {
    
        MazeTravelStack.push( (int_vector_t){1,1} );
    
        bool Maze_array = malloc( Width * Height * sizeof(bool) );
    
        Maze_screen = SDL_SetVideoMode(Width*Wallthickness, Height*Wallthickness, 8, SDL_HWSURFACE|SDL_DOUBLEBUF);
    
        SDL_TimerID Maze_timer = SDL_AddTimer(2000, &Maze::Maze_timer_update, this);
    
        srand ( time(NULL) );
    
        // clear screen
        SDL_FillRect(this->screen, 0, SDL_MapRGB(this->screen->format, 0xff, 0xff, 0xff));
    
        {
            this->MazeTravelXpos = 1;
            this->MazeTravelYpos = 1;
            int x;
            int y;
            for (x=0; x<this->Width; x++) {
                for (y=0; y<this->Height; y++) {
                    MAZE_WALL_ELEMENT(this,x,y) = x&1 && y&1 ? false : true;
                }
            }
        }
    }
    
    
    
    Maze::~Maze() {
        free (Maze::Maze_array);
    }
    
    
    Uint32 Maze::Maze_timer_update(Uint32 interval, void *UserData) {
        Maze_iterate(this);
        Maze_draw(this);
        return interval;
    }
    
    void Maze::Maze_iterate(class Maze* const) {
    
        if ( this->MazeTravelStack.size() || this->MazeOptsMovesAvail ) {
    
            this->MazeOptsMovesAvail = 0;
    
            if (
                 MazeTravelYpos>2
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos-1)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos-3)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-1,MazeTravelYpos-2)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+1,MazeTravelYpos-2)
            ) {
                MazeDirsOK[up]=true;
                this->MazeOptsMoves[this->MazeOptsMovesAvail++] = up;
            }
            else {
                MazeDirsOK[up]=false;
            }
    
    
    
            if (
                 MazeTravelYpos<this->Height-3
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos+1)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos+3)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-1,MazeTravelYpos+2)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+1,MazeTravelYpos+2)
            ) {
                MazeDirsOK[dn]=true;
                this->MazeOptsMoves[this->MazeOptsMovesAvail++] = dn;
            }
            else {
                MazeDirsOK[dn]=false;
            }
    
    
    
            if (
                 MazeTravelXpos>2
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-1,MazeTravelYpos  )
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-3,MazeTravelYpos  )
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-2,MazeTravelYpos-1)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos-2,MazeTravelYpos+1)
            ) {
                MazeDirsOK[lf]=true;
                this->MazeOptsMoves[this->MazeOptsMovesAvail++] = lf;
            }
            else {
                MazeDirsOK[lf]=false;
            }
    
    
    
            if (
                 MazeTravelXpos<this->Width-3
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+1,MazeTravelYpos  )
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+3,MazeTravelYpos  )
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+2,MazeTravelYpos-1)
              && MAZE_WALL_ELEMENT(this,MazeTravelXpos+2,MazeTravelYpos+1)
            ) {
                MazeDirsOK[rt]=true;
                this->MazeOptsMoves[this->MazeOptsMovesAvail++] = rt;
            }
            else {
                MazeDirsOK[rt]=false;
            }
    
    
    
    
    
            // We know which directions are OK, so now check:
            // if we run out of travel distance or the next step in the
            // current direction is not valid, we must pick a new acceptable direction.
    
            if ( this->MazeOptsMovesAvail ) {
                if ( !MazeTravelDist || !MazeDirsOK[MazeTravelDir] ) {
                    MazeTravelDist = rand()%6+1;
                    MazeTravelDir = this->MazeOptsMoves[rand()%this->MazeOptsMovesAvail];
                    MazeTravelStack.top().x = MazeTravelXpos;
                    MazeTravelStack.top().y = MazeTravelYpos;
                }
                else {
                    MazeTravelDist--;
                }
                // Ok, now we punch through a wall.
                switch ( MazeTravelDir ) {
                    case up:
                        MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos-1) = false;
                        MazeTravelYpos-=2;
                    break;
    
                    case dn:
                        MAZE_WALL_ELEMENT(this,MazeTravelXpos  ,MazeTravelYpos+1) = false;
                        MazeTravelYpos+=2;
                    break;
    
                    case lf:
                        MAZE_WALL_ELEMENT(this,MazeTravelXpos-1,MazeTravelYpos  ) = false;
                        MazeTravelXpos-=2;
                    break;
    
                    case rt:
                        MAZE_WALL_ELEMENT(this,MazeTravelXpos+1,MazeTravelYpos  ) = false;
                        MazeTravelXpos+=2;
                    break;
    
                    case dirs_last:
                    break;
    
                }
            }
            else {
                // Can't turn anywhere, so we must pop back on the stack.
                if ( MazeTravelStack.size()>1 ) {
                    MazeTravelXpos = MazeTravelStack.top().x;
                    MazeTravelYpos = MazeTravelStack.top().y;
                }
            }
        }
    }
    
    
    void Maze::Maze_draw(class Maze* const) {
    // Draw the maze on the screen
        int x;
        int y;
    
        Uint8 *p;
        Uint8  MazeWallBlockX;  // This is for filling in blocks in the wall when the block is more than 1 pizel across.
        Uint8  MazeWallBlockY;  // This is for filling in blocks in the wall when the block is more than 1 pizel across.
    
        for (x=0; x<this->Width; x++) {
            for (y=0; y<this->Height; y++) {
                if ( MAZE_WALL_ELEMENT(this,x,y) ) {
                    for (MazeWallBlockX=0; MazeWallBlockX<this->WallThickness; MazeWallBlockX++) {
                        for (MazeWallBlockY=0; MazeWallBlockY<this->WallThickness; MazeWallBlockY++) {
                            p = (Uint8 *)(this->screen->pixels) + (y*this->WallThickness+MazeWallBlockY) * this->screen->pitch + (x*this->WallThickness+MazeWallBlockX) * this->screen->format->BytesPerPixel;
                            *p = 0;
                        }
                    }
                }
            }
        }
        SDL_Flip(this->screen);
    }

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Member functions are tricky. You can't, generally speaking, pass pointers to member functions around because you can't call them without having an object to call them with. (That is, you would need to call your function as MazeObject.MazeTimerUpdate(), and the callback won't have such an object to call it with.) A callback function needs to be free.

  3. #3
    Registered User
    Join Date
    May 2009
    Posts
    9
    Ah, yes. You need the object pointer now, don't you.

    So, I wrote a small wrapper outside of Maze:
    Code:
    Uint32 Maze_TimerCallback(Uint32 interval, void *maze) {
        return ((class Maze *)maze)->Maze_timer_update(interval);
    }
    And from inside the constructor called it like this:
    Code:
        SDL_TimerID Maze_timer = SDL_AddTimer(2000, Maze_TimerCallback, this);
    When the timer expires:

    The timer calls Maze_TimerCallback() with the interval and object pointer.
    The wrapper uses the object pointer to call the object's Maze_timer_update,
    and everybody's happy.

    Thanks, tabstop.

  4. #4
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    It's also possible to use a static function inside a class as a callback function, just the same as with a free function.
    Later on, you might want to read up about function objects (functors?) and templates as they will allow your own functions to accept a callback to either a free function or a class member function (plus they're usually more efficient than the C callback solution).
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  5. #5
    Registered User
    Join Date
    May 2009
    Posts
    9
    Thanks, Elyisa.

    Putting 'static' in front of the function in the class definition was what I was looking for.

    One thing to note for anybody having the same question: if you declare the member
    functions outside of the class definition and in a different file, the compiler doesn't like the static word for the actual definition:


    Code:
    //my_classes.h
    
    class myclass {
    
    public:
    static int myfunc(int);
    
    }
    
    //my_classes.cpp
    
    // NO static below
    int myclass::myfunc(int)
    I also find that a few key words for things I am ignorant of help a lot. I'll read up on functors, templates, etc.

    Thanks again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 4
    Last Post: 05-13-2011, 08:28 AM
  2. Message class ** Need help befor 12am tonight**
    By TransformedBG in forum C++ Programming
    Replies: 1
    Last Post: 11-29-2006, 11:03 PM
  3. Replies: 28
    Last Post: 07-16-2006, 11:35 PM
  4. My Window Class
    By Epo in forum Game Programming
    Replies: 2
    Last Post: 07-10-2005, 02:33 PM
  5. Staticly Bound Member Function Pointers
    By Polymorphic OOP in forum C++ Programming
    Replies: 29
    Last Post: 11-28-2002, 01:18 PM