arkhipenko / TaskScheduler

Cooperative multitasking for Arduino, ESPx, STM32, nRF and other microcontrollers
http://playground.arduino.cc/Code/TaskScheduler
BSD 3-Clause "New" or "Revised" License
1.23k stars 225 forks source link

TaskScheduler seems not compatible with multiple object source files (aka tabs in IDE) #15

Closed adamryczkowski closed 6 years ago

adamryczkowski commented 8 years ago

My project will be rather big and I need to have it splitted between many .cpp files. The source code for your library is included in one big header file, declarations together with definitions. What should I do, to stop linker from complaining about

/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: multiple definition of `StatusRequest::signal(int)'
./.ino.cpp.o:/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: first defined here
./file2.cpp.o: In function `StatusRequest::signal(int)':
/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: multiple definition of `StatusRequest::signalComplete(int)'
./.ino.cpp.o:/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: first defined here
./file2.cpp.o: In function `StatusRequest::signal(int)':
/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: multiple definition of `Task::reset()'
./.ino.cpp.o:/home/Adama-docs/Adam/MyDocs/Programowanie/Arduino/libraries/TaskScheduler/src/TaskScheduler.h:387: first defined here
./file2.cpp.o: In function `StatusRequest::signal(int)':

To reproduce:

  1. Open new sketch, and put there
#include "header.hpp"

void setup()
{
    Serial.begin(9600);
    Serial.println(myfunc());
    Serial.println(globalx);
}

void loop() {}
  1. Add a new tab header.hpp with
#pragma once

#include <stdint.h>
#include <TaskScheduler.h>
extern uint8_t globalx;

uint8_t myfunc();
  1. Add a new tab file2.cpp with
#include "header.hpp"

uint8_t globalx = 42;

uint8_t myfunc()
{
    return 23;
}
  1. Try to compile and get the errors.

If you remove the line #include <TaskScheduler.h> from the header.hpp, everything compiles, but without your library.

I guess you'd need to split the library into proper decraration header and the definition (.cpp) file. Do you have plans for that?

arkhipenko commented 8 years ago

The library initially had 2 files, the header and the definition. Unfortunately, in the Arduino IDE world it means I cannot use the compile parameters (#defines) in the sketch. The compiler just ignores them for some reason. I had to move to a single file due to that limitation, and at this point do not have any plans to split them up.

http://stackoverflow.com/questions/21409042/correct-way-to-include-cpp-and-h-files-in-an-arduino-sketch

From the multiple file perspective, I believe the IDE just concatenates the sketches, going left to right, so if you include TaskScheduler in the first sketch, it should work for the rest.

http://www.avrfreaks.net/forum/arduino-multiple-files-how-does-it-compile-real

and

https://www.arduino.cc/en/Hacking/BuildProcess

adamryczkowski commented 8 years ago

No, it doesn't work as you described either. See the test case under "How to reproduce" in my last response. Did you ever do a project with more then one .cpp/.ino file (excluding headers) where at least two .cpp files were importing the header?

adamryczkowski commented 8 years ago

From the multiple file perspective, I believe the IDE just concatenates the sketches, going left to right, so if you include TaskScheduler in the first sketch, it should work for the rest.

Wrong. It does so only for tabs without extension. Tabs ending in .c and .cpp are compiled separately and linked against each other just like in any other C/C++ toolchain. See https://www.arduino.cc/en/Hacking/BuildProcess

adamryczkowski commented 8 years ago

Unfortunately, in the Arduino IDE world it means I cannot use the compile parameters (#defines) in the sketch. The compiler just ignores them for some reason. I had to move to a single file due to that limitation, and at this point do not have any plans to split them up.

You didn't have to. Arduino ignores #define parameters only because it parses the main .ino file, searches for any #include and moves them into beginning of the resulting, concatenated .cpp file (which goes by the name .ino.cpp). So from the avr-gcc perspective, it first sees the #includes, and only then it parses the #defines.

The solution A:

Stay away from the main sketch (.ino) file. Just leave it empty (literally. Not a single character). Do all work in separate .cpp files, where you define void loop() and void setup() - just remember to include #include<Arduino.h>, otherwise you wouldn't have access to Arduino's custom functions like digitalWrite or the Serial object. Then you can program just like any other C++ project, together with the proper use of #define and other preprocessor stuff.

The solution B:

Do customizations with preprocessor directives in a separate header file, included in the main file (e.g. #include<TaskSchedulerCustomization.hpp>). Every #define will be parsed before the #include<TaslScheduler.h>, and should work as expected.

adamryczkowski commented 8 years ago

Correction to the above: all the methods will result in properly #defined macro variables when parsing library headers and compiling user's object file. It will not work for conditional compilation of TaskScheduler.cpp object file, so disregard my previous post.

I've asked the question on SE: http://arduino.stackexchange.com/questions/21520/arduino-configure-script-for-conditional-compilation-of-libraries

I hope someone would find the answer.

Personally I don't use built-in IDE, but work in Eclipse. It has far better UI, and easily allows editing of included libraries. So what I do, is that I put the configuration directives directly in the library's header. But that is not a solution with public library like yours.

adamryczkowski commented 8 years ago

How do you like my own solution to the http://arduino.stackexchange.com/questions/21520/arduino-configure-script-for-conditional-compilation-of-libraries ? It is hackish, but still very simple and easy to maintain.

You'd still need to separate the definitions from the declarations in your library. I'll do it for you and post a pull request.

arkhipenko commented 8 years ago

Let me have a look.

ricardojlrufino commented 8 years ago

I had the same problem adding TaskScheduler in another library that I am creating. The PullRequest the @adamryczkowski solved my problem

matthijskooijman commented 8 years ago

I just created #17 with an alternative approach to this problem, which should not affect the way this library is configured (as long as the needed configuration #defines are added in every place that #includes TaskScheduler.h, of course).

arkhipenko commented 8 years ago

Sorry guys. Time is at a premium. Could not find a moment to test this.

arkhipenko commented 7 years ago

Fixed in v2.2.0 via making all methods inline

palivoda commented 7 years ago

Thanks!

On Nov 18, 2016 5:59 AM, "Anatoli Arkhipenko" notifications@github.com wrote:

Closed #15 https://github.com/arkhipenko/TaskScheduler/issues/15.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/arkhipenko/TaskScheduler/issues/15#event-863848306, or mute the thread https://github.com/notifications/unsubscribe-auth/AApNrvnfDLHGISJxe7DI-ZvkRjyoDUzDks5q_SKDgaJpZM4HqagT .

arkhipenko commented 7 years ago

Fixed in testing branch. Would you please test/confirm?

arkhipenko commented 6 years ago

Implemented in v2.5.0

chadham commented 6 years ago

Im on 2.6.0 and think I am perhaps running into the same issue.

In my main .ino I've declared the following as a global variable (outside setup), to fire every 60 seconds Task pulse (60000, TASK_FOREVER, &callback, &scheduler, false);

In a different .cpp file for one of my classes, I define the following: #include extern Task pulse;

The purpose is to be able to interrogate the pulse object and enable / disable inside this other class with commands like pulse.enable() / pulse.disable(), ..

I'm not able to get it to compile ,get a long list of multiple definition of Task::isEnabled()' multiple definition ofTask::getInterval()' multiple definition of `Task::getIterations()' .. .. ..

These go away if I remove the #include , but then it doesn't know what Task is.

I also tried passing a pointer reference to the pulse Task object, to one of the methods in the .cpp that I need to manipulate pulse in, but had the same issue.

Not an expert in C/C++, could really use some help here.

Thank you much!

arkhipenko commented 6 years ago

Could you please send me your source files via email? arkhipenko@hotmail.com Are you using Arduino IDE? Which version?

chadham commented 6 years ago

Source files just sent the files. Using Arduino IDE version 1.8.5

chadham commented 6 years ago

Switching the include in other tabs from #include to #include as suggested by Anatoli worked!

I must have missed this somewhere in the documentation. Suggest adding a bold (highly obvious) comment so others can avoid my error.

Never needed to do this before, always something new to learn :)

Thank you so much.