Go Down

Topic: Passing I/O functions to a class. (Read 2064 times) previous topic - next topic

OutOfLine

Feb 23, 2013, 09:21 am Last Edit: Feb 23, 2013, 10:15 am by OutOfLine Reason: 1
I'm a first time c++ explorer having a hard time to figure out how to tell the compiler to *do* what I want...

For my arduino programs I use a kind of menu over serial line.

It might help to understand what I want to do to have a quick look at an example: a simple hardware menu interface over serial line as a kind of arduino software breadboard.
https://github.com/reppr/softboard

After cat&pasting the menu code too many times to too many sketches I decided to implement the menu functionality as a library to make reuse even easier.  I had done similar things in other languages like c and FORTH but decided to take advantage of the situation and try out c++, not so sure any more if that was a good idea ;)

I started coding on a Linux computer as I did not want to load all my early versions on an arduino. So I wrote and tested basic menu functionality on Linux.  On an Arduino the menu would get input from one of it's hw interfaces like Serial and use the same (or another) interface for output.  On the pc the I/O was on stdin/stdout.
So I experimented with passing function pointers to the Menu constructor or with defining menu I/O by #ifdef s.
I thought I could use that later to switch different Arduino interfaces, not being restricted to Serial.

I found different working approaches on Linux, but when I tried to use Serial I/O on the Arduino the compiler confused me a lot by not letting me do that and throwing me in a jungle of constructors/destructors, thinking that I want to redefine this-or-that. After a lot of reading, learning and trying different approaches I'm still not there yet.

Let me explain:

The menu takes input (if any) from one of the interfaces like serial, buffers it and returns immediately unless it just received an end token. So the menu must know how to test for input (nonblocking) and to read next byte. After an end token the menu interprets the input in a way defined by the user program and some internal stuff.

On the other hand the menu must have a way to inform the user about active keys, selected menu page, errors like unknown input, a missing number and the like.

So far so good, but now the arduino sketch defines it's menu pages, saying things like:
if on menu page "TEST" react on receiving menu token 'l' or 'r' by informing the user about the pwm value driving left/right motor and let him set a new value to test how much your two-wheel robot needs to start moving...  and on pressing '!' store that values to the EEPROM...

So the sketch often wants to use the same I/O functions as the menu class does. This lead to all sorts of errors when trying that with Serial I/O and c++ (If I remember right I had an early c version that worked sort of...).

I thought I could do that along the lines
Code: [Select]

/* pseudo code */
Menu MENU(buffsize, submenus, &men_getchar [, other I/O function pointers]);

/* the sketch would then call MENU.out() family or use things like Serial.print() directly
   to define functionality of the menu pages */

Works well on linux, but not when I try to use it with Arduino Serial, one of the most common errors being that the compiler thinks I want to redefine Serial I/O functions. How do I tell that to the compiler and where would I put Serial.begin()?

I have tried a couple of other implementations, like to derive the Menu class from Serial which had the side effect of making me feel very stupid ;)

I think the key point is that I want to be able to use all I/O interfaces (which might be coded as a class) in *both* the sketch *and* the menu class.  Multiple menus on different I/O interfaces should also remain possible.

So before trying next dead end street let me ask:
What is the right approach to do that in c++?

Chaul

I'm not fully grasping the problem here yet, but if you want to be able to use some interface within that class, can't you just have those interfaces as member variables of your class?

I'm assuming that the problem is that the base classes have function implementation by the same name. So, without changing the library functions, you need to access those classes via pointers, references or just instantiate the interface within your class. Just make sure it's initialized before using it. Typically the constructor would be short, and you would have an Init() function that must be called. You do not want exceptions thrown in either constructor or destructor, that's mostly the reason, I think.

OutOfLine


I'm not fully grasping the problem here yet, but if you want to be able to use some interface within that class, can't you just have those interfaces as member variables of your class?


I had tried this with Serial but could not find out how to be able to use Serial from within the sketch *and* the class.
Also this does not look very flexible to me if I want to use another interface or use more then one in different menus.


I'm assuming that the problem is that the base classes have function implementation by the same name. So, without changing the library functions, you need to access those classes via pointers, references or just instantiate the interface within your class. Just make sure it's initialized before using it. Typically the constructor would be short, and you would have an Init() function that must be called.


Yes, I did come across naming conflicts.
Speaking about *.init functions I think one of my problems is that I don't know where to call Serial.init() from.

Another topic might be that I tried to pass a function pointer to an overloaded function.  I don't even know if that is possible at all, but could not work it out with separate functions either...

I do not know how to access a class by a pointer, btw.  What can you do with that?

Thanks for helping me :)

Nick Gammon


So before trying next dead end street let me ask:
What is the right approach to do that in c++?


Pass it by reference. In all that post you didn't have much code. Post a minimal sketch that demonstrates what you are trying to do, even if it doesn't compile.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Chaul

Something like this.
Code: [Select]

MyClass * pClass = new MyClass(var1, var2);
pClass->Init();

var1 and var2 are some variables, just to show that you can pass them into the constructor or Init(). However, I must warn you about using uninitialized pointer variables. You must set a value to the pointer. The example above would crash on the Init-call if the pointer was pointing to either 0 or some random place in memory.

A safer way would be to use reference like MyClass & rClass; but this requires that you pass the reference via the constructor. It would be too late in the Init() function.

The third way is to just instantiate it with MyClass oClass; but this means you can't pass any variables in the constructor and you'll have to set the Init variables separately or with the Init() function.

As for function pointers, I would rather use derived classes. Function pointers are used more in C, but in C++ it's kinda not necessary most of the time. There are object oriented principles that involve implementing a factory for the creation of different kinds of objects, but it would take a lot of time to get a hold of and on simple projects it would just overcomplicate things for what it's worth..

The Serial class is probably instantiated already somewhere. I haven't checked how that works. It looks like the begin() function is enough to initialize it. I haven't seen the need to derive from it, but it also looks like the LCD libraries for example do. I'm sorry I'm still a bit new to Arduino, but I'm not that new to object oriented programming.

Chaul

Yes, or you can just pass the object via reference to a class function. The & marks reference. You have the class already instantiated and you just write the function definition like this:
Execute(MyClass & rClass);

OutOfLine


Pass it by reference. In all that post you didn't have much code. Post a minimal sketch that demonstrates what you are trying to do, even if it doesn't compile.


Yes, only problem that I have tried too many approaches and they have all grown beyond 'minimal',
and need cleaning up (too much #ifdef magic and debugging code inside).

I have the code in git, so could start from any one of some quite different trial implementations.
But I'm not decided on what version to base discussion on.
That's why I ask for the right c++ approach first ;)

Did I get that right, you suggest that I'd pass a reference to (let's say) Serial I/O functions from the sketch to the Menu constructor or some Menu.init() function?  I would call Serial.init() from setup(), right?

Or did you mean to pass a reference to a class (let's say Serial)?  I have never heard of pointers/references to a class before, btw.

Nick Gammon

Serial isn't a class. It's an instance of a class and you can pass a reference to it.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

Example:

Code: [Select]

void displaySomething (HardwareSerial & whichPort)
  {
  whichPort.println ("Hello world."); 
  }

void setup ()
  {
  Serial.begin (115200);
  displaySomething (Serial);
  }  // end of setup

void loop () { }
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

Or even, to be fancier:

Code: [Select]

void displaySomething (Stream & whichPort)
  {
  whichPort.println ("Hello world."); 
  }

void setup ()
  {
  Serial.begin (115200);
  displaySomething (Serial);
  }  // end of setup

void loop () { }
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Nick Gammon

And to use it in a class:

Code: [Select]

class myMenu
  {
  private:
    Stream & port_;
  public:
    myMenu (Stream & port) : port_ (port) { }
    void begin ();
  };

void myMenu::begin ()
  {
  port_.println ("Menu initialized.");
  }

myMenu menu (Serial);

void setup ()
  {
  Serial.begin (115200);
  menu.begin ();
  }  // end of setup

void loop () { }
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

OutOfLine


Serial isn't a class. It's an instance of a class and you can pass a reference to it.


Thanks, that was one of my wrong assumptions, there must be some more.

OutOfLine


Code: [Select]

void displaySomething ();



Your code makes me smile: I once tried:
Code: [Select]

/*
something.cpp  Passing a function pointer to a function to do something:
*/

#include <iostream>

bool something() {
  std::cout << "\t\"Something in the way she moves...\"\n";
  return true;
}

void do_it(bool (*do_something)(void)) {
  (*do_something)();
}


bool (*learned)(void);

void learn_anything(bool (*what)(void)) {
  learned=what;
}

void do_anything() {
  (*learned)();
}

int main() {
  std::cout << "something.cpp\n";

  std::cout << "\n  passing something as argument:\n";
  do_it(&something);

  std::cout << "\n  learning something:\n";
  learn_anything(&something);
  do_anything();

  std::cout << "\n(done)\n";
  return 99; // test only ;)
}


Trying the same in a class was more difficult then I expected, though.

Nick Gammon

References have to be initialized in the constructor, as Chaul said, but it wasn't that bad.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

OutOfLine


And to use it in a class:
[...]


Oh, thanks. This looks like a starting point.
I think I had tried a very similar example.

I must read up about streams, never really used them.
How do I call that for tests on a Linux text console (not Arduino)?

Go Up