OOP in ANSI C



  • Hallo zusammen,

    mein Ziel ist es NICHT C++ durch C zu ersetzen. Ich wollte nur, falls es ohne C nicht geht trotzdem versuchen mit der denkweise von OOP zu arbeiten. Auch hat man so die Chance die Vorteile von UML zu nutzen. Man muss nur die generierten Klassen in struct umwandeln. Momentan mache ich das durch Tipparbeit. Bestimmt lässt sich das aber auch automatisieren 🙂 . Ich stelle hier mal zusammen was ich so zusammen geschrieben habe.

    Mich würden eure meinungen interessieren. Ich auch für hinweise auf denkfehler dankbar. Nun gut. Als erstes habe ich eine Basisklasse definiert. Jede Klasse die erzeugt wird, muss diese Basisklasse beinhalten. Durch die Basisklasse werden die Grundelemente einer Klasse wie der:
    * Konstructor
    * Destructor
    * die Grösse der Klasse

    Die Basisklasse sieht somit so aus:

    struct Class{
        size_t size;	// for allocating memory
        void * (* Constructor) (void * self, va_list * app);
        void * (* Destructor) (void * self);
    };
    

    Man könnte hier noch andere Grundmethoden definieren. Ich belasse es mal auf diesen zwei grundmethoden. Wichtig ist hier die Definition des Konstructors. Da hier noch nicht bekannt ist, wird hier die Definition von va_list benutzt. Dies ernöglicht die Definition von Funktionen:

    func(param, ...)
    

    Dies ermöglicht die Definition von Funktionen die eine unbestimmte Anzahl von Paramteren haben. Eine Beispielfunktion hierfür ist:

    printf (__const char *__restrict __format, ...);
    

    Damit diese Paramter beim erzeugen des Objektes an den Konstruktor weitergegeben werden kann, muss die Mtehode new ebenfalls die Annahme von unbegrenzten Paramtern akzeptieren. Ausgehen von der oben definierten Klasse, ergibt sich somit die Folgende new Methode:

    void * new(const void * __class, ...){
    
        const struct Class * class = __class;
        void * p = calloc(1, class->size);
        assert(p);
        * (const struct Class **) p = class;
    
        //Init the methods of a class
        if (class->Constructor){
    	va_list ap;
    
    	va_start(ap, __class);
    	p = class->Constructor(p, &ap);
    	va_end(ap);
        }
        return p;
    } // Emd new
    

    Die new Methode erwartet als erstes Paramter einen Pointer auf die Basisklasse. Alle weiteren Paramter sind Klassen abhänig und können hier vernachlässigt werden.

    Zuerst wird ein Pointer auf die gegebene Klasse erstellt um so Zugriff auf die Basiselemente zu ermöglichen. dadurch ist die Grösse der Klasse bekannt. Somit wird in Zeile 4 Speicher für die Klasse alokiert. In Zeile 6 wird durch den Doppelpointer auf die Basisklasse gecastet. Somit ist auch immer ein Zugriff auf die Basiselemente möglich. Dies wird benötigt um z.B. später auf den Destructor zuzugreifen.

    Ab Zeile 9 bis 15 wird der Konstructor aufgerufen. Nach dem Aufruf des Konstruktors wird der Zeiger auf den neue erstellten Objekt in Zeiler 16 zurückgeliefert.

    Auf ähnliche Weise funktioniert delete:

    void delete (void * __self){
        const struct Class ** cp = __self;
        if ( __self && *cp && (*cp)->Destructor){
    	__self = (*cp)->Destructor(__self);
        }
        free(__self);
    }
    

    So nun hat man die Basis und es ist Zeit Für eine Beispielklasse. Zuerst einmal brauchen wir eine Beispielklasse. Wir erstellen die Datei Test.h und definieren hier die Klasse als struct.

    typedef struct test
        const void * class; //Muss als erstes definiert werden
    
        // Hier kommen die Operatoren der Klasse
        int Alter;
        char vorname[50];
        char name[50];
    
        //Hier die methoden der Klasse
        int (*getalter)(struct test *)
        void (*printfInfo)(struct test *)
    }Test
    

    Wichtig ist, dass die Methoden der Klasse hier als Funktionspointer definiert werden. Wenn die Funktionen der Klasse in Test.c, implementiert werden, so sollte es static definiert werden also z.B:

    static void printfInfo(struct test *test){
        ...
    }
    

    Damit erreicht man, dass die Methode von aussen nicht aufgerufen werden kann. Man kann sie nur über die Klasse aufrufen. Alle Methoden die als Funktionspointer definiert werden, wären public. Alle Methoden die nicht als Funktionspointer definiert sind, wären private und werden von der Klasse selbst intern benutzt.

    Num muss man es schaffen dass man die Klasse über die Basisklasse aufrufen kann um so den Konstruktor zu benutzen. Man definiert in der Datei Test.h den Typ für die Klasse:

    extern const void * _Test;
    

    Dies wird in der Datei Test.c implementiert:

    static const struct Class __Test = {
    	sizeof(struct test), //Für das alokieren von Test
    	__Constructor,       //Konstruktor von Test
    	__Destructor         //Destructor von Test
    };
    
    const void * _Test = &__Test; // Wird von new als Typ benötigt
    

    Somit wäre ein Grundgerüst vorhanden, um nun folgendes auszuführen:

    Test * t = new(_Test);
    

    Es wird ein Objekt der Klasse Test erzeugt welches durch _Test beschrieben wird. Nach dem alokieren des Speichers wird der Konstruktor aufgerufen. Siehe oben die Methode new(). Im Konstruktor können nun die einzelnen Methoden angebunden werden:

    static void * __Constructor(void * __self, va_list * app){
    
        Test * self = __self;
    
        //Do here the inits
        self->printfInfo = &printfInfo;
        ...
        return self;
    }
    

    Um in den folgenden Methoden die Variable this zu benutzen, definiert man in der Datei Test.c, die globale Variable:

    static Test * this
    

    Der Konstructor würde dann so aussehen:

    static void * __Constructor(void * __self, va_list * app){
    
        Test * self = __self;
        this = self;  
    
        //Do here the inits
        this->printfInfo = &printfInfo;
    
        return self;
    }
    

    Ok. Dies war eine kleine Einführung um OOP in ANSI C zu ermöglichen. Für Anregungen und Kritik, bin ich euch dankbar.

    Danke + Gruss,
    Haksi



  • Zu dem Thema gibt es bereis ein dickes Büchli, welches man z.B. hier auch als PDF herunterladen kann. Da Dein Code dem dort beschrieben aber sehr ähnlich sieht, vermute ich mal, dass Du das PDF bereits kennst. 😉



  • Ja ic abue darauf auf. 😃 Doch habe ich eine Frage. Ist das mit dem this korrekt so? Ich habe manchmal das Phänomen das von mir gesetzte Variabeln in der Funktion nicht mehr gesetzt sind. Z.B:

    Test * t = new(_Test);
    strcpy(t->vorname,"Grosser");
    strcpy(t->vorname,"Mann");
    t->alter = 66;
    t->prinrInfo();
    
    //In printInfo
    printf("Vorname:%s\n",this->vorname);
    

    Bei mir kommt dann vor das this->vorname leer ist. Kann man überhaupt das was von ausen gegeben wird, intern durch eine Globale Variable zugreifbar machen?


Anmelden zum Antworten