devxlogo

A Guide to C++ and C Interoperability

A Guide to C++ and C Interoperability

orting C code to C++ is no big deal: change the source file’s extension from .c to .cpp and recompile. By contrast, porting C++ source code and binaries to C requires more elbow grease and midnight oil. The following sections show how the two languages can share a single declaration of a class with member functions and other C++ goodies. Then you’ll see how C apps can utilize state-of-the-art C++ algorithms and containers with a little help from the linker.


You have a class that declares member functions but you want to use it in a C app as well. Additionally, you want to simplify the implementation of certain tasks in your C app by utilizing C++ algorithms and containers. How do you accomplish these goals?


Use conditional compilation to hide C++ features from a C compiler and wrap compiled C++ code in a C-callable function.

UDT Sharing
The simplest form of sharing a declaration of a user-defined type (UDT) between C and C++ is sticking to the lowest common denominator of C. The following C struct can be used as-is in a C++ program:

typedef struct String{ char * pstr; int size;} String;

C doesn’t recognize the keyword class so you must stick to struct. In addition, using the same token for the tag (the name before the {) and the typedef name (the name following the }) guarantees that the following code will compile successfully in both languages:

//works both in C and C++char buff[100];String mystr; //no 'struct' before Stringmystr.pstr=&buff[0];mystr.size=sizeof(buff);

And yet, C++ users would expect to find member functions, conversion operators, and overloaded operators in String. The fact that these C++ features will not be usable in C is no excuse for depriving C++ users of these conveniences. Your goal is to have a single declaration of String that will be shared both by C and C++ users, without sacrificing essential C++ features. How do you accomplish this feat?

A C++ Overhaul
All data members in String must remain implicitly public. protected and private are out of the question as they might break binary compatibility between C and C++. Additionally, all C++ features must appear inside an #ifdef-endif block like this:

typedef struct String{>#ifdef __cplusplus //anything you put here will be visible only from C++#endif char * pstr; int size;} String;

Here’s the augmented String struct:

typedef struct String{#ifdef __cplusplus char * getpstr() {return pstr;} void init(int sz=0) {pstr=sz? new char[sz]:0;                       size=sz;} operator const char* () const {return pstr;} int getsize() const {return size;} char& operator[] (int id) {return pstr[id];} const char& operator[] (int id) const {return pstr[id];}#endif char * pstr; int size;} String;

C++ users can now use String as an ordinary C++ class with member functions, overloaded operators and conversion operators:

String s={0}; //zero initialize all data memberss.init(10);s[5]='a';cout

C users on other hand will keep using String like this:

#include String s1;s1.pstr=(char *) malloc(10);s1.size=10;printf("%s
", s1.pstr);printf("%d
", s1.size);free(s.pstr);

Unbelievable as it may seem, the two code listings above use the same String type.

ABI Issues
You probably noticed that String doesn't define a constructor, a destructor or an assignment operator. This wasn't an oversight. In spite of its C++ interface, String still remains a POD type, which means that its binary layout is identical in C and C++. Indeed, you're limited with respect to the C++ features that you may add to String but you still have enough leeway to make C++ users content. To summarize, adding any of the following features to String will make it a non-POD type:

  • virtual functions
  • user-defined destructor
  • user-defined assignment operator
  • user-defined copy constructor
  • user-defined constructor
  • reference members
  • data members that are pointers to members
  • base class(es)
  • non-POD member objects

Using C++ Functionality in C
Thus far, this article has focused on the source code level of sharing declarations between the two languages. You can also utilize compiled C++ functionality in C by wrapping C++ code in an intermediary layer. Here's an example.

Suppose you're maintaining a C application that reads char* strings and stores them as char**. Your task is to sort these strings alphabetically and print them onscreen. Instead of wading through strcmp(), qsort() and pointer mayhem, you want an elegant, STL-based solution. It's doable.

First, declare an intermediary function as extern "C". This function will be compiled as a C++ function but it's callable from C, too. C doesn't recognize the extern "C" linkage specification. Therefore, use #ifdef-#endif to hide this part from the C compiler:

// func.h#ifdef __cplusplus extern "C" #endifvoid func (char **s, int n);// end of func.h

func() is implemented in a .cpp source file that's compiled separately. Here's a typical implementation that uses all sorts of STL conveniences:

//func.cpp#include #include #include #include #include extern "C" void func (char **s, int n){ vector vs; for (int i=0; i(cout,"
"));}

Compile func() as usual. Next, add its declaration to your C application:

//main.c#include "func.h"int main(){ char *strings[4]={{"bcd"},{"abc"},{"efg"},{"aaa"}}; func(strings, 4);}

A C compiler doesn't care how func() is implemented; it only inserts a call to a function with this name into main.obj. It's the linker's job to resolve the call to the same function defined in func.obj. Since the linker sees only the .obj files, the extern "C" linkage specification is mandatory. It guarantees that both C and C++ generate the same symbol for func(). Without extern "C", you'll get a linkage error.

Exceptions
Can func() use any C++ feature? Yes, almost. Virtually all implementations nowadays use a single runtime library for C and C++ so yanking compiled C++ code into C should "just work". There's one caveat, though: C functions do not propagate C++ exceptions. If func() throws an exception, your application will behave unpredictably. To avoid this, func() must handle all exceptions, if any, locally.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist