使用 ctyped.cdll.loadLibrary 从 dist-packages 文件夹加载共享对象

问题描述 投票:0回答:1

我创建了这个

setup.py
来编译
libDIV.so
共享库并安装它。

from distutils.core import setup, Extension

libDIV = Extension(
  'DIV',
  sources = ['example.c']
)

setup (
  name = 'divv',
  version = '0.1',
  description = 'DIVV, you want it',
  ext_modules = [libDIV],
  packages = [''],
  package_data={'': ['libDIV.so']}
)

运行

python3 setup.py install
后,共享库被放置在dist-packages文件夹中,如下:

/usr/local/lib/python3.4/dist-packages/DIV.cpython-34m.so

在额外的文件中

example.py
我想使用
ctypes.cdll.LoadLibrary
加载库,这样
example.py
可以将其功能包装在更优雅的、类型检查的Python函数中。

import ctypes
lib = ctypes.cdll.LoadLibrary('DIV.so') # not sure what to do here

def divide_modulo(a, b):
  div = ctypes.c_int(0)
  rest = ctypes.c_int(0)
  lib.divide_modulo(a, b, ctypes.byref(div), ctypes.byref(rest))
  return (div.value, rest.value)

print(divide_modulo(11, 4))

class DynamicArray(ctypes.Structure):
  _fields_ = [("n", ctypes.c_int), ("r", ctypes.POINTER(ctypes.c_int))]

但是我不确定如何为创建的共享对象文件传递正确的文件名和位置。

我应该如何分发/配置 setup.py 来构建共享库和一些 python 代码来包装 c 功能的 c 文件库?

为了完整起见,这是 example.c:

#include <stdlib.h>

void divide_modulo(int a, int b, int *div, int *rest)
{
*div  = a / b;
*rest = a % b;
}

typedef struct dynamic_array {
  int n;
  int *r;
} dynamic_array;

dynamic_array return_dynamic_array(int n)
{
  dynamic_array r;
  r.n = n;
  r.r = (int *)malloc(sizeof(int) * n);
  unsigned int i = 0;
  for (; i < n; ++i)
  r.r[i] = i;
  return r;
}

编辑:

目前我这样做是为了查找文件名:

import ctypes, numpy, site, os.path
from scipy.sparse import csr_matrix

so_file = next(
    os.path.join(directory, filename)
    for directory in site.getsitepackages()
    for filename in os.listdir(directory)
    if filename.startswith("DIV.") and filename.endswith(".so")
)
lib = ctypes.cdll.LoadLibrary(so_file)

但说实话,这很臭。我不确定该文件将始终被称为

DIV.something.so
,反之亦然,其他文件不会这样命名。

python c shared-libraries ctypes setuptools
1个回答
0
投票

尸检。

同时:

setuptools.extension.Extension用于构建扩展模块[Python.Docs]:扩展和嵌入Python解释器),它是一个.dll.so),必须包含(除其他外)由Python加载时执行的(特殊)函数(并且具有一些其他属性/限制)。检查:

有多种方法可以从 Python 执行 C 代码,每种方法都有其 procon完整的示例可以在[SO]中找到:将 str 作为 int 数组传递给 Python C 扩展函数(使用 SWIG 扩展)(@CristiFati 的答案)

现在,选择了此方法(CTypes)而不是其他方法。不打算评论原因,但由于 .dll 是为 Python 打包的(这就是它的目的,因为使用了 setup.py),包装器代码应该一起发布(否则它必须是在所有需要 .dll 的地方都重复 - 这没有任何意义)。

由于某种原因 SetupTools 不提供 OOTB 构建普通 C .dll 的方法(并将其包含在发行版中)。我需要类似的功能,并且在我的一个项目中有一些解决方法。
但在偶然发现这个问题之后,我做了一些更深入的挖掘,并提出了一个可以以通用方式使用的更好的解决方案。我将其放入 [PyPI]:pycfutils(从 v2024.10.26 开始)。它也用于构建自身。

我准备了一个工作示例。

  • div.c:

    #include <stdlib.h>
    
    
    typedef struct {
        unsigned int size;
        int *data;
    } DynamicArray;
    
    
    void divideModulo(int a, int b, int *pDiv, int *pMod)
    {
        if (b == 0)
            return;
        if (pDiv != NULL)
            *pDiv = a / b;
        if (pMod != NULL)
            *pMod = a % b;
    }
    
    
    DynamicArray* createDynamicArray(unsigned int size)
    {
        DynamicArray *pda = malloc(sizeof(DynamicArray));
        pda->size = size;
        pda->data = (int*)malloc(sizeof(int) * size);
        for (unsigned int i = 0; i < size; ++i)
            pda->data[i] = i;
        return pda;
    }
    
  • __init__.py:

    import ctypes as cts
    import os
    import sys
    
    
    __all__ = (
        "DynamicArray",
        "divide_modulo",
        "create_dynamic_array",
        #"destroy_dynamic_array",
    )
    
    IntPtr = cts.POINTER(cts.c_int)
    
    class DynamicArray(cts.Structure):
        _fields_ = (
            ("size", cts.c_uint),
            ("data", IntPtr),
        )
    
    DynamicArrayPtr = cts.POINTER(DynamicArray)
    
    _dll_name = os.path.join(os.path.dirname(os.path.abspath(__file__)), "libdiv." + ("dll" if sys.platform[:3].lower() == "win" else "so"))
    _dll = cts.CDLL(_dll_name)
    
    create_dynamic_array = _dll.createDynamicArray
    create_dynamic_array.argtypes = (cts.c_uint,)
    create_dynamic_array.restype = DynamicArrayPtr
    
    _divide_modulo = _dll.divideModulo
    _divide_modulo.argtypes = (cts.c_int, cts.c_int, IntPtr, IntPtr)
    _divide_modulo.restype = None
    
    def divide_modulo(n0, n1):
        if n1 == 0:
            return None, None
        q = cts.c_int(0)
        r = cts.c_int(0)
        res = _divide_modulo(n0, n1, cts.byref(q), cts.byref(r))
        return q.value, r.value
    
    
    del(_dll)
    del(_dll_name)
    
  • setup.py

    #!/usr/bin/env python
    
    from setuptools import setup
    from pycfutils.setup.command.build_clibdll import build_clibdll
    from pycfutils.setup.command.install_platlib import install_platlib
    
    
    libdiv_dll = (
        "libdiv",
        {
            "sources": ["div.c"],
            "export_symbols": ["divideModulo", "createDynamicArray"],
            "dll": True,
            "copy_files": {
                "libdiv.so": "divv",
            }
        },
    )
    
    
    setup(
        name="divv",
        version="0.1",
        description="DIVV, you want it",
        packages=["divv"],
        package_data={"divv": ["libdiv.so"]},
        libraries=[libdiv_dll],
        cmdclass={
            "build_clib": build_clibdll,
            "install": install_platlib,
        },
    )
    
  • code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import sys
    
    import divv
    
    
    def main(*argv):
        x, y = 20, 7
        print(f"divide_modulo({x}, {y}) = {divv.divide_modulo(x, y)}")
        pda = divv.create_dynamic_array(40)
        da = pda.contents
        print(f"Array:\n  Size: {da.size}\n  Data: ", end="")
        for i in range(min(da.size, 32)):
            print(da.data[i], end=", ")
        print()
        #divv.destroy_dynamic_array(pda)
    
    
    if __name__ == "__main__":
        print(
            "Python {:s} {:03d}bit on {:s}\n".format(
                " ".join(elem.strip() for elem in sys.version.split("\n")),
                64 if sys.maxsize > 0x100000000 else 32,
                sys.platform,
            )
        )
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

注释

除了(主要)问题(构建代码)之外,我还发现了其他(主要/次要)问题,有些我修复了,有些我没有修复:

输出

(py_pc064_03.10_test0) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q034391655]> ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]>
[064bit prompt]> tree
.
+-- demo
¦   +-- code00.py
+-- div.c
+-- divv
¦   +-- __init__.py
+-- setup.py

2 directories, 4 files
[064bit prompt]>
[064bit prompt]> python -m pip install pycfutils
Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com
Collecting pycfutils
  Downloading pycfutils-2024.10.26-py3-none-any.whl.metadata (3.9 kB)
Downloading pycfutils-2024.10.26-py3-none-any.whl (28 kB)
Installing collected packages: pycfutils
Successfully installed pycfutils-2024.10.26

[064bit prompt]>
[064bit prompt]> python ./setup.py build
running build
running build_py
creating build
creating build/lib
creating build/lib/divv
copying divv/__init__.py -> build/lib/divv
running build_clib
building 'libdiv' library
creating build/temp.linux-x86_64-cpython-310
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -fPIC -c div.c -o build/temp.linux-x86_64-cpython-310/div.o
x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 build/temp.linux-x86_64-cpython-310/div.o -o build/temp.linux-x86_64-cpython-310/libdiv.so
[064bit prompt]>
[064bit prompt]> ls build/lib/divv/
__init__.py  libdiv.so
[064bit prompt]>
[064bit prompt]> PYTHONPATH="$(pwd)/build/lib:${PYTHONPATH}" python ./demo/code00.py
Python 3.10.11 (main, Apr  5 2023, 14:15:10) [GCC 9.4.0] 064bit on linux

divide_modulo(20, 7) = (2, 6)
Array:
  Size: 40
  Data: 0, 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,

Done.
© www.soinside.com 2019 - 2024. All rights reserved.