Author Topic: Including C++ library in a C project  (Read 2054 times)

0 Members and 1 Guest are viewing this topic.

Offline krish2487Topic starter

  • Frequent Contributor
  • **
  • Posts: 500
  • Country: dk
Including C++ library in a C project
« on: June 26, 2020, 04:33:02 pm »
Hello,
I apologize if it seems like a common or stupid question. I am trying to use a C++ library in a C project.
A little bit of background information - I am working with the arm-none-eabi-gcc compiler, libopencm3 HAL for the STM32 microcontroller and ArduinoJSON library and makefiles.
I am also using ceedling/unity/cmock framework to test the C code ( This is important as I cannot migrate to using a C++ compiler but test the code using a C compiler).
I would like to use the ArduinoJSON library (which is a C++ library) to create / parse some JSON structures.
This is the link to the ArduinoJSON library I am trying to use.
https://github.com/bblanchon/ArduinoJson

My data structure file looks like so

Code: [Select]
#ifndef DATASTRUCTURES_H
#define DATASTRUCTURES_H

#include "exit_codes.h"
#include "ArduinoJson/ArduinoJson.h"
const char* template_POSTResults;
const char* template_RuntimeSettings;
const char* template_SensorPayload;
const char* template_GSMPayload;

EXIT_STATUS InitJSON(const char* template, DynamicJsonBuffer& Buffer);
EXIT_STATUS SetJSONValue(void);

#endif // DATASTRUCTURES_H

Basically in the InitJSON function I would like to take a template to a datastructure, take a reference to a DynamicJsonBuffer, set all the key values to NULL and set it to a known state. However, I am at a loss as to how to do it.

Trying to compile the code with the basic boilerplate gives the following result ( and understandably so).

Code: [Select]
  CC    main.c
  CC    appLayer.c
In file included from ../libs/ArduinoJson/ArduinoJson.h:5,
                 from DataStructures.h:6,
                 from appLayer.c:3:
../libs/ArduinoJson/src/ArduinoJson.h:15:2: error: #error ArduinoJson requires a C++ compiler, please change file extension to .cc or .cpp
 #error ArduinoJson requires a C++ compiler, please change file extension to .cc or .cpp
  ^~~~~
In file included from appLayer.c:3:
DataStructures.h:14:44: error: unknown type name 'DynamicJsonBuffer'
 EXIT_STATUS InitJSON(const char* template, DynamicJsonBuffer& Buffer);
I am no great shakes at C++ ( For that matter I am not great shakes at C), but I understand that the traditional way would be to tinker with the C++ library to make it compatible with C compilers. However I would prefer to avoid such an approach. I would like to keep the github link to the library untouched as it is likely to be used as a git submodule in the project. Hence no changes to the library if possible.
I would be glad and thankful if someone more experienced can guide me how to use a C++ library in a C source.

Thanks in advance.
If god made us in his image,
and we are this stupid
then....
 

Offline greenpossum

  • Frequent Contributor
  • **
  • Posts: 408
  • Country: au
Re: Including C++ library in a C project
« Reply #1 on: June 26, 2020, 05:32:53 pm »
Look up the extern "C" declaration way to give a C++ library a C API. Naturally you cannot call class methods directly from C, only indirectly via C++.
 
The following users thanked this post: krish2487

Offline krish2487Topic starter

  • Frequent Contributor
  • **
  • Posts: 500
  • Country: dk
Re: Including C++ library in a C project
« Reply #2 on: June 27, 2020, 10:36:46 am »
@greenpossum.
Thank you for the help. If I am understanding you correctly, you are suggesting I create a translation layer to provide C interfaces to a C++ library.
without touching the library itself.

I googled for a bit, and as far I can understand it has to do with name mangling that C++ does for overloading purposes, which a C compiler does not support.
This is specifically what I looked at.
https://isocpp.org/wiki/faq/mixing-c-and-cpp

If I may trouble you a little more, can you briefly explain how I should approach doing so for the problem I am facing.. maybe with a simple example.

In the ArduinoJson library, it essentially includes the ArduinoJSON.hpp file in the src directory whose contents are the following.

Code: [Select]
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License

#pragma once

#include "ArduinoJson/Configuration.hpp"

#if !ARDUINOJSON_DEBUG
#ifdef __clang__
#pragma clang system_header
#elif defined __GNUC__
#pragma GCC system_header
#endif
#endif

#include "ArduinoJson/Array/ArrayRef.hpp"
#include "ArduinoJson/Object/ObjectRef.hpp"
#include "ArduinoJson/Variant/VariantRef.hpp"

#include "ArduinoJson/Document/DynamicJsonDocument.hpp"
#include "ArduinoJson/Document/StaticJsonDocument.hpp"

#include "ArduinoJson/Array/ArrayImpl.hpp"
#include "ArduinoJson/Array/ElementProxy.hpp"
#include "ArduinoJson/Array/Utilities.hpp"
#include "ArduinoJson/Collection/CollectionImpl.hpp"
#include "ArduinoJson/Object/MemberProxy.hpp"
#include "ArduinoJson/Object/ObjectImpl.hpp"
#include "ArduinoJson/Variant/VariantAsImpl.hpp"
#include "ArduinoJson/Variant/VariantImpl.hpp"

#include "ArduinoJson/Json/JsonDeserializer.hpp"
#include "ArduinoJson/Json/JsonSerializer.hpp"
#include "ArduinoJson/Json/PrettyJsonSerializer.hpp"
#include "ArduinoJson/MsgPack/MsgPackDeserializer.hpp"
#include "ArduinoJson/MsgPack/MsgPackSerializer.hpp"

#include "ArduinoJson/compatibility.hpp"

namespace ArduinoJson {
typedef ARDUINOJSON_NAMESPACE::ArrayConstRef JsonArrayConst;
typedef ARDUINOJSON_NAMESPACE::ArrayRef JsonArray;
typedef ARDUINOJSON_NAMESPACE::Float JsonFloat;
typedef ARDUINOJSON_NAMESPACE::Integer JsonInteger;
typedef ARDUINOJSON_NAMESPACE::ObjectConstRef JsonObjectConst;
typedef ARDUINOJSON_NAMESPACE::ObjectRef JsonObject;
typedef ARDUINOJSON_NAMESPACE::Pair JsonPair;
typedef ARDUINOJSON_NAMESPACE::PairConst JsonPairConst;
typedef ARDUINOJSON_NAMESPACE::String JsonString;
typedef ARDUINOJSON_NAMESPACE::UInt JsonUInt;
typedef ARDUINOJSON_NAMESPACE::VariantConstRef JsonVariantConst;
typedef ARDUINOJSON_NAMESPACE::VariantRef JsonVariant;
using ARDUINOJSON_NAMESPACE::BasicJsonDocument;
using ARDUINOJSON_NAMESPACE::copyArray;
using ARDUINOJSON_NAMESPACE::DeserializationError;
using ARDUINOJSON_NAMESPACE::deserializeJson;
using ARDUINOJSON_NAMESPACE::deserializeMsgPack;
using ARDUINOJSON_NAMESPACE::DynamicJsonDocument;
using ARDUINOJSON_NAMESPACE::JsonDocument;
using ARDUINOJSON_NAMESPACE::measureJson;
using ARDUINOJSON_NAMESPACE::serialized;
using ARDUINOJSON_NAMESPACE::serializeJson;
using ARDUINOJSON_NAMESPACE::serializeJsonPretty;
using ARDUINOJSON_NAMESPACE::serializeMsgPack;
using ARDUINOJSON_NAMESPACE::StaticJsonDocument;

namespace DeserializationOption {
using ARDUINOJSON_NAMESPACE::Filter;
using ARDUINOJSON_NAMESPACE::NestingLimit;
}  // namespace DeserializationOption
}  // namespace ArduinoJson



So If I now have to provide a C interface to one of the members like "BasicJSONDocument" then how should I do so?? and would the process remain the same for members declared in subdirectories as well??

Thank you again for the pointers. I appreciate your patience and the effort it takes to respond and handhold others. :-)
If god made us in his image,
and we are this stupid
then....
 

Offline greenpossum

  • Frequent Contributor
  • **
  • Posts: 408
  • Country: au
Re: Including C++ library in a C project
« Reply #3 on: June 27, 2020, 11:00:24 am »
You cannot avoid compiling the library with a C++ compiler that's compatible with the C compiler, e.g. GNU g++/gcc. But by adding the extern "C" declarations you provide interface points to the C++ code. If C++ classes are involved, then you will need to add some C++ code that interfaces between C++ and C. So code modification of the library is required.

Oh and you'll also need to link with the C++ linker because C++ object files are involved.

Perhaps it might be better if you looked for a C library.

I should add that usually extern "C" is used to call C libraries from C++. It can also go the other way but the whole build process gets taken over by the C++ so it's more complicated.
« Last Edit: June 27, 2020, 11:17:02 am by greenpossum »
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1208
  • Country: pl
Re: Including C++ library in a C project
« Reply #4 on: June 27, 2020, 12:48:03 pm »
As greenpossum has said, the C++ library or anything that uses C++ headers must(1) be compiled using a C++ compiler. From that you obtain an object file that you can then use link with your C program(2), if you it’s built in the right way.

Rule #0: be clear where is your C domain and where is your C++ domain. Despite being perceived as such, C++ is not a superset of C. Both languages have a huge common part, but neither completely includes the other. The easiest way is to simply keep them separate.

What you need is:
Code: [Select]
  ,---------.                       .-----------.
  | C++ lib |                       | C wrapper |
  | headers +----------.   .--------+  headers  |
  `----+----'          |   |        `-----+-----'
       |               |   |              |
  ,----V----.   .------V---V--.      .----V-----.
  | C++ lib |   | C++ wrapper |      |  Your C  |
  | sources |   |  exposing   |      |  sources |
  `----+----'   | C functions |      `----+-----'
       |        `------+------'           |
       |               |                  |
    ~~~V~~~~~~~~~~~~~~~V~~~       ~~~~~~~~V~~~~~~~~
          C++ compiler                C compiler
    ~~~~~~~~~~~+~~~~~~~~~~~       ~~~~~~~~+~~~~~~~~
               |                          |
          .----V-------------.     .------V----------.
          | library  ,---------.   |   Your object   |
          | wrapper  | exposed |   | (wanting C API) |
          |          |  C API  |   `------+----------'
          |          `---------'          |
          `---------+--------'            |
                    |                     |
              ~~~~~~V~~~~~~~~~~~~~~~~~~~~~V~~~~~~
                       L  i  n  k  e  r
              ~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~
                               |
                       ,-------V------.
                       | Final binary |
                       `--------------'
The “C wrapper headers” is the only part that must be compatible with both C and C++. For wrapper headers this is easy to achieve. Typically that contains only:
  • struct declarations (not definitions).
  • Function declarations with extern C linkage only. Keep them as simple as possible, with no fancy features. Forget about restrict, about declaring pointer arguments with array notation etc.

Then, in the C wrapper source, you define functions. They are the only interface between your C code and the C++ library. In your C code you call those functions to interact with the library. And that’s all.

Now, there are some traps. Since this is about microcontrollers, some of those are not important, but be aware of a few things.
  • Standard inut/output of those languages, namely the stdio (C) and iostream (C++), is by default synchronized between both implementations and mixing calls to both should not yield unexpected results. However, that synchronization may be disabled on the C++ side. The C side may also directly interface with the system to perform I/O on standard streams, bypassing both libraries. If both your code and the library does I/O over standard input/output, be aware of the risk: you may be standing on the middle of a minefield.
  • Threading may or may not be compatible between both ends. Unless it is clear that both sides use the very same method — e.g. both interface directly POSIX threads — be careful on what you do and when. What seems reasonable is making the wrapper take care about proper synchronization. But it comes with caveats. First, performance may seriously hit due to excessive locking. Second: if both sides use the same threading API you may end with either a deadlock or acquiring a lock may fail unexpectedly.
  • Object ownership woes. Any object created on the C++ side must be deleted using C++ code. Any object created on the C side must be freed using C code. While delete/delete/free/realloc may appear interchangeable for simple cases on most platforms, this behaviour is not guaranteed by either of the languages. It is, however, guaranteed that free and realloc will not work for proper objects created on C++ side.
  • If both compilers are from the same family (e.g. gcc, clang, …) this will not be the issue. But in general ABI may be different between C and C++. This is unexpected, but if you ever experience that, consider using either the leastN types or exact-width types. For that purpose use <stdint.h> (not <cstdint>) in the wrapper headers. C++ guarantees that <stdio.h> must be present and all definitions in it will be in the global namespace.


____
(1) Unless the library explicitly tells you that it can be used with a C compiler, but in the past 15 years I came across only a few of those. This is very rare, as it requires excessive effort from the authors to make it compatible with both languages.
(2) In fact you may with many other languages. This is how PHP extensions work, how you interface native code with JNI or JNA in Java, how it was done in API imports VB6 etc.
« Last Edit: June 27, 2020, 02:11:48 pm by golden_labels »
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8168
  • Country: fi
Re: Including C++ library in a C project
« Reply #5 on: June 27, 2020, 01:46:16 pm »
The world is full of JSON libraries in C.

Are you really really sure you want to use C++ JSON library in a C program?

The fact that anyone makes a C++ library is likely because they want the library user to be able to use features only available in C++, not C. For example, it seems they have overloaded the indexing operations to allow ["indexing with a string"]. Neat, but totally undoable in C in any similar way.

Hence, you need to produce a full wrapper layer that constructs a new C API, defined by you, which you can access from your C program, and then this wrapper uses the C++ library.

Instead, I recommend you look at C JSON libraries which have a C API already. Or, if you like to use said library or other fancy C++ features you don't have in C, why not use C++ for the project?

The opposite is done all the time; i.e., C libraries are equipped with wrappers to other languages.
« Last Edit: June 27, 2020, 01:50:15 pm by Siwastaja »
 

Offline krish2487Topic starter

  • Frequent Contributor
  • **
  • Posts: 500
  • Country: dk
Re: Including C++ library in a C project
« Reply #6 on: June 28, 2020, 05:01:03 pm »
You cannot avoid compiling the library with a C++ compiler that's compatible with the C compiler, e.g. GNU g++/gcc. But by adding the extern "C" declarations you provide interface points to the C++ code. If C++ classes are involved, then you will need to add some C++ code that interfaces between C++ and C. So code modification of the library is required.

Oh and you'll also need to link with the C++ linker because C++ object files are involved.

Perhaps it might be better if you looked for a C library.

I should add that usually extern "C" is used to call C libraries from C++. It can also go the other way but the whole build process gets taken over by the C++ so it's more complicated.


It makes more sense now.. Thank you! :-)

As greenpossum has said, the C++ library or anything that uses C++ headers must(1) be compiled using a C++ compiler. From that you obtain an object file that you can then use link with your C program(2), if you it’s built in the right way.

Rule #0: be clear where is your C domain and where is your C++ domain. Despite being perceived as such, C++ is not a superset of C. Both languages have a huge common part, but neither completely includes the other. The easiest way is to simply keep them separate.

What you need is:
Code: [Select]
  ,---------.                       .-----------.
  | C++ lib |                       | C wrapper |
  | headers +----------.   .--------+  headers  |
  `----+----'          |   |        `-----+-----'
       |               |   |              |
  ,----V----.   .------V---V--.      .----V-----.
  | C++ lib |   | C++ wrapper |      |  Your C  |
  | sources |   |  exposing   |      |  sources |
  `----+----'   | C functions |      `----+-----'
       |        `------+------'           |
       |               |                  |
    ~~~V~~~~~~~~~~~~~~~V~~~       ~~~~~~~~V~~~~~~~~
          C++ compiler                C compiler
    ~~~~~~~~~~~+~~~~~~~~~~~       ~~~~~~~~+~~~~~~~~
               |                          |
          .----V-------------.     .------V----------.
          | library  ,---------.   |   Your object   |
          | wrapper  | exposed |   | (wanting C API) |
          |          |  C API  |   `------+----------'
          |          `---------'          |
          `---------+--------'            |
                    |                     |
              ~~~~~~V~~~~~~~~~~~~~~~~~~~~~V~~~~~~
                       L  i  n  k  e  r
              ~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~
                               |
                       ,-------V------.
                       | Final binary |
                       `--------------'
The “C wrapper headers” is the only part that must be compatible with both C and C++. For wrapper headers this is easy to achieve. Typically that contains only:
  • struct declarations (not definitions).
  • Function declarations with extern C linkage only. Keep them as simple as possible, with no fancy features. Forget about restrict, about declaring pointer arguments with array notation etc.

Then, in the C wrapper source, you define functions. They are the only interface between your C code and the C++ library. In your C code you call those functions to interact with the library. And that’s all.

Now, there are some traps. Since this is about microcontrollers, some of those are not important, but be aware of a few things.
  • Standard inut/output of those languages, namely the stdio (C) and iostream (C++), is by default synchronized between both implementations and mixing calls to both should not yield unexpected results. However, that synchronization may be disabled on the C++ side. The C side may also directly interface with the system to perform I/O on standard streams, bypassing both libraries. If both your code and the library does I/O over standard input/output, be aware of the risk: you may be standing on the middle of a minefield.
  • Threading may or may not be compatible between both ends. Unless it is clear that both sides use the very same method — e.g. both interface directly POSIX threads — be careful on what you do and when. What seems reasonable is making the wrapper take care about proper synchronization. But it comes with caveats. First, performance may seriously hit due to excessive locking. Second: if both sides use the same threading API you may end with either a deadlock or acquiring a lock may fail unexpectedly.
  • Object ownership woes. Any object created on the C++ side must be deleted using C++ code. Any object created on the C side must be freed using C code. While delete/delete/free/realloc may appear interchangeable for simple cases on most platforms, this behaviour is not guaranteed by either of the languages. It is, however, guaranteed that free and realloc will not work for proper objects created on C++ side.
  • If both compilers are from the same family (e.g. gcc, clang, …) this will not be the issue. But in general ABI may be different between C and C++. This is unexpected, but if you ever experience that, consider using either the leastN types or exact-width types. For that purpose use <stdint.h> (not <cstdint>) in the wrapper headers. C++ guarantees that <stdio.h> must be present and all definitions in it will be in the global namespace.


____
(1) Unless the library explicitly tells you that it can be used with a C compiler, but in the past 15 years I came across only a few of those. This is very rare, as it requires excessive effort from the authors to make it compatible with both languages.
(2) In fact you may with many other languages. This is how PHP extensions work, how you interface native code with JNI or JNA in Java, how it was done in API imports VB6 etc.


Thank you for the extremely clear and helpful explanation. I think my understanding is a lot better now.

The world is full of JSON libraries in C.

Are you really really sure you want to use C++ JSON library in a C program?

The fact that anyone makes a C++ library is likely because they want the library user to be able to use features only available in C++, not C. For example, it seems they have overloaded the indexing operations to allow ["indexing with a string"]. Neat, but totally undoable in C in any similar way.

Hence, you need to produce a full wrapper layer that constructs a new C API, defined by you, which you can access from your C program, and then this wrapper uses the C++ library.

Instead, I recommend you look at C JSON libraries which have a C API already. Or, if you like to use said library or other fancy C++ features you don't have in C, why not use C++ for the project?

The opposite is done all the time; i.e., C libraries are equipped with wrappers to other languages.


Well.. its not so much because I want to use.. it is just that I have used it earlier and have some experience with it. However, seeing as the complexity involved with this porting and my use for the library in the first place, I am inclined to take yours and greenpossums and golden_labels advice to looks for a C library. The additional complexity is not adding any use for me in this case. Quite the opposite infact.

I apologize to all for the delay in responding back. This weekend was quite a handful to say the least. I am grateful for all those who responded and gave a more correct approach I must take. So.. back to the drawing board for now. :-)

Thank you again guys! 
If god made us in his image,
and we are this stupid
then....
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf