I have a large program in C that I'd like to use certain C++ objects with such as maps. I followed this post on how to call C++ from C and my two C++ look like this.
///map.cc
#include "map.h"
void* createMap()
{
std::map<int,int> *m = new std::map<int,int>();
return static_cast<void*>(m);
}
void putInMap(int key, int value, void* opaque_map_ptr){
std::map<int,int> *m = static_cast<std::map<int,int>*>(opaque_map_ptr);
m->insert(std::pair<int,int>(key,value));
}
//map.h
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
EXTERNC void* createMap();
EXTERNC void putInMap(int key, int value, void* opaque_map_ptr);
What I'm not clear about is if I do the following call in my C program, how can I be sure my Map exists and how would I make it global? Do I just make the corresponding pointer global?
#include "map.h"
//How do I let everyone use this map
void* ptr = createMap();
//How do I let everyone use this pointer void* ptr = createMap();
Not by declaring a global object with an initializer that is = createMap()
. C doesn't allow the sort of dynamic initialization of globals that C++ has. The pointer must be initialized by a constant expression. And if you want to call createMap()
in C it can only happen after main
is entered, and the function call made explicit.
The usual pattern for this is to provide a function that does lazy initialization and returns a pointer.
void *globalMap() {
static void *ptr = NULL;
if (!ptr)
ptr = createMap();
return ptr;
}
Calling code can then use globalMap()
to obtain the shared pointer to the object. If "object like" syntax is required, then a macro may be used to wrap the function call
#define globalPtr globalMap()
A standard example of this can be seen with errno
.
Another thing that must be mentioned is that I wrote a very naive globalMap()
, seeing as the pointer assignment is not thread safe. It's not impossible to make it thread safe, but the code will be somewhat more complex. You can have it done by the C++ compiler implicitly (C++11 and later). The technique is the so called Meyers Singleton
extern "C" void *globalMap() {
static std::map<int, bool> m;
return &m;
}
And that's it. The C++ compiler will instrument it such that m
is only initialized once and without race conditions.
And one final note, do not forget that you must handle C++ exceptions on the ABI boundary! Making them all fatal with a bit of noexcept
specifiers in the right place is one way, that or convert them to errors that may be returned to C code for inspection. Either way, don't ignore their existence.