C, C++, Python3 之间代码的调用

1 C 与 C++ 之间的互相调用

1.1 链接指示 extern "C" 及其作用

只有 C++ 才支持这种声明,C 不可以。

如果用 extern "C" 声明一个函数,则意思是按照 C 的编译和链接规范来处理这个函数。

符合 C 的编译和链接规范的语言除了有 C 外,还有 Fortran,Assembler 等。

链接指示至少有如下两个作用:

  • 与 extern 声明全局变量和函数类似,表明只是声明一个函数,而该函数的定义可能在其他源文件中。
  • C++ 在编译时会存在名字重整 (name mangling) 过程,而 C,Fortran,Assembler 等在编译时则无此过程。

    因此,对于用链接指示声明的函数,有一个明显的特点就是在编译它们时并不会进行名字重整。

链接指示可以声明一个函数,也可以声明多个函数,还可以声明一个头文件中的所有函数。

1
2
3
4
5
6
7
8
9
10
11
12
extern "C" int add(int, int);

extern "C"
{
void func_1();
void func_2();
}

extern "C"
{
#include "xxx.h"
}

1.2 C++ 中调用 C 代码

1.c

1
2
3
4
int add(int arg1, int arg2)
{
return arg1 + arg2;
}

2.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;



// 链接指示。
// 表明在该函数的定义在其他源文件中,这里只是对它进行声明。
// 另外,编译器在编译时不对其进行名字重整。
extern "C" int add(int, int);



int main(int argc, const char *argv[])
{
// 3。
cout << add(1, 2) << endl;

return 0;
}

然后执行如下命令进行编译链接:

gcc -c 1.c g++ -c 2.cc g++ 1.o 2.o

当然,还可以组织成头文件与源文件的形式:

1.h:

1
extern int add(int, int);

1.c:

1
2
3
4
5
6
7
8
#include "1.h"



int add(int arg1, int arg2)
{
return arg1 + arg2;
}

2.cc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;



extern "C"
{
#include "1.h"
}



int main(int argc, const char *argv[])
{
// 3。
cout << add(1, 2) << endl;

return 0;
}

然后仍用上述命令编译链接即可。

1.3 C 中调用 C++ 普通函数

1.cc:

1
2
3
4
5
// 这里,链接指示只是起到了阻止名字重整的作用。
extern "C" int add(int arg1, int arg2)
{
return arg1 + arg2;
}

2.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>



extern int add(int, int);



int main(int argc, const char *argv[])
{
// 3。
printf("%d\n", add(1, 2));

return 0;
}

然后执行如下命令进行编译链接:

g++ -c 1.cc gcc -c 2.c g++ 1.o 2.o

最后一步一定要用 g++ 而非 gcc,否则会报链接错误。

当然,还可以组织成头文件与源文件的形式:

1.h:

1
extern "C" int add(int, int);

1.cc:

1
2
3
4
5
6
7
8
#include "1.h"



int add(int arg1, int arg2)
{
return arg1 + arg2;
}

2.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>



// 声明使用其他文件中定义的 add 函数。
extern int add(int, int);



int main(int argc, const char *argv[])
{
// 3。
printf("%d\n", add(1, 2));

return 0;
}

然后仍用上述命令编译链接即可。

当然,还可以将 C 源文件封装成一个库文件,然后再在 C++ 文件中调用。

当然,还可以将 C++ 源文件封装成一个库文件,然后再在 C 文件中调用。

1.4 C 中调用 C++ 类对象的成员函数

1.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>



class Foo
{
public:
int add(int arg1, int arg2)
{
return arg1 + arg2;
}
};

// 声明一个函数,用于对成员函数进行 wrap。
extern "C" int add_wrapper(int arg1, int arg2);

1.cc:

1
2
3
4
5
6
7
8
9
10
11
#include "1.h"



// 定义这个 wrap 函数。
int add_wrapper(int arg1, int arg2)
{
Foo obj;

return obj.add(arg1, arg2);
}

2.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>



extern int add_wrapper(int, int);



int main(int argc, const char *argv[])
{
// 5。
printf("%d\n", add_wrapper(2, 3));

return 0;
}

执行如下命令进行编译链接: g++ -c 1.cc gcc -c 2.c g++ 1.o 2.o

最后一步一定要用 g++ 而非 gcc,否则会报链接错误。

当然,还可以将 C++ 源文件封装成一个库文件,然后再在 C 文件中调用。

1.5 __cplusplus

__cplusplus 是一个宏,由 C++ 预处理器在预处理开始之前定义。

对于一段语句,如果用于 C++ 源文件时,想将它包含进来;而在用于 C 源文件时,并不想将它包含进来。此时,就可以用这个宏进行一些预定义操作。这里以 extern "C" 为例:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" \
{
#endif
void func_1(int);
void func_2(int, int);
#ifdef __cplusplus
}
#endif

对于 Linux,在用 g++ 对某个源文件进行编译时,会在预处理开始之前定义 __cplusplus 宏。

对于上述预处理语句,如果它们在 C++ 源文件中,当用 g++ 进行编译时,则会将 extern "C" {} 包含进来。

2 Python3 与 C/C++ 之间的互相调用

Python3 中提供了 ctypes 模块,它支持与 C 兼容的数据类型,可以用来加载 C/C++ 动态库。

2.1 Python3 中调用 C 代码

test.h:

1
extern int add(int, int);

test.c:

1
2
3
4
5
6
7
8
#include "test.h"



int add(int arg1, int arg2)
{
return arg1 + arg2;
}

然后执行如下命令生成动态库: gcc -c -fPIC -o test.o test.c gcc -shared -o libtest.so test.o

编辑并执行 main.py:

1
2
3
4
5
6
7
from ctypes import cdll



lib = cdll.LoadLibrary("./libtest.so")
# 5。
print(lib.add(2, 3))

2.2 Python3 中调用 C++ 代码

ctypes 只兼容 C 符号,如果要调用 C++ 代码,则需要用到 extern "C"

test.h:

1
2
3
4
5
6
7
8
9
10
class Foo
{
public:
int add(int arg1, int arg2)
{
return arg1 + arg2;
}
};

extern "C" int add_wrapper(int, int);

test.cc:

1
2
3
4
5
6
7
8
9
10
#include "test.h"



int add_wrapper(int arg1, int arg2)
{
Foo obj;

return obj.add(arg1, arg2);
}

然后执行如下命令生成动态库: g++ -c -fPIC -o test.o test.cc g++ -shared -o libtest.so test.o

编辑并执行 main.py:

1
2
3
4
5
6
7
from ctypes import cdll



lib = cdll.LoadLibrary("./libtest.so")
# 5。
print(lib.add_wrapper(2, 3))

2.3 C/C++ 中调用 Python3 代码

Python.h 是 Python 开发的头文件,其中提供了 Py_Initialize,PyRun_SimpleString,PyImport_Import 等众多方法来调用 Python 文件。

由于很少这样使用,所以这里不再赘述。


Reference