Download oocx_use.zip - 10.4 KB
Introduction
C would be in relation to OOP as a language which allows only to be defined a unique pure static class, that is, a class with only static members (functions and vars). This will be the global scope. But in an OO programming language we need to be able to define whatever number of named classes and also an instantiation mechanism (that is, not only pure static classes) and also an inheritance mechanism.
With OOCX you will be able to do OO programming with C with the same ammount of hand code typing than in any other OO prepared language.
This is achieved by understanding the difference between static and none static information in a class. Static would be information shared among all objects (instances) of a type or class, and none static would be allocation of memory in each object (instance) to allow for information specific (belongs to) each object. None static elements of a class would be variables, while functions or code or processes would be static, that is, allocated only once and each object having a reference to where the code it is defined when they need to execute that process.
In OOP, you call functions through objects. Doing this you get object itself passed as an implicit parameter to the function being called. In C this is not possible. In C we will end up doing this
a->doSome(a)
to call a function doSome
throght object a
. But if we have also object b
of the same type as a
this will be also correct
b->doSome(a)
and will produce exactly the same effect because we are accessing the same function (because a
and b
are objects of the same type and hence share same static info, that is, for example, functionality) and passing it the same object (a
) as a parameter.
To avoid this duplication we can define a function and let it do the calling for us as this
int f(Obj *obj, char *action, ...){
}
What this function will do it is to call a function ref accessed through the type of an object passing the object itself as a parameter to the function being called. Also it accepts a variable argument list (this is a characteristic of the C standard library), so we can pass any additional argument the function needs.
In OOCX we have two layers (at least conceptually speaking), one in which objects resides and another in which types (classes) resides. The connection between them two is through a bridge or structure which connects them two. An object will have as a first member a pointer to this bridge or structure that will connect the object itself with its type. In the type will reside all static information, this includes function references. So when we need to get a function ref from an object we do it through its type. This is because of the difference between static and none static information, which is the key point from where this implementation has been developed.
So in OOCX we will always call for functions into objects using the same interface, that is, function f()
, like this
f((Obj *)obj1, "doSome");
f((Obj *)obj1, "doThisOther");
f((Obj *)obj2, "doSome");
As you can see we convert (cast) the specific objects obj1
and obj2
into a generic object type Obj *
. This is because of the use of the same interface to dialog with all object of all kinds. f()
only needs to know it gets an object as first parameter, it doesn't care of which type. In OOCX an object will be any data structure which its first member it is a pointer to struct obj
, which is the bridge that connects objects to their respective types. And in C, the address of a data structure in memory it is the address of its first member in memory. So if we say to f()
it is going to receive as a first argument a pointer to Obj
(being Obj
a pointer to struct obj
) we are telling f()
it is going to receive the address of an object (the address of a structure starting by a pointer to struct obj
), which is enough for it to access its type, which is what it needs to eventually get function references.
If we want any output we must get it through the passing of pointers in the variable argument list. That is, for example, if we expect an integer as the output of a computation performed by the call to the function, we must pass a pointer to an int
variable in order to store there the output of the computation. This is like this in this barebone implementation. As you will see, in later development, we eventually will solve this issue.
Inheritance
How does inheritance work? In OOCX objects can be connected in a tree hierarchical manner to develop tree-like compound objects, each one of them connected to their respective type or class. In this manner, by giving an order or command to the object which is the pinnacle of this tree-like compound object, function f()
will be encharged to look for to the tree like structure of types attached to objects in order to find this command we ask for. When it founds a match between the command given as an argument and any command stored in any of the types of the objects that constitute the tree, then it executes that function ref passing as argument the object in the tree where the command was found. Remember I've said we have two layers (conecptually speaking), one were objects resides and another were types resides. The connection between them two is made in each object through a struct obj
bridge.
Coming back to the inheritance concept, when developing types or classes, we tell to which other types or classes the one we are developing will be connected two (which child types it will have). This information or connection we make in the type (class) definition will be used by the instantion function to develop at once the tree like structure of compound objects. Thus, if we develop a type car
and state it will have as a child developed or existing type engine
and existing type (class) wheel
, for example, then when instantiating car
type, we will get a tree like compound object where the pinnacle will be an instance of car
type, followed by a connection to an instance of engine
type and an instance to wheel
type. This last two will be childs of car
type object, and brothers between them. So f()
, when asked for a command in a car
type object, will start looking for a match in car
type, if not found it will keep looking in engine
type, if not found it will keep looking in wheel
type, etc. Functionality identifiers used in each developed type are local to this type definition. This means we can repeat identifiers while defining functions in one type and another. If that is the case, we must know f()
function will execute the first match, so order in child specification matters. We will see how in further development we get around this issue.
So in OOCX we efectively have inheritance, because we can distribute code and data definition and capabilities in a series of types that when asking for instantiation in one of them, we will get a tree like compound object structure where each object will be connected to its type but also to other objects in a tree way, so thanks to f()
we will get access to all of these functionality and data distribution from within the pinnacle of the structure. We don't have to recode or retype any interface, nothing, only specify from who we inherit (childs). This is multiple inheritance, because of the tree like structure.
Objects (compound) contemplated in this way are like real objects in nature. For example human body and all living objects are tree like compound objects. We can think of a head as connected to a chest, this one connected to two arms, etc. An arm connected to a hand connected to five fingers, ... the human nervous system it has a tree like structure. A signal is given in the pinnacle and goes its way down to its destiny (a finger for example). This is the same in this implementation.
Another important fact from this point of veiw is that we can specify a type (class) being a child of another more than once. For example, a car has four wheels. Then in OOCX when defining type (class) car
we will tell or specify it has type wheel
as a child times four, this way when instantiating car
type we will get four instances of wheel
type connected to a parent instance of car
type.
The difference between this way of inheritance and the use of pointers as data members of the structure of the object it is that in the first way all is automated (instantiation, freeing of memory, look for commands through f()
in the tree like structure), while if we declare and use pointers in the data structure of the object, we must hand type code to associate to those pointers an instance, and then we would have to programmatically redirect calls for commands to those instances, etc ... So this will not be inheritance because of the rehand typing work.
Another important aspect to take care in this concept of inheritance or extension it is the subtle difference between the concept of use and the concept of evolution. For example we have a finger of type or class fingerA
but we want to make a better finger type, for example more resistant to hot temperatures. Then we can develop a finger type fingerB
which makes use, or has as a child, fingerA
type, adding in the fingerB
layer an overwriting of the method related to resistance to hot temperatures. Then, with this single (not multiple) inheritance, we have made a kind of evolution type inheritance. When, by the other way, we connect a hand
type to a finger
type four times, telling that a hand
type will have as a child finger
type times four, this is because we want, when instantiating type hand
get also four instances of type finger
connected as childs to the hand
instance. So in this last case we are using the concept of inheritance (multiple) more in a sense of use or constructing of a compound object than in the pure sense of evolution.
Now it's time to look at some pics which reflect what it has been told so far.
Demonstration
I am going to use here the OOCX implementation or framework to do some OO programming with C.
I am going to use mingw as a compiler. Editor could be any. I will also use a make tool to compile, but it is not necessary (later on you can look on tutorials for make when you desire). My structure folder will be as follows. For each type or class developed, I will have two files, one ending in .c extension and another one ending in .h extension. For example, supose I develop a type named car
. Then I will have two source files, car.c and Car.h. See how the header file starts with capitalized letter while the .c source file starts with lower case letter. This is by convention, because really in this implementation or framework types are also objects, instances of a type named type
. That is, in this implementation there is a type named type
which I have developed and which serves as type for all the rest of types to be developed with OOCX. Even the type for type
it's type
itself, that is type
is a type for himself (you can see these in previous pics). When using types in a source .c file, we must use their header files before the use of the type. For example supose I am going to use a type car
in a .c source file, then I will do as follows
#include <stdio.h>
#include "Car.h"
int main (){
return 0;
}
So in our dev folder, we will have all .c source files and .h header files for each type (class). Also test files or main function will be defined in a .c source file contained in the same folder. Also in this folder we will have another folder, named oocx, which contains this implementation, which are a bunch of source files (little). So, really, when developing with this implementation, we must include also oocx.h header file, like this
#include <stdio.h>
#include "oocx/oocx.h"
#include "Car.h"
int main (){
return 0;
}
When you develop with C it is also good to use gdb debugger. This is included with mingw and is a command line debugger tool (look for tut). As you see I am developing in Windows. For other OS you will use another compiler.
Ok. I go with the code with a real example. I will develop a type person
. There will be two source files, person.c and Person.h and a third test source file which will contain main
function to test the type (class). Let's see to the header file (Person.h).
Person.h
#ifndef pERSON_H
#define pERSON_H
#include "oocx/oocx.h"
typedef struct person{
Obj obj;
int age;
char *name;
} *Person;
extern Type const person;
#endif
What you see in comments it is the interface for the type, how to dialog with objects of this type. If we don't give this info the user of the type will not know which commands an object of the type will understand. header files show public information, while .c source files will contain private info. The
extern Type const person;
line of code means that exist an object, which is a type, named person
and defined or declared elsewhere. Remember that this header file will be used in other .c source files to instantiate person
type objects (as you will see in a moment in the test file). Now I show you the person.c source file, where we properly define the type, its functions, etc.
person.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Person.h"
typedef struct this{
struct person public;
struct{
char *thoughts;
} private;
} *This;
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
static err setName(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.name= va_arg(vAL, char *);
return err0;
}
static err getName(Obj *obj, va_list vAL){
This this= (This)obj;
char **name= va_arg(vAL, char **);
*name= this->public.name;
return err0;
}
static err getThoughts(Obj *obj, va_list vAL){
This this= (This)obj;
char **thoughts= va_arg(vAL, char **);
*thoughts= this->private.thoughts;
return err0;
}
static err setThoughts(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.thoughts= va_arg(vAL, char *);
return err0;
}
TYPE(person, this, F(getThoughts) F(setThoughts) F(setAge) F(getAge) F(setName) F(getName) F(NULL), CH(NULL), NULL)
Let's analyse it by parts. First the include's section.
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Person.h"
It's always the same. The first two belongs to the C standard library and are needed because we make use of NULL
and also because we use variable argument list characteristic in function definitions. The third is our implementation and it is needed because I make use of Obj
identifier, for example. Then it comes the header for the type we are going to define the source file, in this case person
the type and Person.h the header we have already seen above. Then it will come other headers for other types we could make use in this .c source file (this can be a type used inside a function definition to do something or a type used as a child for the type being defined, to state inheritance). In this case we neither use any other types neither we don't inherit from any other type (this type has no child types). So we end with the headers.
Now I am going to analize next section of code
typedef struct this{
struct person public;
struct{
char *thoughts;
} private;
} *This;
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
The first part it is for the data. As we have seen in the header (previous file) we had there this
typedef struct person{
Obj obj;
int age;
char *name;
} *Person;
This means we have defined two public data info, this means data that will be accessible directly through objects (not using "get" or "set" functions but using pointer dereferencing as you will see). The first always will be present and means we are dealing with an object. So at a bare minimum we will always have in any header this
typedef struct any{
Obj obj;
} *Any;
Now, in the .c source file we declare the private data. We really declare all the object data structure that will be used in the functions defined later and also by the last call to the macro TYPE
at the end of the file. We do always like this
typedef struct this{
struct any public;
struct{
} private;
} *This;
Later on, in function definitions, you will see how we use this. So in this .c source file we have defined a private variable for this type, which is thoughts
and happens to be a string or pointer to char
(usually what a person thinks it is private by nature).
Next it is first function definition we are going to look at. All function definitions are the same. Here it is a setter.
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
function definitions always have the same signature. Always return an error code (an integer) and always accepts two params, the first being an object (with no specific type stated, this is because f
, remember, doesn't care about types, only about being an object, and function references are called by f
) and a variable argument list (va_list
, defined in the standard library). Also we use keyword static
to make this identifier (setAge
in this case) local to this .c source file, meaning it will be free to be used also in any other .c source file for other type or class definitions.
Always first line inside a function definition will be a cast of the object received as a parameter to the type being defined. We declare a pointer named this
which then will be used in the rest of the code of the function definition to access all private and public info or data of the object of the type. Next always come getting all arguments from the variable argument list received. In this case we get an int
, that we store in age
public var of the object. When we are done from getting all arguments from variable argument list we deal with the code or operation of the function being defined. In this case we are done. Finally we always return an error code, in this case err0
meaning no errors or success.
Now let's look to the getter
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
As you see the signature it is the same. We have the same elements I told you above we always deal with in any function definition inside of a type .c source file in OOCX implementation. In this case the argument we get from vAL
it is a pointer to int
because this argument will serve as an output variable, as you will see in next .c source file, the one where we will define main
function and that will serve as a test for the type. We access the public var of the object age
and store its value into the var pointed by the pointer variable. Then return.
Rest are setters and getters for rest of vars of the type (none static data). Finally we have this (always present at the end of a type or class .c source file definition)
TYPE(person, this, F(getThoughts) F(setThoughts) F(setAge) F(getAge) F(setName) F(getName) F(NULL), CH(NULL), NULL)
TYPE
it is the name of the macro defined in OOCX (the unique one). You can see it is a macro because it doesn't end with a ;
the line of code. A macro in C is a piece of source code that will be replaced when its identifier it is found. In this case it accepts arguments or parameters. The first argument will always the name of the type (name of the object, so it starts with lower case). Then it will follows the tag used in this file for the complete structure definition of the data (private and public). In this case is this
tag (look at the beginning of the file were we do struct this
). This is used by the code replaced in the macro to compute the size of objects of this type (class), which is used at the end by the instantion function. Next is a not comma separated list always ended with F(NULL)
. The F
stands for function. This list it is used to load all functionality we desire into the type being defined. We used local identifiers defined in this .c source file to load function references to the type. Also, the code replaced by the macro automatically generates a list of commands, which are identifiers stored as strings, and store it in the type too, so when we ask for a command to an object through f
it can make a call to the function ref based on the command. If we don't want to load any function in this part of the macro call, we at least must put F(NULL)
as an argument. Then it comes a similar list for the childs or inheritance. In this case we don't inherit, so we put only CH(NULL)
. Last parameter it is meant for static vars defined on the type, meaning data vars shared by all objects. In this case we put NULL
.
Finally we must deal with the test file to test the type (class) just defined. Test means use and check functionality, bugs, results, etc. So I present you first the test .c source file
personT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "Person.h"
err main(){
typeInit();
f((Obj *)type, "init", person);
Person p1= NULL;
f((Obj *)person, "new", &p1, NULL);
p1->age= 30;
p1->name= "roggc";
f((Obj *)p1, "setThoughts", "i want a big hamburguer");
int age;
char *name, *thoughts;
f((Obj *)p1, "getAge", &age);
f((Obj *)p1, "getName", &name);
f((Obj *)p1, "getThoughts", &thoughts);
printf("my name is %s and i am %d and now i am thinking: '%s'", name, age, thoughts);
return err0;
}
First thing to note it is that in OOCX we always dialog with objects (remember types are objects too, a special kind). Types need to be initialized before we can tell things to them or give commands to them. So first thing we do it is initialize type type
(with the use of defined global function typeInit()
. this an f()
are the only really used (called) functions in this implementation). Then we initialize type person
by giving order "init"
to type
type (class) and passing as argument of that command type person
. Once done that we get an instance of person
type (class). We do that by giving order "new"
to person
type (which is an object remember, of type
type, so f
will look for the command in type
and find it there and execute it. So all types, which are instances of type
, will understand this command). We have first declared a Person
var to handle the instance we are going to obtain. Then you see how public vars can be directly accessed from the handler of the object but private vars must be setted and getted with setters and getters.
So this works. Next types are meant to show you (prove) inheritance and multiple inheritance.
Employee.h
#ifndef eMPLOYEE_H
#define eMPLOYEE_H
#include "oocx/oocx.h"
typedef struct employee{
Obj obj;
} *Employee;
extern Type const employee;
#endif
employee.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Employee.h"
#include "Person.h"
typedef struct this{
struct employee public;
struct{
double salary;
} private;
} *This;
static err setSalary(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.salary=va_arg(vAL,double);
return err0;
}
static err getSalary(Obj *obj, va_list vAL){
This this= (This)obj;
double *salary= va_arg(vAL,double *);
*salary= this->private.salary;
return err0;
}
TYPE(employee, this, F(setSalary) F(getSalary) F(NULL), CH(person) CH(NULL), NULL)
employeeT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "Employee.h"
err main(){
typeInit();
f((Obj *)type, "init", employee);
Employee emp1= NULL;
f((Obj *)employee, "new", &emp1, NULL);
f((Obj *)emp1, "setName", "rober");
f((Obj *)emp1, "setAge", 31);
f((Obj *)emp1, "setThoughts", "i have a lot of work to do...");
f((Obj *)emp1, "setSalary", 30000.);
char *name, *thoughts;
int age;
double salary;
f((Obj *)emp1, "getName", &name);
f((Obj *)emp1, "getAge", &age);
f((Obj *)emp1, "getSalary", &salary);
f((Obj *)emp1, "getThoughts", &thoughts);
printf("my name is %s and i am %d and i earn %.2f and now i am thinking: '%s'", name, age, salary, thoughts);
}
So conclusion it is that inheritance works in this implementation because we have defined a type that inherits all data and function capabilities of another type without having to do any hand code typing at all, only specify from which type we inherit.
To show you (prove) multiple inheritance I will develop a type spyder
and then make a type spyderEmployee
which will multiple inherit from spyder
and employee
Spyder.h
#ifndef sPYDER_H
#define sPYDER_H
#include "oocx/oocx.h"
typedef struct spyder{
Obj obj;
} *Spyder;
extern Type const spyder;
#endif
spyder.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Spyder.h"
typedef struct this{
struct spyder public;
struct{
double poisonLevel;
} private;
} *This;
static err getPoisonLevel(Obj *obj, va_list vAL){
This this= (This)obj;
double *poisonLevel= va_arg(vAL, double *);
*poisonLevel= this->private.poisonLevel;
return err0;
}
static err setPoisonLevel(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.poisonLevel= va_arg(vAL, double);
return err0;
}
TYPE(spyder, this, F(getPoisonLevel) F(setPoisonLevel) F(NULL), CH(NULL), NULL)
SpyderEmployee.h
#ifndef sPYDEReMPLOYEE_H
#define sPYDEReMPLOYEE_H
#include "oocx/oocx.h"
typedef struct spyderEmployee{
Obj obj;
} *SpyderEmployee;
extern Type const spyderEmployee;
#endif
spyderEmployee.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "SpyderEmployee.h"
#include "Spyder.h"
#include "Employee.h"
typedef struct this{
struct spyderEmployee public;
struct{
char *superHeroName;
} private;
} *This;
static err setSuperHeroName(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.superHeroName= va_arg(vAL, char *);
return err0;
}
static err getSuperHeroName(Obj *obj, va_list vAL){
This this= (This)obj;
char **superHeroName= va_arg(vAL, char **);
*superHeroName= this->private.superHeroName;
return err0;
}
TYPE(spyderEmployee, this, F(setSuperHeroName) F(getSuperHeroName) F(NULL), CH(spyder) CH(employee) CH(NULL), NULL)
spyderEmployeeT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "SpyderEmployee.h"
err main(){
typeInit();
f((Obj *)type, "init", spyderEmployee);
SpyderEmployee spEmp1= NULL;
f((Obj *)spyderEmployee, "new", &spEmp1, NULL);
f((Obj *)spEmp1, "setAge", 24);
f((Obj *)spEmp1, "setName", "raul");
f((Obj *)spEmp1, "setThoughts", "I fill great!!!");
f((Obj *)spEmp1, "setSalary", 24000.);
f((Obj *)spEmp1, "setPoisonLevel", 56.);
f((Obj *)spEmp1, "setSuperHeroName", "the terrible employee");
char *name, *thoughts, *superHeroName;
int age;
double salary, poisonLevel;
f((Obj *)spEmp1, "getName", &name);
f((Obj *)spEmp1, "getAge", &age);
f((Obj *)spEmp1, "getThoughts", &thoughts);
f((Obj *)spEmp1, "getSalary", &salary);
f((Obj *)spEmp1, "getPoisonLevel", &poisonLevel);
f((Obj *)spEmp1, "getSuperHeroName", &superHeroName);
printf("i am %s and i am %d. my poison level is %.2f and my salary is %.2f. now i am thinking: '%s' and I am known as '%s'.\n", name, age, poisonLevel, salary, thoughts, superHeroName);
return err0;
}
So here you have, multiple inheritance proven.