C to C++

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
djnorthyy
Member
Member
Posts: 49
Joined: Mon Apr 09, 2007 10:50 am
Location: UK, Hants
Contact:

C to C++

Post by djnorthyy »

Hey,

I have started developing my os using a combination of the Kernel C++ tutorial over at Bona Fide and Brans kernel development tutorials. I have completed the first tutorial which has left me with try to convert Brans C code to C++.

I have started out with converting the basic functions he implemented early on into a basic system class. He uses some inline assemble, but i assume there is no problem with this? Is there any problem with my plan :wink: about converting these functions to classes?

Any tips, please let me know. Thank you for all your help!
Harry.
Reflect Desktop Operating System - ' You only remember the name of the OS when it crashes '
Silver Blade
Posts: 8
Joined: Wed Jan 02, 2008 1:19 pm
Location: Oxfordshire, UK

Post by Silver Blade »

It's pretty much what I'm doing, too. :lol: I started out on that same tutorial.

The main things to be aware of when doing a kernel in C++ can be found here:
http://www.osdev.org/wiki/C_PlusPlus

Calling object methods in C++ doesn't make the CPU do anything weirder than if you called a C function with a single parameter. (Why single parameter? Effectively it does pass a parameter to the method, the pointer to the object itself - otherwise it has no way to figure out whose member variables you're referencing! ;) )

I preferred to implement things using namespaces where a single-instance were used (GDT, IDT...) and did toy with a Cursor class, where you could just do a ++ on its object and it'd advance the cursor by one space :) This was used by a Screen class, which was just "ColourScreen" (where I intended to maybe later add MonoScreen as an alternative, though I see little point!)

Inline asm shouldn't pose any problems. As far as I can tell, anyway.
User avatar
djnorthyy
Member
Member
Posts: 49
Joined: Mon Apr 09, 2007 10:50 am
Location: UK, Hants
Contact:

Post by djnorthyy »

Thanks for that!

I am not sure about object methods, though after a bit of research i am sure to find the answer.

What did you go onto after you found Bran's Kernel Development tutorial?

Also, here is how I am implementing my system class:

System.h

Code: Select all

#ifndef SYSTEM_H
#define SYSTEM_H // So we dont get multiple declarations

class System
{
public:
    void *memcpy(void *dest, const void *src, size_t count);
    void *memset(void *dest, char val, size_t count);
    unsigned short *memsetw(unsigned short *dest, unsigned short val, size_t count);
    size_t strlen(const char *str);
    unsigned char inportb (unsigned short _port);
    void outportb (unsigned short _port, unsigned char _data);
};

#endif





System.cpp

Code: Select all


#include "include\System.h"
#include "include\Video.h"

Video vid;

System::System()
{
   vid.Write("System Starting up....");
}

System::~System();
{
   vid.Write("System Shutting down....");
}

// This function is the basic outline of how i would impliment the others

void *memcpy(void *dest, const void *src, size_t count)
{

}



This is the basic outline of how I would impliment the functions that were in Brans' Tutorial. Is this ok? This is a fairly major step in my C++ codeing world so please forgive me if there are some stupid mistakes!

Many Thanks,
Harry.
Reflect Desktop Operating System - ' You only remember the name of the OS when it crashes '
Silver Blade
Posts: 8
Joined: Wed Jan 02, 2008 1:19 pm
Location: Oxfordshire, UK

Post by Silver Blade »

OK a brief explanation of what happens when you call a C function vs. a C++ object method (to my knowledge, anyway - some inaccuracies may be present!)

Say you have a "memset" function (as you do ;) ) - in C, you'd have something like this:

Code: Select all

void* memset(void* dest, char value, unsigned int count);


Which, if I'm not mistaken, typically becomes:

Code: Select all

push count
push value
push dest
call memset
; your memset code here... at the end of memset "ret" is called
mov result, eax


Now, if you want to do this the way you're doing it, you'd first need to create an instance of the System class. The class acts like structural DNA - it describes an object, and you can quite happily create instance after instance of it.

In this case, you'd probably only have one single System, right? I'll pick back up on this in a moment...

When you call the memset within a class, you'd be calling it like this I guess:

Code: Select all

System the_computer;
the_computer.memset(&foo, 0, 1234);


Consider if you had more than one System instance - there needs to be a way to work with each one individually. To save space in memory, your class's methods will usually be shared among instances, and to differentiate between each instance, a "this" pointer is passed to the methods.

So your "memset" effectively has an additional parameter, which would get pushed on the stack prior to the function call taking place. If you were doing this in C, it'd probably look like:

Code: Select all

void* memset(System* this, void* dest, char value, unsigned int count);


This may seem redundant in this example - and it is. If you had more than one System object, however, you'd be able to tell which one the method was called on.

So IMO it's not really worth creating an object for something you're only going to have a single instance of and be using frequently.

My own approach has been to use namespaces, which basically lets you divide up functions into named groups, which seems more appropriate. So you could have System::memset and System::outportb. The beauty of this is that it allows you to have something that *looks* object-oriented, without any of the overhead associated with classes/objects.

Not saying that classes/objects are evil, of course - my own implementation has a few things like a Port class which is declared as inline and implemented in a header file.

Works like this:

Code: Select all

Port master_pic_command(0x20);
Port master_pic_data(0x21);
master_pic_data.writeByte(0xff);

(note: the above port I/O with regard to the PIC may be incorrect as it's just a random example!)

In this case, I have some information associated with the object (a port number) which I'd have to push on the stack for the function call anyway otherwise ;) This is by no means the most optimal way to do it (most optimal way is probably not to use objects again, but since there may be multiple Ports this seemed sensible).

Sorry for rambling there...

And I just realised that you're using the System class to represent the kernel (startup/shutdown) - that'd work, but again I personally don't see the advantage.

When the kernel runs, it'll just ultimately loop repeatedly. Something else is likely to take care of shutdown/restarts. So as far as my own kernel main() goes, I just set the environment up by using the objects and namespaces I've created.

Effectively it's that tutorial but with some fluff added to it ;)

Another thing to beware of - name mangling. Make sure whatever code gets called from external asm (including startup code) is marked extern "C" otherwise the linker will probably complain.

Hope this helps?
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Post by Solar »

djnorthyy wrote:Also, here is how I am implementing my system class:

System.h

Code: Select all

#ifndef SYSTEM_H
#define SYSTEM_H // So we dont get multiple declarations

class System
{
public:
    void *memcpy(void *dest, const void *src, size_t count);
    void *memset(void *dest, char val, size_t count);
    unsigned short *memsetw(unsigned short *dest, unsigned short val, size_t count);
    size_t strlen(const char *str);
    unsigned char inportb (unsigned short _port);
    void outportb (unsigned short _port, unsigned char _data);
};

#endif



Hmmm... why put those functions into a class at all? Do you intend to have state (data members) added to your System class (which I would advise against), or do you intend to have more than one System instance on which to apply those functions?

I smell "because I can" here. Maybe "System.memcpy()" looks "cool", but where is the added value to simply calling memcpy(), or System::memcpy() (introducing a namespace System) if you must?

Coding in C++ does, by no means, mean that everything has to be a class. You simply get more ways to express things, which you should use where required and where meaningful.

No offense intended, but could it be that you don't have much coding experience in C++? If that's the case, don't use it for your kernel (IMHO).
Every good solution is obvious once you've found it.
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

@Solar: I personally wrap my memset/memcpy functions in a Kernel class. Because I like it that way. It's a kernel function - I have no need for global functions, and in fact IIRC many C++ books seem to call for the hangman at the mere mention of the word 'global'.

@Others: Have you considered making those functions static members of the System class? That means that they function identically to global functions defined in a namespace but you define them nicely in your class.

Code: Select all

class System
{
public:
  static int memcpy(u32int *dest, u32int *src);
};


Remember that you can't give a this pointer to a static member function, so you call it thus:

Code: Select all

System::memcpy(dest, src);


James
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

I agree. I'm writing a kernel in C++ and to start with, everything was put inside global classes. It very quickly became apparrent that there was none of this "added value" in a lot of my classes.

If you a) don't need more than one instance of a class and b) don't have private member variables that need to be set in a certain way via functions, then chances are you are looking more for a namespace than a class.

Classes become a lot more useful a bit later on when you are writing process/thread control structures and when using linked-list / binary tree template classes.

Cheers,
Adam
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Post by Solar »

JamesM wrote:It's a kernel function - I have no need for global functions, and in fact IIRC many C++ books seem to call for the hangman at the mere mention of the word 'global'.


Global variables, yes. But if all you want is to limit the scope of functions, namespace is the tool you're looking for. I'd suggest kernel:: instead of System::, both because lowercase is "standard" in C/C++ and because System:: smells of Java.

A class, by definition, defines a data structure and the functions that operate on it. strlen() and inportb() don't operate on common data, and really have nothing in common but a lose categorization as "system functions". More, one is a C standard function (so why not having it in <cstring> where it belongs?), and the other is a non-standard function, so why mash them into a class?

Sorry, but this is violating pretty basic C++ design concepts. It smells of Java, and points to the OP not being "at home" with the language - which, in my opinion, means he's better off not trying to write an OS kernel in it.

PS: A focus on making everything a class, as well as doing (too) many things by operator overloading, is one of the symptoms of a C++ beginner. It's the kind of "because I can"-mentality that sets in after reading a C++ book (which, unfortunately, tend to put much too much focus on classes), and is usually healed after a couple of years of "real" work. C++ offers the whole spectrum from procedural (C-style) over OO (Java-style) to generics (what I would call C++-style). Use what is appropriate, don't get caught in a paradigma or ideology.
Every good solution is obvious once you've found it.
GuiltySpark
Posts: 7
Joined: Sun Dec 16, 2007 9:47 am
Location: The (Other) Counterweight Continent

Post by GuiltySpark »

I suggest reading Bjarne Stroustrup's guidelines on class definition.

The basic guideline is that classes are only really needed when you have an invariance of some kind to maintain. If you only have methods, as Solar said, put them in a namespace. If you only have getters and setters (something almost unavoidable in Java) just use a struct. There's also the pImpl design pattern which is related, but not about methods of a class.

Furthermore, Bjarne Stroustrup (and from my own experience), you have to make a distinction between methods that are intrinsic to the operation of the class (ie maintains an invariant) and methods that act on classes. The example he uses is the Date class. In most cases, class methods for converting a Date class from using UK format to ISO format are superfluous to maintaining the Date's invariances (such an invariance may be ensuring no Date contain a 13th month). Such methods are much better suited for a library which act on Date objects.

The whole "System" object design pattern basically contains a whole lot of methods that do/need not maintain an invariant. All the methods suggested as part of the class are actually those methods that may act on objects rather than methods that are an intrinsic property of that class of objects.

I actually learned the hard way many things I later found out by reading, for example, Bjarne Stroustrup's C++ FAQ.
"Pissing people off since 1986."

"Edible: n, As in a worm to a toad, a toad to a snake, a snake to a pig, a pig to a man, and a man to a worm."
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

Solar wrote:
JamesM wrote:It's a kernel function - I have no need for global functions, and in fact IIRC many C++ books seem to call for the hangman at the mere mention of the word 'global'.


Global variables, yes. But if all you want is to limit the scope of functions, namespace is the tool you're looking for. I'd suggest kernel:: instead of System::, both because lowercase is "standard" in C/C++ and because System:: smells of Java.


The naming convention (using camel-case etc) is completely dependent on programmer taste. My company uses camel-case for all classes (with capitalised first letter) and camel-case for all functions/vars (with lowercase first letter), so that is what I use in my own code.

Code: Select all

A class, by definition, defines a data structure and the functions that operate on it. strlen() and inportb() don't operate on common data, and really have nothing in common but a lose categorization as "system functions". More, one is a C standard function (so why not having it in <cstring> where it belongs?), and the other is a non-standard function, so why mash them into a class?


In my personal code there *are* functions and internal variables in the Kernel class. The extras (inb,outb,strlen,etc) are put in there because that is the place I wanted to put them. They are static and so behave in *exactly* the same way to a namespace definition. And I don't have a <cstring> header file in my kernel. My kernel uses my own classes / ADTs, end of. I have a <cstring> in the userspace libraries, but that is a whole different beastie.

Sorry, but this is violating pretty basic C++ design concepts. It smells of Java, and points to the OP not being "at home" with the language - which, in my opinion, means he's better off not trying to write an OS kernel in it.


What your complaining about here, imho, has nothing to do with C++, per se. It seems more to do with OO design principles, which can be flouted in any language. The concrete implementation of static class member functions and global functions defined in a namespace are, iirc, completely identical.
PS: A focus on making everything a class, as well as doing (too) many things by operator overloading, is one of the symptoms of a C++ beginner. It's the kind of "because I can"-mentality that sets in after reading a C++ book (which, unfortunately, tend to put much too much focus on classes), and is usually healed after a couple of years of "real" work. C++ offers the whole spectrum from procedural (C-style) over OO (Java-style) to generics (what I would call C++-style). Use what is appropriate, don't get caught in a paradigma or ideology.


"Appropriate" is subjective. Given that multiple methodilogies result in an identical implementation, it seems hard to define what is an "appropriate" method for a given task.

And yes, throughout all the code that I'm working on here at work (which is quite substantial, 300000+ lines) there is not once single namespace definition. Everything is done as const static members. It is in our coding standard, and thus suggests to me, again, that the 'principle' being infringed here is entirely subjective.
jnc100
Member
Member
Posts: 775
Joined: Mon Apr 09, 2007 12:10 pm
Location: London, UK
Contact:

Post by jnc100 »

I agree its not sensible to keep things like C library functions in a class. A separate namespace is probably more useful (e.g. namespace kernel) as that way when you call the function it is more clear in the code that the function you are calling is not a part of the CRT or some gcc builtin but rather your own function. If you're using C++ and want to write in a more OO way you should probably consider looking at the problem a totally different way, for example make string and stream classes and use operator overloading on them.

An additional advantage of classes is that you can use them to make interfaces, so that you can easily port your os to different architectures. For example, the final kernel image of my os is to link the core.a library with an architecture-specific one. All hardware-dependent calls in core.a call through a generic interface. You could then, for example, use a port interface, where the in and out functions (which you can overload for different data sizes) are specific to the particular architecture you are using. I don't actually use it for something as fundamental as a port because the functions are called too often and calling via virtual functions is slow, however you can use it for many higher level functions (e.g. I have a vga class which implements both the console interface and the ostream interface. By simple switching to have the global 'ostream kout' object be a serial class instead, I can then dump startup information to a serial port).

Regards,
John.
User avatar
djnorthyy
Member
Member
Posts: 49
Joined: Mon Apr 09, 2007 10:50 am
Location: UK, Hants
Contact:

Post by djnorthyy »

Thank you all very much for your help. I will try and take this all on board. I will also study OO much more throughly before I attempt to develop an os.

BTW:

he's better off not trying to write an OS kernel in it.


I do have a name, you know!

Many thanks,
Harry.
Reflect Desktop Operating System - ' You only remember the name of the OS when it crashes '
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Post by Solar »

Apologies. I got into the habit of consciously not looking at who's actually asking the question, so I judge a question by it's face value, not by who asked it.

Regarding JamesM's namepace-less work project... *shudder* 8)
Every good solution is obvious once you've found it.
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

Solar:

Ooh, tell a lie, I did another grep and found 2 instances of namespace. It seems to be to be completely superflous, but perhaps you can tell me why they are used?

Code: Select all

namespace A
{
   enum Type
  {
    ...
  };
}
typedef A::Type AType;


Now I think it would be better written as:

Code: Select all

typedef enum
{
  ...
} AType;


Is there any reason it isn't?
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Post by Solar »

A typedef to get rid of a namespace you defined yourself?

Ouch.

Looks like the whole thing was "designed" by someone not really "at home" either...
Every good solution is obvious once you've found it.
Post Reply