Are macros from a library's dependent library accessible to a sketch?

Apologies for the contrived example here, but I’m struggling to understand if there’s a limitation in what I’m trying to do.

I’ve created a custom library (test-base-library) that is included in another custom library (test-library) which then gets included in a sketch (test-sketch-includes-library):

test-base-library (.h/.cpp)
➤ test-library (.h/.cpp)
➤➤ test-sketch-includes-library (.ino)

test-base-library defines macro MY_BLUE in its header.

test-library’s implementation file includes test-base-library.h and its library.properties file lists it as a dependency, so test-library has access to test-base-library’s macro MY_BLUE. All this makes sense to me up to this point.

The test-sketch-includes-library sketch can print the value of test-base-library’s macro MY_BLUE via test-library’s public printStuff() method, but I’m trying to understand if/how MY_BLUE can be accessed from the sketch without:

  1. Creating a getter method in test-library
  2. Adding a separate #include "test-base-library.h" statement to the sketch

Am I wrong to assume that since test-library already includes test-base-library that MY_BLUE should (somehow) be accessible to the sketch?

Thanks in advance for taking the time to help.


// test-base-library.h
#pragma once
#include "Particle.h"
#define	MY_BLUE 0x001F
class Testbaselibrary
{
  public:
    Testbaselibrary();
};

// test-base-library.cpp
#include "test-base-library.h"
Testbaselibrary::Testbaselibrary() {}

// test-library.h
#pragma once
#include "Particle.h"
class Testlibrary
{
public:
  Testlibrary();
  void printStuff();
};

// test-library.cpp
#include "test-library.h"
#include "test-base-library.h"
Testlibrary::Testlibrary() {}
void Testlibrary::printStuff()
{
  Serial.println(MY_BLUE); // expected output: 31
}

// test-sketch-includes-library.ino
#include "test-library.h"
Testlibrary testlibrary;
void setup() {
  Serial.begin(9600);
}
void loop() {

  // this works:
  // -----------------------------------------------------------------
  testlibrary.printStuff(); // prints 31

  // these attempts to access MY_BLUE from test-base-library (which is
  // the base for test-library) all fail:
  // -----------------------------------------------------------------
  // Serial.println(MY_BLUE); // error: 'MY_BLUE' was not declared in this scope
  // Serial.println(testlibrary.MY_BLUE); // error: 'class Testlibrary' has no member named 'MY_BLUE'
  // Serial.println(Testbaselibrary::MY_BLUE); // error: 'Testbaselibrary' has not been declared
  // Serial.println(Testlibrary::Testbaselibrary::MY_BLUE); // error: 'Testlibrary::Testbaselibrary' has not been declared
  // Serial.println(Testlibrary::MY_BLUE); // error: 'MY_BLUE' is not a member of 'Testlibrary'

  // QUESTION: is it possible to access MY_BLUE from test-base-library
  //           without #including it again in this file since test-library
  //           already #includes it?

  delay(1000);
}

An include statement doesn’t do much else than copy the text of the included file into the file where it is included (more nuance in my next post further down :blush:)
When you then include that “expanded” file into another file the same thing happens again. So you will end up with the entire code of the first header plus the entire code of the second header copied into this file.
And so on.

But that is only true for files that directly or indirectly get included via the include statement. Code that resides elsewhere (e.g. in a .cpp) will not be accessible that way.

However, #define does not create constants.
You also need to consider the intent of #pragma once - this guards against including already present code again.

1 Like

Thanks @ScruffR . My bad on thinking that #define directive was creating a constant (I’ve updated the post title and body to use ‘macro’ instead), and your point on using #pragma carefully makes sense.

So I’m thinking that the best way to handle this then is to just #include the test-base-library in the sketch so MY_BLUE is available for use there. All I’m hoping to accomplish is keeping the code as DRY as possible.

I appreciate your time.

Have you tried creating a proper constant?

const uint32_t MY_BLUE = 0x001F;

This way I’d suspect that constant will be available to the top level code too.

#define lines are not copied over to the “compile file” but are only directives to the preprocessor how to modify the file it is currently working on before it’s handed to the compiler.
With each #include statement you must imagine a new, independent instance of the PP to be spun up recursively. So the #define is only known in that “dependent” instance but not in the instance that invoked it.

Proper constants on the other hand will be present as they are no business for the preprocessor and have to be copied over as they are for the compiler to do its job.

Hm :thinking:. @ScruffR, I tried testing your suggestion but I still can’t seem to get a proper constant passed through the intermediary library so it’s accessible from the sketch.

Please let me know if I implemented your suggestion incorrectly. Thanks.

// test-base-library.h
#include "Particle.h"
const uint32_t MY_BLUE = 0x001F; // define MY_BLUE as constant
class Testbaselibrary
{
  public:
    Testbaselibrary();
};

// test-base-library.cpp
#include "test-base-library.h"
Testbaselibrary::Testbaselibrary() {}

// test-library.h
#include "Particle.h"
class Testlibrary
{
  public:
    Testlibrary();
};

// test-library.cpp
#include "test-library.h"
#include "test-base-library.h"
Testlibrary::Testlibrary() {}

// test-sketch-includes-library.ino
#include "test-library.h"
Testlibrary testlibrary;
void setup() {
  Serial.begin(9600);
}
void loop() {
  Serial.println(MY_BLUE); // error: 'MY_BLUE' was not declared in this scope
  delay(1000);
}

This works just fine

// test.ino
#include "derived.h"

void setup() {
    Serial.println(b);
    Serial.println(c);
    Serial.println(d);
}

// derived.h
#pragma once
#include <Particle.h>
#include "intermediate.h"

const int d = 5678;

// intermediate.h
#pragma once
#include <Particle.h>
#include "base.h"

const int c = 3456;

// base.h
#pragma once
#include <Particle.h>

const int b = 1234;

You need the headers you want to derive from included in the headers you refer to.
When you only include them in the .cpp files the actual code of the “parent” headers will only be included there but not in the header you finally include in your main project file.

As I said

Much to my surprise this proprocessor even keeps #define macros down the whole line.

I tried out your example and got the expected results. Lesson learned:

You need the headers you want to derive from included in the headers you refer to.

Good stuff. Thanks @ScruffR :beers:

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.