将 C++ 类放入 C-struct 以供 C++ 函数使用(C/C++ 混合代码)

Posted

技术标签:

【中文标题】将 C++ 类放入 C-struct 以供 C++ 函数使用(C/C++ 混合代码)【英文标题】:Put C++ class in C-struct for for use in C++ function (C/C++ mixed code) 【发布时间】:2021-09-20 08:13:46 【问题描述】:

注意:这与 C/C++ 链接或 extern 关键字无关。在将我的问题与看似相似的问题联系起来之前,请仔细阅读,谢谢!

我认为这是一个有点不寻常的问题,因为我在浏览网页时没有找到任何相关内容。

我正在编写一个嵌入式系统。主模块是用 C 编写的,而子模块是用 C++ 编写的。为了说明这一点:

    submodule.hpp
   /            \
  /              \
main.c        submodule.cpp

现在我想保留 C++ 子模块使用的数据,并将其包含在我的主脚本中的静态变量中,这样我就可以从 main 调用子模块函数并为每次调用提供上下文数据。但是该数据还必须包含一个类,因为它被子模块函数使用,但当然 C 不知道如何处理类。所以我必须在不注意 main.c-script 的情况下偷偷地将一个类放入我的 C 结构中(给我一个错误)。我怎样才能做到这一点?

我认为这样的事情可以工作:

struct data_for_cpp_submodule 
   int a;
   int b;
   void *class;

然后将 void 指针“类”转换回适当的类,以便我可以在 C++ 脚本中使用它。这可以工作还是我完全错误的方式?

【问题讨论】:

我会考虑以相反的方式进行。使用 C-struct 并实现一个可以处理它的类。反过来,似乎介于不可能和不明智之间。 携带指向 C++ 类的指针很好。在 C 代码中取消引用该指针将产生未定义的行为(在特定情况下除外,例如类是 POD)。只要您转换为有效类型(例如,如果对象的类型为 A,则将指针转换为 A*),在 C++ 代码中进行转换即可。如果您将 struct data_for_cpp_submodule 从 C 传递到 C++ 代码,则该类型的定义需要在 C 和 C++ 中都有效 - 这意味着有几件事,例如指针不能命名为 class(因为 class 是C++ 中的关键字)。 您可以这样做,但要考虑所有权。如果 C 代码拥有该对象,则每次初始化和取消初始化都需要 1 个函数(基本上是对 C++ 对象使用 new/delete 的功能)。如果对象归 C++ 方所有,则任何适合标识该对象的唯一 ID 就足够了。你可以例如在向量中创建对象并为您的 C 代码提供索引;只要您可以保证在 C++ 端删除对象后,C 代码不会尝试访问该对象,提供一个 void 指针就可以了。 我认为这样的事情可能会起作用:这只是因为 C++“类”实际上只不过是一个 C struct。可以包含在 C struct 中的唯一 类型的类只不过是 C struct。其他任何事情都无法完成,因为 C 没有语法来处理实际的 C++ class 构造。因此,将 C struct 硬塞到 C++ 类中的唯一事情就是将来有人将实际的 C++ 特定代码放入 C-struct-masquerading-as-a-C++-class 并破坏事物。希望非常壮观,因此可以立即修复... 一个问题:你不能命名那个指针classclass 是 C++ 中的保留字。 【参考方案1】:

请注意,OP强调:

主模块是用 C 编写的,而子模块是用 C++ 编写的。

恕我直言,为 C++ submodule 添加 C 绑定完全没问题,这样它就可以在 C 代码中使用。

OP 的例子(增强一点使其成为MCVE):

submodule 的 C++ 代码

标头submodule.hpp:

#ifndef SUB_MODULE_HPP
#define SUB_MODULE_HPP

class Test 

  private:
    int a = 0;
    int b = 0;
    
  public:
    Test() = default; // default constructor
    Test(int a, int b): a(a), b(b)   // value constructor
    Test(Test&) = delete; // say: copy constructor disabled
    Test& operator=(Test&) = delete; // say: copy assignment disabled
    
  public:
    int exec() const;
;

#endif // SUB_MODULE_HPP

来源submodule.cpp:

#include "submodule.hpp"

int Test::exec() const  return a + b + 42; 

submodule 的 C 绑定:

标头:submodule.h

#ifndef SUB_MODULE_H
#define SUB_MODULE_H

#ifdef __cplusplus
extern "C" 
#endif

struct TestC_;
typedef struct TestC_ TestC; // opaque structure

extern TestC* testNew();

extern TestC* testNewValues(int a, int b);

extern void testDelete(TestC *pTest);

extern int testExec(TestC *pTest);

#ifdef __cplusplus

#endif

#endif // SUB_MODULE_H

来源submoduleC.cpp:

#include "submodule.h"
#include "submodule.hpp"

TestC* testNew()  return (TestC*)new Test(); 

TestC* testNewValues(int a, int b)  return (TestC*)new Test(a, b); 

void testDelete(TestC *pTest)  delete (Test*)pTest; 

int testExec(TestC *pTest)  return ((Test*)pTest)->exec(); 

最后但同样重要的是:使用 submodule 的示例 C 程序:

#include <stdio.h>

#include "submodule.h"

int main(void)

  TestC *pTest = testNew();
  printf("pTest->exec(): %d\n", testExec(pTest));
  testDelete(pTest);
  
  pTest = testNewValues(123, -123);
  printf("pTest->exec(): %d\n", testExec(pTest));
  testDelete(pTest);

如何编译和链接g++/gcc:

$ ## compile C++ code
$ g++ -std=c++17 -O2 -Wall -pedantic -c submodule.cpp submoduleC.cpp
$ ## compile C code
$ gcc -std=c11 -O2 -Wall -pedantic -lstdc++ main.c submodule.o submoduleC.o
$ ## test
$ ./a.out

输出:

testExec(pTest): 42
testExec(pTest): 42

Demo on coliru

最值得注意的部分可能是opaque structure

struct TestC_;
typedef struct TestC_ TestC; // opaque structure

(这是我在开始编写自己的绑定之前从其他 C 绑定中学到的。)

opaque structure 实际上是一个不完整的结构,但这是有意为之。

不透明结构旨在仅在 C 中用作指针。它不旨在允许取消引用任何内容(并且,对于不完整的结构,编译器只会拒绝这样做)。它的唯一目的是通过 C 代码“隧道”指向 C++ Test 实例的指针。

Test (submoduleC.cpp) 的 C 绑定中,TestC* 在必要时简单地转换为 Test*(反之亦然)。

提示:

我必须链接-lstdc++(标准 C++ 库)。否则,我会收到有关运算符 newdelete 的未定义引用的投诉:

submoduleC.o: In function `testNew':
submoduleC.cpp:(.text+0xa): undefined reference to `operator new(unsigned long)'
submoduleC.o: In function `testNewValues':
submoduleC.cpp:(.text+0x30): undefined reference to `operator new(unsigned long)'
submoduleC.o: In function `testDelete':
submoduleC.cpp:(.text+0x4b): undefined reference to `operator delete(void*, unsigned long)'
collect2: error: ld returned 1 exit status

其他想法(但可能不那么相关)

我记得有一个著名的框架

用C编写 并提供 C++ API(也称为 C++ 绑定)。

我说的是GTK+ 和gtkmm(它的C++ 绑定)。 因此,应该提到 GTK+ 实际上是一个 OOP 库——提供用 C 实现的面向对象的类。(我不确定这个事实是否会使 C++ 绑定的事情变得更容易或更难。)

两者都是开源项目。因此,每个人都可以看看他们是如何做到的。曾经看过,印象深刻……

除此之外,我总是建议相反:用 C++ 编写一个库并添加一个 C 绑定,因为这对我来说似乎更容易。

出于多种原因,我们总是在 Windows 中这样做:

    实现不同版本Visual Studio编译的DLL可以链接在一起。 (不久之前,这在 C++ 中几乎是不可能的,但现在,我相信,我读到了 Microsoft 引入的 ABI,它可能使之成为可能。)

    为其他语言的绑定提供基础,例如C#。

【讨论】:

这并没有真正回答如何将实际的 C++ 类放入 C struct 的问题。这个问题当然不能回答,因为做不到……

以上是关于将 C++ 类放入 C-struct 以供 C++ 函数使用(C/C++ 混合代码)的主要内容,如果未能解决你的问题,请参考以下文章

将 c-struct 放入 NSArray 的最佳方法是啥?

如何将类中的函数放入线程中? (使用 Boost 的 C++)

Qt C++ 类没有命名类型

将目录中的文件名添加到 char* 数组。 C++

通过 Boost Python 将 Python 函数转换为 C++,用作回调

为啥我无法将对象 push_back 放入 C++ 多维向量中