Struggling with multiple source files to keep code separate

Hi all,

Now that I have my project working rather well, I decided to improve the structure of the code and organize the code into separate source files to facilitate better reuse of the code in other projects. I am struggling with how to declare and define the variables and functions, and seek some guidance.

Here is what I would like to have:

  1. My main project sketch that contains the setup() and loop() functions and whatever #include statements I need. Lets call this file: Sketch.ino
  2. An accompanying file that is only used with Sketch.ino that holds configuration parameters for my sketch such as the network SSID and password, etc. Lets call this Sketch.h
  3. A file of functions that I will call from Sketch.ini and other projects - lets call this mylib.cpp
  4. An accompanying file that mylib.cpp as needed, such as mylib.h

(The file extensions need not be what I list above.)

I have, for example, a struct that mylib defines and an object of that type, that I want to assign values to in Sketch.h

I would like mylib files to not reference Sketch.* so that they are portable and could be used in somothersketch.ino

Example structure that I am aiming for:

Sketch.ino

#include "ESP8266WiFi.h"
#include "mylib.h"
#include "mylib.cpp" // ??
#include "Sketch.h"


void setup() { 
  WiFi.begin(ssid, password);  // defined in sketch.h
}

void loop{
  int i = 1;
  dostuff(i);  // defined in mylib
}

Then sketch.h

#include mylib.cpp //?
#include mylib.h //?

// project specific data
const char* ssid = "xxx";
const char* password =  "xxx";

myvar[] = {{ .type='M', .pin=4}, { .type='X', .pin=5}};  //struct defined in mylib.h, myvar declared in ?

Then mylib.h

struct mystruct  {
  char type;
  int pin;
}

Then mylib.cpp

#include "mylib.h"
extern mystruct myvar[];

void dostuff(int thing); {
   Serial.println(myvar[thing].type);
}

I have tried using extern, various includes, etc but I just have not managed to get the right permutation.

In my code I am getting a link error: undefined reference and I don't even have the structure I am aiming for above yet!

I have tried reading up on the subject but I just get more confused. Can anyone point me to a working simple example of what I am trying to do?

DaleSchultz:
Now that I have my project working rather well,

That seems like a good place to stop.

But if you anticipate using this as a library in future projects, maybe you should read https://www.arduino.cc/en/Hacking/LibraryTutorial

thanks that looks good, I will look at making it all into a Class.

I expect in order to populate the struct I will have to make a function that adds an item to the array, and call that to build up the array...

and I will probably have to pass in a WiFiClient object and find out how to destroy it after handing data that came in on it...

thanks...

If you have aspirations to be a professional C/C++ programmer then please ignore this Reply :slight_smile:

I organize my programs as a .ino file and several .h hiles. I don't bother with .cpp files. Code works fine in a .h file

Generally I define global variables in the .ino file, but they can be in a .h file if it is #included into all other files that refer to the variables.

When there are functions in a .h file it is necessary to put function prototypes at the top of the .h file. (In a .ino file the Arduino IDE does that for you).

...R

I also use a separate definitions.h file to inlude all my variable #defines.
I usually have lots as i use #define s to organise memory locations of the eeprom and other fixed parameters.

I then copy the same definitiins.h file in all my sketches so that i always use the same var names.

1 Like

I took the Arduino example of a class, renamed it and got it working, then proceeded to break it by trying to morph it to my needs....

/*
  RemoteSign.h - defines RemoteSign class
*/
  
#ifndef RemoteSign_h
#define RemoteSign_h

#include "ESP8266WiFi.h"

class RemoteSign {
  private:
    WiFiServer _rsserver();  
  public:
    RemoteSign(long port);
    void begin();
  
};

#endif

and my cpp file:

/*
  RemoteSign.cpp
*/
#include "ESP8266WiFi.h"
#include "RemoteSign.h"

RemoteSign::RemoteSign(long port) {
     WiFiServer  _rsserver(port);
    // _rsserver.begin();    // works here but not in begin()
}

void RemoteSign::begin() {
   _rsserver.begin(); 
}

At this stage all I am trying to get working is wrapping the creation of a WiFiServer object and getting it to begin.

if I place the _rsserver.begin(); inside the RemoteSign constructor. I want it to only begin when I call the begin() method of my class however, so I have void begin()

Compilation fails with:

Arduino: 1.8.7 (Windows 10), Board: "NodeMCU 1.0 (ESP-12E Module), 80 MHz, Flash, 4M (1M SPIFFS), v2 Lower Memory, Disabled, WIFI, Only Sketch, 115200"

sketch\RemoteSign.cpp: In member function 'void RemoteSign::begin()':

RemoteSign.cpp:14:13: error: '((RemoteSign*)this)->RemoteSign::_rsserver' does not have class type

_rsserver.begin();

^

exit status 1
'((RemoteSign*)this)->RemoteSign::_rsserver' does not have class type

I keep thinking that the class definition is defining rsserver as a function instead of an object. But if I say:

WiFiServer _rsserver;

Then the compiler chokes on

RemoteSign::RemoteSign(long port) {

with

no matching function for call to 'WiFiServer::WiFiServer()'

Can you get it to work if all the code is in your .ino file?

If so please post that version.

...R

the all in the .ino version does not define a class. I am trying to work out how to define and uses a class properly in a separate file. I need to use the class in multiple projects and if I make a change to the class I want all the others to inherit those changes.

I dont believe this is an issue with multiple files, I suspect it is to do with instantiation of class objects.

DaleSchultz:
[...] I keep thinking that the class definition is defining rsserver as a function instead of an object. [...]

You are absolutely correct. You are defining a private member function called _rsserver which returns a WiFiServer object.

DaleSchultz:
[...] no matching function for call to 'WiFiServer::WiFiServer()'

That is, because there is no constructor for WifiServer that takes zero arguments. The WifiServer class provides two constructors (source):

WiFiServer(const IPAddress& addr, uint16_t port);
WiFiServer(uint16_t port);

Change your code to

class RemoteSign {
  private:
    WiFiServer* _rsserver;  
  public:
RemoteSign::RemoteSign(long port) {
     _rsserver = new WifiServer(port);
}

void RemoteSign::begin() {
   _rsserver->begin(); 
}

When you want to allocate the _rsserver statically you need to make it static instead of a pointer and initialize it outside the class with WifiServer RemoteSign::_rsserver(port);

EDIT: Your code for constructing a new RemoteSign instance does not work as intended:

RemoteSign::RemoteSign(long port) {
     WiFiServer  _rsserver(port);
}

This will instantiate a local variable called _rsserver, that has nothing to do with your member variable this->_rsserver. After the constructor finishes, the destructor of your local WifiServer will be called.

Thanks LightuC, this seems to be progress....

I now have:

class RemoteSign {
  private:
    WiFiServer* _rsserver;  
  public:
    RemoteSign(long port);
    void begin();
};

and

RemoteSign::RemoteSign(long port) {
      _rsserver = new WifiServer(port);
}
void RemoteSign::begin() {
   _rsserver->begin();
}

But the line creating the new object trips up the compiler with:

RemoteSign.cpp:10:23: error: expected type-specifier before 'WifiServer'

_rsserver = new WifiServer(port);

^

RemoteSign.cpp:10:23: error: expected ';' before 'WifiServer'

There seems to be an issue with other code, I've tested my solution and it compiles just fine. Could you post the complete .h and .cpp files again?

sure...

/*
  RemoteSign.h - defines RemoteSign class
*/
  
#ifndef RemoteSign_h
#define RemoteSign_h

#include "ESP8266WiFi.h"

class RemoteSign {
  private:
    WiFiServer* _rsserver;  
  public:
    RemoteSign(long port);
    void begin();
};

#endif
/*
  RemoteSign.cpp
*/
#include "ESP8266WiFi.h"
#include "RemoteSign.h"

//WifiServer RemoteSign::_rsserver();

RemoteSign::RemoteSign(long port) {
      _rsserver = new WifiServer(port);
}
void RemoteSign::begin() {
   _rsserver->begin();
}

Sorry, I missspelled WiFiServer in my example (Wifi instead WiFi):

_rsserver = new WiFiServer(port);

should fix the issue.

wonderful, yes thanks it now compiles for me also....

now you lost me a bit on the static declaration so that I can use the rsserver object outside of the constructor...

as you can see I added your suggestion in the cpp file but even with the correct casing I am not sure how to declare it statically.

Thanks so much for this help, more Karma will be added when I am allowed to add more after na hour!

For embedded applications, dynamic memory allocation is often unwanted, because it is hard to predict and might lead to catastrophic failures of the system. Because of this, in most embedded applications, variables will be declared statically (meaning the memory will be allocated at compile-time).

To do this you need to change the type of your _rsserver variable from a pointer (WiFiServer*) to a static variable (static WiFiServer). Because the variable will be initialized at compile time, you need to provide the initialization outside of the class (like a global variable).

class RemoteSign {
  private:
    static WiFiServer _rsserver;  
[...]
}

and in your *.cpp file

WiFiServer RemoteSign::_rsserver(1234);

Note: When instanciating more than one object of class RemoteSign, the _rsserver variable will be shared between them. Also you have to provide a constant argument to the initialization (maybe the WiFiServer provides a SetPort() function or something to change the port at runtime.

thanks again, I will work through that carefully. It certainly all makes sense. I think I can probably live with the constant port number anyway since I will only have one listener per device.

Another (better) way would be to use an initializer list:

class RemoteSign {
  private:
    WiFiServer _rsserver;  
  public:
    RemoteSign(long port) : _rsserver(port)
    {
      
    }
[...]
}

This way, the variable is not shared between the instances and you can pass the port variable.

in an attempt to keep taking baby steps, I decided to try the static object and fixed port. So I removed the port parameter to the class.

The .cpp and .h files are ok but when I try to actually call .begin() the wheels fall of again...

class RemoteSign {
  private:
    static WiFiServer _rsserver;  
  public:
    RemoteSign();
    void begin();
};

// cpp file


WiFiServer RemoteSign::_rsserver(50601);  


RemoteSign::RemoteSign() {
   // also tried   WiFiServer RemoteSign::_rsserver(50601);  here
}
void RemoteSign::begin() {

  _rsserver.begin();
}

// ino sketch
  // start server listening 
 RemoteSign rs();  
 rs.begin();

on the rs.begin() line I get:

error: request for member 'begin' in 'rs', which is of non-class type 'RemoteSign()'

rs.begin();

^

exit status 1
request for member 'begin' in 'rs', which is of non-class type 'RemoteSign()'

Bewildering.

That is because, again, from the compilers point of view you are declaring a function prototype. In C++, when creating an instance of a class, that has a default constructor, you must omit the parentheses.

RemoteSign rs; // this invokes the RemoteSign() constructor
// RemoteSign rs(50601); // this invokes the RemoteSign(long) constructor
rs.begin();

ah yes thanks!
I can actually connect to the ES08266 again now! I think I will now be able to add back in all the other functionality again. (In small steps)

You have been exceedingly helpful.