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

Posted

技术标签:

【中文标题】具有 C 链接和 C++ 实现的不透明结构【英文标题】:Opaque struct with C linkage & C++ implementation 【发布时间】:2017-12-07 04:16:58 【问题描述】:

假设我想要一个带有 C 链接的结构 foo。我将在 C 风格的头文件 (foo.h) 中不透明地声明它:

struct foo;
typedef struct foo foo;

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

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

【问题讨论】:

结构没有链接 ...extern "C" 也不会让您的代码编译为 C。 以建设性的方式陈述同一件事,extern "C" 所做 是使所有在其范围内声明或定义的函数 (a) 使用 C 命名约定,并且(b) 使用 C 调用约定。但在所有其他方面,它仍然是 C++ 代码。 【参考方案1】:

我最终在 C 下大量填充了 C++...这是它的一般要点。 (顺便说一句,让 C++ 异常展开 C 堆栈可能会导致 C 代码出现问题,而这些问题不知道会发生此类事情......因此建议在 C++ 接口函数中执行一些 catch(...) 块。)

lib.h:一个头文件,它声明了一些具有 C 调用约定的函数,无论它是编译为 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 调用约定声明的函数

#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);
  

ma​​in.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: %d\n", do_get_size(obj));

  do_cleanup(obj);

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

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

【讨论】:

如果从析构函数中删除 virtual 关键字是否有效?如果你想与 C 兼容,你的类不能有 vtable。 @MaxRahm...我不明白你的问题。它适用于 Foo 中的 vtable,因为该结构只有一个指向 Foo 的指针。 (如果结构对 C 不透明,它将通过 void * 保存 Foo 对象)。 OP 要求一个可以保存 STL 的不透明结构,而不是一种制造与 C 兼容的 C++ 对象的方法。这实际上是一个 MCVE。【参考方案2】:

只要结构的内容是 POD(plain old data),结构在 C 和 C++ 之间是兼容的。如果您将非 POD 添加到其中,例如您的向量,它不再与 C 兼容,并且在与典型的 C 函数(如 memsetmemcpy)一起使用时会导致未定义的行为。非 POD 成员导致结构的另一个内存布局,它在它的开头获取一个虚拟表以支持继承。

【讨论】:

但是如果 C 看到的都是一个不透明的结构,那么成员的兼容性肯定是无关紧要的吗? 如果成员有构造函数/虚拟表等,则不会。【参考方案3】:

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

回答您的问题,std::vector 实现包含某些字段,这些字段成为结构布局的一部分。 c++ 理解它们,因为它理解模板和所有其他面向对象的东西,'c' 将首先扼杀模板以及其他 c++sness。因此,如果您使用非 POD 数据,则结构定义不兼容。

如果你使用 POD、标准的 'c' 数据类型、没有成员函数和位域,你应该在定义上都设置和兼容。这意味着两个编译器都将编译结构并且布局将相似,因此您可以跨语言保存/恢复它。

如果您只是在谈论通过“c”代码将指针传递给 c++ 结构,那么无论如何您都应该设置好。您可以使用 cast to 'void*' 以标准方式执行此操作。

【讨论】:

以上是关于具有 C 链接和 C++ 实现的不透明结构的主要内容,如果未能解决你的问题,请参考以下文章

在 C 和 C++ 中,链接器如何找到具有已实现函数的正确文件?

悬停链接以更改图像的不透明度

如何在鼠标移动时在图片上方显示链接,同时降低图片的不透明度?

是否可以在 DLL 中导出具有 C 链接的 C++ 成员方法?

可触摸的不透明标题 React Native

无法控制 div 内图像的不透明度