我创建了这个
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
,反之亦然,其他文件不会这样命名。
尸检。
同时:
Distutils 已弃用(由 SetupTools 取代 - 其中包含它的副本)
pyproject.toml 推荐使用 setup.py 来构建:[Python.Packaging]:如何现代化基于 setup.py 的项目?
setuptools.extension.Extension用于构建扩展模块([Python.Docs]:扩展和嵌入Python解释器),它是一个.dll(.so),必须包含(除其他外)由Python加载时执行的(特殊)函数(并且具有一些其他属性/限制)。检查:
[SO]:版本名称“cp27”或“cp35”在Python中意味着什么?有关文件命名约定的详细信息
[SO]:c 程序 SWIG 到 python 给出“导入错误:动态模块未定义 init 函数”(@CristiFati 的答案) 有关(上述)函数的详细信息
有多种方法可以从 Python 执行 C 代码,每种方法都有其 pro 和 con。 完整的示例可以在[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)
注释:
除了(主要)问题(构建代码)之外,我还发现了其他(主要/次要)问题,有些我修复了,有些我没有修复:
调用函数时,Undefined Behavior。检查一下 [SO]:通过 ctypes 从 Python 调用的 C 函数返回不正确的值(@CristiFati 的答案)
将 .dll 和 Python 包装代码分组到一个包中。这样,在搜索 .dll 时就无需依赖(蹩脚的)解决方法 (gainarii
)在某些情况下,从函数返回 struct 可能会出现问题。返回一个 struct 指针
其他小改进(重命名、创建 Python 更友好的函数、错误处理......)
在C中分配的任何内存也必须被释放,否则会出现内存泄漏。 没有照顾这个。 [SO]:CTypes:Numpy 数组始终具有不同的值(@CristiFati 的答案) 包含一个雄辩的示例
输出:
(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.