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"
#endif
void 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 <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <string>
extern "C" void func (char **s, int n)
{
vector<string> vs;
for (int i=0; i<n; i++)
vs.push_back(s[i]);
sort(vs.begin(), vs.end());
//print the contents of vs using a stream iterator
copy(vs.begin(),
vs.end(),
ostream_iterator<string>(cout,"\n"));
}
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.