Welcome Guest ( Log In | Register )

[ Big| Medium| Small] -



Post new topic Reply to topic  [ 7 posts ] 
    Xilef
  Wed May 08, 2013 11:53 am
User avatar
Staff

Big Dumb Guy

Location: UK
Object Orientated C
This is an odd one.

Reason for doing this
The main requirement of what I am doing at my job is that I keep the project multi-platform with minimal effort for integration, the reason is that we've been "porting" apps from iOS to Android by simply rewriting with Android in mind, this has the benefit of good usage of the Android tools and practises (We can thus make it perform better on Android or add in Android specific features).

The project I'm working on already uses an API that both iOS and Android share, so there's no need for platform specific benefits, I just need to keep it multiplatform.

Objective C, iOS's recommended programming language, is described as a "strict superset of C", meaning that it can run C code perfectly with a simple copy-paste or through using the #include preprocessor (This essentially copy-pastes in the code at compile-time).
Android's SDK is all Java but provides the NDK, a way of hooking on libraries native to the device (In my case, ARM processors), so I can compile C/C++ code into a library that Android's Java side connects with.

Here's the problem, the nature of this project is VERY object-orientated, however C is NOT an object-orientated programming language.
I cannot use C++ as the steps for linking C++ into iOS is more complicated than simple, light C #includes, not to mention that C++ is a bit of a mess to program with (Sorry C++, you're my favourite language, but it's true). At least C++'s syntax isn't all crazy like in objective-C :smile:.

So part of the project is re-creating C++ in C (C++ started off as a preprocessor to produce OOP C code, these days C++ is rather different to C, it is not a strict superset at all).

The technique I use is exploiting the C structures to act as my classes.

Why NOT to do this - Read this if you're considering C!
I do NOT recommend doing this at all unless you absolutely need to use C, for example the Sony PSP (And I think the DS) only has a C compiler so programming in C is pretty much the only option.
Why pretend to use C++ when you can actually use C++? There is no benefit beyond the rare circumstances such as my one.

Extra benefits
Before I even started this we did some performance tests in the office against iOS's Objective-C. Calling one of these C classes' functions versus calling an Objective-C method we've seen as much as twice the performance speed in nanoseconds (Tested using mach timer), this was not scientifically tested and will certainly not be true in some circumstances, so don't start throwing away Objective-C code just because I wrote this! There is no reason to write an application you're not planning to port in C just for performance benefits.
The reason for this speed-up is most likely due to Objective C's messaging system and garbage collector (Yes it does have a garbage collector, I've had battles against this thing for it's "smart" memory resizing during one of it's clean-up duties. iOS devices do not feature the garbage collector, so it's only a problem for desktop applications and the iOS simulator, which is essentially a desktop application anyway).

For legal reasons I cannot include any code I've written at work, but I'll show the methods behind adding OOP into C as it's already documented online.

Topic 1: Basic Classes
In C++ a class looks like this:
Expand to see the code.

Using C structures, we can somewhat emulate this:
Expand to see the code.

Things to note:
  • The C function pointer, if it's a self-modifying function, must include a pointer to it's calling instance, this is the big drawback that exposes how hacky this really is!
  • The argument names in the C structure's function pointer are optional.
  • We need to define the "new" keyword for the C structure, this is unique to every class type so we call it NewMyClass (In C++ the call would be "new xiMyClass", so I name the C function to mimic this ordering).

The implementation of this particular class in C++ can be something like this:
Expand to see the code.

However, in C we need to manually build the structure with our NewMyClass function, in C++ the new keyword does this for us so we need to define it ourselves.
Expand to see the code.

Things to note:
  • The NewMyClass is used to assign the function pointers, this is critical for this to work.
  • NewMyClass() can also be written as a constructor, so some parameters can be thrown in to create the instance and even some function calls used after the pointers are assigned.
  • Not only does the "new" keyword need to be defined in C, the "delete" can also be defined if simply using free() on the instance is not enough. Using DeleteMyClass() as a custom deconstructor means you can clean your memory and perform additional operations.

To use this class, C++ we have
Expand to see the code.

And in C
Expand to see the code.


Additional Info
You'll probably begin to see how C++ works when it is translated into C.
I have actually ported some C++ libraries to C using this method. Some features of C++ are harder to mimic than others, particularly template types, C has the benefit of the void pointer which can somewhat act as template types, but there are limits.

Here's the big lesson, the more you program in C the more you see how messy C++ is. C++ is my favourite language and it's my weapon of choice for any kind of programming challenge, however after writing in C for so long at work I can definitely say that C++ is quite broken in some aspects, particularly STL and argument overrides, personally I prefer FunctionWithInt( int num ), FunctionWithFloat( float num ) to Function( int num ), Function( float num ), how do you know which one will be used and what if the compiler chooses to cast the int to a float or vice-versa? Operator overloading is another complaint of mine, while I think the feature is great, it's been very badly abused, especially with<<the<<stl<<string<<stream!

Next Topic: Class Inheritance in C


Last edited by Xilef on Sat Sep 28, 2013 10:54 am, edited 2 times in total.

Top Top
Profile      
 

    Toams
  Wed May 08, 2013 3:30 pm
THAT foreigner
User avatar
Sponsor


Location: Netherlands
Nice.
Even though I don't have a particular use for it.
I didn't even read it all, because I don't know either language.
I still want to learn C++, but I'm doing weblanguages first.

Still nice.

_________________
?????


Top Top
Profile      
 

    Xilef
  Thu May 09, 2013 2:18 pm
User avatar
Staff

Big Dumb Guy

Location: UK
If you want to learn C/C++ I can write some lessons up for everyone, I suggest you learn bad C++ before moving onto real C++ and then C, bad C++ practices will teach you C in a simpler environment while preparing you for C++ without scaring you with classes and inheritance.

Class Inheritance in C

The rest of this post is very hacky and somewhat ridiculous to read, but it is crucial for an object-orientated C approach.

In C++ inheritance is pretty simple:
Expand to see the code.


So now an instance of xiSquare can access colours as such:
Expand to see the code.


The C version of this is a bit crazy.
Expand to see the code.


The implementation of NewSquare() assigns the base variable to the inheriting class.

The usage would look something like this:
Expand to see the code.

I know this looks very cheaty, but it's practically what C++ is doing underneath.

Things to note:
  • Any multi-inheritance would simply be an array of pointers to the inheriting classes.

Accessing Through Classes

If I had a list of xiShapes that all have the base class of xiSquare, in both C-with-classes and C++ I need to cast to my target class if I want to grab all the squares.

The scenario is, we have a list of shapes, we don't need to know if they are squares, circles or triangles, we just want to run through this list and get the size of any squares in the list.

C++
Expand to see the code.

C Equivalent
Expand to see the code.

The big difference in this is that we grab the square's class through it's base class with the inherit.
inherit is a void pointer ( void * ), a pointer to any kind of data, so inside the class xiShape_t is the member void * inherit, which is set to 0 in the NewShape() constructor.

The key piece of this is during the NewSquare() constructor, inside that we have parsed the parent class as a pointer which we assign base. We can actually access this class during the construction, so we can use this line inside the construction:
new->base->inherit = ( void * )new;

This is where mutation can be done, not only can we assign a variable in the base class that points back to the derived class, we can also override some of the base class's functions:
new->base->Update = &SquareUpdate(); /* Square update is implemented within the Square class' C file */

That last part is something that I use at work, without that ability the entire project would not be able to move forward, we can set up an update loop that runs code of any class without having to cast between classes:
shape->Update( shape ); /* Because we changed this pointer to SquareUpdate(), SquareUpdate() is in stead called */

And to get a handle on the square itself within SquareUpdate() we can now do this:
Expand to see the code.

Things to note:
  • Inheritance is a lot easier in C++ compared to the C-with-classes method of inheritance, see my reasons for not doing this in the first post.

Next Topic: Fake member visibility in C


Top Top
Profile      
 

    cheetahxing
  Thu May 09, 2013 5:10 pm
User avatar
Sponsor

Nice write up. Like you said, writing in lower level languages gives you a ton of control and often highlights shortcomings of using a more abstract language. The stuff C++ gives you out of the box is very nice, but if you want more control and (perhaps) better performance, recreating it in C might be the way to go. I think this is why Jonathan Blow advocates building your own engine rather than using something like flash punk, or unity. Still, IMO, the "best" approach is the one that results in actually making a game which varies wildly.

_________________
Of course we could make things more challenging, Lisa, but then the stupider students would be in here complaining, furrowing their brows in a vain attempt to understand the situation.


Top Top
Profile      
 

    Xilef
  Thu May 09, 2013 7:23 pm
User avatar
Staff

Big Dumb Guy

Location: UK
Making your own engine is always going to be better than using an out of the box engine if you can program, but I'd never recommend programming a game in C when C++ fits the job more.

The control gained when writing in straight C is incredible, makes C++ feel like Java, however the only performance gained is where C doesn't use the C++ STL, C++'s standard libs on some systems are much slower than straight C, qsort() comes to mind, but if you're not using STL then the performance is about the same, especially when some compilers actually compile C++ to the equivalent ASM as C with classes.

The one problem with C's STD is that it's just as slow as C++'s equivalent STL on Windows as Microsoft decided to ship C++ code inside their C libraries.

As for games specifically, Allegro game library is C and I highly recommend it for C games.

Edit: every benefit and issue I described is explained in this website http://unthought.net/c++/c_vs_c++.html
Like me, the author champions C++, he even shows how the C++ STL is much slower than equivalent C STD usage, but highlights readability as the problem.

But there's always something pure and good about C programming, even when doing this crazy OOP C code you have glimpses of benefits and good code


Last edited by Xilef on Mon Jun 10, 2013 3:47 pm, edited 2 times in total.

Top Top
Profile      
 

    Xilef
  Wed May 22, 2013 6:18 pm
User avatar
Staff

Big Dumb Guy

Location: UK
Fake member visibility in C

Yet another crazy C tactic for doing something that you should probably use C++ for.

The common method for doing struct member visibility in C is as such:

HEADER
Expand to see the code.

IMPLEMENTATION
Expand to see the code.


The benefit of this one is that all the variables and functions inside the private members are only visible to the implementation file, which is exactly what we wanted.
The draw-back is that the private members are no longer defined along with the header file, we need to swap between header and implementation when defining the class.

I like to keep everything together, I also like to respect the original nature of C, which is to not have private visibility to begin with.
So what I do is essentially the following:
Expand to see the code.

I know it seems silly, but the whole reason I started exploring the idea of putting private variables into C is not because I need to hide stuff, but because things were getting confusing.

At work I have the following function in a C class:
void SetFOV( xiClass_t * const this, const float newFieldOfView );

I also have this variable:
float fov;

During development I got a bit confused and started writing:
myClass->fov = 65.0f;
And I wondered why nothing happened.

The contents of SetFOV looks similar to this (For legal reasons I cannot show what it really looks like but here's the idea):
Expand to see the code.

So when I was using myClass->fov = 65.0f and nothing was happening it was because it was not calling the functions that needed to be called to notify the system that the FOV has changed.
myClass->SetFOV( myClass, 65.0f ) would have called these functions and would have worked fine.

Okay so there's a real problem here, my variables and function names are a bit ambiguous and I was getting confused, one solution would be to just remember the problem, but I knew that when I needed to pass the code-base on or train someone else in using this code I'd have to go through all of these situations and inform them of the problem.

The easier method was to just flag the variables that should not be set by the user as private so then we're encouraged to use the function (Note the word encouraged).

So in the implementation file, in stead of writing:
this->PRIVATE_STUFF_PLEASE_DONT_LOOK_INSIDE_THANKS.x;
I used a macro to make things a bit easier:
Expand to see the code.


Macros are a bit cheaty, but I use them wherever it makes things easier to read and easier to write.

At work I have macros setup so I can do this in the header file:
Expand to see the code.


The PUBLIC() macro expands to empty, so it's there to keep things familiar with C++, the PRIVATE macro expands to generate a unique structure name for the private variables, I do this numerically so it generates struct private_#_s, with the hash being an incrementing number using my preprocessor's __COUNTER__ macro.

In C++ a similar class would be as such:
Expand to see the code.

So you can see that I attempted to keep in line with my C++ experience on this one.

I am currently looking into expanding this so the MACRO produces unique names for the instance of the struct, that way we can't just define the PRIVATE variable to avoid the discouraging variable name where it shouldn't be seen.

So this is how I fake member visibility in C with classes, and it is faked because anyone can simply write out that discouraging name and then get access to the private members, but as I stated in my example above, this is probably the best way to go about it in C without having the split up the class implementation.

Next Topic: Default function arguments in C - (Yes this is possible! More macro hacks ahead...)


Top Top
Profile      
 

    Xilef
  Mon Jun 10, 2013 3:39 pm
User avatar
Staff

Big Dumb Guy

Location: UK
Default function arguments in C

Unlike C++, C does not support default arguments.
Here's a C++ function with a default argument
Expand to see the code.

In C there would be a compile error at the forward declaration about the setting of the number 4, with LLVM on OSX it actually errors with "C does not support default arguments".
Expand to see the code.


So we could turn around and shed a tear, or we could do more crazy macro stuff.

The method I use for pulling this off is default argument structures:
Expand to see the code.


So now we call the function as such:
Expand to see the code.

Quite a bit to add for default params! So now we use the macros:
Expand to see the code.

Macros that take arguments (Some call them function macros) simply insert the arguments into the line, so in the case of #define FUNCTION_MACRO( X, Y ) ( X + Y ) the X + Y will be replaced with the X, Y from the macro arguments

Just like with varargs in C (Function arguments with infinite length, such as the printf() function) macros can also have the (...) arguments, as long as we expand it into __VA_ARGS__.

So what this macro does is it replaces any use of MyFunction() with the version that casts the arguments into the myFunctionArgs_t structure.

Now we can finally call MyFunction() and expect the default value to be 4.

Note: I usually name my macros in all capitals with underscores, for the sake of looking sane the default argument functions retain the camel-case with uppercase first letter convention.

The BIG draw-back!
As usual, there is a problem with this.
We use the inline conditional to check if the argument is there or not, const int variable = args.variable ? args.variable : 4;.
C checks against a certain value for cases where we are checking if things are there or not and in this case it checks against the value zero.
so the problem is essentially:
Expand to see the code.

It works, until we WANT the argument to be zero, then it's just going to throw us back to 4!

So the draw back is the default arguments will always cover the case of zero, a lot of the time your default value is zero so it won't matter, but for cases when it's not zero this will be a paint.

My (terrible) solution was to parse a macro TINY_NUM in the cases where I wanted zero and expect a float value of 0.00000001f, which is not a very friendly method as it means you need to inform everyone using the code of this problem.
The alternative is to probably use pointers as the arguments, so we check if the pointer is 0x00000000, which it shouldn't be if we set it. Using pointers like this doesn't always work and it means that all the arguments must be set to a variable before being parsed. Throwing pointers around like that is not the best thing to do.

Usually I'd give up at such drastic draw-backs. However, there is something that C gains here that C++ DOES NOT HAVE!
A really, REALLY cool feature that I've used so much in my C projects.

In C++ if we have multiple arguments we do this:
Expand to see the code.

And here's the limitation of this:
Expand to see the code.


This C cheaty macro default argument method with structures system (CCMDAMWSS in short) totally beats C++ on this aspect.

Here's the first case:
Expand to see the code.


This also has an amazing method of pointing around the structure as you build it:
Expand to see the code.

Study those calls at the bottom of that last code block. Because C structs can be built with .member = we can have some amazing function calls. This is massively useful at work where my 3D engine may need to create an object with all default value except for the position when position is the last value to be set on the list. This is a huge leap over C++ in my opinion, however the zero draw-back is pretty damaging, so it depends on what you want.

Extra:
This form of default arguments is compatible with C-With-Classes function pointers, handy!

So from all this discussed and shown in the thread you can actually start translating C++ libraries into C. I have already translated C++ STL's vector class into a C version (With pointers used as the containers, you can't win them all).

Again, the question of why bother doing this when C++ exists is a big one, use C++ if you can, it's designed to do this stuff, but when C++ isn't an option and you need this OOP approach in C, then you have this thread as a guide.

If anyone has any questions on how other C++ features can be emulated in C, please write them and I'll have a think about it.
If it's lambda functions you're going to suggest then go away, those are ugly in every language I've seen, they're going to be worse in C (There's an easy method for GCC, but it only exists on GCC).


Top Top
Profile      
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 


Who is online

Users browsing this forum: No users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

We are an independent, not-for-profit game making community.
Homepage
Board Index
About Us
Downloadable Games
Free Browser Games
Games in Development
RPG Maker Support
Game Maker Support
Construct 2 Support
HBGames the eZine
Advanced RPG Maker
Site Announcements
Powered by phpBB © phpBB Group