具有 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);
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: %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 函数(如 memset
或 memcpy
)一起使用时会导致未定义的行为。非 POD 成员导致结构的另一个内存布局,它在它的开头获取一个虚拟表以支持继承。
【讨论】:
但是如果 C 看到的都是一个不透明的结构,那么成员的兼容性肯定是无关紧要的吗? 如果成员有构造函数/虚拟表等,则不会。【参考方案3】:结构本身只是一种类型,换句话说,它是一种语法糖,它在编译之外不作为结构存在,而只是内存中的几个字节和指向它的指针。因此,它没有任何特定于语言的链接。字节的含义由编译器在编译时定义,并且在那里创建对它们的引用。因此,它的布局取决于您使用的语言。
回答您的问题,std::vector 实现包含某些字段,这些字段成为结构布局的一部分。 c++ 理解它们,因为它理解模板和所有其他面向对象的东西,'c' 将首先扼杀模板以及其他 c++sness。因此,如果您使用非 POD 数据,则结构定义不兼容。
如果你使用 POD、标准的 'c' 数据类型、没有成员函数和位域,你应该在定义上都设置和兼容。这意味着两个编译器都将编译结构并且布局将相似,因此您可以跨语言保存/恢复它。
如果您只是在谈论通过“c”代码将指针传递给 c++ 结构,那么无论如何您都应该设置好。您可以使用 cast to 'void*' 以标准方式执行此操作。
【讨论】:
以上是关于具有 C 链接和 C++ 实现的不透明结构的主要内容,如果未能解决你的问题,请参考以下文章
在 C 和 C++ 中,链接器如何找到具有已实现函数的正确文件?
如何在鼠标移动时在图片上方显示链接,同时降低图片的不透明度?