Thread: Simulating OO "methods" in plain C

  1. #1
    Registered User
    Join Date
    May 2010
    Posts
    15

    Simulating OO "methods" in plain C

    Hi all,

    I've just been trying to solidify my understanding of OO techniques and how C can be used to implement things like "methods" from higher-level object-oriented languages that operate on the object on which they are called. Also things like macro expansion, variable arguments and structure function pointers.

    I've written the code below and it seems to accomplish the desired aim but I'm curious to hear other people's thoughts on it and are you aware of other ways of achieving some level of OOP style code in plain C?

    Sure, I could just use C++ and avoid a lot of this altogether by just defining a class but my goal has been to understand OO techniques in just plain C - for example, Glib/GObject and its inner workings.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    /**
     * Macros that call the "method" of the object,
     * passing in the "this" pointer so that the methods will work on
     * the structure on which it is called.
     *
     * Note that method_call_va() is a slight variation of the normal
     * method_call() but allows additional arguments to be passed into
     * a method whose "signature" specifies parameters other than just the "this" pointer.
     */
    #define method_call(inst, method)           ((inst).method (&inst))
    #define method_call_va(inst, method, ...)   ((inst).method (&inst, __VA_ARGS__))
    
    /**
     * Define the object type.
     * The function pointers correspond
     * to functions we will write which then
     * become the methods to objects of this type.
     */
    struct Object_One
      {
        int X;
        int Y;
        char *label;
        void (*get_data) (struct Object_One *item);
        void (*set_data) (struct Object_One *item, int X, int Y);
      };
    
    typedef struct Object_One Object_One;
    
    /**
     * Methods that operate on instances of Object_One.
     * Each function takes a pointer to an Object_One structure which
     * equates to the "this" keyword in other languages and allows the current
     * object (really, a structure) to be changed by the method.
     */
    void  object_one_get_data (Object_One *item);
    void  object_one_set_data (Object_One *item, int X, int Y);
    
    int
    main (int ac, char *av[ ])
    {
        Object_One object1; // First instance
    
        // Set properties...
        object1.X = 7;
        object1.Y = 9;
        object1.label = "Object 1";
        // Assign "methods"...
        object1.get_data = object_one_get_data;
        object1.set_data = object_one_set_data;
    
        // Call "methods"...
        method_call (object1, get_data);
        method_call_va (object1, set_data, 3, 6);
        method_call (object1, get_data);
    
        printf ("\n\n");
    
        Object_One object2; // Second instance
    
        // Set properties...
        object2.X = 1;
        object2.Y = 2;
        object2.label = "Object 2";
        // Assign "methods"...
        object2.get_data = object_one_get_data;
        object2.set_data = object_one_set_data;
    
        // Call "methods"...
        method_call (object2, get_data);
        method_call_va (object2, set_data, 4, 7);
        method_call (object2, get_data);
    
        return EXIT_SUCCESS;
    }
    
    /**
     * Object_One method implementations
     */
    void
    object_one_get_data (Object_One *item)
    {
        printf ("%s at %p -- X: %d, Y: %d\n", item->label,
                                              item,
                                              item->X, item->Y);
    }
    
    void
    object_one_set_data (Object_One *item, int X, int Y)
    {
        item->X = X;
        item->Y = Y;
    }
    The output from a run of this program is:

    Code:
    Object 1 at 0x7ffdfe42a2c0 -- X: 7, Y: 9
    Object 1 at 0x7ffdfe42a2c0 -- X: 3, Y: 6
    
    
    Object 2 at 0x7ffdfe42a2e0 -- X: 1, Y: 2
    Object 2 at 0x7ffdfe42a2e0 -- X: 4, Y: 7
    Which shows the two "instances" and how their X and Y values are changed by the method calls.

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Since you aren't worried about exactly following C++ method-calling syntax, why not just call the methods directly without a "method_call" macro?

    So instead of this:

    Code:
        method_call (object2, get_data);
        method_call_va (object2, set_data, 4, 7);
    do this:

    Code:
        get_data(&object2);
        set_data(&object2, 4, 7);
    That looks cleaner to me and more idiomatic C.

    As an aside, Common Lisp is also object oriented (the Common Lisp Object System (CLOS) is perhaps the most sophisticated object system among languages), and it's convention to call methods with the object as their first argument. This is just to illustrate that not all OOP languages follow C++'s "object.method()" syntax. Also, the C standard library already has some OOP functionality—stdio is a good example. fprintf() is basically a "method" of the FILE "class". So it's not a bad convention to follow in C either.

  4. #4
    Registered User
    Join Date
    May 2010
    Posts
    15
    Thanks for the replies, and that's an interesting note regarding Lisp since I'm not familiar with it at all.

    As far as the
    Code:
    method_call
    macro, the intention was to have the macro pass in the instance automatically so the programmer doesn't have to.

    Since

    Code:
    method_call (object1, get_data);
    is essentially the same as

    Code:
    object1.get_data (&object1);
    it's more just a safeguard to prevent typing mistakes by the programmer if they happen to pass in the wrong object by accident. The macro seems to prevent that error.

    And rather than calling set_data or get_data on their own and passing in a pointer to the structure as the first argument the thinking was that the function pointers in the structure might point to different functions and we're just using the get_data label to refer to it. But the macro could call whatever the member get_data ends up pointing to as well as passing in an arbitrary number of arguments. I was thinking that each "instance" might have its function pointers set differently, possibly depending on some runtime condition where the function pointer is reassigned. Hard-coding the call to a specific set_data wouldn't appear to support that sort of dynamic programming.

    So if we had an array of structures with some of the structures using version1 of get_data while others are using version2 of some other implementation for get_data we could just iterate over the array, calling the method on each structure and having it "do its own thing in its own way" or "program to an interface":

    Code:
    for (int i = 0; i < ARRAY_LENGTH(a); i++)
        method_call (a[i], get_data);
    regardless of which version each structure refers to.

    But I see where you're coming from on those points which are very valid.
    Last edited by Eclipse07; 12-29-2023 at 08:11 PM.

  5. #5
    Registered User
    Join Date
    Apr 2021
    Posts
    140
    Quote Originally Posted by christop View Post
    Also, the C standard library already has some OOP functionality—stdio is a good example. fprintf() is basically a "method" of the FILE "class". So it's not a bad convention to follow in C either.
    Ironically, stdio is a *bad* example. The stdio functions as a whole put the file pointer at the end of the argument list about half the time, and the front of the list the other half.

    Code:
        size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
        size_t fwrite( void *buffer, size_t size, size_t count, FILE *stream );
        char *fgets( char *str, int count, FILE *stream );
        int fputs( const char *str, FILE *stream );
    But your point about making the invocant be the first parameter is also true in other languages. Python puts self as the first parameter, Perl 4 and Perl 5 defined their object syntaxes around the invocant-is-first parameter, and in fact Perl defined two different calling syntaxes, allowing both f->open and open f to be valid (because Larry Wall is a strange guy).

  6. #6
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Quote Originally Posted by aghast View Post
    Ironically, stdio is a *bad* example. The stdio functions as a whole put the file pointer at the end of the argument list about half the time, and the front of the list the other half.
    Yeah, that has always irked me. But to be fair to the original stdio designers, they created it long before the convention existed to put the object parameter first.

  7. #7
    Registered User
    Join Date
    Jan 2024
    Posts
    1
    I once wrote an article, how to create a very small Objective-C runtime just in C: mini_objc: A minimal Objective-C like runtime in less than 50 lines of code – Nat!'s Journal
    I feel this could be useful to you, since it's fairly concise and shows how to inherit from classes and how to call "methods" dynamically. If you don't need dynamic methods, you could put a lot of function pointers into each class, for non-dynamic lookup, but that doesn't make it necessarily easier (though a bit faster).

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Simulating a "brookshear machine"
    By kiwisfan in forum C Programming
    Replies: 3
    Last Post: 04-30-2012, 04:55 AM
  2. output "double" (like 0x1234123412341234) in plain c
    By delete123 in forum C Programming
    Replies: 10
    Last Post: 07-01-2008, 11:39 AM
  3. Simulating a "Mouse Click"
    By The_Krahe in forum C++ Programming
    Replies: 3
    Last Post: 05-18-2005, 11:45 AM
  4. "itoa"-"_itoa" , "inp"-"_inp", Why some functions have "
    By L.O.K. in forum Windows Programming
    Replies: 5
    Last Post: 12-08-2002, 08:25 AM
  5. "CWnd"-"HWnd","CBitmap"-"HBitmap"...., What is mean by "
    By L.O.K. in forum Windows Programming
    Replies: 2
    Last Post: 12-04-2002, 07:59 AM

Tags for this Thread