为了账号安全,请及时绑定邮箱和手机立即绑定

如何在多个 C 扩展之间共享 C 单例

如何在多个 C 扩展之间共享 C 单例

冉冉说 2023-08-03 17:06:59
我有一个静态库(或一堆 c/cpp 文件),其中包含一个单例,并由/链接到两个不同的 C 扩展使用。然而,C 库中的单例不再表现得像单例:import getterimport 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=3cdef extern from "lib.h":    int get_singleton()def get():    return get_singleton()# setter.pyx:#cython: language_level=3cdef extern from "lib.h":    void set_singleton(int new_val);def set(new_val):    set_singleton(new_val)此SO-post之后的安装文件:#setup.pyfrom setuptools import setup, find_packages, Extensionsetup(      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-扩展之间共享 C-单例的正确方法是什么?
查看完整描述

1 回答

?
湖上湖

TA贡献2003条经验 获得超2个赞

目前的问题是该变量singleton存在两次:一次在扩展中setter,一次在扩展中getter(也可以运行get_singletonset_singleton存在两次,即每个有两个不同的地址),这或多或少违反了一个定义规则(ODR),甚至如果这个规则只存在于C++中。违反 ODR 并不是世界末日,但在大多数情况下,该行为变得不可移植,因为不同的链接器/编译器/操作系统以不同的方式处理这种情况。

例如,对于 Linux 上的共享库,我们有符号插入。然而,Python 使用ldopenwithout RTLD_GLOBAL(隐含 with RTLD_LOCAL)来加载 C 扩展,从而防止符号插入。我们可以在 Python 中强制使用RTLD_GLOBAL

import sys; import ctypes;
sys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL)

getter在导入并setter再次恢复单例属性之前。然而,这在 Windows 上不起作用,因为 dll 不支持符号插入。

确保“单例属性”的可移植方法是避免违反 ODR,并且为了实现这一点,应该使静态库/文件束动态化。该动态库只会被进程加载一次,从而确保我们只有一个singleton.

根据具体情况,可以选择如何使用此 dll:

  1. 这些扩展仅在本地使用,而不是使用共享对象

  2. 扩展仅分布在某些平台上,那么可以预构建共享对象/dll 并像第三方库一样分布它们,例如参见此 SO- post

  3. 可以覆盖setuptools的build_clib命令,因此它将构建一个共享对象/dll而不是静态库,当扩展被链接并复制到安装时将使用该库。然而,添加更多的扩展,这将使用这个 dll 是相当麻烦的(即使不是不可能)。

  4. 可以编写 dll 的 cython 包装器,它的优点是使用 Python 的机制来加载和推迟符号的结果直到运行时,而不是底层操作系统的链接器/加载器,这使得例如,稍后可以更轻松地根据动态库创建进一步的扩展。

我认为默认情况下应该使用最后一种方法。这是一个可能的实现:

  1. 创建静态库的包装并通过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']),],

     )

构建 via 后python setup.py build_ext --inplace(在源代码分发中,即python setup.py build sdisth 文件将丢失,但对于此问题可能有许多不同的解决方案),示例将set/get相同的单例(因为只有一个)。


查看完整回答
反对 回复 2023-08-03
  • 1 回答
  • 0 关注
  • 77 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信