简体   繁体   中英

Opaque struct with C linkage & C++ implementation

Say I want a struct foo with C linkage. I'll declare it in a C-style header file (foo.h) opaquely:

struct foo;
typedef struct foo foo;

But I want to use C++ in the implementation of foo . Say I want foo to contain a std::vector<int> . Since C code wouldn't have access to foo 's fields, I don't see why the compiler (or language standards) should prohibit this. But how would I do that? Can I just put extern "C" around the implementation of foo in foo.cc like this?

extern "C" {
    struct foo {
        ....
        std::vector<int> bar;
    }
}

A struct is compatible between C and C++ as long as the content of the struct is POD (plain old data). If you add non-POD into it eg your vector, it no longer is compatible with C and will cause undefined behavior when used with typical C functions like memset or memcpy . The non-POD members cause another memory layout of the struct, it gets a virtual table at the start of it to support inheritance.

I've ended up stuffing C++ under C a lot...here's the general gist of it. (And as an aside, letting C++ exceptions unwind the C stack can cause issues for the C code that is unaware such things can happen...so it's advisable to do some catch(...) blocks in the C++ interface functions.)

lib.h : A header file that declares a few functions with C calling convention no matter whether it's compiled as C or C++

#pragma once

#if defined(__cplusplus)
extern "C" {
#endif

/* Looks like a typical C library interface */

struct c_class;

struct c_class *do_init();
void do_add(struct c_class *tgt, int a);
int  do_get_size(const struct c_class *tgt);
void do_cleanup(struct c_class *tgt);


#if defined(__cplusplus)
}
#endif

lib.cpp : A C++ library with a few functions declared with C calling convention

#include "lib.h"

#include <iostream>
#include <vector>
#include <cstdlib>

class Foo
{
  std::vector<int> m_vec;
public:
  Foo() : m_vec() {}
  virtual ~Foo() {}

  void add(int a) {
    m_vec.push_back(a);
  }

  int getSize() {
    return m_vec.size();
  }
};


/* Exposed C interface with C++ insides */
extern "C" {

  struct c_class
  {
    Foo *guts;
  };

  struct c_class *do_init()
  {
    struct c_class *obj = static_cast<c_class*>(malloc(sizeof(struct c_class)));
    obj->guts = new Foo();
    return obj;
  }

  void do_add(struct c_class *tgt, int a) {
    tgt->guts->add(a);
  }

  int do_get_size(const struct c_class *tgt) {
    return tgt->guts->getSize();
  }

  void do_cleanup(struct c_class *tgt) {
    delete tgt->guts;
    free(tgt);
  }
}

main.c : AC program that uses the C calling convention functions exported from lib

#include <stdio.h>
#include "lib.h"

int main(int argc, char *argv[])
{
  int i;
  struct c_class *obj;

  obj = do_init();

  for(i = 0; i< 100; i++)
  {
    do_add(obj, i);
  }

  printf("Size: %d\n", do_get_size(obj));

  do_cleanup(obj);
}

Makefile : a makefile that builds the C as C, and the C++ as C++, then uses the C++ compiler to do the link

CXXFLAGS ?= -Wall -Werror -pedantic
CFLAGS ?= -Wall -Werror -pedantic

.PHONY: all
all : test

test: lib.o main.o
    $(CXX) $(CXXFLAGS) -o test lib.o main.o

lib.o: lib.cpp lib.h
    $(CXX) $(CXXFLAGS) -c $< -o $@

main.o: main.c lib.h
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    -rm lib.o main.o test

Output :

$ make
g++ -Wall -Werror -pedantic -c lib.cpp -o lib.o
cc -Wall -Werror -pedantic -c main.c -o main.o
g++ -Wall -Werror -pedantic -o test lib.o main.o

$ ./test
Size: 100

Struct by itself is just a type, in other words a syntactic sugar which does not exist beyond compilation as a struct, but just as several bytes in memory and pointers to it. Therefore it does not have any language specific linkage. Meaning of bytes is defined by the compiler at compilation time and references to them are created also there. So, its layout depends on the language you use.

Answering your question, the std::vector implementation contains certain fields which become a part of the struct layout. c++ understands them, since it understands templates and all other object-oriented stuff, 'c' will choke on the template in the first place as well as other c++sness. So, struct definitions are not compatible if you use non POD data.

If you use POD, standard 'c' data types, no member functions and no bit fields, you should be all set and compatible in definition. Which means that both compilers will compile the struct and the layout will be similar, so that you can save/restore it cross languages.

If you are talking about just passing the pointers to a c++ struct through a 'c' code, than you should be all set in any case. you can use cast to 'void*' to do so in a standard way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM