November 08, 2024, 05:39:25 PM

News:

IWBasic runs in Windows 11!


Converting C++ Part 1

Started by Parker, October 13, 2007, 07:05:54 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Parker

This is in some ways an extension of the OOP tutorials posted earlier, and in some ways not. In these tutorials, I am going to go over converting C++ code that doesn't have a direct EBasic equivalent. You should have a good grasp on EBasic's OOP because these tutorials will deal with advanced features of C++ that you don't find in very many other places. I'm also assuming that you have at least some understanding of C++. I may, after I'm finished with these tutorials, write some general C->EB conversion tutorials.

In this first tutorial I'm going to go over the C++ code that does have EBasic equivalents, and I will try to provide some working C++ code to try on your own in these tutorials.




- Structures
We've seen over and over again that if you see 'struct' in C code, it means TYPE in EBasic. This is true in C++ most of the time too, but it is important to note that in C++ you are allowed to have methods, constructors, etc. inside of structs. Therefore, it is usually up to you which EBasic keyword you want to use when you see the 'struct' keyword in C++. The general rule however is that if there are only variables, and no private, public, or protected, and no inheritance, then you should use the TYPE keyword. Otherwise, you'll need an EBasic CLASS.

The pack argument for TYPEs in EBasic can also be specified in C/C++, but it is done differently. Usually this is not important, but if you are writing to a file or sharing with a library or other program, you will need to pay attention to it. In C, a preprocessor directive ( #pragma pack(n) ) tells you what the packing is. The default in both C++ (at least VC++, and probably GCC also) and EBasic is 8, so don't worry about this if you don't see a #pragma pack directive.

C++:
struct SimpleType
{
  int n;
  char ch;
};

struct ComplexType
{
  int n;
  ComplexType( ) { n = 0; }
};

#pragma pack(1)
struct Pack1Type
{
  int big;
  short little;
};

struct AnotherPack1Type
{
  short short1;
  short short2;
};


EBasic:
type SimpleType
  int n
  char ch
end type

class ComplexType '' Since we have a constructor in that struct, I have to make it an EB class.
  int n
  declare ComplexType( ) '' note that we can't put the definition here like we did in C++.
end class

'' so we put it right here
sub ComplexType::ComplexType( )
  n = 0
end sub

type Pack1Type, 1 '' Notice the ,1 on the EBasic type and the #pragma pack(1) before the C++ type.
  int big
  word little '' In C++, the short type is WORD in EBasic.
end type

type AnotherPack1Type, 1 '' Note that #pragma pack(1) affects _all_ types after it, not just the one directly following it.
  word short1 '' even though we don't have the "short" type in EBasic, it's a good idea to preserve variable names.
  word short2
end type


- Classes
Classes are a big subject in C++, and various features of them will be covered in future tutorials, but right now I just want to go over the features that are shared with EBasic.

- Constructors and destructors
In C++ constructors and destructors look much like those in EBasic. A constructor is basically a method with the same name as the class, defined with no return type. In C++, you can have as many constructors as you want, provided they have different types and numbers of parameters. I'm not going to cover constructors with parameters right now.
Destructors are almost the exact same as EBasic. They don't have a return type, and their signature looks almost identical. For a class MyClass, the destructor would be called MyClass::~MyClass. Compare to EBasic's MyClass::_MyClass.

C++:
class MyClass
{
private:
  SomeOtherClass *thePointer;
public:
  MyClass( );
  ~MyClass( );
};

MyClass::MyClass( )
{
  thePointer = new SomeOtherClass( );
}

MyClass::~MyClass( )
{
  delete thePointer;
}


EBasic:
class MyClass
private
  pointer thePointer
public
  declare MyClass( )
  declare _MyClass( )
end class

sub MyClass::MyClass
  thePointer = new( SomeOtherClass, 1 )
end sub

sub MyClass::_MyClass
  delete thePointer '' Note that this does _not_ at the current time call the destructor. Hopefully this will be fixed ASAP.
end sub


- Member access
I'm referring to private, public, and protected. In C++ the default is private for classes, but public for structs. Therefore, if you see something like this,
class Stuff
{
  int x;
  //...
};


Don't translate it like this!
class Stuff
  int x
end class


I mean, you could and it would work, but the developer meant for x to be private (unless he was unfamiliar with C++, but in that case the code is usually not important enough to port). Make sure you write this:
class Stuff
private
  int x
end class


Note that in my previous tutorials I said it is always a good idea to put the access modifier there even if you know what the default is. This is the reason.

Another thing worth noting is you might see something like this in C++ code:
class C
{
private:
  int x;
public:
  void setBothX( C &other, int newx );
};

void C::setBothX( C &other, int newx )
{
  this->x = newx;
  other.x = newx; // what? this is private...
}


The standard in most languages is to allow a method to access private variables from _any_ object of that type. So that code is legal in C++, but Aurora and EBasic have not adopted that practice. There are two workarounds for this:
1) Change the this pointer, and pretend you're in the other object.
sub C::setBothX( other:C, newx:int )
  *<C>this.x = newx
  this = &other '' hacky bad don't do it!!
  *<C>this.x = newx
end sub


2) Write a workaround in the code
sub C::setBothX( other:C, newx:int )
  *<C>this.x = newx
  other.SetX( newx ) '' I wrote this method to help me
end sub


- Member variables and methods
In C++ you can have static variables and methods which I will go over at a later time. An EBasic compatible variable looks very much the same as in EBasic. Here are the (standard) C++ types and their EB equivalents:
C++: EBasic
char: char/schar
short: word/sword
int: uint/int
long: uint/int
long long int: uint64/int64
__int64: uint64/int64
float: float
double: double
long double: no equivalent (sometimes double can be used here)
char*: string,pointer (depending on context)
void*: pointer
anything***....: pointer (this means, any type with any number of *'s after it)

Note that different compilers may treat char and short as signed or unsigned. C++ has two keywords, signed and unsigned, to designate which is which. int and bigger types are usually signed by default. long long int may appear as just long long, and it is not adopted by Microsoft's compilers. It is somewhat more of a standard than __int64 though, which is what MS's compilers use.

If you see a parameter that looks something like this:
Classname &paramname
it just means that paramname is passed BYREF.

If you see it on a return type like this:
Classname &MyFunction( );
stay away from that code :D. I'll get to that later.

Finally, if you see a parameter with only a type, just copy the name from the method that it is defined in. For example,
class MyClass
{
  //...
  void Stuff( int );
};

void MyClass::Stuff( int a ) { printf( "%d\n", a ); }


would translate to:
class MyClass
  ''...
  declare Stuff( a:int )
end class

sub MyClass::Stuff( a:int )
  print a
end sub


C++ methods have a lot of features that EBasic's don't, and those will be covered later. For now, I'll just say that a C++ method looks like a C function inside of a class.
C++:
class MyClass
{
  //...
public:
  double MyFunction( int a, char *s );
};


EBasic:
class MyClass
  ''...
public
  declare MyFunction( a:int, s:string ),double
end class


In C++ methods may be virtual, but they may also be pure virtual. A virtual method looks like this:
class MyClass
{
  //...
  virtual int GetSomeInt( );
};


A pure virtual method looks like this:
class MyClass
{
  //...
  virtual int NotDefinedForThisClass( ) = 0;
}


A pure virtual method means it is not defined for that class. Since EBasic doesn't support pure virtual methods, just give it an empty body and take out the '=0' part. I'll go over this concept in more detail in a later tutorial.

In C++ destructors can be virtual. This is actually an important feature, and I'll tell you how to convert it later, but for now just ignore virtual when it appears on a destructor.

-Inheritance
In C++ classes can inherit from more than one base class. I will get to this later. For now, we want to know that inheritance in C++ looks like this:
class Derived : public Base { /* ... */ };
It is very unusual to use anything other than public (private or protected) in the inheritance declaration. That means that all inherited members must be at least that level, where public allows them to take whatever modifier they had at first, protected makes them all protected or private, and private makes them all private. For the sake of conversion, you can just ignore that modifier. Therefore, the above definition should look like this:
class Derived, Base
  /* ... */
end class




That's it for the first tutorial. I know that is kind of a lot to absorb at one time, so don't hesitate to ask questions. Like I promised, here's some C++ code to convert. This is actual code that I wrote for a project of mine.

To aid in converting, I will give you some EBasic functions that you can use as the bodies of my class methods (these are not real functions, but I will allow using them in the solution).

To strip the path part off of a filename, use GetFileNamePart( name ).
To print out some data, you can either keep the printf's just as I wrote them (but without the semicolons) or you can convert it to PRINT statements if you know how.
For the filename string, you can do one of two things:
1) If you keep it as a pointer, pretend that _strdup returns a copy of your string allocated with NEW. Change the free() to DELETE.
2) If you make it a string, you don't need to worry about duplicating it or freeing it.

class Reporter
{
protected:
int maxErrors;
int numErrors;
char *filename;

public:
virtual ~Reporter( ) { }
virtual void setMaxErrors( int max )
{
maxErrors = max;
}
virtual bool error( int, char * ) = 0;
virtual void warning( int, char * ) = 0;
};

class ConsoleReporter : public Reporter
{
public:
ConsoleReporter( char *file, int max = 32 )
{
numErrors = 0;
maxErrors = max;
// chop off the path part
char *backslash, *slash;
backslash = strrchr( file, '\\' ) + 1;
slash = strrchr( file, '/' ) + 1;
// compare to 1 because of adding 1 above
if( (int)slash == 1 && (int)backslash == 1 )
filename = _strdup( file );
else
filename = _strdup( slash > backslash ? slash : backslash );
}

~ConsoleReporter( )
{
free( filename );
}

bool error( int line, char *message )
{
printf( "error: %s (%d): %s\n", filename, line, message );
numErrors++;
if( maxErrors > 0 && numErrors >= maxErrors )
{
printf( "too many errors in source file...\n" );
return false;
}
return true;
}

void warning( int line, char *message )
{
printf( "warning: %s (%d): %s\n", filename, line, message );
}
};


I will post the solution sometime later this week.

Techno

Parker,

Very interesting Tutorial when do you release the next parts?

Kind regards
Stephane

LarryMc

Since Part 1 was released nearly 8 years ago I really don't expect there will ever be a Part 2.
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library