具有 C 链接和C++实现的不透明结构

Opaque struct with C linkage & C++ implementation

本文关键字:不透明 结构 实现 C++ 链接 具有      更新时间:2023-10-16

说我想要一个带有C链接的结构foo。我将在C风格的标头文件(FOO.H)中声明:

struct foo;
typedef struct foo foo;

,但我想在foo的实现中使用C 。说我希望foo包含std::vector<int>。由于C代码无法访问foo的字段,因此我不明白为什么编译器(或语言标准)应该禁止这一点。但是我该怎么办?我可以在这样的foo.cc中将extern "C"放在foo的实现周围吗?

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

我最终在c下填充了c ...这是它的一般要旨。(顺便说一句,让C 例外放弃C堆栈可能会导致C代码不知道这些事情的问题...因此建议在C 接口函数中执行一些catch(...)块。)

) 。

lib.h :一个标头文件,该文件声明了一些函数,无论是否编译为C还是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 :c 库带有几个函数,用C调用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 :使用从lib

导出的c调用约定功能的C程序
#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: %dn", do_get_size(obj));
  do_cleanup(obj);
}

makefile :将C构建为C的Makefile,而C 为C ,然后使用C 编译器进行链接

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

输出

$ 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

只要结构的内容是pod(纯旧数据),c和c 之间的结构是兼容的。如果您在其中添加非pod,例如您的向量,它不再与C兼容,并且与典型的C函数(如memsetmemcpy)一起使用时会导致不确定的行为。非POD成员导致结构的另一个内存布局,它在其开始时获得了一个虚拟表以支持继承。

struct本身只是一种类型,换句话说,句法糖不仅仅是编译为结构,而是记忆和指针中的几个字节。因此,它没有任何特定语言的链接。字节的含义由编译时的编译器定义,并在其中创建了对它们的引用。因此,它的布局取决于您使用的语言。

回答您的问题时,std ::向量实现包含某些字段,这些字段是结构布局的一部分。C 理解它们,因为它了解模板和所有其他面向对象的东西,因此" C"首先会在模板上以及其他C s扼流。因此,如果您使用非POD数据,则结构定义不兼容。

如果您使用POD,标准" C"数据类型,没有成员功能,并且没有位字段,则应在定义上全部设置和兼容。这意味着两个编译器都将编译结构,并且布局将相似,因此您可以保存/还原其跨语言。

如果您只是在谈论通过" C"代码将指针传递给C 结构,那么无论如何都应该设置。您可以使用铸件来以标准方式进行"无效*"。