visibility属性在cpython中的应用方法是什么

2023-05-13,

本篇内容介绍了“visibility属性在cpython中的应用方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

GCC attribute 语法

GCC 的attribute语法可以为函数,结构体,类,枚举, 变量,标签等添加属性。 例如为函数添加属性__attribute__((noreturn)),可以让编译器知道该函数不会返回给调用者,并作出相应优化。

在Windows专用修饰符中也有类似gcc attribute语法的关键字 —— __declspec. 在追求跨平台通用性的时候,通常会同时加入gcc __attribute__ 和windows __declspec,在编译时判断平台,决定使用哪一种方式

__declspec( 属性 ) 声明语句

// 举例
__declspec( dllimport ) int i;
__declspec( dllexport ) void func();

Q: __attribute__((属性列表)) 到底写在什么位置?

A: 相同作用对象添加不同属性,或相同属性作用于不同对象,有的紧跟关键字,有的在整个声明之后。具体可查阅官方文档

//可以紧跟在 struct, class, union, enum 关键字之后,也可以在右括号之后,首选前者
struct __attribute__ ((aligned (8))) S { short f[3]; };    //为结构体S添加属性 aligned (8)

//在typdef声明中使用
typedef int T1 __attribute__ ((deprecated)); //T1在任意地方被使用时,deprecated属性会产生告警

//函数
__attribute__((属性列表)) void foo1 (void);
void foo2 (void) __attribute__((属性列表));

Q: 有哪些属性可以用?

A: 对于函数,结构体,类等不同的作用对象,都有各自支持的属性,其中又分为最常用的公共属性和针对特定CPU架构的特殊属性,详情请查阅官方文档。

visibility 属性

visibility 属性用于指定可见性,可以用于 函数, class, struct, union, enum,使用语法一致。

void __attribute__ ((visibility ("protected"))) f () { /* Do something. */; }
int i __attribute__ ((visibility ("hidden")));

属性值:

  • default:具有外部链接性 (external linkage),可以被外部其它模块引用,并且有可能被重写

  • hidden:只能在同一共享对象(可简单理解为库文件)中被引用

  • internal:无法被其它模块直接引用,但是可以通过指针间接引用

  • protected:可以被引用,但无法被重写

visibility 属性使用方法

在讲visibility属性用法之前 ,我们先了解一下,

__declspec( 属性 ) 声明语句
    
// 举例
__declspec( dllimport ) int i;
__declspec( dllexport ) void func();

此处我们看gcc wiki中的一例经典模板,可以用于定义共享库

/* 定义宏:
标识符: FOX_HELPER_DLL_IMPORT   default,指定函数, class, struct 等为公开可重写的。不编译,直接导入。
标识符: FOX_HELPER_DLL_EXPORT   default,指定函数, class, struct 等为公开可重写的。编译并导出为动态库(DLL或so)
标识符: FOX_HELPER_DLL_LOCAL    hidden,指定函数, class, struct 等只能在同一共享对象中被引用 */

/* 对于win32和Cygwin,使用__declspec()指定属性 */
#if defined _WIN32 || defined __CYGWIN__
  #define FOX_HELPER_DLL_IMPORT __declspec(dllimport)	//从dll导入。其它模块可见
  #define FOX_HELPER_DLL_EXPORT __declspec(dllexport)	//导出到dll。其它模块可见
  #define FOX_HELPER_DLL_LOCAL
#else
  /*对于gcc,使用__attribute__ ((visibility ("属性值"))) 指定可见性*/
  #if __GNUC__ >= 4
    #define FOX_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
    #define FOX_HELPER_DLL_EXPORT __attribute__ ((visibility ("default"))) 
    #define FOX_HELPER_DLL_LOCAL  __attribute__ ((visibility ("hidden"))) 
  #else
  /*代码中已经引用了宏,即使什么属性都不加,也要定义宏*/
    #define FOX_HELPER_DLL_IMPORT
    #define FOX_HELPER_DLL_EXPORT
    #define FOX_HELPER_DLL_LOCAL
  #endif
#endif

/*定义宏 FOX_API 与 FOX_LOCAL
标识符: FOX_API    default,指定函数, class, struct 等为公开可重写的,要么编译导出,要么直接导入
标识符: FOX_LOCAL    hidden,指定函数, class, struct 等只能在同一共享对象中被引用 */

#ifdef FOX_DLL // 如果FOX需要被编译为动态库
  #ifdef FOX_DLL_EXPORTS // 如果是正在构建 (而非使用) FOX
    #define FOX_API FOX_HELPER_DLL_EXPORT //导出动态库
  #else
    #define FOX_API FOX_HELPER_DLL_IMPORT  //不构建,直接导入动态库
  #endif // FOX_DLL_EXPORTS
  #define FOX_LOCAL FOX_HELPER_DLL_LOCAL //定义FOX_LOCAL
#else // 没有定义 FOX_DLL 宏,则说明FOX是静态库,不添加任何属性
  #define FX_API
  #define FOX_LOCAL
#endif // FOX_DLL

引用方式:

//声明一个公开可重写(default)的函数
extern FOX_API PublicFunc1();   //extern __attribute__ ((visibility ("default"))) PublicFunc1(); 

//声明一个仅内部使用(hidden)的函数
extern FOX_LOCAL PublicFunc2();  //extern __attribute__ ((visibility ("hidden"))) PublicFunc2();

//声明一个公开可重写(default)的类
class FOX_API PublicClass1;  //class __attribute__ ((visibility ("default"))) PublicClass1;

 //声明一个仅内部使用(hidden)的类
class FOX_LOCAL PublicClass2; //class __attribute__ ((visibility ("hidden"))) PublicClass2;

实际应用 - cpython

研究过cpython的朋友看到以上demo可能会有点眼熟。是的,cpython中有一段相似的代码。当然,cpython中还兼容了clang。

#ifndef Py_EXPORTS_H
#define Py_EXPORTS_H

/* 定义宏:
标识符: Py_IMPORTED_SYMBOL    default,指定函数, class, struct 等为公开可重写的。不编译,直接导入
标识符: Py_EXPORTED_SYMBOL    default,指定函数, class, struct 等为公开可重写的。编译导出到动态库(DLL或so)
标识符: Py_LOCAL_SYMBOL    hidden,指定函数, class, struct 等只能在同一共享对象中被引用 */

/* 对于win32和Cygwin,使用__declspec()指定属性 */
#if defined(_WIN32) || defined(__CYGWIN__)
    #define Py_IMPORTED_SYMBOL __declspec(dllimport)	//从dll导入。其它模块可见	
    #define Py_EXPORTED_SYMBOL __declspec(dllexport)	//导出到dll。其它模块可见
    #define Py_LOCAL_SYMBOL
#else
    //__has_attribute参数为属性名,可以评估当前编译目标是否支持该属性,支持为1,不支持为0
    //下面三行代码用于兼容non-clang编译器。对于non-clang编译器,__has_attribute(x)表达式直接转0
    #ifndef __has_attribute
      #define __has_attribute(x) 0
    #endif
    //如果 gcc版本>=4 或 clang判定编译目标支持visibility属性,则添加属性
    #if (defined(__GNUC__) && (__GNUC__ >= 4)) ||
        (defined(__clang__) && __has_attribute(visibility))
        #define Py_IMPORTED_SYMBOL __attribute__ ((visibility ("default")))
        #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

#endif /* Py_EXPORTS_H */
  • Py_LOCAL_SYMBOL :在源码之中暂未发现引用之处

  • Py_IMPORTED_SYMBOL:用于在Windows编译器或cygwin中,构建非核心模块时定义宏 PyAPI_FUNCPyAPI_DATA,意义是直接导入核心模块,防止编译器再次编译。原因参考此处。

  • Py_EXPORTED_SYMBOL :定义宏 PyAPI_FUNCPyAPI_DATA, PyMODINIT_FUNC

以下代码中会用到的一些宏标识符,先进行一下说明:

  • 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”符号。

/*不同平台构建的差异请参考:https://docs.python.org/zh-cn/3/extending/windows.html#a-cookbook-approach*/

/* 首先处理windows平台及cygwin */
#if defined(Py_ENABLE_SHARED) || defined(__CYGWIN__)
#       if defined(HAVE_DECLSPEC_DLL)
		//构建Python内核,而非Python stdlib模块
#               if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#                       define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
#                       define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
        		//核心模块初始化函数不需要外部链接性,除非Cygwin来处理嵌入
#                       if defined(__CYGWIN__)
#                               define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
#                       else /* __CYGWIN__ */
#                               define PyMODINIT_FUNC PyObject*
#                       endif /* __CYGWIN__ */
#               else /* Py_BUILD_CORE */
		//核心构建结束,接下来构建扩展模块(非核心模块)或嵌入式环境,需要先导入公共函数及数据
			//使用cygwin会自动导入公共函数防止编译,如果没用使用cygwin,则需显式导入
#                       if !defined(__CYGWIN__)
#                               define PyAPI_FUNC(RTYPE) Py_IMPORTED_SYMBOL RTYPE
#                       endif /* !__CYGWIN__ */
#                       define PyAPI_DATA(RTYPE) extern Py_IMPORTED_SYMBOL RTYPE
			//按C语言的语法,编译导出非核心模块的初始化函数
#                       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 */

/*如果任然未定义PyAPI_FUNC, PyAPI_DATA, PyMODINIT_FUNC, 说明不是windows平台或cygwin。默认采用gcc方式定义*/
#ifndef PyAPI_FUNC
	//__attribute__ ((visibility ("default"))) RTYPE
#       define PyAPI_FUNC(RTYPE) Py_EXPORTED_SYMBOL RTYPE
#endif
#ifndef PyAPI_DATA
	//extern __attribute__ ((visibility ("default"))) RTYPE
#       define PyAPI_DATA(RTYPE) extern Py_EXPORTED_SYMBOL RTYPE
#endif
//无论是核心还是非核心模块,都需要外部链接性
#ifndef PyMODINIT_FUNC
#       if defined(__cplusplus)
		//extern "C" __attribute__ ((visibility ("default"))) PyObject*
#               define PyMODINIT_FUNC extern "C" Py_EXPORTED_SYMBOL PyObject*
#       else /* __cplusplus */
		//__attribute__ ((visibility ("default"))) PyObject*
#               define PyMODINIT_FUNC Py_EXPORTED_SYMBOL PyObject*
#       endif /* __cplusplus */
#endif

注:以上说的Windows平台/编译器,默认指Microsoft Visual C++,但理论上其它编译器也可以支持

今天研究的是gcc,接下来按照gcc处理方式分析PyAPI_FUNC, PyAPI_DATA, PyMODINIT_FUNC是如何使用的。

PyAPI_FUNC

pycore_hashtable.h中的_Py_hashtable_compare_direct方法为例

extern "C" PyAPI_FUNC(int) _Py_hashtable_compare_direct {//函数体略};

根据define的使用方法替换一下,此处就相当于

extern "C" Py_EXPORTED_SYMBOL int _Py_hashtable_compare_direct {//函数体略};

继续替换

extern "C" __attribute__ ((visibility ("default"))) int _Py_hashtable_compare_direct {//函数体略};

到这一步我们就清晰了,这里其实是定义了一个公开的可以被其它模块调用或重写的函数int _Py_hashtable_compare_direct

结论:PyAPI_FUNC 指定函数可以被各个模块访问。

PyAPI_DATA

boolobject.h中的PyBool_Type为例

PyAPI_DATA(PyTypeObject) PyBool_Type;	//PyTypeObject是一个结构体

替换

extern Py_EXPORTED_SYMBOL PyTypeObject PyBool_Type;

继续替换

extern __attribute__ ((visibility ("default"))) PyTypeObject PyBool_Type;

结果是声明了一个可以被其它模块访问的PyTypeObject结构体变量。

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

PyMODINIT_FUNC

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

PyMODINIT_FUNC
PyInit_array(void)
{
    return PyModuleDef_Init(&arraymodule);
}

替换

//c语言
Py_EXPORTED_SYMBOL PyObject* PyInit_array(void)
{
    return PyModuleDef_Init(&arraymodule);
}

//C++
extern "C" Py_EXPORTED_SYMBOL PyObject* PyInit_array(void)
{
    return PyModuleDef_Init(&arraymodule);
}

到这里可以看出来,PyInit_array()函数调用了PyModuleDef_Init()方法对arraymodule进行了初始化,然后然返回一个PyObject结构体的指针变量

继续替换

//c语言
__attribute__ ((visibility ("default"))) PyObject* PyInit_array(void)
{
    return PyModuleDef_Init(&arraymodule);
}

//C++
extern "C" __attribute__ ((visibility ("default"))) PyObject* PyInit_array(void)
{
    return PyModuleDef_Init(&arraymodule);
}

结果是定义了一个其它模块可以访问的模块初始化函数,用于初始化arraymodule,并返回PyObject结构体的指针变量

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

总结

  1. GCC 的attribute语法可以为函数,结构体,类,枚举, 变量,标签等添加属性。语法:

    __attribute__ ((属性列表))

    位置一般在关键字之后,或整个声明之后,具体查阅官方文档

  2. visibility属性

    extern __attribute__ ((visibility ("default"))) PublicFunc1();
    class __attribute__ ((visibility ("default"))) PublicClass1;

    • default:具有外部链接性 (external linkage),可以被外部其它模块引用,并且有可能被重写

    • hidden:只能在同一共享对象(可简单理解为库文件)中被引用

    • internal:无法被其它模块直接引用,但是可以通过指针间接引用

    • protected:可以被引用,但无法被重写

  3. visibility属性实际应用之cpython

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

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

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

“visibility属性在cpython中的应用方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注本站网站,小编将为大家输出更多高质量的实用文章!

《visibility属性在cpython中的应用方法是什么.doc》

下载本文的Word格式文档,以方便收藏与打印。