6

We have a large project with C and C++ code.

For every C++ implementation, apart from the C++ header, we usually have provide a C-header to allow functionality to be available for .c files, also.

So, most of our files look like so:

foo.hpp:

class C { 
    int foo();
};

foo.h:

#ifdef __cplusplus
extern "C" {
    typedef struct C C;  // forward declarations
#else
    class C;
#endif

    int foo( C* );               // simply exposes a member function
    C*  utility_function( C* );  // some functionality *not* in foo.hpp  

#ifdef __cplusplus
}
#endif

foo.cpp:

int C::foo()  { /* implementation here...*/ }

extern "C"
int foo( C *p ) { return p->foo(); }

extern "C"
C*  utility_function ( C* ) { /* implementation here...*/ }

QUESTION:

Suppose I wanted to add a namespace to the class like so:

foo.hpp:

namespace NS {
    class C { 
        int foo();
    };
}

what is the best scheme to follow in the C-headers?

I have considered a few options, but I'm looking for the most elegant, safe and easy to read. Is there a standard way you use?


Here are the options I've considered: (I've ommitted the extern "C" constructs for simplicity)

  • Option 1: fool the compiler by adding some code in each header:

foo.h

#ifdef __cplusplus
    namespace NS { class C; }  // forward declaration for C++ 
    typedef NS::C NS_C;
#else
    struct NS_C;  // forward declaration for C
#endif

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

this adds some complexity to the header, but keeps the implementations unchanged.


  • Option 2: Wrap the namespace with a C-struct:

    Keeps the header simple but makes the implementation more complex:

foo.h

struct NS_C;  // forward declaration of wrapper (both for C++ and C)

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

foo.cpp

namespace NS {
    int C::foo() { /* same code here */ }
}

struct NS_C {     /* the wrapper */
    NS::C *ptr;
};

extern "C" 
int foo( NS_C *p ) { return p->ptr->foo(); }

extern "C"
NS_C *utility_function( NS_C *src ) 
{
    NS_C *out = malloc( sizeof( NS_C ) );  // one extra malloc for the wrapper here...
    out->ptr = new NS::C( src->ptr );
    ...
}

are these the only schemes? Are there any hidden disadvantages in any of these?

Grim Fandango
  • 2,296
  • 1
  • 19
  • 27
  • 2
    This is very obscure. The strange ifdef'ed declaration of `C` smells like undefined behaviour from miles away; but more importantly, I can't see the point: What is a C program going to do with `foo()`? It can't have any useful `C`-pointer around, so what's going on there? – Kerrek SB Nov 14 '11 at 00:33
  • Originally `class C` was implemented as `struct C` in a .c file. The .h file always contained the `foo(C*)` functions. Clients of foo.h never knew the contents of `struct C`. It's encapsulation in 'C' terms. At some point, `struct C` needed to evolve and we changed it into a `class C` and moved its implementation in a .cpp file. No C program did anything with the `C` object, ever. It always referenced the `C-`pointer though. This is typical for a >10 year old code. I hope this clarifies my intent. – Grim Fandango Nov 14 '11 at 09:10
  • In that case I'd simply make the C function take a `void*` argument, and perform a cast in the C++ implementation: `inf foo(void * p) { return reinterpret_cast(p)->foo(); } The "never used" C struct type is just plain confusing and makes things worse. If you want a pointer that you never dereference, use a void pointer. – Kerrek SB Nov 14 '11 at 12:26
  • 1
    Although your proposal is technically correct from the standard's point of view, we widely use the technique I've described because it provides the type safety that `void*` can't. It is also more readable (at least if someone has explained this idiom). We have used both idioms and have settled to this one, because we found we had fewer mistakes and it reads better. – Grim Fandango Nov 14 '11 at 12:49

3 Answers3

3

I find it easier to factor code in a way so that foo.h only contains the bare minimum of C++ specifics while foo.hpp takes care of the gritty bits.

The file foo.h contains the C API and should not be included directly from C++ code:

#ifndef NS_FOO_H_
#define NS_FOO_H_

// an incomplete structure type substitutes for NS::C in C contexts
#ifndef __cplusplus
typedef struct NS_C NS_C;
#endif

NS_C *NS_C_new(void);
void NS_C_hello(NS_C *c);

#endif

The file foo.hpp contains the actual C++ API and takes care of including foo.h into C++ files:

#ifndef NS_FOO_HPP_
#define NS_FOO_HPP_

namespace NS {
    class C {
    public:
        C();
        void hello();
    };
}

// use the real declaration instead of the substitute
typedef NS::C NS_C;
extern "C" {
#include "foo.h"
}

#endif

The implementation file foo.cpp is written in C++ and thus includes foo.hpp, which also pulls in foo.h:

#include "foo.hpp"
#include <cstdio>

using namespace NS;

C::C() {}

void C::hello() {
    std::puts("hello world");
}

C *NS_C_new() {
    return new C();
}

void NS_C_hello(C *c) {
    c->hello();
}

If you do not want to make the C API available to C++ code, you could move the relevant parts from foo.hpp to foo.cpp.

As an example for use of the C API a basic file main.c:

#include "foo.h"

int main(void)
{
    NS_C *c = NS_C_new();
    NS_C_hello(c);
    return 0;
}

This example has been tested with the MinGW edition of gcc 4.6.1 using the following compiler flags:

g++ -std=c++98 -pedantic -Wall -Wextra -c foo.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c main.c
g++ -o hello foo.o main.o

The code assumes that the types NS::C * and struct NS_C * have compatible representation and alignment requirements, which should be the case virtually everywhere, but as far as I know is not guaranteed by the C++ standard (feel free to correct me if I'm wrong here).

From a C language perspective, the code actually invokes undefined behaviour as you're technically calling a function through an expression of incompatible type, but that's the price for interoperability without wrapper structures and pointer casts:

As C doesn't know how to deal with C++ class pointers, the portable solution would be to use void *, which you should probably wrap in a structure to get back some level of type safety:

typedef struct { void *ref; } NS_C_Handle;

This would add unnecessary boilerplate on platforms with uniform pointer representation:

NS_C_Handle NS_C_new() {
    NS_C_Handle handle = { new C() };
    return handle;
}

void NS_C_hello(NS_C_Handle handle) {
    C *c = static_cast<C *>(handle.ref);
    c->hello();
}

On the other hand, it would get rid of the #ifndef __cplusplus in foo.h, so it's actually not that bad, and if you care about protability, I'd say go for it.

Christoph
  • 164,997
  • 36
  • 182
  • 240
  • I'm so grateful for your answer. The undefined behaviour issues you are talking about have nothing to do with the namespace, though, right? I mean these issues are there because we refer to a class pointer as a struct pointer in C, not because of the addition of the namespace. Or are there any extra problems we are introducing here? By the way, how popular is this pattern/scheme you are describing? It looks you haven't just made it up right now, but do many people/groups use it? – Grim Fandango Nov 14 '11 at 11:25
  • @Grim: yes, the UB has nothing to do with namespaces, but rather type compatibility; added a portable solution to my answer; as to the popularity of my factoring scheme: the most common solution is probably to have a single header full of `#ifdef`s, which I don't like at all; I'm a C programmer first, so if I look at a C compatible header file, I don't want to wade through a sludge of C++ code to get at the bits I'm interested in (more...) – Christoph Nov 14 '11 at 12:37
  • if you already have separate headers, it doesn't really matter if you use `#ifdef __cplusplus \n extern "C" {` in the *.h* file or `extern "C" { \n #include` in the *.hpp* file - I just find the latter a bit easier on the eyes... – Christoph Nov 14 '11 at 12:48
1

I don't fully understand what you're trying to do, but this may help:

If you want it still accessible by C then do this:

void foo();

namespace ns {
  using ::foo;
}

Or use a macro:

#ifdef __cplusplus
#define NS_START(n) namespace n {
#define NS_END }
#else
#define NS_START(n)
#define NS_END
#endif

NS_START(ns)
void foo();
NS_END
Pubby
  • 51,882
  • 13
  • 139
  • 180
  • Does the first code block mean 'move the foo() in namespace ns' ? If so, does that mean that foo() now exists both in ns *and* global namespace? Thanks, Pubby. – Grim Fandango Nov 14 '11 at 09:18
  • @GrimFandango The first allows you to use `foo()` and `ns::foo()`. This is how C headers work in C++ - you can use `printf()` or `std::printf()` – Pubby Nov 14 '11 at 21:33
0

Your header is all messed up.

You probably want something more like:

struct C;

#ifdef __cplusplus
extern "C" {
#else
typedef struct C C;
#endif

/* ... */

#ifdef __cplusplus
}
#endif
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • It seems I did not communicate this well enough: The header may be included in a .cpp file, so I need to declare C as `class C` under __cplusplus, and `struct C` otherwise. Otherwise it will not compile. – Grim Fandango Nov 14 '11 at 08:52