Results 1 to 1 of 1
  1. #1
    TrollerCoaster's Avatar
    Join Date
    Sep 2010
    Gender
    male
    Location
    I am a fish
    Posts
    633
    Reputation
    61
    Thanks
    801

    Post Object Systems in Plain C/C++

    All code is written for PLAIN C.

    This tutorial will briefly explain different types of object systems you can use in C or C++ and how to implement them.
    With these, you can implement object systems better than C++ style objects.

    For each example code, these compiler definitions are used:
    Code:
    #define NEW(type) (type*)malloc(sizeof(type));
    In each object system, I try to explain the following associated with the object system:
    1. Class definitions
    2. Casting & Field access
    3. Type checking
    4. Inheritance
    5. Method invocation


    Pick an object system you like.

     
    Pros
    • Fast and simple
    • Easy to implement

    Cons
    • Not very dynamic
    • Limited type checking capabilities
    • There is only simple inheritance
    • Not much support for plugin systems
    • Doesn't even surpass C++ objects
    • Code is awkwardly typed
    • Difficult to enforce garbage collection


    You lose a lot of functionality in return for a very simple and fast system.

    1. Class definition
    This is the most simple type of object system in which each class is represented by a struct. Functions are statically defined in some header file.

    Code:
    typedef struct {
      float x;
      float y;
    } Point;
    
    Point *point_create(float x, float y) {
      Point *self = NEW(Point);
      self->x = x;
      self->y = y;
      return self;
    }
    
    typedef struct {
      Point topLeft;
      Point bottomRight;
    } Rectangle;
    
    Rectangle *rectangle_create(float x1, float y1, float x2, float y2) {
      Rectangle *self = NEW(Rectangle);
      self->topLeft.x = x1;
      self->topLeft.y = y1;
      self->bottomRight.x = x2;
      self->bottomRight.y = y2;
      return self;
    }
    2. Inheritance
    Only simple inheritance is possible. Not only that, you must know the object's parent field to access any super fields.
    Fortunately, you can inherit objects that inherit other objects and it will work the same.

    Code:
    #define as(type, object) ((type*)(object))
    #define extend(object, type) (type*)realloc(object, sizeof(type));
    //as is used to access inherited fields
    //extend is needed for multi constructing objects
    
    typedef struct {
      char *name;
      float x;
      float y;
    } Entity;
    
    Entity *entity_create(char *name, float x, float y) {
      Entity *entity = NEW(Entity);
      entity->name = name;
      entity->x = x;
      entity->y = y;
      return entity;
    }
    
    typedef struct {
      Entity super; //Parent object
      unsigned int hp;
    } Enemy;
    
    Enemy *enemy_create(char *name, unsigned int hp, float x, float y) {
      Enemy *self = (Enemy*)entity_create(name, x, y);
      extend(self, Enemy);
      self->hp = hp;
      as(Entity, self)->x++; //As an example...
      as(Entity, self)->x--; //You must know when you are referencing an inherited field
      return self;
    }
    3. Type checking
    Type checking is very limited or tedious to implement. In the base class of an object, you can include a bit-field representing the type of object, or put booleans in the base object.

    Code:
    typedef union {
      struct {
        unsigned char Person:1; //A single bit in the unsigned char represents the Person type
        unsigned char Student:1; //This bit will represent the Student type
        unsigned char reserved:6; //Fill the remaining bits
      } types;
      unsigned char value;
    } Type;
    
    typedef struct {
      Type data;
    } Object;
    
    Object *object_create() {
      Object *self = NEW(Object);
      self->data.value = 0;
      return self;
    }
    
    #define instanceof(object, type) (((Object*)(object))->data.types.type)
    
    typedef struct {
      Object super; //inherit Object
      char *name;
    } Person;
    
    Person *person_create(char *name) {
      Person *self = (Person*)object_create();
      self = extend(self, Person); //Allocate the rest of the Person struct
      as(Object, self)->data.types.Person = 1;
      self->name = name;
      return self;
    }
    
    typedef struct {
      Person super; //inherit Person which inherits Object
      float grade;
    } Student;
    
    Student *student_create(char *name, float grade) {
      Student *self = (Student*)person_create(name); //Construct person
      self = extend(self, Student); //Think "extend object to Student struct"
      as(Object, self)->data.types.Student = 1;
      self->grade = grade;
    }

     
    Pros
    • Objects can be easily shared between functions and programs without casting
    • Very dynamic
    • Objects can have multiple inheritances
    • Not much awkward casting
    • Works across multiple languages and platforms
    • Very easy to implement garbage collection
    • Easy to serialize objects and import them into any object

    Cons
    • A little speed loss, but not much
    • Objects must be functionally accessed
    • Fields must constantly be casted (At least... primitive fields must)
    • Requires much more memory


    This is my favorite object system thanks to all the pros. It's great for plugin systems.
    Now all your functions for accessing objects should be put into a DLL. This way, you can load external DLLs that call the Object DLL to create a dynamic plugin system.

    In this object system, every object is a dictionary. Every field is named with a string, and has an ABSTRACT native field. The size of abstract is dependent on the pointer size of the machine. Abstract values can be pointers, primitives, or methods. I won't provide much code for the more advanced stuff (garbage collection, serialization, etc), but I will provide the structs so you can get an idea of how to implement it.

    Code:
    typedef void* ABSTRACT;
    typedef ABSTRACT (*Method)(Object *self, ...); //Methods of an object with cdecl calling conventions
    
    typedef struct {
      unsigned char isRef; //"isReference" This one is optional and is for garbage collection and/or type checking purposes!
      char *key;
      ABSTRACT value;
    } Field;
    
    typedef struct {
      unsigned int refCount; //Optional field. "reference count" It is for garbage collectors that work on reference counts.
      unsigned char protected; //If 1, this object is protected from garbage collection. This is for native access purposes.
      unsigned int inheritCount;
      char **inherits;
      unsigned int fieldCount;
      Field *fields;
    } Object;
    
    char *_StringClone(char *s) {
      char *ret = (char*)malloc(sizeof(char)*(strlen(s)+1));
      strcpy(ret, s);
      return ret;
    }
    
    void ObjectAddType(Object *self, char *type) {
      self->inheritCount++;
      self->inherits = (char**)malloc(sizeof(char*)*self->inheritCount);
      self->inherits[self->inheritCount-1] = _StringClone(type);
    }
    
    unsigned char ObjectInstanceOf(Object *self, char *type) {
      unsigned int i;
      for(i = 0; i < self->inheritCount; i++) {
        if(!strcmp(self->inherits[i], type)) {
          return -1;
        }
      }
      return 0;
    }
    
    unsigned char ObjectSetField(Object *self, char *key, ABSTRACT value) {
      unsigned int i;
      for(i = 0; i < self->fieldCount; i++) {
        if(!strcmp(self->fields[i].key, key)) {
          self->fields[i].value = value;
          return 0;
        }
      }
      //Field not found, let's create one
      self->fieldCount++;
      self->fields = (Field*)realloc(sizeof(Field)*self->fieldCount);
      self->fields[self->fieldCount-1].key = _StringClone(key);
      self->fields[self->fieldCount-1].value = value;
      return -1;
    }
    
    ABSTRACT ObjectGetField(Object *self, char *key) {
      unsigned int i;
      for(i = 0; i < self->fieldCount; i++) {
        if(!strcmp(self->fields[i].key, key)) {
          return self->fields[i].value;
        }
      }
      return NULL; //or throw an exception / set error
    }
    
    Method ObjectGetMethod(Object *self, char *key) {
      return (Method)ObjectGetField(self, key); //This is exactly the same as GetField, but does the cast for you
    }
    
    void ObjectDelete(Object *self) {
      unsigned int i;
      Method m;
      ABSTRACT deconstruct = ObjectGetField(self, "finalize");
      if(deconstruct != NULL) {
        m = (ABSTRACT)deconstruct;
        m(self);
      }
      for(i = 0; i < self->inheritCount; i++) {
        free(self->inherits[i]);
      }
      for(i = 0; i < self->fieldCount; i++) {
        free(self->fields[i].key);
      }
      free(self->fields);
      free(self->inherits);
      free(self);
    }
    
    ABSTRACT ObjectToString(Object *self) {
      //I recommend changing this to return a String object for garbage collection purposes
      return self->inherits[0];
    }
    
    ABSTRACT ObjectProtect(Object *self) {
      self->protected = -1;
      return NULL;
    }
    
    Object *ObjectCreate() {
      Object *self = NEW(Object);
      self->refCount = 0;
      self->protected = 0;
      self->fieldCount = 0;
      self->fields = (Field*)malloc(0);
      self->inheritCount = 0;
      self->inherits = (char**)malloc(0);
      ObjectAddType(self, "Object");
      ObjectAddType(self, "Serializable");
      ObjectSetField(self, "protect", ObjectProtect); //Method that protects the object from garbage collection
      // Some default methods to give you ideas on how to improve this object system
      ObjectSetField(self, "finalize", NULL); //Deconstructor
      ObjectSetField(self, "equals", ObjectEquals); //Test if two objects are equal
      ObjectSetField(self, "clone", ObjectClone); //Clone the object
      ObjectSetField(self, "toString", ObjectToString); //Convert object to string representation
      ObjectSetField(self, "serialize", ObjectSerialize); //Serialize the object (save as a structured byte field)
      return self;
    }
    Just pack these functions in a dll and export the Set/GetField, AddType, and Delete functions. Other DLLs can use these functions to interact with the objects in your program.
    And an example code on how to use it

    Code:
    ABSTRACT PersonSayThis(Object *self, char *msg) {
      printf("%s: %s\n", (char*)ObjectGetField(self, "name"), msg);
      return NULL;
    }
    
    ABSTRACT PersonSayHello(Object *self) {
      printf("%s: Hello! I am a person.\n", (char*)ObjectGetField(self, "name"));
      return NULL;
    }
    
    Object *Person(char *name) {
      Object *self = ObjectCreate();
      ObjectAddType(self, "Person");
      ObjectSetField(self, "name", name);
      ObjectSetField(self, "sayThis", PersonSayThis);
      ObjectSetField(self, "sayHello", PersonSayHello);
      return self;
    }
    
    ABSTRACT StudentSayHello(Object *self) {
      printf("%s: Hello! I am a student.\n", (char*)ObjectGetField(self, "name"));
      return NULL;
    }
    
    Object *Student(char *name) {
      Object *self = Person(name);
      ObjectAddType(self, "Student");
      ObjectSetField(self, "sayHello", StudentSayHello); //overwrite 
      return self;
    }
    
    int main() {
      Object *person = Person("Bob");
      Object *student = Student("Steve");
      ObjectGetMethod(person, "sayHello")(person);
      if(ObjectInstanceOf(student, "Person")) { //type checking isn't required, but just to make sure it works!
        ObjectGetMethod(student, "sayHello")(student); //Uses StudentSayHello
        ObjectGetMethod(student, "sayThis")(student, "The sky is falling!"); //Uses PersonSayThis
      }
      return 0;
    }

     
    Pros
    • All the great features of shared objects
    • Tries to combine the speed of data structure objects with the sharing capabilities of shared objects by only exporting the needed fields
    • Protects the fields you don't want visible to the programmer
    • Reduces the amount of calls to ObjectGetField
    Cons
    • Lose some inheritance functionality
    • Objects with protected fields need their own serialization methods
    • Re-introduces data structure objects
    • Might confuse the programmer about the size of the object during memory moves or copies
    • Should type check more often


    In this object system, the programmer will always see objects as Object*. In the object methods, however, there is some casting to gain access to the protected fields of the object. This can effectively be more user friendly at the cost of potentially buggy code if the creator is not careful.

    Code:
    #define extend(object, type) (type*)realloc(object, sizeof(type));
    
    typedef union {
      ABSTRACT a;
      float f;
    } AbstractFloat; //float/abstract conversion factory
    
    typedef struct {
      float x;
      float y;
    } Point_t;
    
    typedef struct {
      Object base;
      Point_t topLeft;
      Point_t bottomRight;  
    } Rectangle_t;
    
    ABSTRACT RectangleGetArea(Object *self) {
      Rectangle_t *rect = (Rectangle_t*)self;
      AbstractFloat af;
      af.f = (rect->bottomRight.x - rect->topLeft.x) * (rect->bottomRight.y - rect->topLeft.y);
      return af.a;
    }
    
    ABSTRACT RectangleSetDimensions(Object *self, float x1, float y1, float x2, float y2) {
      Rectangle_t *rect = (Rectangle_t*)self;
      rect->topLeft.x = x1;
      rect->topLeft.y = y1;
      rect->bottomRight.x = x2;
      rect->bottomRight.y = y2;
      return NULL;
    }
    
    Object *Rectangle(float x1, float y1, float x2, float y2) {
      Rectangle_t *self = (Rectangle_t*)ObjectCreate();
      extend(self, Rectangle_t);
      self->topLeft.x = x1;
      self->topLeft.y = y1;
      self->bottomRight.x = x2;
      self->bottomRight.y = y2;
      ObjectSetField(self, "getArea", RectangleGetArea);
      ObjectSetField(self, "setDimensions", RectangleSetDimensions);
      return (Object*)self;
    }

     
    Pros
    • Probably the fastest way to utilize shared objects
    • Extremely modular and modifiable
    • Easily allows for static functions
    Cons
    • Lots of files required to maintain objects
    • Awkward type checking unless using a class hierarchy system
    • Good luck with garbage collection

    No code here, since it would be all the over place. Export your functions for your object in a DLL header. Use data structures to represent your objects. Any field of an object must be accessed through a function in the DLL.


    Each object system ranks as so:

    SPEED -> Data Structure Objects
    FUNCTIONALITY -> Shared Objects
    MODULAR DESIGN -> DLL Objects
    HYBRID OF ALL OF THE ABOVE -> Protected Shared Objects

    Hope this gave you some ideas!!! And post what you think is best

Similar Threads

  1. cool system.mrs
    By 1h1a1i in forum Gunz General
    Replies: 25
    Last Post: 03-16-2008, 11:51 AM
  2. Unpacked system.mrs
    By 1337Sasuke in forum Gunz Hacks
    Replies: 1
    Last Post: 03-22-2006, 02:05 AM
  3. EndLess' edited system.mrs
    By EndLess in forum Gunz Hacks
    Replies: 22
    Last Post: 02-22-2006, 09:22 AM
  4. [Request] System.mrs
    By Xgs in forum Gunz General
    Replies: 10
    Last Post: 02-21-2006, 12:11 PM
  5. System Hacker For Gunz
    By gameking85 in forum Gunz Hacks
    Replies: 11
    Last Post: 02-09-2006, 11:57 AM

Tags for this Thread