如何在 C 共享库中正确使用 Python/C API?

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

提前为这个非常长的问题道歉——这是一段旅程……所以提前感谢您的耐心等待。

长话短说:

python 在使用此设置时抛出段错误:

[numpy 代码]<-- [python/C api] <-- [C-implemented .so library] <-- [py driver using ctypes'

cdll
]

注意 py 驱动程序仅用于测试 .so 库,该库最终旨在用于另一个 3rd 方应用程序。

背景

我们的团队有一些 python 代码,我们正试图将其集成到另一个第 3 方应用程序中,并且该应用程序需要以共享对象文件(.so 库)的形式实现符号。为实现这一点,我试图在 C 中创建一个利用 Python/C API 的共享对象库。我遇到了一些让事情按预期运行的问题,所以我试图将问题提炼成一个“尽可能简单”的例子:

我正在使用 Python/C API 制作一个用 C 编写的基本共享对象库,特别是这个库的方法具有

numpy
依赖性(这在以后很重要)。

该库最终旨在由“预先存在”(我无法控制编译/链接等)的“运行时应用程序”(通过动态加载符号,例如使用dlfcn.h)使用. 所述程序)。

我遇到了一个看起来像是链接问题的问题,但可能是 so 的编译方式和/或我如何通过 C API 使用/配置 python 的问题。

我知道这是可能的,因为我在互联网上发现了一些现有的用例,但我似乎无法将我的设置失败的地方归零。

设置

假设我在 ubuntu 20.04 上安装了 python3.8-dev 系统范围(在这个例子中我实际上是在一个 docker 容器中工作,所以如果它最终可能是一个系统设置问题我很乐意提供 dockerfile ).

此外,我还设置了一个虚拟环境,例如

~/venv
,它已激活,并且安装了 numpy——我可以确认这一点,例如,

(venv) user@4189d31a5bbe:~$ which python && python -c "import numpy; print(numpy)"
/home/user/venv/bin/python
<module 'numpy' from '/home/user/venv/lib/python3.8/site-packages/numpy/__init__.py'>

图书馆文件

我有以下头文件/源文件:

mylibwithpy.h

#ifndef __MYLIBWITHPY__
#define __MYLIBWITHPY__

#include <stdio.h>
#include <Python.h>

void someFunctionWithPython();

#endif

mylibwithpy.c

someFunctionWithPython
所做的所有功能是检查 python 是否已初始化,如果未初始化则进行初始化,然后尝试导入
numpy
.

#include "mylibwithpy.h"

void someFunctionWithPython()
{
    if (!Py_IsInitialized())
    {
        printf("Initializing python...\n");
        Py_Initialize();
    }
    else
    {
        printf("python alread initialized.\n");
    }

    printf("importing numpy...\n");
    PyObject* numpy = PyImport_ImportModule("numpy");
    if (numpy == NULL)
    {
        printf("Warning: error during import:\n");
        PyErr_Print();
        Py_Finalize();
        exit(1);
    }
    return;
}

库 *.so 文件是通过这些

Makefile
目标编译的:

mylibwithpy.o:
    gcc -L/usr/lib/x86_64-linux-gnu -I/usr/include/python3.8 -Wall -c mylibwithpy.c -o $@ -lpython3.8

mylibwithpy.so: mylibwithpy.o 
    gcc -L/usr/lib/x86_64-linux-gnu -Wall -fPIC -shared -Wl,-soname,$@ -o $@ mylibwithpy.o -lpython3.8

在这一点上,

ldd
似乎到目前为止“还可以”:

(venv) user@4189d31a5bbe:~$ ldd mylibwithpy.so 
    linux-vdso.so.1 (0x00007ffc36ba6000)
    libpython3.8.so.1.0 => /lib/x86_64-linux-gnu/libpython3.8.so.1.0 (0x00007f295c5ea000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f295c3f8000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f295c3ca000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f295c3ae000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f295c38b000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f295c385000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f295c37e000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f295c22f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f295cb4f000)

运行时示例

这就是事情开始出错的地方......

首先,让我们尝试一个基本的 python 驱动程序,使用 python 的

ctypes
库。

driver.py

from ctypes import cdll

if __name__ == "__main__":

    print("opening mylibwithpy.so...");
    my_so = cdll.LoadLibrary("mylibwithpy.so")

    print(".so object: ", my_so)
    print(".so object's 'someFunctionWithPython': ", my_so.someFunctionWithPython)

    print("calling someFunctionWithPython...");
    my_so.someFunctionWithPython()

此基本脚本将在 numpy 尝试导入 lib 函数时导致

(Segmentation fault)
错误:

(venv) user@4189d31a5bbe:~$ LD_LIBRARY_PATH=. python driver.py 
opening mylibwithpy.so...
.so object:  <CDLL 'mylibwithpy.so', handle 19f43b0 at 0x7fd873b72610>
.so object's 'someFunctionWithPython':  <_FuncPtr object at 0x7fd873ac91c0>
calling someFunctionWithPython...
python alread initialized.
importing numpy...
Segmentation fault (core dumped)

好吧,所以我什至不确定如何 start 来调试这个人,所以让我们用 C:

中的等效驱动程序再试一次

driver.c

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("opening mylibwithpy.so...\n");
    void* mylibwithpy_so = dlopen("mylibwithpy.so", RTLD_LAZY);
    if (mylibwithpy_so == NULL){
        printf("an error occurred during loading mylibwithpy.so: \n%s\n", dlerror());
        exit(1);
    }

    void (*soFunc)();
    soFunc = dlsym(mylibwithpy_so, "someFunctionWithPython");
    if (soFunc == NULL){
        printf("an error occurred during loading symbol someFunctionWithPython: \n%s\n", dlerror());
        exit(1);
    }

    soFunc();

    return 0;
}

通过以下方式编译此程序:

gcc -L/usr/lib/x86_64-linux-gnu -Wall driver.c -o cdriver -ldl

运行此驱动程序会导致报告一个有趣得多的详细错误:

(venv) user@4189d31a5bbe:~$ LD_LIBRARY_PATH=. ./cdriver 
opening mylibwithpy.so...
Initializing python...
importing numpy...
Warning: error during import:
Traceback (most recent call last):
  File "/home/user/venv/lib/python3.8/site-packages/numpy/core/__init__.py", line 23, in <module>
    from . import multiarray
  File "/home/user/venv/lib/python3.8/site-packages/numpy/core/multiarray.py", line 10, in <module>
    from . import overrides
  File "/home/user/venv/lib/python3.8/site-packages/numpy/core/overrides.py", line 6, in <module>
    from numpy.core._multiarray_umath import (
ImportError: /home/user/venv/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyObject_SelfIter

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/user/venv/lib/python3.8/site-packages/numpy/__init__.py", line 141, in <module>
    from . import core
  File "/home/user/venv/lib/python3.8/site-packages/numpy/core/__init__.py", line 49, in <module>
    raise ImportError(msg)
ImportError: 

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.8 from "/home/user/venv/bin/python3"
  * The NumPy version is: "1.24.2"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: /home/user/venv/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyObject_SelfIter

啊哈!(...?)

据此,numpy 似乎有一些自己的共享对象,但不知何故缺少一些符号(PyObject_SelfIter,准确地说——旁注,这个符号列在Python/C API“稳定的 ABI 内容” ):

/home/user/venv/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyObject_SelfIter

(另一个注意事项:列出的 numpy 文档参考 https://numpy.org/devdocs/user/troubleshooting-importerror.html 似乎不太适用于我遇到的情况或错误,但更挑剔眼睛可能会发现一些我忽略的有用的东西......)

此外,快速

ldd
检查表明确实
libpython
不在动态链接库中:

(venv) user@4189d31a5bbe:~$ ldd /home/user/venv/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so
    linux-vdso.so.1 (0x00007ffed7df2000)
    libopenblas64_p-r0-15028c96.3.21.so => /home/user/venv/lib/python3.8/site-packages/numpy/core/../../numpy.libs/libopenblas64_p-r0-15028c96.3.21.so (0x00007f98a1ae6000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f98a198f000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f98a196c000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f98a177a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f98a3f29000)
    libgfortran-040039e1.so.5.0.0 => /home/user/venv/lib/python3.8/site-packages/numpy/core/../../numpy.libs/libgfortran-040039e1.so.5.0.0 (0x00007f98a12ed000)
    libquadmath-96973f99.so.0.0.0 => /home/user/venv/lib/python3.8/site-packages/numpy/core/../../numpy.libs/libquadmath-96973f99.so.0.0.0 (0x00007f98a10ae000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f98a1092000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f98a1077000)

测试链接器问题假设

由于我们在

cdriver
程序中打印出一个未定义的符号错误,我可以尝试通过以下方式将 libpython 强制链接到 cdriver:

gcc -L/usr/lib/x86_64-linux-gnu -Wall driver.c -o cdriver -ldl -Wl,--no-as-needed -lpython3.8

你瞧,这次程序没有错误地完成了:

(venv) user@4189d31a5bbe:~$ LD_LIBRARY_PATH=. ./cdriver 
opening mylibwithpy.so...
Initializing python...
importing numpy...
(venv) user@4189d31a5bbe:~$ 

注意在实际构建中我将无法访问编译/链接运行时程序,因此此检查NOT解决方案但似乎有助于诊断问题?

所以......实际问题

  1. 例如,让 python 驱动程序按预期工作需要什么?
  2. 为什么 numpy 的内部共享对象文件没有链接到
    libpython3.8.so
  3. 我错过了什么?! :呜咽:

我希望我只是错过了编译 .so 或配置 python 的一些小但关键的步骤。

python c linker shared-libraries
1个回答
1
投票

使用

cdll.LoadLibrary
导出的函数时,您将在进入方法时释放全局解释器锁 (GIL)。如果要调用python代码,需要重新获取锁

例如

void someFunctionWithPython()
{
    ...
    PyGILState_STATE state = PyGILState_Ensure();
    printf("importing numpy...\n");
    PyObject* numpy = PyImport_ImportModule("numpy");
    if (numpy == NULL)
    {
        printf("Warning: error during import:\n");
        PyErr_Print();
        Py_Finalize();
        PyGILState_Release(state);
        exit(1);
    }

    PyObject* repr = PyObject_Repr(numpy);
    PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
    const char *bytes = PyBytes_AS_STRING(str);

    printf("REPR: %s\n", bytes);

    Py_XDECREF(repr);
    Py_XDECREF(str);

    PyGILState_Release(state);
    return;
}
$ gcc $(python3.9-config --includes --ldflags --embed) -shared -o mylibwithpy.so mylibwithpy.c
$ LD_LIBRARY_PATH=. python driver.py
opening mylibwithpy.so...
.so object:  <CDLL 'mylibwithpy.so', handle 1749f50 at 0x7fb603702fa0>
.so object's 'someFunctionWithPython':  <_FuncPtr object at 0x7fb603679040>
calling someFunctionWithPython...
python alread initialized.
importing numpy...
REPR: <module 'numpy' from '/home/me/test/.venv/lib/python3.9/site-packages/numpy/__init__.py'>
© www.soinside.com 2019 - 2024. All rights reserved.