1

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();
knowads
  • 705
  • 2
  • 7
  • 24
  • 1
    You have to add `extern "C"` to map.cc. `how can I be sure my Map exists and how would I make it global?` Why not just run the code as it is? Does `map.cc` includes `map.h`? Is there no `#include ` in `map.cc`? Why is code outside a function in the last code snippet? Is `EXTERNC` protected with `#ifdef __cplusplus`? – KamilCuk Nov 03 '21 at 12:23
  • *"if I do the following call in my C program"* - But you can't do it. C requires all objects with static storage duration be initialized by constant expressions. – StoryTeller - Unslander Monica Nov 03 '21 at 12:28
  • @KamilCuk Why does extern "C" need to be added to map.cc as well? The rest of the stuff exists in the real file and createMap() is being called from a large function. – knowads Nov 03 '21 at 12:28
  • @StoryTeller - Unslander Monica Can you provide an example of what I need to modify? You can init a C++ object from C right? – knowads Nov 03 '21 at 12:29
  • Oh okay, yes map.h is included in the real file. – knowads Nov 03 '21 at 12:31
  • 1
    This is not about initializing the C++ object, it's about initializing *the pointer*. C doesn't let you call functions in initializers of static objects. – StoryTeller - Unslander Monica Nov 03 '21 at 12:32
  • `extern "C"` does *not* have to be added to `map.cc` if you *do* include `map.h` there as well. Once having been declared as such it remains if this declaration is visible to the implementation... – the macro definition EXTERNC is pretty ugly, though, nicer is as block: `extern "C" { /* your functions here without macro */ }`. Sure, you need to protect with `#ifdef __cplusplus`, as mentioned already, as C does not know of `extern "C"`. – Aconcagua Nov 03 '21 at 12:36
  • Off-topic: `std::pair` is wrong for `std::map`... `auto m = ...` avoids repeating the type information, thus duplicate code (both functions). Maybe you might want to have a typedef (in .cc file). – Aconcagua Nov 03 '21 at 12:39
  • @StoryTeller - Unslander Monica The ptr is just the mechanism to access the object, following the best practice as shown in the other post. I understand you wouldn't malloc a C struct to exist at program load, but I'm not clear on how to let all my files in my program use this same map. – knowads Nov 03 '21 at 12:45
  • 1
    About the global pointer in C: Leave it default-initialised and initialise it correctly as very first thing you do in `main` function. For this, I recommend a header having the extern declaration for as usual and defining the variable in the same file as the main function. – Aconcagua Nov 03 '21 at 12:45
  • You might, as well, try defining the variable in another C++ file (again with associated header), *there* you can initialise by calling a function, though I'm not fully sure how well that goes together with a main function written in C. – Aconcagua Nov 03 '21 at 12:47

1 Answers1

4
//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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458