Kaja is writing a series of articles on the shortcomings and solutions to the current object paradigm. Very interesting.
Tag: oop
‘Objects’ in C – Part 3
In Part 3 of this mini serie I`ll discuss how interfaces / (multi)inheritance like behaviour can be achieved in a language like C as in C++.
In the posts before (part 1, part 2) I discussed the possibility to inherit behaviour of an ‘object’ in C by adding a type pointer which handles initialization and destruction of the object; achieving the possibility to extend an ‘object’ by adding fields at the end and updating the type pointer to a new type.
Any object contains a pointer at the start to its type. The type of an object is passed to the CreateInstance
function when an instance needs to be created of the object. The type itself just contains a few fields:
- Length, the required length of the object, this allows an inheritance to extend the amount of data.
- Constructor Function Pointer, the Constructor Function Pointer is called after the object is allocated to construct its contents.
- Destructor Function Pointer, the Destructor Function Pointer is called before the object is freed to destruct its contents.
These fields, along with using function pointers in the instance itself instead of functions, allow an inherited type to expand the existing type. The problem is that an object can’t inherit from more than one object, for both object’s expect to start just after the pointer to the type. To solve this an additional field needs to be added:
- QueryInterface Function Pointer, the QueryInterface Function Pointer can be used to get an instance of a supported type from the instance called.
- Top Object Pointer, the Top pointer points to the top object. (An object returned from QueryInterface its Top pointer would point to the object where QueryInterface was called)
nb. The latter one will be added after the type pointer in the instance instead of in the type
A good example would be a type called ICloneable
which itself only contains one field: Clone, a functionpointer to a function to clone the object. When you would want to clone an object given you would use:
ICloneable* interface = toClone->top->type->QueryInterface(toClone->top->type, toClone->top, ICloneableType);
Object* clone = interface->Clone(interface);
Lets take a look to the first line:
ICloneable* interface = toClone->top->type->QueryInterface(toClone->top->type, toClone->top, ICloneableType);
This line queries the interface that the object that will be cloned wants you to use to clone it. The top object is used for that is the only way to be certain that you are dealing with the top object and the latest code instead of some base. Using this code an object can be cloned by just providing its interface!
(the QueryInterface takes 3 parameters: first one is the type itself where the function pointer relies, second is the object in question, third is the requested type where ICloneableType is an instance of the ICloneable type).
Object* clone = interface->Clone(interface);
The second line just calls the functionpointer in the interface queried.
To ensure that every bit of behaviour can be overriden, the object queried using ->top->type->QueryInterface
on the provided object must be used instead of the provided object itself for the provided object can be a whole different object merely supporting that interface secondarily.
The nice thing about this architecture is that a top object merely wraps its implemented objects and returns them (possibly addapted) when queried for it. This allows way more access to the inherited types than in any other object orientated model.
This model is quite easy to implement in C and (I hope) will be easy to intergrate with for instance objects in a Virtual Machine.
Intrepid C – ‘Objects’ in C
I’m working on a library which can be used to achieve most OOP functionality of OO languages in C, by providing an API (not by extending the syntax).
I’ve written 2 articles (artc. 1, artc. 2) so far about getting OO like behaviour in C, and will write a few more about the more advanced stuff which I’m still experimenting with.
You can download the source of the API I have developed so far here, which by the way is far from finished. Stuff will change, and I am aware of some issues.
I’ll explain how it works in the next parts in the ‘Objects in C’ serie, but to give you a kickstart in the code I’ll explain it briefly:
Every object has got a pointer to its type and its toplevel object, which usualy is itself. The type instance pointed to from the instance provides the size, constructor, destructor and the interfacegetter.
The GetInterface
function in the type (the interfacegetter) returns a pointer to an instance of an object of the given type if supported by the object.
Using interfaces can serve different purposes:
Exposing certain objectwide functionality
((ICloneable*)object->top->type->GetInterface(object->top->type, object->top, GetICloneableType()))->Clone(object->top);
This code will return a Clone of the object
, even if only the object is passed by a not top level pointer. Using the top
pointer ensures proper polyforism, and enables the ability for the top most class to hide, or show some internal functionality of wrapped objects.
Exposing interface specific functionality
((String*)object->type->GetInterface(object->type, object, GetIToStringAble()))->ToString(object);
It now depends on which interface of a certain object is passed which behaviour results.
And more.. and more.. which I will explain in the coming articles.
Some more on my blog
I’m keeping this blog mainly to channel some thoughts about computer science (but probably about physics and other subjects which I find interesting too). I hope people will find the stuff I’ll write about interesting, and hopefully usefull. Most stuff I’ll post will as far as I know be quite new, or a new approach to an existing issue.
It seems that so far 3 people have linked to an article on this blog (all of them to the Objects in C article) already. (Thanks! without readers a webblog is quite useless!)
‘Objects’ in C – part 2, ‘type’ing stuff
In my previous article I’ve talked about the basics of using ‘object like’ programming in C. Actually they rather were more like examples. To continue further I’ll discuss the stuff behind it more detailed, but first I’ll explain the basics of structs, if you know them already, just skip that part of this part.
Structs in C (and in the memory)
An example structure in C:
struct example{int a; int b;};
A structure is nothing more than a way of giving some parts in a fixed size amount of memory a specific name, and a recommended type.
When one would create a new instance of the example structure it would require 8 bytes of space in the memory, 4 for a
, the first integer and again 4 bytes for b
, the second integer:
00 a
01 a
02 a
03 a
04 a
05 b
06 b
07 b
08 b
An instance of the example struct could begin at the memory address0x0A001000
. In that case the value of a
would be at the memory adress 0x0A001000
too, for a
is the first field in the structure. The value of b
however would be located at 0x0A001004
for b
is the second field and is located 4 bytes after the start of the structure. I could use that knowladge to access b
in several different ways:
example* e = malloc(sizeof(example)); e->a = 1; e->b = 10;
printf("%d", e->b); // The usual way
printf("%d", *((int*)((int)e + 4)); // Converts e to an integer, adds 4 and then interprets the result as a pointer to an integer
printf("%d", *((int*)((int)(&(e->a)) + 4)); // Basicly the same way, but now via 'a' which is at the offset of the struct anyway
Structure inheritance
typedef struct { int a; int b; int result; } SumHandle; void GetResult(SumHandle* handle){ handle->result = handle->a + handle->b; }
The example SumHandle
struct is a very simple (and useless) structure to store 2 operands, and the result which is filled by calling the GetResult
function providing a pointer to the handle.
This works fine, but we could want to add more functionality, for instance why not want to know the result of multiplying those 2 operands? We could make a new struct defenition and a new GetResult
function which still uses the old GetResult
:
typedef struct { int a; int b; int sumResult; int multResult; } SumHandle2; void GetResult2(SumHandle2* handle){ handle->multResult = handle->a * handle->b; GetResult((SumHandle*)handle); }
As you can see changing the name of the originaly named result
field hasn’t got any effect for only the offset from the offset from the struct offset is used at runtime, therefore I could add the extra field and still use the original GetResult
function. This can be done for the original GetResult
function only uses the first 12 bytes of the pointed to piece of memory. Adding stuff after that does not effect the original function. Note that the oposite is not possible for changing memory outside your own allocated memory could do really nasty stuff causing the all-too-known GPF‘s.
Virtual functions
In the previous example only an extension has been made to the original behaviour. To overwrite the previous behaviour you need to use virtual functions to overwrite the previous call, the original struct would look like:
typedef void (*GetResultHandler)(void* result); typedef struct { int a; int b; int result; GetResultHandler GetResult; } SumHandle; void GetResultImpl(void* handle){ ((SumHandle*)handle)->result = ((SumHandle*)handle)->a + ((SumHandle*)handle)->b; }
The GetResult
field is a function pointer which would be set in an initialization function of SumHandle
to the GetResultImpl
method.
Overwriting the behaviour would be as simple as writing a new initialization function used which would use another GetResult
implementation. This for calling resultHandleInstance->GetResult(resultHandleInstance)
is nothing more than calling the function at the address specified in the GetResult
field.
A type
Every inheritance at the moment still needs a custom constructor and destructor function. Therefore some functionality could not be ashieved like making a copy of a handle for that would need both the constructor function and the actual size of the struct, which both aren’t available when only a pointer is passed. Possibly a custom clone function would need to be provided.
If every object would expose a pointer to a type struct containing information about the struct this problem would be eliminated:
#include <malloc.h> #include <stdio.h> // Default defenitions typedef struct _Type Type; typedef struct _Object Object; typedef void (*ConstructorHandler) (Type* type, Object* instance); typedef void (*DestructorHandler) (Type* type, Object* instance); struct _Type { int size; ConstructorHandler Construct; DestructorHandler Destruct; }; struct _Object { Type* type; }; Object* Construct(Type* type) { Object* object = malloc(type->size); object->type = type; type->Construct(type, object); return object; } void Destruct(Object* object) { object->type->Destruct(object->type, object); free(object); } Type* CreateType(int size, ConstructorHandler constructor, DestructorHandler destructor) { Type* type = malloc(sizeof(Type)); type->size = size; type->Construct = constructor; type->Destruct = destructor; return type; } // An example implementation typedef struct _Example Example; typedef void (*GenerateResultHandler)(Example* example); struct _Example { Type* type; int a; int b; int result; GenerateResultHandler GenerateResult; }; void GenerateResultImpl(Example* example) { example->result = example->a + example->b; } void ConstructExample (Type* type, Object* instance) { ((Example*)instance)->a = 0; ((Example*)instance)->b = 0; ((Example*)instance)->result = 0; ((Example*)instance)->GenerateResult = GenerateResultImpl; } void DestructExample (Type* type, Object* instance) { } Type* CreateExampleType() { return CreateType(sizeof(Example), ConstructExample, DestructExample); } void main() { Type* exampleType = CreateExampleType(); Example* example = (Example*)Construct(exampleType); example->a = 5; example->b = 5; example->GenerateResult(example); printf("%d", example->result); Destruct((void*)example); getchar(); }
To add an inheritance:
#include <malloc.h> #include <stdio.h> // Default defenitions typedef struct _Type Type; typedef struct _Object Object; typedef void (*ConstructorHandler) (Type* type, Object* instance); typedef void (*DestructorHandler) (Type* type, Object* instance); struct _Type { int size; ConstructorHandler Construct; DestructorHandler Destruct; }; struct _Object { Type* type; }; Object* Construct(Type* type) { Object* object = malloc(type->size); object->type = type; type->Construct(type, object); return object; } void Destruct(Object* object) { object->type->Destruct(object->type, object); free(object); } Type* CreateType(int size, ConstructorHandler constructor, DestructorHandler destructor) { Type* type = malloc(sizeof(Type)); type->size = size; type->Construct = constructor; type->Destruct = destructor; return type; } // An example implementation typedef struct _Example Example; typedef void (*GenerateResultHandler)(Example* example); struct _Example { Type* type; int a; int b; int result; GenerateResultHandler GenerateResult; }; void GenerateResultImpl(Example* example) { example->result = example->a + example->b; } void ConstructExample (Type* type, Object* instance) { ((Example*)instance)->a = 0; ((Example*)instance)->b = 0; ((Example*)instance)->result = 0; ((Example*)instance)->GenerateResult = GenerateResultImpl; } void DestructExample (Type* type, Object* instance) { } Type* CreateExampleType() { return CreateType(sizeof(Example), ConstructExample, DestructExample); } // An inheritance typedef struct _Example2 Example2; typedef void (*GenerateResult2Handler)(Example2* example); struct _Example2 { Type* type; int a; int b; int result; GenerateResult2Handler GenerateResult; int result2; }; void GenerateResult2Impl(Example2* example) { example->result = example->a * example->b; example->result2 = example->a + example->b; } void ConstructExample2 (Type* type, Object* instance) { ((Example2*)instance)->a = 0; ((Example2*)instance)->b = 0; ((Example2*)instance)->result = 0; ((Example2*)instance)->result2 = 0; ((Example2*)instance)->GenerateResult = GenerateResult2Impl; } void DestructExample2 (Type* type, Object* instance) { } Type* CreateExampleType2() { return CreateType(sizeof(Example2), ConstructExample2, DestructExample2); } void main() { Type* exampleType = CreateExampleType(); Type* example2Type = CreateExampleType2(); Example* example = (Example*)Construct(exampleType); Example2* example2 = (Example2*)Construct(example2Type); example->a = 5; example->b = 5; example2->a = 5; example2->b = 5; example->GenerateResult(example); example2->GenerateResult(example2); printf("example: result: %dn", example->result); printf("example2: result: %d result2: %dn", example2->result, example2->result2); Destruct((void*)example); Destruct((void*)example2); getchar(); }
Limitations
Although using types, and casts creates a lot more freedom, it still has its limitations:
- No multi inheritance possible
- No ‘new’ functions (functions that are only used when a pointer is specificly cast to a struct)
- No static support throughout types
- No type behaviour inheritance (a type still has its own basetype and isn’t an object itself)
I’ll explain some stuff about wrapping old implementations, or even multiple implementations in the next part, which will overcome these limitations.
‘Objects’ in C – part 1, the basics
C is on memory allocation quite faster than C++ for C++ contains a lot of overhead due to its object orientated nature.
Although not as convenient in C it is possible to get ‘Object’ like stuff in C as in C++.
I’ll explain the basics in this first part, and continue with more complicated (and neater stuff) in the later parts. I hope you’ll find them usefull.
The base
For this example we’ll use a ‘class’ called ‘example’, first the base:
typedef struct{ int dummy; } Example; Example* ConstructExample(){ return malloc(sizeof(Example)); } void DestructExample(Example* example){ free(example); } void main(){ Example* e; e = ConstructExample(); DestructExample(e); }
Simple inheritance
Lets add some values, and some simple functions and inheritance. Using the inherited class as the base class is just a simple matter of casting via void*
.
typedef struct { int a; } Example; typedef struct { int a; int b; } Example2; Example2* ConstructExample2() { Example2* example = malloc(sizeof(Example2)); example->b = 10; example->a = 11; return example; } Example* ConstructExample() { Example* example = malloc(sizeof(Example)); example->a = 1; return example; } void DestructExample(Example* example) { free(example); } void DestructExample2(Example2* example) { free(example); } void PrintExample(Example* example) { printf("%d ", example->a); } void PrintExample2(Example2* example) { printf("%d ", example->b); } void main() { Example* e; Example2* e2; e = ConstructExample(); e2 = ConstructExample2(); PrintExample((void*)e); PrintExample((void*)e2); PrintExample2((void*)e2); DestructExample(e); DestructExample2(e2); }
When inheriting you have to copy the original defenition and only append at the bottom, changing nothing of the previous stuff for otherwise you’ll get nasty errors. When you want to override stuff you got to use neat tricks, more on that lateron.
Virtual functions
Using function pointers, virtual functions can be used:
typedef void (*PrintExample)(void* example); typedef struct { int a; PrintExample Print; } Example; typedef struct { int a; PrintExample Print; int b; } Example2; void PrintExampleImpl(void* example) { printf("(printexample) %d", ((Example*)example)->a); } void PrintExample2Impl(void* example) { printf("(printexample2) %d ", ((Example2*)example)->b); PrintExampleImpl(example); } Example2* ConstructExample2() { Example2* example = malloc(sizeof(Example2)); example->b = 10; example->a = 11; example->Print = PrintExample2Impl; return example; } Example* ConstructExample() { Example* example = malloc(sizeof(Example)); example->a = 1; example->Print = PrintExampleImpl; return example; } void DestructExample(Example* example) { free(example); } void DestructExample2(Example2* example) { free(example); } void main() { Example* e; Example2* e2; e = ConstructExample(); e2 = ConstructExample2(); e->Print(e); e2->Print(e2); DestructExample(e); DestructExample2(e2); getchar(); }
A virtual function still has to be supplied with the function in which it has been called. 2 simple macro can be made to make a this call a bit easier, (for some :p):
#define THISCALLPAR(x,y,z) x->y(x,z)
#define THISCALL(x,y) x->y(x)
Wrapped inheritance
To override some functionality and retain other functionality while adding your own functionality is virtually impossible by using one simple object. Multi-inheritance would be virtually impossible.
2 parts ahead I will talk about these limitations and how to overcome them, the next part discusses inheritance in more detail, and adding ‘Types’ in the mix.