[Solved] How to register a C++ instance method with Spark.function?


#1

Hey guys!
I have been using my Photons for day, and I have found a problem. My code will compile and flash, but whenever I call one of the cloud functions from my library the Photon crashes. I have included the tinker framework into my code, and it does not crash when one of its cloud functions is called.

In the .cpp file, I had to write the cloud functions like this to make it want to compile.

Spark.function("moveNeck", (int (*)(String))&sparkbot::moveNeckCloud);
Spark.function("moveRight", (int (*)(String))&sparkbot::moveRightCloud);
Spark.function("moveLeft", (int (*)(String))&sparkbot::moveLeftCloud);
Spark.function("moodlights", (int (*)(String))&sparkbot::moodlightsCloud);
Spark.function("enableSlave", (int (*)(String))&sparkbot::slaveToggle);

In the .h file, they are defined as:

int moveNeckCloud(String data);
int moveRightCloud(String data);
int moveLeftCloud(String data);
int moodlightsCloud(String red, String green, String blue);
int slaveToggle(String data);

I don’t know why this is happening. It’s probably something to do with the rest of my code. If anyone wants to create a pull request, here is the Github Link.


Particle Device OS Updates: Comments
#2

Update:
I just read the docs again, and I realized that you can only have 4 cloud functions. :cry:


#3

You can use the arguments to make them do different things though. I see three ‘move’ functions, which can be combined into one, should you send the direction in the argument.


#4

Yes I did that, now I have three cloud functions, but the Photon crashes when any cloud function is called.


#5

I think this is because of the conversions that happen in order to make the code compile. This also happens with Spark.subscribe() and and atttachInterrupt()

Compiling code for photon

Including:
/home/nrobinson/github/sparkbot-default/firmware/main.ino
/home/nrobinson/github/sparkbot-default/firmware/sparkbot-default.h
/home/nrobinson/github/sparkbot-default/firmware/sparkbot-default.cpp
attempting to compile firmware
pushing file: /home/nrobinson/github/sparkbot-default/firmware/main.ino
pushing file: /home/nrobinson/github/sparkbot-default/firmware/sparkbot-default.h
pushing file: /home/nrobinson/github/sparkbot-default/firmware/sparkbot-default.cpp
Errors
sparkbot-default.cpp: In member function ‘void sparkbot::begin()’:
sparkbot-default.cpp:53:60: warning: converting from ‘int (sparkbot::)(String)’ to 'int ()(String)’ [-Wpmf-conversions]
Spark.function(“moveServos”, (int ()(String))&sparkbot::moveCloud);
^
sparkbot-default.cpp:54:60: warning: converting from 'int (sparkbot::
)(String)’ to ‘int ()(String)’ [-Wpmf-conversions]
Spark.function(“moodlights”, (int (
)(String))&sparkbot::moodlightsCloud);
^
sparkbot-default.cpp:55:61: warning: converting from ‘int (sparkbot::)(String)’ to 'int ()(String)’ [-Wpmf-conversions]
Spark.function(“enableSlave”, (int ()(String))&sparkbot::slaveToggle);
^
sparkbot-default.cpp:57:58: warning: converting from 'void (sparkbot::
)(const char*, const char*)’ to ‘EventHandler {aka void ()(const char, const char*)}’ [-Wpmf-conversions]
Spark.subscribe(“syncServos”, (EventHandler)&sparkbot::syncServosSlave, MY_DEVICES);
^
sparkbot-default.cpp:58:51: warning: converting from ‘void (sparkbot::)(const char, const char*)’ to ‘EventHandler {aka void ()(const char, const char*)}’ [-Wpmf-conversions]
Spark.subscribe(“RGB”, (EventHandler)&sparkbot::RGBSlave, MY_DEVICES);
^

sparkbot-default.cpp: In member function ‘void sparkbot::startLeftButton()’:
sparkbot-default.cpp:400:65: warning: converting from ‘void (sparkbot::)()’ to 'raw_interrupt_handler_t {aka void ()()}’ [-Wpmf-conversions]
attachInterrupt(LEFTBUTTON, (raw_interrupt_handler_t)&sparkbot::switchLights, RISING);
^
sparkbot-default.cpp: In member function ‘void sparkbot::startRightButton()’:
sparkbot-default.cpp:405:66: warning: converting from ‘void (sparkbot::)()’ to 'raw_interrupt_handler_t {aka void ()()}’ [-Wpmf-conversions]
attachInterrupt(RIGHTBUTTON, (raw_interrupt_handler_t)&sparkbot::sync, RISING);
^
make[1]: *** […/build/target/user/platform-6sparkbot-default.o] Error 1
make: *** [user] Error 2


#6

Well I wish Mat was back from vacation, but I think you are running into the difference between calling a C function and calling C++ method. Spark.function() is always called in with C calling conventions.

The short answer is you need a C wrapper function around your C++ methods to make this work. You can put these wrappers in your C++ class file if you declare them as extern “C” them so the linker knows what to do.

The longer answer can be found here:

https://isocpp.org/wiki/faq/mixing-c-and-cpp#call-cpp


#7

So I put something like extern “C” bool Spark.function(); into my cpp file for Spark.function(), Spark,subscribe(), and attachInterrupt() ? :confused:


#8

Hi @nrobinson2000

No, that is not right. Go read that linked page on how to call C++ methods from C–I will wait here for you! OK, now, good.

You cannot just cast your function pointer to the C++ method and expect the compiler to know what to do you have to write more code:

#include "sparkbot.h"

int moveWrapper(String cmd) {
  return sparkbot::move(cmd);
}

void setup() {
  Spark.function("move", moveWrapper);
}
...

#9

Ohhhh.
I see what you mean. I just didn’t entirely get what it said on the linked page.


#10

The main issue is the that Spark.function expects a C-style function pointer which means you can’t call a method from a C++ object.

For the Photon (not sure for the Core at this moment) the firmware adds support for C++ function objects.

So, one workaround as @bko mentionned while I was writing this reply is to create a C function to call the method on your object.

int moveNeckCloudHandler(String arg) {
  return sparkbot::moveNeckCloud(arg);
}

Spark.function("moveNeck", &moveNeckCloudHandler);

The C++ ninja solution is using std::function

This will solve your issue @nrobinson2000:

    // auto means: let the compile figure out what kind of std::function needs to be created
    auto moveNeckCloudHandler = std::bind(&sparkbot::moveNeckCloud, &sb, std::placeholders::_1);
    Spark.function("moveNeck", moveNeckCloudHandler);

Full example:

class Service {
    public:
    int handler(String arg) {
        return 0;
    }
};

Service myService;

void setup() {
    // See http://stackoverflow.com/questions/7582546/using-generic-stdfunction-objects-with-member-functions-in-one-class
    
    // auto is replaced by std::function<int(String)>
    // std::placeholders::_1 is necessary for a method taking 1 argument
    auto serviceHandler = std::bind(&Service::handler, &myService, std::placeholders::_1);
    Spark.function("test", serviceHandler);
}

void loop() {

}

#11

Thank you very much, I am trying this, but always get this error.

sparkbot-default.cpp:64:44: error: no matching function for call to 'CloudClass::function(const char [11], std::_Bind<std::_Mem_fn(sparkbot*, std::_Placeholder<1>)>*)' Spark.function("moveServos", &moveWrapper);

#12

How do you create moveWrapper?


#13

I was trying the way that @bko mentioned. Now I am trying the way that you are suggesting when I made that reply, I was using moveWrapper as the name of the handler, similar to the example you gave.


#14

This is what the error looks like now:

sparkbot-default.cpp:64:44: error: no matching function for call to 'CloudClass::function(const char [11], std::_Bind<std::_Mem_fn<int (sparkbot::)(String)>(sparkbot, std::_Placeholder<1>)>*)'
Spark.function(“moveServos”, &moveHandler);
^


#16

OK. I get it. Can you try with the C++ function object instead?

    auto moveHandler = std::bind(&sparkbot::move, &sb, std::placeholders::_1);
    Spark.function("move", moveHandler);

#17

Yes, I am already doing this.

It gives a lot of errors:

sparkbot-default.cpp:64:44: error: no matching function for call to 'CloudClass::function(const char [11], std::_Bind<std::_Mem_fn<int (sparkbot::*)(String)>(sparkbot*, std::_Placeholder<1>)>*)'
   Spark.function("moveServos", &moveHandler);
                                            ^
sparkbot-default.cpp:64:44: note: candidates are:
In file included from ../wiring/inc/spark_wiring.h:45:0,
                 from ./inc/application.h:29,
                 from sparkbot-default.h:1,
                 from sparkbot-default.cpp:1:
../wiring/inc/spark_wiring_cloud.h:49:17: note: static bool CloudClass::function(const char*, int (*)(String))
     static bool function(const char *funcKey, user_function_int_str_t* func)
                 ^
../wiring/inc/spark_wiring_cloud.h:49:17: note:   no known conversion for argument 2 from 'std::_Bind<std::_Mem_fn<int (sparkbot::*)(String)>(sparkbot*, std::_Placeholder<1>)>*' to 'int (*)(String)'
../wiring/inc/spark_wiring_cloud.h:54:17: note: static bool CloudClass::function(const char*, user_std_function_int_str_t, void*)
     static bool function(const char *funcKey, user_std_function_int_str_t func, void* reserved=NULL)
                 ^
../wiring/inc/spark_wiring_cloud.h:54:17: note:   no known conversion for argument 2 from 'std::_Bind<std::_Mem_fn<int (sparkbot::*)(String)>(sparkbot*, std::_Placeholder<1>)>*' to 'user_std_function_int_str_t {aka std::function<int(String)>}'




#19

Can you post the relevant lines of codes? It’s hard to figure out why you get the errors without the actual code.

Or just push your code to Github and I’ll take a look. [edit]: I see you did. Let me take a look.


#20

I just pushed a commit.

Repo


#21

By the way, thanks a lot for helping me out with my code. :thumbsup:
I’m going to put you in the credits of my project,


#22

Thanks. I’ve been meaning to figure out the answer to this question myself for a while too.

First thing I see is that you have 2 instances of sparkbot called sb in 2 different files. You should have only 1. Keep the one in main.ino

Since you are doing Spark.function inside the class itself, you can bind to this instead of sb.

The other thing is you don’t need a & in front of moveHandler inside the Spark.function call.

  auto moveHandler = std::bind(&sparkbot::moveCloud, this, std::placeholders::_1);
  Spark.function("moveServos", moveHandler);