2

Suppose I have mixed C++ and C code. Inside the C++ code, I have a struct in my_cpp_code.h:

typedef struct {
    std::string some_string;
    int some_int;
    bool some_bool;
} CppStruct;

Having another source file, say C my_c_code.c. Is it possible to access the some_string member of the struct in this file as a const char *?

Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
Matt
  • 435
  • 4
  • 17

4 Answers4

4

A C compiler wouldn't understand the definition of CppStruct. So no, it is not possible for a C program to access CppStruct::some_string. But you can provide a set of C++ functions exposing a C-compatible signature to read/write from/to CppStruct::some_string.

// === compat.h ===
struct CppStruct;
#ifdef __cplusplus
extern "C" {
#endif
    CppStruct* get_the_damn_struct();
    int set_some_string(CppStruct*, char* data, size_t len);
    int get_some_string(CppStruct*, char* data, size_t* len);
#ifdef __cplusplus
}
#endif

// === compat.cpp ===
// Implementation

// === c_program.c ===
#include <compat.h>

// usage (e.g.:
CppStruct* wrapper = get_the_damn_struct();
char* data = "Hello, World!";
if (0 != set_some_string(wrapper, data, strlen(data)) {
    // error handling
}
//)
YSC
  • 38,212
  • 9
  • 96
  • 149
  • If you don't typedef the initial struct you can use a forwarding defintion of the struct in the C header and pass a struct * to the functions. so you don't need to cast in the Cpp Code and you don't get any issues when including both the C header and the C++ header. But this is a matter of taste. C++ header: `struct CppStruct { std::string some_string; };` C header: `struct CppStruct; int get_some_string(struct CppStruct*, char* data, size_t len); ` – Christian T May 02 '19 at 17:13
3

Not directly no.

One solution would be to have an opaque pointer on the C language side (a bit like FILE*), that you pass to a function which is implemented on the C++ side (and is compiled with C linkage: consult your compiler documentation on how to do that). That function can have a const char* return type since both C and C++ understand that. That function can then reintepret_cast that opaque pointer (p, say) to an instance of your struct, then return p->some_string.c_str() back.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
2

You can have one C++ function that returns a pointer to such a struct-object, yet as void*, and you can have another C++ function that takes such a void*-pointer to a struct object and - after having casted it back - accesses the member's c_str-function. Declare these functions a pure C-functions in a separate header file (not mixed with C++ code):

Example:

// my_cpp_bridge.h:
void* getAStructPtr();
const char* getStringMemberPtr(void* structPtr);

// my_cpp_bridge.cpp:
#include "my_cpp_code.h"
#include "my_cpp_bridge.h"
void* getAStructPtr() {
   static CppStruct someSample;
   someSample.some_string = "Hello world!";
   return (void*)&someSample;
}
const char* getStringMemberPtr(void* structPtr) {
   const CppStruct *structPtr = reinterpret_cast<CppStruct*>(structPtr);
   return structPtr->c_str();
}

// my_test.c:
#include "my_cpp_bridge.h"
int main() {
   void* s = getAStructPtr();
   const char* content = s->getStringMemberPtr();
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
0

Your question might be an "XY problem" (you ask question 'X' but really you'd do better to ask 'Y' if only you knew it might be a better option). This answer poses an alternative that technically doesn't answer the question that you asked, but it can solve the problem that prompted the question:

Compile your legacy C code as C++ and this isn't even an issue.

If you must keep your C code pure C, then any of the other answers are what you're looking for. But if, like me, you're working on a project that uses legacy C code mixed with new C++ code, then perhaps you should start moving the legacy code away from pure C. Of course if you need to keep the legacy code "pure C" because it's shared with a separate project that's still compiled as strict C then this isn't an option, but let's suppose that's not a requirement. I've done this twice now on two large firmware projects with excellent results.

If you compile your C code as C++, then you can start to take advantage of C++ features even in your legacy "C" code. One way to do that is to find a compiler option to treat C as C++ code; e.g. Visual Studio's /TP option (see this SO post for more info). For GCC, change your makefile to use g++. Or, if there is no such option, rename your .c files with .cpp extensions.

Once you're compiling C code as C++, you may run into other minor problems, but those shouldn't be too difficult to work through, and the long-term benefits will outweigh the initial hassle. See this SO post for more information about that. Perhaps the main issue is that if you use malloc(), you will need to typecast the return value before assigning it to a typed pointer.

Once you're compiling your legacy code as C++, then you can do everything in your legacy code that you can do in your new C++ code, so the issue posed by your original question is no longer an issue at all. You #include <string> in your C file and then just call myCppStruct.some_string.c_str() any time you need a raw const char* pointer (e.g. to call other legacy C functions that take const char* parameters). No need for a utility function to do it for you.

phonetagger
  • 7,701
  • 3
  • 31
  • 55
  • "_I can't imagine why some coward would downvote without providing any feedback_" this is a shared and recurrent problem ^^ I am not one of the down voters, but probably you received DV because you are out of subject. The OP ask "how to mix C and C++" and you answer "do not mix and do only C++" ;-) – bruno May 02 '19 at 17:33
  • @bruno I guess I figured this might be an "XY problem" (OP asks 'X' but if they knew better they'd ask a different question, 'Y'). My answer is an alternative that technically doesn't answer the question that was asked, but it can solve the problem that prompted the question. – phonetagger May 02 '19 at 17:50
  • I think this is a valuable contribution to the discussion and perfectly fine to point this out since it offers an alternative solution under some circumstances. I don't get the down votes either, especially since your answer is quite comprehensive and well motivated. Thanks for writing this down! – Matt May 02 '19 at 19:00