Thread: GUI structure in C

  1. #16
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    I've been crawling my way up this learning curve, going back and forth between the code and the advice. Here I try to implement "inheritance" and I feel a little stuck. Here's what I've got so far. My question is - is this the right direction or not to implementing "C classes"?

    The base class:

    Code:
    typedef struct coords {
        int x,y;
        void (*init)(struct coords *c);
        void (*add)(struct coords *c);
    } Coords;
    
    // Dynamic allocation and initialisation of Coords 'instance'
    Coords * coordNew() {
        Coords *c = malloc(sizeof(Coords));
        if (c != NULL) {
            c->x = 1;
            c->y = 100;
            c->add = addOne;
            c->init = initCoords;
        }
        return c;
    }
    
    void initCoords (Coords *c) {
        c->x = 55;
        c->y = 200;
        c->add = addOne;
        c->init = initCoords;  //is this self-reference ok??
        printf("\nInit Coords: x,y = %d, %d", c->x, c->y);
    }
    And this is where I got to with creating a "child class". At the moment it is not much more than a copy of Coords struct.

    Code:
    typedef struct square {
        Coords base;
        void (*init)(struct coords *c);
    }Square;
    
    //Dynamic allocation of Square
    Square * squareNew() {
        Square *s = malloc(sizeof(Square));
        if (s != NULL) {
            s->init = initCoords;
            s->init(&(s->base));  //static memory allocation??
        }
        return s;
    }
    Am I on the right tracks here?

    The things I am struggling with include:

    a) Should I dynamically allocate and initialise the base struct by calling coordNew(), or as I have tried to do here, call the initCoords(Coords *c) function.
    b) Should I rewrite a new 'initSquare()' function which calls the initCoords function?
    c) Won't the Coords struct members which are function pointers (init and add) become redundant and unused?

  2. #17
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Well, you can guess that I will say that you can just use C++. Is there a reason you want to do this in C?? If you want this not to be OOP then you shouldn't simulate classes/objects. But creating a GUI is one of the really popular things that show the OOP features.

    If you are doing this to learn and for the fun of it, then continue the good job

    Let me explain a bit what is a function pointer. There is a block of memory for each function. So when you call a function you actually call the routines/commands of that specific block of memory. The address of that block of memory is the function name. As it is for a variable. So when you point at a function you point at a memory location. As when you point to a variable you point to a memory location. Nothing more.

    a) The whole struct is still allocated on the heap. When you allocate space for a struct you allocate space for each individual member. That goes also for other structs. If you allocate the struct dynamically on the heap EVERYTHING will be in the heap.

    b) Of course you will need to make an initSquare function which will initialize also the extra members of square. Well, in your case there is no extra members, but generally for each class there should be one initX function.

    c) Yes they will. But that cannot be avoided. The same goes for C++. If you inherit from an class that also has useless members then they will stay useless. That doesn't really matter though.

    The tricky part comes when you try to use polymorphism of any kind.

  3. #18
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    So you have this:
    Code:
    typedef struct coords {
        int x,y;
        void (*init)(struct coords *c);
        void (*add)(struct coords *c);
    } Coords;
    
    typedef struct square {
        Coords base;
        void (*init)(struct square* s);
    }Square;
    
    Square * squareNew() {
        Square *s = malloc(sizeof(Square));
        if (s != NULL) {
            s->init = initSquare;
            s->init(s);                           //initialize Square        
            s->base.init(&(s->base));  //initialize Coords
        }
        return s;
    }
    So, if that is what you where asking, coords.init() and coords.add() don't become useless! They can be used normally.
    What you would want though, is to put inside the initSquare the initialization of the Coords so you call only one init and not to everytime you initialize.

    Now, what you can do is use function pointers to make it simpler to call the base structs functions. Like, lets say add is a really good function that you will call a lot of times for every square. You don't want to call everytime this:
    Code:
    s->base.add(...)
    but you would want to call this:
    Code:
    s->add(...)
    That is possible if you add this code in the initialization/creation of square:
    Code:
    s->add = s->base.add;
    provided that you make a function pointer called add inside the square structure.
    Last edited by C_ntua; 11-13-2008 at 12:54 PM.

  4. #19
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    A big thank you for the time you have taken to explain this C_ntua - it's incredibly helpful. I think with all the advice, coments and explanations in this thread I feel reasonably confident in making progress.

    I do understand that the simplest way is actually to simply commit to learning C++ classes and ditch the idea of a GUI in favour of a GUI library (I'm working on a mac which does have lots of ready made stuff). However, this path is interesting, it helps me to learn more about the underlying mechanics of C and GUIs.

    I've spent all day so far simply re-working and refining the base struct (rectangle), so I can try out these ideas. I'll post some of the code incase anyone else is interested. The base class is a rectangle, Here is the struct, complete with function pointers:

    Code:
    typedef struct rectangle {
        float convertToOrtho, convertToPixel;
        float x1,y1,x2,y2;
        float centreXOrtho, centreYOrtho;
        
        int screenBoundsMinX;
        int screenBoundsMaxX;
        int screenBoundsMinY;
        int screenBoundsMaxY;
        
        //Final drawing data
        float length, height;
        float vertices[4][2];
        float colour [4]; //rgba
        
        /*******************************************/
        //"public" methods
        void (*init)(struct rectangle *r, float coords[4]);
        
        void (*setCoords)(struct rectangle *r, float coords[4]);
        void (*setCentre)(struct rectangle *r, float coords[2]);
        void (*setLength)(struct rectangle *r, float length);
        void (*setHeight)(struct rectangle *r, float height);
        void (*setColour)(struct rectangle *r, float colour[4]);
        void (*draw)(struct rectangle *r);
        
        //"private" methods
        void (*rprint)(struct rectangle *r);
        void (*updateCentre)(struct rectangle *r);
        void (*updateScreenBounds)(struct rectangle *r);
        void (*updateDimensions)(struct rectangle *r);
        void (*updateVertices)(struct rectangle *r);
    } Rectangle;
    These are the function declarations:

    Code:
    //Constructor
    Rectangle * rectNew(float coords[4]);
    //still need to make destructor
    
    //setters
    void rectSetCoords(Rectangle *r, float coords[4]);
    void rectSetCentre(Rectangle *r, float coords[2]);
    void rectSetLength(Rectangle *r, float length);
    void rectSetHeight(Rectangle *r, float height);
    void rectSetColour(Rectangle *r, float colour[4]);
    
    //Draw
    void rectDraw(Rectangle *r);
    
    //"Private" functions
    void rectPrint(Rectangle *r);
    
    //Updater functions
    void rectUpdateCentre(Rectangle *r);
    void rectUpdateScreenBounds(Rectangle *r);
    void rectUpdateDimensions(Rectangle *r);
    void rectUpdateVertices(Rectangle *r);

  5. #20
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    Don't forget to free() it officedog

    See my original post...

  6. #21
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    No worries zacs7! Cheers for reminder though. After that my last post I zipped back to your post and hey presto

    Code:
    //Constructor
    Rectangle * rectNew(float coords[4]);
    void rectInit(Rectangle *r, float coords[4]);
    
    //Destructor
    void rectDestroy(Rectangle *r);
    Code:
    void rectDestroy(Rectangle *r) {
        if (&r != NULL) {
            free(r);
            r = NULL;
        }
    }
    Still not clear exactly why we're testing the address of the pointer itself though

    Thanks again for that help yesterday too. It was a big help in getting this thing going in the right direction.
    Last edited by officedog; 11-13-2008 at 05:00 PM.

  7. #22
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    That's not exactly what I had...

    &r != NULL is always true. Have a look again, I pass a pointer to a pointer so it can be set to NULL after it's free'd. Remember free(NULL) is safe and does nothing.

  8. #23
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Thanks for picking up the error zacs7 and for explanation. I must have been getting ahead of myself, I thought it was an equivalent. I'll have a re-read and a think over this.

  9. #24
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    It's just that it makes it safe after you free a Rectangle that it isn't used.

    ie,
    Code:
    Rectangle * r = newRectangle();
    
    /* use r */
    
    freeRectangle(&r);      /* notice we pass the address of r */
    
    /* r is now NULL so we won't accidentally use it */
    Just make sure you don't reference NULL, ie freeRectangle(NULL); should be safe.

    PS: I love your name and dp

  10. #25
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Seems good. I don't think you will have a problem learning C++ . And from there Java/C# if you are interested, which should be better for a GUI.

    P.S.
    I would like to finish my idea about polymorphism in C. I don't know if you are familiar with the virtual function concept, but I ll post it anyway just for the sake of completion:
    Code:
    typedef struct control {
        //members
        //rest of control
        void (*resize)(void* ptr, Point P1, Point P2);
    } Control;
    
    typdef struct {
        base Control;
        //rest of control
        void (*resize)(void*ptr, Point P1, Point P2);        //this will overwrite Control's resize
    } Button;
    Now you declare the two resizes like this:
    Code:
    void controlResize(void* ptr, Point p1, Point p2)
    {
        Control* c = (Control*)ptr
        //do whatever you do
    }
    
    void buttonResize(void* ptr, Point p1, Point p2)
    {
        Button* b = (Button*)ptr;
        //do whatever you do
    }
    
    new_button(Button* b)
    {
       //blah blah
       b->resize = b->base.resize = buttonResize;
       return b;
    }
    and in the main program you can do:
    Code:
    Button* b = new_Button();
    Control* c = new_Control();
    Control* c = Control_cast(c);
    c->resize(c, p1, p2);
    c = Control_cast(b);
    c->resize(c, p1, p2);
    Now, even if you call the same code, c->resize(c, p1, p2) the code will refer to different functions. The first time it will refer to control's resize. The second time to button's resize.

    In C++ the above code would be:
    Code:
    Button* b = new Button();
    Control* c = new Control();
    Control* c = static_cast<Control*>(c);  //or some other cast
    c->resize(p1, p2);
    c = static_cast<Control*>(b);
    c->resize(p1, p2);
    the same line of codes.

  11. #26
    Woof, woof! zacs7's Avatar
    Join Date
    Mar 2007
    Location
    Australia
    Posts
    3,459
    officedog, if you like the idea of adding some OO features (such as in C_ntua's posts) into C check out glib - http://library.gnome.org/devel/glib/stable/
    You'll probably find it has a lot of stuff you're wanting to implement.

    But perhaps look into C++ if you want that much OO stuff .

  12. #27
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Thanks for the comments zacs7. I had a look at Glib and I ended up reading about GObject. So, it's a sort of class type system which extends C via a library, but is not a new language like C++ or objC. Very interesting and very relevant - I'll have a look into this in more detail when I finish this project. Glad you like the dp (does this mean photo?) - it looks like we are both dog owners!

    And thanks C_ntua for sharing more of your experience and knowledge on this. There is so much useful stuff in this thread, I'm going to try and make a summary of it for future reference. I'm returning back to some of your earlier posts today as I try to develop a new struct "Label", based on the Rectangle struct.

    Am very pleased with the rectangle struct - the functions seem to work perfectly if I call them directly, e.g.
    rectSetHeight(Rectangle *r, float height);

    Or when calling them from the function pointer in the struct:
    r->setHeight(Rectangle *r, float height);

    Back to 'work' now

  13. #28
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    officedog is very pleased. After much painstaking work, the "label" struct is almost complete. The base struct (Rectangle) functions seem to work and there is an added layer now which is the text label itself. I share some of the code for others who may be interested:

    This is the new struct "Label"
    Code:
    typedef struct label {
        Rectangle base;  //the foundations
        
        //Font
        void * font;  //to hold GLUT bitmap fonts
        int fontHeightInPixels;
        int fontWidthInPixels;
        float fontHeightInOrtho;
        float fontWidthInOrtho;
        float textColour[4]; //rgba
        
        //Label
        char label[MAX_SIZE_LABEL];
        int LabelLengthInChars;
        float labelLengthInOrtho;
        float labelHeightInOrtho;
    
        float indentFromLeftInOrtho; // = 1 character
        float indentFromRightInOrtho; // = 1 character
        char justify; // 'l', 'c', 'r' == left, centre, right
        
        float rasterStartPos[2];
        
        /*******************************************/
        // Function pointers - work in progress.....
    
        void (*init)(struct label *lbl, float coords[4], const char *str);
        //destructor
        
    }Label;
    This is the function which dynamically allocates the struct and calls the init function:
    Code:
    Label * labelNew(float coords[4], const char * str) {
        Label *lbl = malloc(sizeof(Label));
        if (lbl != NULL) {
            lbl->init = labelInit;
            lbl->init(lbl, coords, str);  // calling labelInit via the struct function pointer
        }
        return lbl;
    }
    And this is function which initialises the struct, also calling on the base (Rectangle) init function
    Code:
    void labelInit(Label *lbl, float coords[4], const char * str) {
        lbl->base.init = rectInit;
        lbl->base.init(&(lbl->base), coords);
    
        lbl->font = GLUT_BITMAP_8_BY_13; //Default font
        lbl->justify = 'c'; //Default: text is centred
        labelUpdateFontDimensions(lbl);
        labelSetLabel(lbl, str);
        labelSetTextColour(lbl, black); //default text col = black    
    }
    Finally here's a snippet of the code which draws the label (using openGL and GLUT):
    Code:
    void ODDisplay()
    {
        glClear(GL_COLOR_BUFFER_BIT);
    
        float red[4] = {1,0,0,0};
        float coords[4] = {1,1,2,2};
        Label *lbl = labelNew(coords, "hello");
        labelPrint(lbl);
        lbl->base.setColour(&(lbl->base), red);  //calls base function via function pointer
        labelDraw(lbl);
        
        glFlush();
    }

  14. #29
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    Nice. Will wait to see how this progresses

  15. #30
    Registered User officedog's Avatar
    Join Date
    Oct 2008
    Posts
    77
    Thanks c-ntua. The help from you and zacs7 has opened a door on something I am only gradually coming to understand. I really appreciate the help I've had with this - these function pointers are fascinating! I'm still working through the GUI objects at the moment (am on a textbox at the moment, and a whichObjectIsTheMouseClickingOn struct).

    I'm also trying to abstract out some general rules about the OOP-stye implementation and increase the modularity of my program structure. The reason is partly because the program quickly grows in size and starts to get harder to keep track of. The complexity is also added when I have more than one layer of "inheritance" e.g. a textbox struct which is based on a label struct which is based on a rectangle struct. The issues I'm seeking solutions to at the moment include:

    a) Is it better to create wrappers for all of the base struct functions which will be used by the child struct (including the ones which are essentially "private" like the updating functions) or simply assign the child struct function pointer to the base struct function?

    I'll try and explain this a little more. As I've been working through this I can see that there are some functions which are nothing more than a wrapper around the base struct function and could be simply assigned:

    Code:
    label->setCoords = label->base.setCoords;
    Rather than

    Code:
    label->setCoords = labelSetCoords;
    Where the function labelSetCoords is just a wrapper:

    Code:
    void labelSetCoords(struct label *lbl, coords[4]) {
         lbl->base.setCoords(&(lbl->base), coords);
    }
    Writing it the first way is less complicated when writing the code for the struct but more complicated for the 'user' of the struct because they have to remember to pass the address of the base struct for some functions i.e.

    Code:
    newLabel->setCoords(&(newLabel->base), coords)
    Rather than a user interface which consistently only requires the 'child' struct pointer:
    Code:
    newLabel->setCoords(newLabel, coords)
    Potentially this can get more complex with more layers of inheritance, conceivably even resulting in lines like:

    Code:
    newStructType->functionName(&(newLabel->base.base.base), coords)
    Sorry, this was a more long-winded explanation than intended.... The upshot is, that I feel that I should write wrappers for absolutely all functions that will be used by the child struct (including even the updater functions which are never supposed to be accessed by the 'user'). I wonder if this would lead to bugs which bite later on (with potentially pointers to pointers to pointers to functions being involved), or whether this is the only sensible way to go?

    I'll leave the other question until another time

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Problem referencing structure elements by pointer
    By trillianjedi in forum C Programming
    Replies: 19
    Last Post: 06-13-2008, 05:46 PM
  2. Dikumud
    By maxorator in forum C++ Programming
    Replies: 1
    Last Post: 10-01-2005, 06:39 AM
  3. Window priority handling for a GUI - data structure
    By rmullen3 in forum C++ Programming
    Replies: 0
    Last Post: 04-15-2003, 07:06 PM
  4. Serial Communications in C
    By ExDigit in forum Windows Programming
    Replies: 7
    Last Post: 01-09-2002, 10:52 AM
  5. C structure within structure problem, need help
    By Unregistered in forum C Programming
    Replies: 5
    Last Post: 11-30-2001, 05:48 PM