如何在多个C扩展名之间共享C-Singleton

问题描述

我有一个静态库(或一堆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扩展名之间共享C-singleton的正确方法是什么?

解决方法

当前的问题是变量singleton存在两次:一次在扩展名setter中,一次在扩展名getter中(还有函数get_singleton和{{1 }}存在两次,即每个都有两个不同的地址),即使此规则仅在C ++中存在,也或多或少违反了一个定义规则(ODR)。违反ODR并不是世界末日,但在大多数情况下,此行为变得不可移植,因为不同的链接器/编译器/ OS对这种情况的处理方式不同。

例如,对于Linux上的共享库,我们使用符号插入。但是,Python使用不带set_singleton的{​​{3}}(用RTLD_GLOBAL隐式表示)来加载C扩展名,从而防止了符号插入。我们可以在Python中强制使用RTLD_LOCAL

RTLD_GLOBAL

在导入import sys; import ctypes; sys.setdlopenflags(sys.getdlopenflags() | ctypes.RTLD_GLOBAL) getter并再次恢复单例属性之前。但是,这在Windows上不起作用,因为dll不支持符号插入。

确保“单一属性”的可移植方法是避免违反ODR,并且为了实现使动态静态文件库/文件束动态化。该动态库仅由该过程加载一次,因此确保我们只有一个setter

根据情况,有一些如何使用此dll的选项:

  1. 扩展只能在本地使用,而不能通过共享对象(请参见ldopen)或dll(请参见SO-post)进行分发。
  2. 扩展仅在某些平台上分发,然后可以预构建共享的对象/ dll并像第三方库一样分发它们,例如,请参见此SO-post
  3. 可以覆盖setuptools的singleton命令,因此它将构建共享的对象/ dll而不是静态库,该静态库将在扩展链接并复制到安装时使用。但是,添加更多扩展名将使用此dll是非常麻烦的(即使不是没有可能)。
  4. 可以编写dll的cython-wrapper,它的优点是使用Python的机制进行加载,SO-post直到运行时,而不是底层操作系统的链接器/加载器,例如,使以后根据动态库创建更多扩展更加容易。

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

  1. 创建静态库的包装,并通过build_clib文件公开其功能:
pxd

一个重要的部分:包装器引入了一个间接级别(因此导致了许多应该自动执行的样板代码编写),因此在其他模块中使用它时,既不需要头文件也不使用c文件/库

  1. 调整其他模块,它们只需要# 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) 包装器:
cimport
  1. 设置不再需要# 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

在通过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中,h文件将丢失,但是有许多解决此问题的可能方案),示例将python setup.py build sdist / set相同的单例(因为只有一个)。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...