简体   繁体   中英

Put C++ class in C-struct for for use in C++ function (C/C++ mixed code)

NOTE : This is not about C/C++ linkage or the extern keyword. Please read carefully before associating my question with seemingly similar questions, thank you!

I think this is a somewhat unusual problem as I didn't find anything on this while browsing the web.

I'm programming an embedded system. The main module is written in C while a submodule is written in C++. To illustrate this:

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

Now I want to hold on to the data that is used by the C++-submodule and contain it in a static variable in my main script so I can call submodule functions from main and provide context data with every call. But that data must also contain a class as it is used by the submodule functions, but of course C doesn't know how to deal with classes. So I have to covertly put a class into my C-struct without main.c-script noticing (giving me an error). How could I achieve this?

I figured something like this could work:

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

And then casting the void pointer "class" back to the appropriate class so I can work with it in the C++ script. Could this work or am I going about it completely the wrong way?

Please, note that OP emphasized:

The main module is written in C while a submodule is written in C++.

IMHO, it's completely fine to add a C binding for the C++ submodule , so that it can be used in C code.

The example of OP (enhanced a bit to make it an MCVE ):

C++ code for submodule :

header 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

source submodule.cpp :

#include "submodule.hpp"

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

C Binding for submodule :

header: 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

source 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(); }

Last but not least: A sample C program using submodule :

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

How it has to be compiled and linked with 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

Output:

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

Demo on coliru

The most notable part might be the opaque structure :

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

(This is what I learnt from other C bindings before I started to write my own.)

It might be surprising that the opaque structure is actually an incomplete structure but that's intentional.

The opaque structure is intended to be used as pointer only in C. It's not intended to allow dereferencing of any contents (and, for an incomplete structure, this is merely denied by the compiler). It's only purpose is to "tunnel" a pointer to a C++ Test instance through the C code.

In the C binding of Test ( submoduleC.cpp ), TestC* is simply converted to Test* (and vice versa) where necessary.

Hint:

I had to link -lstdc++ (the standard C++ library). Otherwise, I got complaints about undefined references for the operators new and delete :

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

Other thoughts (but probably not that relevant) :

I remember a prominent framework which

  • is written in C
  • and provides a C++ API (aka. C++ binding).

I'm talking about GTK+ and gtkmm (its C++ binding). Thereby, it should be mentioned that GTK+ is actually an OOP library – providing object-oriented classes implemented in C. (I'm uncertain whether this fact makes things easier or harder concerning the C++ binding.)

Both are Open Source projects. Hence, everybody can have a look at it how did they do. I once had a look and was impressed…

This aside, I always would recommend the opposite: writing a library in C++ and adding a C binding as this appears much easier to me.

We always did this in Windows for multiple reasons:

  1. to achieve that DLLs compiled with different versions of Visual Studio can be linked together. (Until soon, this was nearly impossible with C++ but now, I believe, I read about an ABI introduced by Microsoft which may it make possible.)

  2. to provide a base for bindings in other languages like eg C#.

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