0%

[python源码分析] PyAPI_xxx解析: GCC __attribute__语法

PyAPI_xxx#

在cpython源码中, 可以看到很多地方使用了PyAPI_DATA, PyAPI_FUNC, PyMODINIT_FUNC, 我们一起来看看这些都是什么。 pyport.h中,我们可以看到对它们的定义(摘选部分)

以下代码中会用到的一些宏标识符,先进行一下说明: - Py_ENABLE_SHARED 值为1 ,windows平台下,Python核默认在DLL中,允许外部链接性 - HAVE_DECLSPEC_DLL 所有windows编译器和cygwin均会定义,用于支持__declspec(). - Py_BUILD_CORE 构建Python内核。提供对Python内部构件的访问权,但不应被第三方模块使用。 - Py_BUILD_CORE_MODULE 构建一个Python stdlib模块作为一个动态库,Windows上导出“PyInit_xxx”符号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* only get special linkage if built as shared or platform is Cygwin */
#if defined(Py_ENABLE_SHARED) || defined(__CYGWIN__)
# if defined(HAVE_DECLSPEC_DLL)
# if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
//被预定义了的
# ...
# else /* Py_BUILD_CORE */
/* Building an extension module(扩展模块), or an embedded situation */
/* public Python functions and data are imported */
# if !defined(__CYGWIN__)
# define PyAPI_FUNC(RTYPE) Py_IMPORTED_SYMBOL RTYPE
# endif /* !__CYGWIN__ */
# define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE
/* module init functions outside the core must be exported */
# if defined(__cplusplus)
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
# else /* __cplusplus */
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
# endif /* __cplusplus */
# endif /* Py_BUILD_CORE */
# endif /* HAVE_DECLSPEC_DLL */
#endif /* Py_ENABLE_SHARED */

/* If no external linkage macros defined by now, create defaults(如 GCC, Unix) */
#ifndef PyAPI_FUNC
# define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
#endif
#ifndef PyAPI_DATA
# define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
#endif
#ifndef PyMODINIT_FUNC
# if defined(__cplusplus)
# define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
# else /* __cplusplus */
# define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
# endif /* __cplusplus */
#endif
我们可以看到基本采用了Py_EXPORTED_SYMBOL, 只有shared模式或Cygwin平台下的扩展模块才采用了Py_IMPORTED_SYMBOL

Py_xxx_SYMBOL#

我们在export.h下可以看到Py_EXPORTED_SYMBOL、Py_IMPORTED_SYMBOL的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//跨平台通用性
#if defined(_WIN32) || defined(__CYGWIN__)
/* 对于win32和Cygwin,使用__declspec()指定属性 */
#define Py_IMPORTED_SYMBOL __declspec(dllimport) //从dll导入。其它模块可见
#define Py_EXPORTED_SYMBOL __declspec(dllexport) //导出到dll。其它模块可见
#define Py_LOCAL_SYMBOL
#else

#else
//__has_attribute参数为属性名,可以评估当前编译目标是否支持该属性,支持为1,不支持为0
#ifndef __has_attribute
#define __has_attribute(x) 0 // Compatibility with non-clang compilers.
#endif
#if (defined(__GNUC__) && (__GNUC__ >= 4)) ||\ //如果 gcc版本>=4 或 clang判定编译目标支持visibility属性,则添加属性
(defined(__clang__) && __has_attribute(visibility))
#define Py_IMPORTED_SYMBOL __attribute__ ((visibility ("default"))) //具有外部链接性 (external linkage),可以被外部其它模块引用,并且有可能被重写
#define Py_EXPORTED_SYMBOL __attribute__ ((visibility ("default")))
#define Py_LOCAL_SYMBOL __attribute__ ((visibility ("hidden"))) //只能在同一共享对象(可简单理解为库文件)中被引用
#else
#define Py_IMPORTED_SYMBOL
#define Py_EXPORTED_SYMBOL
#define Py_LOCAL_SYMBOL
#endif
#endif
gcc下采用**关键字__attribute__可以为函数,结构体,类,枚举, 变量,标签添加属性;在winfows下关键字__declspec**可以实现同样的特性。

  • Py_IMPORTED_SYMBOL:用于在Windows编译器或cygwin中,构建非核心模块时定义宏 PyAPI_FUNC,PyAPI_DATA,意义是直接导入核心模块,不编译, 防止编译器再次编译。
  • Py_EXPORTED_SYMBOL: default,指定函数, class, struct 等为公开可重写的。编译导出到动态库(DLL或so)
  • Py_LOCAL_SYMBOL: hidden,指定函数, class, struct 等只能在同一共享对象中被引用

visibility 属性#

visibility 属性用于指定可见性,可以用于 函数, class, struct, union, enum

1
2
void __attribute__ ((visibility ("protected"))) f () { /* Do something. */; }
int i __attribute__ ((visibility ("hidden")));
属性值: - default:具有外部链接性 (external linkage),可以被外部其它模块引用,并且有可能被重写。 不编译,直接导入 - hidden:只能在同一共享对象(可简单理解为库文件)中被引用 - internal:无法被其它模块直接引用,但是可以通过指针间接引用 - protected:可以被引用,但无法被重写

PyAPI_FUNC#

在gcc下, 以genobject.h中的PyGen_New为例:

1
PyAPI_FUNC(PyObject *) PyGen_New(PyFrameObject *);
根据define的使用方法替换一下,此处就相当于
1
Py_EXPORTED_SYMBOL PyObject * PyGen_New(PyFrameObject *)
继续替换
1
__attribute__ ((visibility ("default"))) PyObject * PyGen_New(PyFrameObject *)
这里其实是定义了一个公开的可以被其它模块调用或重写的函数PyObject * PyGen_New(PyFrameObject *)

结论#

PyAPI_FUNC 指定函数可以被各个模块访问

PyAPI_DATA#

以boolobject.h中的PyBool_Type为例

1
PyAPI_DATA(PyTypeObject) PyDictRevIterKey_Type;
替换
1
Py_EXPORTED_SYMBOL PyTypeObject PyDictRevIterKey_Type;
继续替换
1
__attribute__ ((visibility ("default"))) PyTypeObject PyDictRevIterKey_Type;
结果是声明了一个可以被其它模块访问的PyTypeObject结构体变量。

结论#

PyAPI_DATA指定变量可以被其它模块访问

PyMODINIT_FUNC#

binassi.h的中的模块初始化函数为例

1
2
3
4
5
PyMODINIT_FUNC
PyInit_binascii(void)
{
return PyModuleDef_Init(&binasciimodule);
}
替换
1
2
3
4
Py_EXPORTED_SYMBOL PyObject* PyInit_binascii(void)
{
return PyModuleDef_Init(&binasciimodule);
}
继续替换
1
2
3
4
__attribute__ ((visibility ("default"))) PyObject* PyInit_binascii(void)
{
return PyModuleDef_Init(&binasciimodule);
}
到这里可以看出来,PyInit_binascii()函数调用了PyModuleDef_Init()方法binasciimodule进行了初始化,然后返回一个PyObject结构体的指针变量

结论#

PyMODINIT_FUNC指定模块初始化函数可以&&被其它模块访问&&,并返回PyObject结构体的指针变量

reference#

1.GCC __attribute__语法:visibility属性在cpython中的应用(PyAPI_FUNC, PyAPI_DATA, PyMODINIT_FUNC)