Go Down

Topic: C++ getting address of a function (Read 4100 times) previous topic - next topic

GreyGnome

Hi,
I am trying to get the address of a function so as to send it to another function upon interrupt.  However, I get "(my function) was not declared in this scope."  According to http://www.newty.de/fpt/fpt.html#call, calling a function using a function pointer seems to be pretty easy but I am at a loss.  Can you help correct my syntax?  Thanks.  BTW the below code is just a stub.  I want to get past this compiler issue before I move on into the meat of what I'm attempting to do.

Code: [Select]
#include <PinChangeInt.h>

#define TESTPIN 4

class it {
  int aPin;
  public:
    it(int arduinoPin);
    void onInterrupt(void);
  private:
};

it::it(int arduinoPin) {
  aPin=arduinoPin;
}

void it::onInterrupt(void) {
  Serial.println(aPin, DEC);
};


void setup () {
  Serial.begin(115200);
  it testIt=it(TESTPIN);
  testIt.*onInterrupt; // ERROR here, and/or below (if I uncomment the below).
  // PCintPort::attachInterrupt(TESTPIN, (testIt.*onInterrupt), CHANGE);
}

void loop () {
  delay(1000);
}

EVP

#1
Sep 24, 2011, 08:23 pm Last Edit: Sep 24, 2011, 11:48 pm by EVP Reason: 1
i think you have missed the beginning from your it::it declaration, can they have the same name as well i don't know of the top of my head. Their could be other problems with it as well.
The main thing i think is that a class has to be in it's own file/tab. You do this with the create tab button to the left of the tabs in the Arduino IDE. The info for doing a class is all in the arduino doc's some were, i managed to follow them and get it right after a few goes.
Another thing which may or may not be relevant o you is if you call a function from within another function you have to declare them in the right order. I can remember if when i was doing just c++ a few years back now if this was the case i didn't think it was but it's tripped me up a few times.

GreyGnome

Thanks for the reply, but I don't think we're there yet...

The it::it() is actually a constructor for the class.

I don't know why a class would need to be in its own file.  As a matter of fact, I don't get any complaints about my class until I try to get a pointer to a function in the class.  So the class seems to work. It's true that for a library you certainly want to organize your work but I don't think that's a requirement imposed by ide or compiler.

olikraus

Problem one:
The address of a function simply is the function name, which is in your case
Code: [Select]
it::onInterrupt

so it should be like this (if i remember C++ correctly)
Code: [Select]
PCintPort::attachInterrupt(TESTPIN, it::onInterrupt, CHANGE)

However, problem two, you must not use a class member function as argument for attachInterrupt, because all class member functions expect the this pointer as first argument, which is not passed by the attachInterrupt, which means, that you are not able to access aPin.

Code: [Select]

it testIt=it(TESTPIN);

void onInterrupt(void) {
  Serial.println(testIt.aPin, DEC);
};

void setup () {
  Serial.begin(115200);
  PCintPort::attachInterrupt(TESTPIN, onInterrupt, CHANGE);
}


Oliver

EVP

I am i wrong? maybe i am but i'm sure with arduino a class does need to be in it's own file and also i thought that constructors weren't allow? Well not in arduino anyway. You have to create a function to set all the stuff you would normally set in the constructor.

nickgammon

You can get the address of a function easily enough. Test case ... Header file (functionheader.h):

Code: [Select]
// Generic arithmetic function
typedef int (*GeneralFunction) (const int arg1, const int arg2);


Sketch (functionheader.cpp):

Code: [Select]
#include "functionheader.h"

int Add (const int arg1, const int arg2)
{
return arg1 + arg2;
} // end of Add

int Subtract (const int arg1, const int arg2)
{
return arg1 - arg2;
} // end of Subtract

int Divide (const int arg1, const int arg2)
{
return arg1 / arg2;
} // end of Divide

int Multiply (const int arg1, const int arg2)
{
return arg1 * arg2;
} // end of Multiply

int DoSomething (GeneralFunction f, const int arg1, const int arg2)
{
Serial.println (f (arg1, arg2));    // call the passed-in function
}

void setup ()
{
  Serial.begin (115200);
  Serial.println ();

  GeneralFunction foo = Add;  // take address of function
 
  DoSomething (Add, 40, 2);
  DoSomething (Subtract, 40, 2);
  DoSomething (Divide, 40, 2);
  DoSomething (Multiply, 40, 2);
}  // end of setup

void loop () {}


Near the bottom of that you see I set "foo" to the address of the Add function. Also I passed Add down to DoSomething.

However having said that, you seem to be wanting to take the address of a member function of a class. In this case the function address isn't enough, you need the class instance address too (ie. the "this" variable).

Isn't this a bit convoluted for an ISR? Somehow taking the address of a function which is a class member?
Please post technical questions on the forum, not by personal message. Thanks!

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

GreyGnome


I am i wrong? maybe i am but i'm sure with arduino a class does need to be in it's own file and also i thought that constructors weren't allow? Well not in arduino anyway. You have to create a function to set all the stuff you would normally set in the constructor.


If what you say is true, this wouldn't work.  But it does:
Code: [Select]
#include <PinChangeInt.h>

#define TESTPIN 4

class it {
  int aPin;
  public:
    it(int arduinoPin);
    void onInterrupt(void);
  private:
};

it::it(int arduinoPin) {
  aPin=arduinoPin;
}

void it::onInterrupt(void) {
  Serial.print ("Test of class and constructor created in sketch... WORKS! ");
  Serial.println(aPin, DEC);
};

it testIt=it(TESTPIN);

void setup () {
  Serial.begin(115200);

  testIt.onInterrupt();
}

void loop () {
  delay(1000);
}


...The output is, "Test of class and constructor created in sketch... WORKS! 4"

So we have both a class created in a sketch, with a constructor.

GreyGnome


You can get the address of a function easily enough. Test case ... Header file (functionheader.h):
...
However having said that, you seem to be wanting to take the address of a member function of a class. In this case the function address isn't enough, you need the class instance address too (ie. the "this" variable).

Isn't this a bit convoluted for an ISR? Somehow taking the address of a function which is a class member?


Unfortunately I am having trouble with C++, not C.  So I agree that taking addresses of functions is easy, but I want to do it in a C++ kind of way.

According to http://www.newty.de/fpt/fpt.html#call , it should be easy:
Code: [Select]
int result3 = (instance1.*pt2Member)(12, 'a', 'b');   // C++
  but as you can see, I get the "was not declared in this scope" error.

I wouldn't expect that calling an object's function by reference in an ISR would be so convoluted.  To me, it makes perfect sense.  ...Oh, I know what you're thinking:  "GreyGnome, silly, the interrupt is an out-of-band action, divorced from normal flow of your sketch.  How would you expect that to work???"  Well hear me out a second:

When I used PinChangeInt to create my rotary encoder library, I noticed that I was doing some convoluted actions to get it to work.  I also noticed that I was doing convolutions on top of convolutions.  In other words, the library works by:

  • Calls the interrupt vector for the PORT on which the interrupt occurred (vector 0, 1, or 2).

  • The vector function figures out what pin caused the interrupt.

  • Based on that pin, call the user-defined function.


Well, because of the fact that the library was designed to call a user-defined function in a C way (ie, no this pointer allowed), I needed to create a static function.  That is:

  • The third step above was useless, because the user-defined function was always the same.  So trying to determine a pin-to-function correlation was unnecessary work.

  • I needed to repeat the second step in my static function.  That is, in my static function I figured out what pin caused the interrupt.  Additionally,

  • I created another array of structures containing all the encapsulated data I needed for each individual pin, so that the static function would grab the pin-specific structure from the array, then perform the proper functions.



It is because I performed that last item that I am looking to do it differently now. Because what is a "structure containing all the encapsulated data"?  ...Seems to me, they call that a "class".   ;)  In other words, by using the Arduino it seems I must forgo the benefits that C++ classes bring to me and jump back into C. But as a relative n00b I'm not yet comfortable doing that.

If I was able to have the interrupt routine

  • Call the interrupt vector for the PORT on which the interrupt occurred (vector 0, 1, or 2).

  • The vector function figures out what pin caused the interrupt.

  • Based on that pin, call a class's method.


Then, based on the fact that a class is an encapsulation of my data and the actions I want to perform on that data, I think this way is a lot cleaner.  At least, it's more object-oriented.

nickgammon



Code: [Select]


...
  testIt.*onInterrupt; // ERROR here, and/or below (if I uncomment the below).
...



What are you imagining that line of code will do anyway? It doesn't call a function. It doesn't assign anything. This compiles:

Code: [Select]
void setup () {
  Serial.begin(115200);
  it testIt=it(TESTPIN);
  testIt.onInterrupt ();
}


As I said, a C++ non-static function requires two things: the class instance address, and the function within the class. You can't pass a single address to an interrupt service routine in that way. This is closer:

Code: [Select]
  attachInterrupt(TESTPIN, testIt.onInterrupt, CHANGE);

But that gives the error:

Code: [Select]
sketch_sep25a.cpp: In function 'void setup()':
sketch_sep25a:25: error: argument of type 'void (it::)()' does not match 'void (*)()'


And the compiler is right. That function is not a void function taking a void argument. It is a class member. Not what the ISR expects. You need to rework your design.

Quote
That is, in my static function I figured out what pin caused the interrupt.


Can't you have 3 ISRs? One for each pin?

In any case a rather serious objection is this:

Code: [Select]
void setup () {
  Serial.begin(115200);
  it testIt=it(TESTPIN);
  PCintPort::attachInterrupt(TESTPIN, (testIt.*onInterrupt), CHANGE);
}


Assuming the attachInterrupt line worked, which it doesn't, then when the interrupt fires testIt has gone out of scope, and the program will crash.

And if you just move the instantiation into global space, you have the static initialization order problem. The whole thing needs rethinking, sorry.

Quote
Oh, I know what you're thinking:  "GreyGnome, silly, the interrupt is an out-of-band action, divorced from normal flow of your sketch.  How would you expect that to work???"


I'm not thinking that, interrupts are supposed to be out of the normal flow, that isn't the issue.
Please post technical questions on the forum, not by personal message. Thanks!

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

nickgammon

Quote
In other words, by using the Arduino it seems I must forgo the benefits that C++ classes bring to me and jump back into C.


Not at all. You are compiling with C++. This is g++, nothing specific to do with the Arduino here.
Please post technical questions on the forum, not by personal message. Thanks!

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

GreyGnome

#10
Jan 17, 2012, 12:44 am Last Edit: Jan 17, 2012, 12:49 am by GreyGnome Reason: 1
Code: [Select]
[code]:smiley-roll: Well, here it is, months later and I've made much progress.

What I wanted to do all along was create a callback- virtual functions to the rescue!  The problem, as Nick mentioned, was that it's not enough in C++ to get the address of a method- in order to correctly identify a method the "this" pointer is required.  Because methods are defined in the Class, but instantiated as part of the payload of an object.

So I have found the below code (at the end) and made a little library (cb.h), and created an Interface (that's Java terminology, don't know if it applies to C++).  A full example will be given at my Tigger library at http://code.google.com/p/tigger/ when I upload it.

One subclasses the CallBackInterface class, e.g.:
Code: [Select]

class Tigger : public CallBackInterface {
       public:
       // constructors an whatnot go here
}

implement a method called
Code: [Select]
void cbmethod(void) in your class ("Tigger", above), e.g.:
Code: [Select]

void Tigger::cbmethod() {
       uint8_t oldSREG = SREG;
       cli();
       tigermillis = timer0_millis;
       SREG = oldSREG;
       if (tigermillis-startTime <= _delay) return;
       startTime=tigermillis;
       count++;
}

Instantiate the class:
Code: [Select]

Tigger myTigger=new Tigger();


Then finally, call the cbmethod from, say, an interrupt (pseudo-code).  First, you need to setup the situation; call
Code: [Select]

    addPin(myTigger):

The addPin method would look something like this:
Code: [Select]

void PCintPort::addPin(CallBackInterface* cbIface)
{
       InterruptPin p=createPin(arduinoPin, CHANGE);
       // ...nefarious details deleted for brevity
       // cbIface is the object instantiated from a subclass of CallBackInterface
       CallBack<CallBackInterface, void>* my_callback=new CallBack<CallBackInterface, void> (cbIface, &CallBackInterface::cbmethod);
       p->pinCallBack=my_callback;

In the Interrupt code, call the CallBack at your leisure.  All variables in the object are accessible.  This call will actually call the cbmethod passed to it above, and previously defined in the class:
Code: [Select]

       (*(p->pinCallBack))();


Here's the code I found:
Code: [Select]

/*
 **********************************************************************
 * cb by GreyGnome aka Mike Schwager                                  *
 * version 1.0 Mon Jan 16 09:25:59 CST 2012                           *
 *                                                                    *
 * based on:                                                          *
 *      Variable Parameter Call-Back Template (version 0.0.1)         *
 *                                                                    *
 * Author: Arash Partow - 2000                                        *
 * URL: http://www.partow.net/programming/templatecallback/index.html *
 *                                                                    *
 * Copyright Notice:                                                  *
 * Free use of this library is permitted under the guidelines and     *
 * in accordance with the most current version of the Common Public   *
 * License.                                                           *
 * http://www.opensource.org/licenses/cpl.php                         *
 *                                                                    *
 **********************************************************************
*/


#ifndef INCLUDE_CALLBACK_H
#define INCLUDE_CALLBACK_H

class CallBackInterface
{
  public:

    CallBackInterface() {};

    virtual void cbmethod() {
    };

};

template < class Class, typename ReturnType>
class CallBack
{
  public:

   typedef ReturnType (Class::*Method)(void);

   CallBack(Class* _class_instance, Method _method)
   {
      class_instance = _class_instance;
      method         = _method;
   };

   ReturnType operator()()
   {
      return (class_instance->*method)();
   };

   ReturnType execute()
   {
      return operator()();
   };

   private:

     Class*  class_instance;
     Method  method;

};
#endif

[/code]

GoForSmoke


The main thing i think is that a class has to be in it's own file/tab.


Unless that's some screwy Arduino thing then no, it doesn't.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

Go Up