python - 如何在多个 C 扩展之间共享 C 单例
问题描述
我有一个静态库(或一堆 c/cpp 文件),其中包含一个单例并由/链接到两个不同的 C 扩展。但是,来自 C 库的单例不再像单例那样:
import getter
import setter
# set singleton:
setter.set(21)
# get singleton:
print("singleton: ", getter.get())
#prints the old value:42
为了简单起见,这是一个使用 Cython 说明此问题的最小示例(所有文件都在同一个文件夹中):
C库:
//lib.h:
int get_singleton(void);
void set_singleton(int new_val);
//lib.c:
#include "lib.h"
static int singleton=42;
int get_singleton(void){
return singleton;
}
void set_singleton(int new_val){
singleton=new_val;
}
两个 Cython 扩展:
# getter.pyx:
#cython: language_level=3
cdef extern from "lib.h":
int get_singleton()
def get():
return get_singleton()
# setter.pyx:
#cython: language_level=3
cdef extern from "lib.h":
void set_singleton(int new_val);
def set(new_val):
set_singleton(new_val)
此SO-post之后的设置文件:
#setup.py
from setuptools import setup, find_packages, Extension
setup(
name='singleton_example',
ext_modules=[Extension('getter', sources=['getter.pyx']),
Extension('setter', sources=['setter.pyx']),],
# will be build as static libraries and automatically passed to linker for all extensions:
libraries = [('static_lib', {'sources': ["lib.c"]}) ]
)
通过构建后python setup.py build_clib build_ext --inplace
,就可以运行上面的python脚本了。
在多个(Cython)-C-extensions 之间共享 C-singleton 的正确方法是什么?
解决方案
手头的问题是变量singleton
存在两次:一次在扩展中setter
,一次在扩展中getter
(也是函数get_singleton
并set_singleton
存在两次,即每个都有两个不同的地址),这或多或少违反了一个定义规则(ODR),甚至如果此规则仅存在于 C++ 中。违反 ODR 并不是世界末日,但在大多数情况下,行为变得不可移植,因为不同的链接器/编译器/操作系统处理这种情况的方式不同。
例如,对于 Linux 上的共享库,我们有符号插入。但是,Python 使用ldopen
without RTLD_GLOBAL
(意味着隐式 with RTLD_LOCAL
)来加载 C 扩展,从而防止符号插入。我们可以强制RTLD_GLOBAL
在 Python 中使用 :
import sys; import ctypes;
sys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL)
在再次导入getter
和setter
恢复单例属性之前。然而,这在 Windows 上不起作用,因为 dll 不支持符号插入。
确保“单例属性”的可移植方法是避免违反 ODR,并且为了实现这一点,应该使静态库/文件束动态化。这个动态库只会被进程加载一次,从而确保我们只有一个singleton
.
根据场景,有一些选项可以如何使用这个 dll:
- 扩展仅在本地使用而不是分发,使用共享对象(请参阅此SO-post)或 dll(请参阅此SO-post)。
- 扩展仅在某些平台上分发,然后可以预构建共享对象/dll 并像第三方库一样分发它们,例如看到这个SO-post
- 可以覆盖 setuptools 的
build_clib
命令,因此它将构建一个共享对象/dll 而不是静态库,当扩展被链接并复制到安装时将使用该库。然而,添加更多的扩展,这将使用这个 dll 非常麻烦(即使不是不可能)。 - 可以编写 dll 的 cython 包装器,它的优点是使用 Python 的机制来加载和推迟符号的结果直到运行时,而不是底层操作系统的链接器/加载器,这使得它例如,稍后根据动态库更容易创建更多扩展。
我认为应该默认使用最后一种方法。这是一个可能的实现:
- 创建静态库的包装器并通过
pxd
-file 公开其功能:
# lib_wrapper.pxd
cdef int get_singleton()
cdef void set_singleton(int new_value)
#lib_wrapper.pyx
cdef extern from "lib.h":
int c_get_singleton "get_singleton" ()
void c_set_singleton "set_singleton" (int new_val)
cdef int get_singleton():
return c_get_singleton()
cdef void set_singleton(int new_val):
c_set_singleton(new_val)
一个重要的部分:包装器引入了一个间接级别(因此引入了许多应该自动化的样板代码编写),因此在进一步的模块中使用它时,既不需要头文件也不需要 c 文件/库。
- 调整其他模块,它们只需要
cimport
包装器:
# getter.pyx:
#cython: language_level=3
cimport lib_wrapper
def get():
return lib_wrapper.get_singleton()
# setter.pyx:
#cython: language_level=3
cimport lib_wrapper
def set(new_val):
lib_wrapper.set_singleton(new_val)
- 安装程序不再需要
build_clib
-step:
from setuptools import setup, find_packages, Extension
setup(
name='singleton_example',
ext_modules=[Extension('lib_wrapper', sources=['lib_wrapper.pyx', 'lib.c']),
Extension('getter', sources=['getter.pyx']),
Extension('setter', sources=['setter.pyx']),],
)
通过构建后python setup.py build_ext --inplace
(在源代码分发中,即python setup.py build sdist
h 文件将丢失,但可能有许多不同的解决方案来解决这个问题)示例将set
/get
相同的单例(因为只有一个)。
推荐阅读
- javascript - 为什么我的组件在使用 React 的 Context API 和 useEffect 钩子时会渲染两次?
- ios - 清除 VNDocumentCameraScan Swift 中的扫描图像缓存
- prolog - Prolog初学者错误:超出本地堆栈
- javascript - 如何将 selenium python 脚本转换为 Selenium Javascript
- canvas - 画布不跟随鼠标的实际位置
- django - 从本地计算机删除 postgresql 后,用户“postgres”的密码不匹配
- optimization - 如何在离散时间使用 Gekko 进行轨迹优化
- php - PHP foreach 循环表单没有出现在第一行
- java - 在重复调用的方法中创建的对象会发生什么?
- java - 如何以编程方式安装Android系统应用程序