Including C++ library in a C project - Page 1
Forum section: electronics / general

author: krish2487
author_rank: Frequent Contributor
timestamp: June 26, 2020, 04:33:02 pm
content: 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/ArduinoJsonMy data structure file looks like soCode:#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_HBasically 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:CC main.cCC appLayer.cIn 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.

author: greenpossum
author_rank: Frequent Contributor
timestamp: June 26, 2020, 05:32:53 pm
content: 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++.

author: krish2487
author_rank: Frequent Contributor
timestamp: June 27, 2020, 10:36:46 am
content: @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-cppIf 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:// 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 ArduinoJsonSo 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. :-)

author: greenpossum
author_rank: Frequent Contributor
timestamp: June 27, 2020, 11:00:24 am
content: 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.

author: golden_labels
author_rank: Super Contributor
timestamp: June 27, 2020, 12:48:03 pm
content: As greenpossum has said, the C++ library or anything that uses C++ headersmust(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:,---------. .-----------.| 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:structdeclarations (not definitions).Function declarations with extern C linkage only. Keep them as simple as possible, with no fancy features. Forget aboutrestrict, about declaring pointer arguments witharray notationetc.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. Whiledelete/delete/free/reallocmay appear interchangeable for simple cases on most platforms, this behaviour is not guaranteed by either of the languages. It is, however, guaranteed thatfreeandreallocwill 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.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 27, 2020, 01:46:16 pm
content: The world is full of JSON libraries in C.Are youreally reallysure 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?Theoppositeis done all the time; i.e., C libraries are equipped with wrappers to other languages.

author: krish2487
author_rank: Frequent Contributor
timestamp: June 28, 2020, 05:01:03 pm
content: Quote from: greenpossum on June 27, 2020, 11:00:24 amIt makes more sense now.. Thank you! :-)Quote from: golden_labels on June 27, 2020, 12:48:03 pmThank you for the extremely clear and helpful explanation. I think my understanding is a lot better now.Quote from: Siwastaja on June 27, 2020, 01:46:16 pmWell.. 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!