oh oh it's OO - calling encapsulated functions - question for the experts

Discussion in 'C Programming' started by Gibby Koldenhof, Jan 10, 2005.

  1. Hiya,

    I'm setting up some code in the spirit of Design Patterns, OOP, etc. All
    nice and well, it handles pretty much all OO style things and serves my
    purposes well. There's one last final question remaining: How to properly,
    simple and eleganty implement functions. The functions are encapsulated in
    the objects, the objects themselves are stored in a binary tree and objects
    contain 'methods' (functions) along with data. I don't want to make publicly
    available routines since the encapsulation is then gone.

    So practically, how do I call a function that is encapsulated in some
    structure without using that structure directly (e.g. only through void *)?

    The code below are four examples of how to fix this, only example2 is kinda
    okay. The biggest problem however is that it is very slow.

    If anybody has got an idea of how to do this better (or even different) I'd
    love to hear it because I'm kinda stuck with this.

    P.s. I don't want to use C++ - it breaks more than fixes in this code base.

    This is all in the spirit of stuff like
    http://www.planetpdf.com/codecuts/pdfs/ooc.pdf and related techniques and
    docs. The GTK system implements something simalar (but in a terrible way
    IMHO).

    Any tips welcome!

    Cheers,
    Gibby Koldenhof

    /*--------------------------------------------------------------------------
    ----------*/
    /* compile with -ansi -pedantic -Wall */
    /*--------------------------------------------------------------------------
    ----------*/

    /* example 1, using func. pointer */

    typedef void (*method_t)(void);

    extern method_t find_method(const char * name);

    void foo1(void)
    {
    int (*getpixel)(int x, int y) = (int (*)(int,int))find_method("getpixel");

    /* this is terrible, we need to cast a heck of a lot for each function
    we're gonna call, this clutters up the code and invites trouble if the cast
    is wrong */

    int pixel = getpixel(1, 2);
    }

    /*--------------------------------------------------------------------------
    ----------*/

    /* example 2, using var. args */

    void do_method(const char * name, ...)
    {
    /* find method through name and parse arg list */
    }

    void foo2(void)
    {
    extern void * new(const char *, ...);

    int pixel ;

    do_method("getpixel", 1, 2, &pixel);

    /* two problems : (1) it is *SLOW*, e.g. parsing arg list and finding
    'getpixel' method in the tree take a long time. The latter could be solved
    by caching the found pointer.
    (2) we can't check var. args on correctness, e.g. if a double should be
    passed and the user passes an int it buggers up */

    /* possible solution, only pass objects, so we can check the type */

    if(1)
    {
    void * x = new("int", 1);
    void * y = new("int", 2);
    void * result = new("int", 0);
    void * method = new("method::getpixel");

    /* this is more elegant but adds tons of indirection, ergo TERRIBLY slow
    .... ;-( */

    do_method(method, x, y, result);
    }
    }

    /*--------------------------------------------------------------------------
    ----------*/

    /* example 3, using var. args */

    typedef struct {
    /* table with all possible methods ... ugh */

    int (*get_1)(int a);
    int (*get_2)(int a, int b);
    int (*get_3)(int a, int b, int c);
    } methods_t ;

    static methods_t methods;

    #define some_get(x) methods.get_##x

    void foo3(void)
    {
    /* Yikes, this is terrible! we can only use a real integer (due to
    pasting) and the syntax of the thing is terrible */

    int pixel = some_get(2)(1,2);
    }

    /*--------------------------------------------------------------------------
    ---------*/

    /* example 4, using casts and stuff */

    typedef struct {
    int xsize, ysize, bpp ;
    void * data ;
    int (*getpixel)(int x, int y);
    } image_t ;

    void * new(const char * name)
    {
    static image_t image ;

    return &image ;
    }

    void foo4(void)
    {
    void * picture = new("image");

    /* yuck - there goes the encapsulation, and this is terrible to call -
    even if we fix it with a macro */

    int pixel = ((image_t *)picture)->getpixel(1,2);
    }
    Gibby Koldenhof, Jan 10, 2005
    #1
    1. Advertising

  2. Gibby Koldenhof

    Michael Mair Guest

    Re: oh oh it's OO - calling encapsulated functions - question forthe experts

    Gibby Koldenhof wrote:
    > Hiya,
    >
    > I'm setting up some code in the spirit of Design Patterns, OOP, etc. All
    > nice and well, it handles pretty much all OO style things and serves my
    > purposes well. There's one last final question remaining: How to properly,
    > simple and eleganty implement functions. The functions are encapsulated in
    > the objects, the objects themselves are stored in a binary tree and objects
    > contain 'methods' (functions) along with data. I don't want to make publicly
    > available routines since the encapsulation is then gone.
    >
    > So practically, how do I call a function that is encapsulated in some
    > structure without using that structure directly (e.g. only through void *)?
    >
    > The code below are four examples of how to fix this, only example2 is kinda
    > okay. The biggest problem however is that it is very slow.
    >
    > If anybody has got an idea of how to do this better (or even different) I'd
    > love to hear it because I'm kinda stuck with this.


    C99:

    #define CALL_METHOD(classvar,method,...) \
    classvar.method(&classvar, __VA_ARGS__)

    where method is a function pointer to be "initialised" by your
    "constructor".


    C89: Use another calling convention. Something along the lines of
    CALL_METHOD_N() where N is the number of additional arguments is
    easy to realise but maybe there is a more elegant solution out
    there.

    As these are macros, they are pretty fast.


    Cheers
    Michael
    --
    E-Mail: Mine is an /at/ gmx /dot/ de address.
    Michael Mair, Jan 10, 2005
    #2
    1. Advertising

  3. <snip>
    > > The code below are four examples of how to fix this, only example2 is

    kinda
    > > okay. The biggest problem however is that it is very slow.
    > >
    > > If anybody has got an idea of how to do this better (or even different)

    I'd
    > > love to hear it because I'm kinda stuck with this.

    >
    > C99:
    >
    > #define CALL_METHOD(classvar,method,...) \
    > classvar.method(&classvar, __VA_ARGS__)
    >
    > where method is a function pointer to be "initialised" by your
    > "constructor".
    >
    >
    > C89: Use another calling convention. Something along the lines of
    > CALL_METHOD_N() where N is the number of additional arguments is
    > easy to realise but maybe there is a more elegant solution out
    > there.
    >
    > As these are macros, they are pretty fast.


    Cool! I didn't know of this C99 feature (I really need to read up on it) -
    using var. args in C89's macro's is a no-no though, but the alternative
    (less elegant) way would be to pass an array (or a string for that matter
    but then parsing the args back in would take to long) - but assuming I'm
    going to compile everything in C99 this still has a problem:

    It breaks with the encapsulation, e.g. I can't use generic pointers to
    objects ... or can I? (somehow) or maybe I'm missing something?

    What I mean is: (compile with gcc test.c --std=c99 --pedantic)

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>

    #define CALL_METHOD(classvar,method,...) \
    classvar.method(&classvar, __VA_ARGS__)

    typedef struct {
    void (*func)(void * class, ...);
    } foo_t ;

    static void blah(void * class, ...)
    {
    printf("blah!\n");
    }

    int main(void)
    {
    foo_t foo;

    foo.func = blah;

    CALL_METHOD(foo, func, 1, 2, 3);

    /* what I would like but what (of course) doesn't work in this construct :
    */

    if(1)
    {
    extern void * find_class(const char * name);

    void * foobar = find_class("something");

    CALL_METHOD(foobar, func, 1, 2, 3);
    }

    return 0;
    }

    Ergo, the code within the if(1) scope is what I would really like to do so
    the definition of foo_t is invisible to the user program.

    Thanks, More tips welcome!

    Cheers,
    Gibby Koldenhof
    Gibby Koldenhof, Jan 10, 2005
    #3
  4. Gibby Koldenhof

    Michael Mair Guest

    Re: oh oh it's OO - calling encapsulated functions - question forthe experts

    Gibby Koldenhof wrote:
    > <snip>
    >
    >>>The code below are four examples of how to fix this, only example2 is

    >
    > kinda
    >
    >>>okay. The biggest problem however is that it is very slow.
    >>>
    >>>If anybody has got an idea of how to do this better (or even different)

    >
    > I'd
    >
    >>>love to hear it because I'm kinda stuck with this.

    >>
    >>C99:
    >>
    >>#define CALL_METHOD(classvar,method,...) \
    >> classvar.method(&classvar, __VA_ARGS__)
    >>
    >>where method is a function pointer to be "initialised" by your
    >>"constructor".
    >>
    >>
    >>C89: Use another calling convention. Something along the lines of
    >>CALL_METHOD_N() where N is the number of additional arguments is
    >>easy to realise but maybe there is a more elegant solution out
    >>there.
    >>
    >>As these are macros, they are pretty fast.

    >
    >
    > Cool! I didn't know of this C99 feature (I really need to read up on it) -
    > using var. args in C89's macro's is a no-no though, but the alternative
    > (less elegant) way would be to pass an array (or a string for that matter
    > but then parsing the args back in would take to long) - but assuming I'm
    > going to compile everything in C99 this still has a problem:
    >
    > It breaks with the encapsulation, e.g. I can't use generic pointers to
    > objects ... or can I? (somehow) or maybe I'm missing something?


    Nope. However, passing generic pointers effectively does not get you
    very far -- and not in a very fast way.

    > What I mean is: (compile with gcc test.c --std=c99 --pedantic)
    >
    > #include <stdio.h>
    > #include <stdlib.h>
    > #include <stdarg.h>
    >
    > #define CALL_METHOD(classvar,method,...) \
    > classvar.method(&classvar, __VA_ARGS__)
    >
    > typedef struct {
    > void (*func)(void * class, ...);
    > } foo_t ;
    >
    > static void blah(void * class, ...)
    > {
    > printf("blah!\n");
    > }
    >
    > int main(void)
    > {
    > foo_t foo;
    >
    > foo.func = blah;
    >
    > CALL_METHOD(foo, func, 1, 2, 3);
    >
    > /* what I would like but what (of course) doesn't work in this construct :
    > */
    >
    > if(1)
    > {
    > extern void * find_class(const char * name);
    >
    > void * foobar = find_class("something");
    >
    > CALL_METHOD(foobar, func, 1, 2, 3);
    > }
    >
    > return 0;
    > }
    >
    > Ergo, the code within the if(1) scope is what I would really like to do so
    > the definition of foo_t is invisible to the user program.
    >
    > Thanks, More tips welcome!


    Hints:
    * Use OO. Inheritance helps.

    Example:

    typedef struct base {
    unsigned long control;
    Env_Item v;
    int (*Construct)(struct base *);
    int (*Destruct) (struct base *);
    int (*Init) (struct base *, int argc, char **argv);
    int (*Execute)(struct base *, int argc, char **argv);
    int (*Display)(struct base *);
    } Base;

    Every derived structure now contains as first member a
    variable of type Base. You can store information about the
    type in control (bitwise). You are not passing around void *
    but base *.
    Env_Item belongs to some environment, e.g. an environment
    directory, effectively storing the name etc.

    The types are registered by either the control entry or
    by prefixes and are "created" at the beginning (with storing
    the necessary size). When something is to be constructed,
    you find out the type by a prefix or suffix (or the complete name).

    You can always call Construct, Destruct, Init, Execute and Display
    when casting the derived types (ideally: type pointers) to Base(Base *).

    e.g.
    #define CALL_BAS_METHOD(classvar_p,method,...) \
    IS_BASE((Base *)(classvar_p)) ? \
    ((Base *)(classvar_p))->method((Base *)(classvar_p), __VA_ARGS__) \
    : /* Do something sensible */

    (untested, just a sketch)

    * Make it only as abstract as you need it.
    Re-creating C++ with C is worse than using C++ in the first place.

    * If you need "private" methods and data, you can hide them in standard
    C ways. First idea: One file per class, private stuff hidden in a
    "private" * member.
    Extension to inheritance left as exercise to the reader. (Ugly)


    Cheers
    Michael
    --
    E-Mail: Mine is an /at/ gmx /dot/ de address.
    Michael Mair, Jan 10, 2005
    #4
  5. <snip>
    > > It breaks with the encapsulation, e.g. I can't use generic pointers to
    > > objects ... or can I? (somehow) or maybe I'm missing something?

    >
    > Nope. However, passing generic pointers effectively does not get you
    > very far -- and not in a very fast way.
    >
    > > What I mean is: (compile with gcc test.c --std=c99 --pedantic)
    > >
    > > #include <stdio.h>
    > > #include <stdlib.h>
    > > #include <stdarg.h>


    <snip code>

    > > /* what I would like but what (of course) doesn't work in this

    construct :
    > > */
    > >
    > > if(1)
    > > {
    > > extern void * find_class(const char * name);
    > >
    > > void * foobar = find_class("something");
    > >
    > > CALL_METHOD(foobar, func, 1, 2, 3);
    > > }
    > >
    > > return 0;
    > > }
    > >
    > > Ergo, the code within the if(1) scope is what I would really like to do

    so
    > > the definition of foo_t is invisible to the user program.
    > >
    > > Thanks, More tips welcome!

    >
    > Hints:
    > * Use OO. Inheritance helps.
    >
    > Example:
    >
    > typedef struct base {
    > unsigned long control;
    > Env_Item v;
    > int (*Construct)(struct base *);
    > int (*Destruct) (struct base *);
    > int (*Init) (struct base *, int argc, char **argv);
    > int (*Execute)(struct base *, int argc, char **argv);
    > int (*Display)(struct base *);
    > } Base;


    Okay, if I understand correctly these are the base methods. Agreed - I
    already have those, they include stuff like contruct, destruct, cast, clone,
    cout, cin, serialize, etc. Since these routines are generally not
    speed-critical I've got them hidden and find them through a string.

    I wouldn't mind having one 'Base' type though - but I don't think it helps
    me - read below.

    > Every derived structure now contains as first member a
    > variable of type Base. You can store information about the
    > type in control (bitwise). You are not passing around void *
    > but base *.
    > Env_Item belongs to some environment, e.g. an environment
    > directory, effectively storing the name etc.


    Okay, but I already do this - my objects are 'simply' encapsulated structs
    (a tad more complex than that but anyway) which I can reference through a
    void pointer, e.g I can do stuff like:

    (just as an example)

    void * pic = new("image", 320, 200, 32);

    new * blah = clone(pic);

    printf("%s\n", typeof(blah)); /* prints 'image' */

    delete(pic);

    the 'image' object is in turn made up of 'RGB' objects and those are made up
    3 doubles. When I call:

    void * a = new("int", 2);

    call(blah, "divide", a);

    it will cascade the call down to the 'double' object and and will figure out
    that it will needs to divide all double objects which are contained in RGB
    and in turn contained in image. Ergo, I only have to implement a divide
    function for the most basic types and inherited types will figure out how to
    find it and apply it to themselves. Of course to speed it up it would be
    wise to implement the "divide" higher up the chain - but this is later work,
    it already works as it is.

    > The types are registered by either the control entry or
    > by prefixes and are "created" at the beginning (with storing
    > the necessary size). When something is to be constructed,
    > you find out the type by a prefix or suffix (or the complete name).


    Agreed, this is what new() also does, you can give it some string and it
    will find out what to do, e.g.:

    new("int:3", 2, 3, 4)
    new("vector", 2.3, 1.2, 4.5);

    or even

    void * method(void * obj) { return 0; }

    void * a = composite(new("int"), new("double"), new("callback", method));

    calling the "divide" from the previous example will work on the composite
    too - it will divide the int, the double and the result object from method -
    at least if that object is dividable.

    It's somewhat comparable to templates in C++ in that it doesn't matter what
    type it is it will get resolved at run-time, the downside of course (as
    opposed to templates) is that it's slower the nice thing is that it's very
    dynamic.

    > You can always call Construct, Destruct, Init, Execute and Display
    > when casting the derived types (ideally: type pointers) to Base(Base *).
    >
    > e.g.
    > #define CALL_BAS_METHOD(classvar_p,method,...) \
    > IS_BASE((Base *)(classvar_p)) ? \
    > ((Base *)(classvar_p))->method((Base *)(classvar_p), __VA_ARGS__) \
    > : /* Do something sensible */


    Okay, so I can call the basic 'methods' - yet I still cannot call functions
    that are not part of the more traditional routines such as constr., destr.
    etc. So a getpixel() is very specific to an image object yet I can't put it
    in the struct - at least not without throwing away the abstraction .... or
    am I missing your point here?

    Of course I could make a image_get_pixel(void * obj, int x, int y) which
    would only work on an image object but this is not really what I want -
    partly because I'm going to duplicate code, and partly because it isn't
    contained in the image class.

    What would be nicer would be to have a 'read' method, so that:

    void * obj1 = call(image, "read", x, y);
    void * obj2 = call(volume, "read", x, y, z);

    and even

    void * file = new("io", "/usr/local/etc/blah.txt", "rb");
    void * string = call(file, "read", 12);

    would work.

    > (untested, just a sketch)
    >
    > * Make it only as abstract as you need it.
    > Re-creating C++ with C is worse than using C++ in the first place.


    True - and maybe I'm going overboard - maybe it's overkill and will bite me
    later on because the whole damn system becomes way too slow ...

    It's just annoying the hell out of me that the whole system is pretty
    simple, straightforward and elegant yet that this last thing seems to be a
    real problem.

    > * If you need "private" methods and data, you can hide them in standard
    > C ways. First idea: One file per class, private stuff hidden in a
    > "private" * member.
    > Extension to inheritance left as exercise to the reader. (Ugly)


    What I do is just have a number of fields per method/data - e.g. it can be
    protected, private, etc. And indeed each (major) function in a seperate
    file. It's just like a nice generic plugin type-thing. The nice thing is
    that it can generate all kinds of stuff by itself, like output XML, GUI
    code, you can embed scripts into the classes, etc. And finding the classes
    is quite fast since they are all in a binary tree that balances itself to
    the # of generated objects per class. The big plus of inheritance is that I
    can open any file and the system will figure out what type of object it
    would need to represent, so an image file will generate an image object, a
    MRI dataset will generate a volume file, etc.

    It's like C++ only without all the surrounding crap :)

    Thanks Michael for the tips!

    Cheers,
    Gibby Koldenhof
    Gibby Koldenhof, Jan 10, 2005
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Chris
    Replies:
    16
    Views:
    3,949
    Chris
    Feb 28, 2006
  2. Daniel Vallstrom
    Replies:
    2
    Views:
    1,825
    Kevin Bracey
    Nov 21, 2003
  3. S?ren Gammelmark
    Replies:
    1
    Views:
    1,865
    Eric Sosman
    Jan 7, 2005
  4. jrpfinch

    Calling Queue experts

    jrpfinch, Mar 26, 2007, in forum: Python
    Replies:
    3
    Views:
    310
    Gabriel Genellina
    Mar 26, 2007
  5. Gianpiero Colagiacomo

    Strange <img> tag behaviour when encapsulated in IF

    Gianpiero Colagiacomo, Jul 5, 2003, in forum: ASP General
    Replies:
    10
    Views:
    209
    Evertjan.
    Jul 5, 2003
Loading...

Share This Page