ATmega programming using Arduino Library - #2 [continuing]

Go To Last Post
76 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

This is a very similar question to the other "ATmega programming ..." but with a markedly different emphasis. 

 

Suppose that I have an Arduino library written for a specific add-on device. In this case, it is LoRa radio chip. It uses a C++ class to interface to the radio. I would like  to incorporate this code into a standard C program (for Mega328P). Rub is, I know ALMOST nothing about C++!

 

I plan to provide substitute functions for the accesses to the MCU IO pins. Still called DigitalWrite() and such, but hand crafted to be MCU specific and much tighter. Likewise, I plan to provide substitute functions ("wrappers", I guess) to call the AVR LibC delay functions instead of the Arduino ones. Looks like some substitute serial functions will also be needed. I think those are the three big interface issues.

 

What I am much less clear about is how to make this library/class part of my program. I think that I need to call the class "constructor" somehow, but not sure how to do that or anything else to get it started. Nor, am I clear on calling functions within that class from the "regular C" part of the program.

 

So, if some expert on such matters could provide a little guidance on the details of incorporating an Arduino interface library into a non-Arduino C program, I would appreciate it VERY much!

 

Thanks

Jim

This topic has a solution.

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 07:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Constructor is basically "init" so if you have a class "Foo" then just copy the constructor code into a Foo_init() and call that first. You may find that you have to change the member initialization stuff though.
.
BTW why not turn this round and simply make main.c into main.cpp and have the whole program as C++?

Last Edited: Sat. Jun 17, 2017 - 07:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What would starting out with main.cpp do? What are the ramifications? Especially for someone who has never knowingly coded in C++?

 

JIm

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Apart from stronger type checking a C program renamed to be C++ should just compile and run as before but now you can instantiate C++ classes in it too (and do all the other stuff C++ then offers) but the point is that apart from accessing classes you dont HAVE to use any of the new stuff.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So why don't we generally use main.cpp rather than main.c?

 

My big question, though: how does one "instantiate" a class? I see code like:

RH_RF95 rf95;

Is that the instantiate statement?

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't know why people don't choose C++ (though Arduino sensibly did!)
.
And yes that's an instantiation of a class. Just think of a class a bit like a typedef struct.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Appreciate your help!

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When going to C++, one thing to be aware of is 'name mangling'. In order to avoid clashes of the same name in different objects, the compiler 'mangles' the names. Normally this is not an issue, but if you have external references to C things, you need to know of extern "C" that tells the compiler the name is not mangled. Interrupt vectors always seem to catch me out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can you elaborate on what the programmer needs to do?

 

Thanks,

Jim

 

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Extern "C"

Google C++ name mangling
For a more complete treatment of the subject.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

You have a main.cpp with C++ code. You have some old but good and proven C-functions in sub.c. There is an accompanying header file called sub.h. You want to call the C-functions from the C++ code. You tell the C++ compiler that is should look for functions using the C-convention for symbol identification by doing

// main.cpp

extern "C" {
#include "sub.h""
}

That's it.

 

After a while you get tired writing that into every .cpp file you call C-code from, and you figure that you can make the header files agnostic to whether they are #included from C or C++:

 

//sub.h

#ifdef __cplusplus
extern "C" {
#endif

int foo(double bar);
void baz();

#ifdef __cplusplus
}
#endif

With the above technique, if this header file is seen by the C++ compiler it will see the prototypes surrounded by the required extern "C" since the __cplusplus preprocessor symbol is always defined when the C++ compiler runs. If, on the other hand, a C compiler sees this file it will  not see the extern "C" since _cplusplus is not defined when a C compiler runs. Now your header files are C/C++ agnostic!

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you Johan! Big help.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But if you stick to all C++ you don't need worry. Even if you use some established C, perhaps Fleury for I2C or LCD then you should be able to just rename those c to cpp

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is that all it takes? Is there anything that you can do in a macro or other way to make that automatic?

 

Thanks

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The common method is what Johan described. Looking at the arduino core code should answer most of your queries on using c++

Last Edited: Sun. Jun 18, 2017 - 11:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Had not thought of that as a learning tool. Good idea. 

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The recurring question we get here is how to handle interrupts in C++. As the vector points to a function, you can't have it call a member function of an object. If you look at the Arduino core class for serial you'll see how they handle interrupts. You'll see the application of extern "C" here. I've just been through the process on another platform.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks -

 

Have not run into that yet, though I am sure I will. Knowing almost nothing about C++, I didn't know that would be an issue.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 04:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does a C++ program use the same io.h file and same libc headers? Do I have to use the same extern C mechanism with these? If I read Johan's notes correctly, you would need to apply the same

#ifdef __cplusplus

 

mechanism. But, these headers already  have the prototypes, don't they? So, would the headers need to be rewritten? 

 

Thanks for putting up with my uninformed questions!

Jim

 

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's all been done for you. You can write your code as you've been used to. Take the plunge!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
It's all been done for you.
+1

C:\SysGCC\avr\avr\include>grep -H cplusplus * -r
assert.h:#ifdef __cplusplus
assert.h:#ifdef __cplusplus
avr/eeprom.h:#ifdef __cplusplus
avr/eeprom.h:#ifdef __cplusplus
avr/interrupt.h:#ifdef __cplusplus
avr/interrupt.h:#ifdef __cplusplus
avr/interrupt.h:#ifdef __cplusplus
avr/interrupt.h:#ifdef __cplusplus
avr/interrupt.h:#else     /* !__cplusplus */
avr/interrupt.h:#endif  /* __cplusplus */
avr/pgmspace.h:#ifdef __cplusplus
avr/pgmspace.h:#ifdef __cplusplus
compat/deprecated.h:#ifdef __cplusplus
ctype.h:#ifdef __cplusplus
ctype.h:#ifdef __cplusplus
errno.h:#ifdef __cplusplus
errno.h:#ifdef __cplusplus
inttypes.h:#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS)
inttypes.h:#endif       /* !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) */
math.h:#ifdef __cplusplus
math.h:#ifdef __cplusplus
setjmp.h:#ifdef __cplusplus
setjmp.h:#ifdef __cplusplus
stdint.h:#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS)
stdint.h:#endif /* !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) */
stdint.h:#if (!defined __cplusplus || __cplusplus >= 201103L \
stdint.h:#endif /* (!defined __cplusplus || __cplusplus >= 201103L \
stdio.h:#ifdef __cplusplus
stdio.h:#ifdef __cplusplus
stdlib.h:#ifdef __cplusplus
stdlib.h:#ifdef __cplusplus
string.h:#ifdef __cplusplus
string.h:#ifdef __cplusplus
time.h:#ifdef __cplusplus
time.h:#ifdef __cplusplus
util/eu_dst.h:#ifdef __cplusplus
util/eu_dst.h:#ifdef __cplusplus
util/usa_dst.h:#ifdef __cplusplus
util/usa_dst.h:#ifdef __cplusplus 

AVR-LibC has always been written to support both C and C++.

 

(note it does require you to use things like <string.h>, the <cstring> equivalent is not available)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

BTW regarding #13 I just tried an experiment. I got a copy of the Fleury LCD code and just took lcd.c, lcd.h and test_lcd.c from it. I then built in C as follows:

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os -DF_CPU=1000000UL test_lcd.c lcd.c -o lcd.elf

C:\SysGCC\avr\bin>avr-nm lcd.elf | grep " T"
000000d2 T __bad_interrupt
000000a8 T __ctors_end
000000a8 T __ctors_start
000000b4 T __do_copy_data
000000a8 T __dtors_end
000000a8 T __dtors_start
00000370 T __itoa_ncheck
00000054 T __trampolines_end
00000054 T __trampolines_start
00000388 T __utoa_common
00000386 T __utoa_ncheck
00000000 T __vectors
000003de T _etext
000003da T _exit
000001ba T lcd_clrscr
00000180 T lcd_command
00000192 T lcd_data
000001b2 T lcd_getxy
000001a4 T lcd_gotoxy
000001c0 T lcd_home
00000224 T lcd_init
000001c6 T lcd_putc
000001ee T lcd_puts
00000206 T lcd_puts_p
00000292 T main
000003ba T strrev
000000d6 T wait_until_key_pressed

That built the code with no warnings/errors as C code. The avr-nm output shows the functions that were created with their expected "C like" names.

 

If I do nothing more than rename the files and rebuild as C++ I get:

C:\SysGCC\avr\bin>ren lcd.c lcd.cpp

C:\SysGCC\avr\bin>ren test_lcd.c test_lcd.cpp

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os -DF_CPU=1000000UL test_lcd.cpp lcd.cpp -o lcd.elf

C:\SysGCC\avr\bin>avr-nm lcd.elf | grep " T"
000000d2 T __bad_interrupt
000000a8 T __ctors_end
000000a8 T __ctors_start
000000b4 T __do_copy_data
000000a8 T __dtors_end
000000a8 T __dtors_start
00000370 T __itoa_ncheck
00000054 T __trampolines_end
00000054 T __trampolines_start
00000388 T __utoa_common
00000386 T __utoa_ncheck
00000000 T __vectors
000003de T _etext
000003da T _exit
000001ba T _Z10lcd_clrscrv
000001a4 T _Z10lcd_gotoxyhh
00000206 T _Z10lcd_puts_pPKc
00000180 T _Z11lcd_commandh
000000d6 T _Z22wait_until_key_pressedv
00000192 T _Z8lcd_datah
000001c0 T _Z8lcd_homev
00000224 T _Z8lcd_inith
000001c6 T _Z8lcd_putcc
000001ee T _Z8lcd_putsPKc
000001b2 T _Z9lcd_getxyv
00000292 T main
000003ba T strrev

Once again it builds without errors but it is clearly build as C++ code as the function names are now "mangled" so that something like:

000001a4 T lcd_gotoxy

has become:

000001a4 T _Z10lcd_gotoxyhh

The 10 is the number of characters in the "real" name (up to 'y') so the "hh" on the end are added to shows the types of parameters. Those h say this is a version of lcd_gotoxy() that takes two uint8_t as parameters.

 

Oh and as a further bit of interest. This is the size of the C code:

C:\SysGCC\avr\bin>avr-size lcd.elf
   text    data     bss     dec     hex filename
    990      42       0    1032     408 lcd.elf

(so 990 bytes of code).

And this is the size of the C++ version:

C:\SysGCC\avr\bin>avr-size lcd.elf
   text    data     bss     dec     hex filename
    990      42       0    1032     408 lcd.elf

So those who would say "ah but C++ is large and bloaty compared to C" have once again been proven to have unfounded concerns. ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You just wait, Cliff. Soeone will come along with a splendidly ignorant argument..

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I really do appreciate this input. I WILL be trying it very shortly. Spending a big chunk of today reviewing Arduino.

 

Cheers, and thanks, again!

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One of these days I will have a tricky electronics question, and it's payday Jim! ;-)
Consider that joke a fine compliment!

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I will certainly help with electronics questions you might have. You have earned it! Several times over!

 

For anyone who might be reading - this topic would certainly benefit from a well-crafted tutorial that includes a template for a main.cpp that includes ISR examples and examples of working with libraries that have not been adapted to be C/C++ friendly.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 05:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:
For anyone who might be reading - this topic would certainly benefit from a well-crafted tutorial that includes a template for a main.cpp that includes ISR examples and examples of working with libraries that have not been adapted to be C/C++ friendly.
As others have said the Arduino serial stuff is probably the "template" most people look at when trying to determine how to do this kind of thing.

 

The class it uses is:

 

https://github.com/arduino/Ardui...

 

with the implementation of (almost) all that here:

 

https://github.com/arduino/Ardui...

 

but then for each potential serial port (up to 4) there is a separate file such as:

 

https://github.com/arduino/Ardui...

 

and not only does that actually instantiate the class:

 

#if defined(UBRRH) && defined(UBRRL)
  HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR);
#else
  HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
#endif

so that "Serial" is a live copy of the "HardwareSerial" stuff for the first/only UART but that second implementation file also has:

#if defined(USART_RX_vect)
  ISR(USART_RX_vect)
#elif defined(USART0_RX_vect)
  ISR(USART0_RX_vect)
#elif defined(USART_RXC_vect)
  ISR(USART_RXC_vect) // ATmega8
#else
  #error "Don't know what the Data Received vector is called for Serial"
#endif
  {
    Serial._rx_complete_irq();
}

So that makes a call to the copy of the rx_complete_irq() that is in the copy of the HardwareSerial class that has been instantiated as "Serial". The two functions called from the ISRs are held in:

 

https://github.com/arduino/Ardui...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Valuable stuff, Cliff. 

 

What is this "virtual" all about?

 

virtual int available(void);
virtual int peek(void);
virtual int read(void);

 

And I don't see any reference to avr/io.h. How does it assign interrupt vector locations? In other words, do I have to have something else besides avr/io.h?

 

More appreciation!

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 05:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I need to switch from the pad to a proper laptop to deal with 'virtual'. Meanwhile you take a deep breath, Jim.. You are now entering object-oriented heart-land..

Hang in there. It's going to be a substantial piece of text...

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am really happy to see this stuff all in one thread. Pretty sure others will find it useful.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wouldn't (at first) try to learn more C++ than you have to.

 

"virtual" is about different variants of objects derived from a common base that has some things they share. The "virtual" basically means (and yes I know C++ purists this is an over-simplification) "each object type derived from this base needs to provide their own version of this functionality". The classic example is "shape" as the base and "rectangle" and "triangle" as the derived objects and each provides their own "area" function where the rectangle version is simply width * height while the triangle version is more complex.

 

But you can write a lot of C++ before any of that stuff starts to matter ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, Cliff -

 

That is as much as I need to know, probably. 

 

On interrupts, my "classic" program structure stacks the interrupts for the whole project at the top of the main.c module (well, after the #includes and the #defines and the variable declarations), even if I have a separate serial.c to implement serial code. Now, I do understand that it might be cleaner, code-wise, to have the interrupt(s) all together with the functions and operations it might be related to. 

 

In the Arduino code you you referenced, that all seems to be bundled together in a class. Is this just the coder's preference or enhanced "code reuse" or is there some underlying "big reason" for doing this?

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 05:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First and foremost: Cliffs note above is (largely) correct [1]. You can write a lot of C++ without having to use virtual. But if you want to read C++ code, e.g. some of the Arduino "libraries", then it helps to have some understanding of the concept.

 

Of-course I just had to say "largely correct". After all - I am a C++ purist. ;-) 

 

With that I start

 

The ridiculously short introduction to object-oriented programming in C++

Part 1 - a C example

 

Luckily, we can not get to virtual without dealing with a few other things first. I say "luckily" because this will mean we cover all of the "basics of object orientation" which is classically covered by these three words: Encapsulation, Inheritance, Polymorphism.

 

To deal with encapsulation I'd like to start with some plain C. I will also steal Cliffs excellent example with the "shapes" and calculation of their areas.

 

Plain C:

typedef struct {
    int radius;
    double area;
} circle_t;

double circle_compute_area(circle_t circle) {
    circle.area = circle.radius * 2 * 3.1415;
}

void circle_print_area(circle_t circle) {
    printf("Area is %f", circle.area);
}

typedef struct {
    int side;
    double area;
} square_t;

double square_compute_area(square_t) {
    square.area = square.side * square.side;
}

void square_print_area(square_t square) {
    printf("Area is %f", square.area);
}

int main(int argc, char * argv[]) {
    circle my_circle;
    my_circle.radius = 7;
    circle_compute_area(my_circle);
    circle_print_area(my_circle);

    // And something similar for a square not typed in to save
    // lines and fingertips..
}

So, we have the possibility to create both circles and squares, and to compute their areas and to get them printed out. I hope there's nothing strange here (unless I plated a big typo...).

 

The code above is

i) Untested. I'm just typing (I just realized I had no x86 or i64-targeting C++ compiler on this machine...)

ii) Incomplete when it comes to stuff not important for the sublect (i.e. no #included header files etc)

 

So as not to lose what I'm typing I will post this now. Each subsequent step will be in a separate post. Go make a cup of tea or coffee now..

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Mon. Jun 19, 2017 - 06:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Johan -

 

You are a Prince! Maybe even a King! I will carefully review what you write.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
You just wait, Cliff. Soeone will come along with a splendidly ignorant argument..

 

// 'Hello World!' program 
 
#include <iostream>
 
int main()
{
  std::cout << "Hello World!" << std::endl;
  return 0;
}

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

deleted (keeps  the best part of topic consistent)

Last Edited: Mon. Jun 19, 2017 - 06:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The ridiculously short introduction to object-oriented programming in C++

Part 2 - C++ Encapsulation

 

Encapsulation is the first pillar (or "tier") of object orientd programming. It has two aspects:

 

- Keeping things together (as an "object")

 

- Keeping the inner state of the object "protected" from inappropriate manipulation from the outside. (In fact, I will break the principle of this second aspect in my examples below. I just might return to deal with it in a supplementary piece.)

 

I will deal only with the first aspect here.

 

Consider the C example from above. We are calling a function circle_compute_area, and we are passing it a struct representing a circle. In some sense we are doubling up on info here.

 

And why do we keep the procedural stuff separate from the data stuff? What if we could glue them together so that, say, a circle, was something that held both it's "state" (the radius value, the area value) and its procedural "knowledge". I.e. what if we could glue together those two?

 

Well, you've guessed it already.. The thing in C++ that sort of corresponds to a struct type in C is a class.

 

I am now myself calling on even worse C++ purists to correct this. I plead to you - don't go into the discussion of structs and classes being almost equivalent. It will fog the scene at this m,oment. Later maybe, but not now, please!.

 

I will now show the C++ main function first, so that you can see how we get a cleaner syntax, and then well move on to the class definitions involved. All you need to know now is that I will b(eventually) have two classes (that is types as opposed to instances) called Circle and Square. (In C++ the convention is to use capitalized words for types.)

int main(int argc, char * argv[]) {
   Circle my_circle;
   my_circle.radius = 7;
   my_circle.compute_area();
   my_circle.print_area();

   // And again something similar for a square...
}

Now, that wasn't so hard, was it? Notice how we're now not both mentioning a function with an explicit type as part of its name, and passing it a struct of that type. Instead, were "talking to the object" (in OO talk "object" is an instance), whether its about it getting a value or it being "asked to do something".

 

Encapsulation is about keeping state and procedure together in one place! It makes sense since it makes for better organized code. It makes sense since it caters for a nicer syntax, removing "redundant" source code text.

 

So what does these classes look like? Well...

class Circle {
   int radius;
   double area;

   void compute_area() {
      area = radius * 2 * 3.1415;
   }

   void print_area() {
      printf("Area is %f", area);
   }
}

class Square {
   int side;
   double area;

   void compute_area() {
      area = side * side;
   }

   void print_area() {
      printf("Area is %f", area);
   }
}

Whoaaaa! That wasn't hard, was it?!

 

With your initial exaggeration that C++ is really simple out of the way, let's do some observations.

 

1) To the purists: Yes I've left out access qualifiers. Don't mention them! At least not yet..

 

2) Functions that are "inside" a class knows of the variables "inside" the class without any qualification. The variables have a scope over the complete class and functions can refer to them without any qualification.

 

3) Notice that I can have functions named the same in different classes? We've got rid of that pesky redundant textual information in our source code.

 

4) To the purists: No, I will not discuss the this-pointer here and now. I beg of you to refrain too.

 

Just to make sure you got it: I can create another object of type Circoe (or Square, if I wanted) and manipulate that object too.

 

int main(int argc, char * argv[]) {
   Circle my_circle;
   my_circle.radius = 7;
   my_circle.compute_area();
   my_circle.print_area();

   Circle my_other_circle;
   my_other_circle.radius = 42;
   my_other_circle.compute_area();
   my_other_circle.print_area();
}

I could fill 10 pages more just about this firts "pillar" called Encapsulation but I'll stop here. I need a brake. You take one too...

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you, dbrion!

 

That was a typo. As I said, I just type without testing...

 

If anyone finds an obvious typo, then please let me know and I'll correct it.

 

If alerts and corrections makes this thread messy I can eventually merge it all into one nice tutorial so don't you worry right now about making a mess. My "chapter posts" are easily to spot and locate by their headings, bold and underlined.

 

Now for that break... (Let's hope we get all the way to virtual tonight!)

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Mon. Jun 19, 2017 - 06:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

 

 

 

 

 

Well, I got so excited over finding an online C++ compiler, I had to write this part too..

 

The ridiculously short introduction to object-oriented programming in C++

Part 3 - C++ Inheritance

 

Tell me something.. What does Circles and Shapes have in common? Well, they both have an area, you say. And I go, yes, but you missed that they both know how to print their area, and have you noticed that the way they do that is identical?!

 

No, wouldn't it be nice if we didn't have to double up on that? (In real OO life, it  won't be about doubling. The factor will not be 2. It will be a substantially large integer..)

 

You guessed it. We can avoid that doubling up. We can declare a Class with the things that Circles and Squares have in common, and then let Circles and Squares inherit the things from that class. We will call that base class (or super class) Shape. By the way, notice how easy it is to give things really meaningful names?

 

Here we go..

 

class Shape {
   double area;

   void print_area() {
      printf("Area is %f\n", area);
   }
};

class Circle :  Shape {
   int radius;

   void compute_area() {
      area = radius * 2 * 3.1415;
   }
};

class Square : Shape {
   int side;

   void compute_area() {
      area = side * side;
   }
};

int main(int argc, char * argv[]) {
   Circle my_circle;
   my_circle.radius = 7;
   my_circle.compute_area();
   my_circle.print_area();

   Square my_square;
   my_square.side = 9;
   my_square.compute_area();
   my_square.print_area();

   Circle my_other_circle;
   my_other_circle.radius = 42;
   my_other_circle.compute_area();
   my_other_circle.print_area();

}

Observe the output from the above:

 

Area is 43.981000
Area is 81.000000
Area is 263.886000

 

There's only one thing I think needs to be mentioned regarding new syntax here: The notation

 

class Circle :  Shape
  ...

is to be read: Here is a definition of a class Circle which inherits from the class Shape.

 

Now for the important observations - of the code:

 

1. There is one implementation of print_area().

 

2. The variable area is defined in one place.

 

3. Both Circles and Squares obviously have an area and knows how to print it.

 

4. There are individual/specialized  implementations of the value needed for computing an area (Circle has radius, Square has side).

 

5. There are individual/specialized  implementations of the computations, but they both yield the value to the generalized area variable that both types have.

 

6. The name of the computation functions happens to be the same (it made sense that we shouldn't have specialized names of those functions). But, and that's a big BUT, they need not have the same name. We could have held on to our awkward habits of the deplorable C language ;-) and had

 

my_circle.compute_circle_area();
.
.
my_square.compute_square_area();
.
.

but that would just have been silly, IMO. I hope you agree, since that would mean that you've grasped "it".

 

The teaser is: When we get to the virtual thing that started all this the use of identical names becomes crucial... 

 

Now, I'm really gonna have that break! You too!

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Mon. Jun 19, 2017 - 07:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can you clarify the distinction between a "class" and an "object"? For this discussion, are they they same?

 

And, without derailing this beautiful presentation, what is this about: 

 main(int argc, char * argv[]) 

 

Great job. Can't wait for More From The Master!

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 07:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Well, I got so excited over finding an online C++ compiler, I had to write this part too..

For us that are following along, using Studio7, you might want to "publicize" appropriately...

/*
 * GccApplication3.cpp
 *
 * Created: 6/19/2017 12:49:49 PM
 * Author : ltheusch
 */ 

#include <avr/io.h>
#include <stdio.h>


class Shape {
double area;

void print_area() {
		printf("Area is %f\n", area);
	}
};

class Circle :  Shape {
	int radius;

void compute_area() {
		area = radius * 2 * 3.1415;
	}
};

class Square : Shape {
	int side;

	void compute_area() {
		area = side * side;
	}
};

int main(void) {
	Circle my_circle;
	my_circle.radius = 7;
	my_circle.compute_area();
	my_circle.print_area();

	Square my_square;
	my_square.side = 9;
	my_square.compute_area();
	my_square.print_area();

	Circle my_other_circle;
	my_other_circle.radius = 42;
	my_other_circle.compute_area();
	my_other_circle.print_area();

    while (1) 
    {
    }
}

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An object is just one instance of some class.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

, what is this about: 

 

 main(int argc, char * argv[]) 

Well , on PC(cygwin; gnuLinux) and alikes, you can give info to the executable (ex : gcc/g++ needs to know what to compile "gcc tt.c -o tt.out", pick up some options (a lot). argc is the number of space separated trings which ar given +1, argv is each of the argumements -argv[0] being the name of the executable; printf("%s succeeded\n" , argv[0]); is a valid -at least I hope, depending on success definition- instruction line on a PC /RPi. Of course, it works with gcc and g++ , and similar mechanisms exist for python -optarg, IIRC...- One can of course use the syntax : main() and the full syntax is int main(argc, char * argv[]){} -allows to test the exit status ....

Last Edited: Mon. Jun 19, 2017 - 07:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's not the full prototype for main() ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Which raises the question: all other functions require prototypes. main() seems to be a sort of built-in or integral part of C/C++.  Just what is it's prototype?  

 

Do ISRs get by without prototypes because they are normally defined (in my code, anyway) before main()? It looks like (see msg #27) that C++ uses prototypes for ISRs. Is that because they are not defined before main()? Is this also true for plain C?

 

Jim

 

 

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

duplicate deleted

 

 

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Mon. Jun 19, 2017 - 08:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The full prototype allowed by gcc is:
.
int main(int argc, char * argv[], char *envp[])
.
Only makes sense on a hosted OS of course.

Last Edited: Mon. Jun 19, 2017 - 08:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Where do these arguments come from (in the embedded environment)?

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My edit crossed your post. You need an OS for main() to receive anything but void.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Best break I've had all day! One fag and half a litre of water. (I do not allow myself the habit of smoking while at work, and we have near tropical heat here at the moment..)

 

Take a deep breath, because here it comes!

 

The ridiculously short introduction to object-oriented programming in C++

Part 4 - C++ Polymorphism, or virtual functions

 

So we dealt with what different shapes have in common.

 

Or did we? Think!

 

.

.

.

 

Yes, we missed one thing. Both Circles and Squares can compute their areas. "But they're doing it in a different way!", you shout. I calmly respond: "Yes, but the fact that they can do it is common." If you get the distinction you're really beginning to grasp the power of object-oriented programming!

 

We must make a small deviation here: pointers.

 

Yes, pointers. They're not different from pointers in C. They point to something. Let's just redo our example from the Inheritance chapter, but using pointers:

 

class Shape {
   double area;

   void print_area() {
      printf("Area is %f\n", area);
   }
};

class Circle : Shape {
   int radius;

   void compute_area() {
      area = radius * 2 * 3.1415;
   }
};

class Square : Shape {
   int side;

   void compute_area() {
      area = side * side;
   }
};

int main(int argc, char * argv[]) {
   Circle * my_circle = new Circle;
   my_circle->radius = 7;
   my_circle->compute_area();
   my_circle->print_area();

   Square * my_square = new Square;
   my_square->side = 9;
   my_square->compute_area();
   my_square->print_area();
}

So, what have changed?

 

1. My variables my_circle and my_square are now pointers to objects rather than being objects themselves.

 

2. We refer to members by dereferencing with the -> operator rather than the dot-operator (.), just as in C.

 

3. We can refer to both member variables and member functions that way.

 

4. Nothing has changed in the class declarations themselves.

 

With that done we can deal with the fact that Circles and Squares share the ability to compute their areas. Let's start with complementing the super-class with a compute_area() function. What should it produce as the area value? A shape in general can not compute its area. Let's, for the sake of the example, make a slightly dirty design (don't ask "Why dirty?", just accept ;-) ) and have -1 denote a non-computable area. Our Shape class would go

 

class Shape {
   double area;

   void compute_area() {
       area = -1;
   }

   void print_area() {
      printf("Area is %f\n", area);
   }
};

Let's experiment:

 

class Shape {
   double area;

   void compute_area() {
       area = -1;
   }

   void print_area() {
      printf("Area is %f\n", area);
   }
};

class Circle : Shape {
   int radius;

   void compute_area() {
      area = radius * 2 * 3.1415;
   }
};

class Square : Shape {
   int side;

   void compute_area() {
      area = side * side;
   }
};

int main(int argc, char * argv[]) {
   Circle * my_circle = new Circle;
   my_circle->radius = 7;
   my_circle->compute_area();
   my_circle->print_area();

   Shape * my_shape = new Shape;
   my_shape->compute_area();
   my_shape->print_area();
}

So far, so good. Just as we expected! (Now, remember that I did that for the sake of the argument - it is not necessarily good design, and only a demonstration we need to eventually get to The Good Stuff[tm].

 

Now for the next diversion. And this is both good design and important: Whenever you use a type (e.g. a pointer) to refer to an object of a class, you can also use a pointer to a super-class. I.e. you can

   Shape * my_shape = new Circle;

Read that again, just to make sure you see  what I changed!

 

This has implications, some of which you might not expect. Here's a new main() (everything else as before):

 

int main(int argc, char * argv[]) {
   Shape * my_shape = new Circle;
   my_shape->radius = 7;
   my_shape->compute_area();
   my_shape->print_area();
}

The compiler protests immediately:

 

 In function 'int main(int, char**)':
36:14: error: 'class Shape' has no member named 'radius'

Hmmm... It seems that the compiler does not understand that my Shape actually is a circle. And so we can't set its radius.

 

Time for another deviation then.. We need a constructor. Well' we actually already had one or we could not have instantiated the objects as we've deone above. But that was a default constructor that the compiler provided, and it essentially did nothing (purists: Stay quiet!).

 

A constructor is a special function that is called when an object is instantiated. it can take parameters. It can not return any value. It always has the same name ass the class itself. Here's our classes, some of them with explicit constructor supplied by me, the coder:

 

#include <stdio.h>

class Shape {
   double area;

   void compute_area() {
       area = -1;
   }

   void print_area() {
      printf("Area is %f\n", area);
   }
};

class Circle :  Shape {
   int radius;

   Circle(int a_radius) {
       radius = a_radius;
   }

   void compute_area() {
      area = radius * 2 * 3.1415;
   }
};

class Square : Shape {
   int side;

   Square(int a_side) {
       side = a_side;
   }

   void compute_area() {
      area = side * side;
   }
};

int main(int argc, char * argv[]) {
   Shape * my_shape = new Circle(7);
   my_shape->compute_area();
   my_shape->print_area();

   Shape * my_other_shape = new Square(9);
   my_other_shape->compute_area();
   my_other_shape->print_area();
}

See how we pass the values of the radius and the square at the time of construction? Now the compiler does not emit any error, but what happens when we run this? Well...

Area is -1.000000
Area is -1.000000

Still not that good. Obviously the compiler does not understand that the objects actually are one Circle and one Square at the time of the call to compute_area(). The compiler determines which compute_area() implementation to actually call based on the type of the reference (the type of the pointer) to the object, i.e it calls the Shape version of compute_area(). Sometimes this is desired behavior, but in our case it's not.

 

And finally we have arrived! You tell the compiler to call a member function based on the type of the object itself by declaring a function virtual. It goes like this:

 

 

 

In fact, we can do better than that - we can demand that anyone that inherits from Shape (e.g. declaring a class Triangle) must implement a function to compute the area. The super-class puts a demand on something that all sub-classes must implement, but without saying anything about how the sub-classes specifically does this. What is established in the super-class is the interface to the function: It's name, its parameters and its return type (in out case it's simple - no parameters and a void return type).

 

It goes like this:

 

 

 

 

 

 

#include <stdio.h>

class Shape {
   double area;

   virtual void compute_area()
   {
       area = -1;
   }

   void print_area() {
      printf("Area is %f\n", area);
   }
};

class Circle :  Shape {
   int radius;

   Circle(int a_radius) {
       radius = a_radius;
   }

   void compute_area() {
      area = radius * 2 * 3.1415;
   }
};

class Square : Shape {
   int side;

   Square(int a_side) {
       side = a_side;
   }

   void compute_area() {
      area = side * side;
   }
};

int main(int argc, char * argv[]) {
   Shape * my_shape = new Circle(7);
   my_shape->compute_area();
   my_shape->print_area();

   Shape * my_other_shape = new Square(9);
   my_other_shape->compute_area();
   my_other_shape->print_area();
}

And the output is...

Area is 43.981000
Area is 81.000000

Stop. Take a deep breath. Exhale...

 

What just happened was important. We made some objects referring to them using a general (as in non-specific) super-class and then, without being explicit about what types they where we called functions that exposed very specific behavior. Just to drive the point home, take a look at this main():

 

int main(int argc, char * argv[]) {
   // An array of pointers to Shapes
   Shape * my_shapes[3];
   // Fill it with different shapes
   my_shapes[0] = new Circle(15);
   my_shapes[1] = new Square(8);
   my_shapes[2] = new Circle(3);

   for (int i = 0; i<3; i++) {
       my_shapes[i]->compute_area();
       my_shapes[i]->print_area();
   }
}

with output

Area is 94.245000
Area is 64.000000
Area is 18.849000

What you should note is that

 

1. The array is an array of pointers to Shapes. There is nothing about the array that says anything about whether the pointers will point to Circles or Squares or, as I did above, a mix.

 

2. There is nothing in the for-loop that says anything about whether it is looping through pointers to Circles or Squares. It loops over pointers to Shapes.

 

Still, the output is correct. The  code asks the objects themselves to do what they specifically can do but the code in main (except for the instantiation) does not know about the specifics!

 

If that does not stun you, then I don't know what will.

 

Just one more thing before we round up: It might have crossed your mind that it is stupid to have an implementation of compute_area() in the Shape super-class. I agree. Let's get rid of it. We can tell the compiler that all classes that inherit from Shape must implement a compute_area() by doing this in Shape:

class Shape {
   double area;

   virtual void compute_area() = 0;

   void print_area() {
      printf("Area is %f\n", area);
   }
};

Notice the difference from before!

 

Not only does this put a demand on sub-classes to implement a compute_area(). It also tells the compiler that Shape has no implementation. The net effect is that now Shapes as such can not be instantiated - the compiler will emit an error if you try. The only instantiatable classes in our example now are Circle and Square. That makes a lot of sense to me, and  I hope to you too!

 

Now there's only one question you have left: WHY? Why this elaborate mechanism? What is it good for?

 

Here are some sketched examples:

 

You write drivers for serial communication for different hardwaare interfaces. Things they have in common is e.g. functions that are named send_char(), send_string() etc. They might all have a built in ring-buffer. But they are very specific as to hoe they are initialized. A serial port takes baudrate-databits-parity-stopbits. Some other serial interface has a different initialization. Share the common code and data structure in a superclass. This example might not be a good one for polymorphism, but then again it might.

 

Another example would be a font system. Al string can be rendered into a bitmap by a font so that's something common. But some fonts are proportional, others are not.

 

And Cliff was a wise man when he picked the Shapes example. It is one of the most used introductory examples to object-orientation. I think you can imagine a graphical system that not only does computation of areas, but that might actually have functions where objects can paint themselves on a canvas. In fact, one of the first applications where object orientation proved itself was "paint programs". And regardless of what graphical icons'n'windows'n'stuff OS you use I can assure you that it is object orientation behind the scenes to handle it. It might not be C++, but it's OO.

 

Finale: I'm sure I've missed out on some things, and made a mess about others. I also fully understand that this last part of this installation got BIG, but there was no other way about it. You aked about  virtual - you got it..

 

Now (well, tomorrow...): Ask questions! Point to spelign errors and blatant erors!

 

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Mon. Jun 19, 2017 - 08:35 PM

Pages