gpt4 book ai didi

python - 使用 ctypes/cffi 解决循环共享对象依赖关系

转载 作者:行者123 更新时间:2023-12-01 01:26:04 30 4
gpt4 key购买 nike

我想使用 cffi(如果必须,甚至使用 ctypes)从 Linux 上的 Python 3 访问 C ABI。该 API 由许多 .so 文件实现(我们称它们为 libA.solibB.solibC.so ),这样 libA 包含主要导出函数,其他库提供对 libA 的支持。

现在,libA 依赖于 libBlibB 依赖于 libC。然而,有一个问题。 libA 定义了一个 libC 期望存在的全局数组。所以libC实际上依赖于libA——一个循环依赖。尝试使用与 dlopen 等效的 cffi 或 ctags 加载 libA 会导致 libBlibC 中缺少符号,但是尝试加载 libC 首先会导致有关缺少数组(位于 libA 中)的错误。

由于它是一个变量,而不是一个函数,因此 RTLD_LAZY 选项似乎不适用于此处。

奇怪的是,ldd libA.so 没有将 libBlibC 显示为依赖项,所以我不确定这是否是问题。我想这依赖于与这些库链接的任何程序来显式指定它们。

有办法解决这个问题吗?一种想法是创建一个依赖于 libAlibBlibC 的新共享对象(例如“all.so”),以便dlopen("all.so") 可能会一次性加载它需要的所有内容,但我也无法让它工作。

处理这种情况的最佳策略是什么?实际上,我尝试访问的 ABI 相当大,可能有 20-30 个共享对象文件。

最佳答案

这(如果我正确理解了问题)是 Nix 上的一个完全正常的用例,并且应该运行没有问题。

在处理与ctypes([Python 3]: ctypes - A foreign function library for Python)相关的问题时,解决这些问题的最佳(通用)方法是:

  • 编写一个(小型)C应用程序来完成所需的工作(当然,是有效的)
  • 然后才转到ctypes(基本上这是翻译上面的应用程序)

我准备了一个小(虚拟)示例:

  • defines.h:

    #pragma once

    #include <stdio.h>

    #define PRINT_MSG_0() printf("From C: [%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__)
  • libC:

    • libC.h:

      #pragma once


      size_t funcC();
    • libC.c:

      #include "defines.h"
      #include "libC.h"
      #include "libA.h"


      size_t funcC() {
      PRINT_MSG_0();
      for (size_t i = 0; i < ARRAY_DIM; i++)
      {
      printf("%zu - %c\n", i, charArray[i]);
      }
      printf("\n");
      return ARRAY_DIM;
      }
  • libB:

    • libB.h:

      #pragma once


      size_t funcB();
    • libB.c:

      #include "defines.h"
      #include "libB.h"
      #include "libC.h"


      size_t funcB() {
      PRINT_MSG_0();
      return funcC();
      }
  • libA:

    • libA.h:

      #pragma once

      #define ARRAY_DIM 3


      extern char charArray[ARRAY_DIM];

      size_t funcA();
    • libA.c:

      #include "defines.h"
      #include "libA.h"
      #include "libB.h"


      char charArray[ARRAY_DIM] = {'A', 'B', 'C'};


      size_t funcA() {
      PRINT_MSG_0();
      return funcB();
      }
  • code.py:

    #!/usr/bin/env python3

    import sys
    from ctypes import CDLL, \
    c_size_t


    DLL = "./libA.so"


    def main():
    lib_a = CDLL(DLL)
    func_a = lib_a.funcA
    func_a.restype = c_size_t

    ret = func_a()
    print("{:s} returned {:d}".format(func_a.__name__, ret))


    if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ls
code.py defines.h libA.c libA.h libB.c libB.h libC.c libC.h
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libC.so libC.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libB.so libB.c -L. -lC
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libA.so libA.c -L. -lB
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ls
code.py defines.h libA.c libA.h libA.so libB.c libB.h libB.so libC.c libC.h libC.so
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. ldd libC.so
linux-vdso.so.1 => (0x00007ffdfb1f4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f56dcf23000)
/lib64/ld-linux-x86-64.so.2 (0x00007f56dd4ef000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. ldd libB.so
linux-vdso.so.1 => (0x00007ffc2e7fd000)
libC.so => ./libC.so (0x00007fdc90a9a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdc906d0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdc90e9e000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. ldd libA.so
linux-vdso.so.1 => (0x00007ffd20d53000)
libB.so => ./libB.so (0x00007fdbee95a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdbee590000)
libC.so => ./libC.so (0x00007fdbee38e000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdbeed5e000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> nm -S libC.so | grep charArray
U charArray
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> nm -S libA.so | grep charArray
0000000000201030 0000000000000003 D charArray
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. python3 code.py
Python 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609] on linux

From C: [libA.c] (9) - [funcA]
From C: [libB.c] (7) - [funcB]
From C: [libC.c] (7) - [funcC]
0 - A
1 - B
2 - C

funcA returned 3

但是,如果您的数组被声明为static ([CPPReference]: C keywords: static)(因此,它不能像示例中那样extern),那么您'有点烤了。

@EDIT0:扩展示例,使其更适合描述。

由于ldd不显示.so之间的依赖关系,我将假设每个都是动态加载的。

  • utils.h:

    #pragma once

    #include <dlfcn.h>


    void *loadLib(char id);
  • utils.c:

    #include "defines.h"
    #include "utils.h"


    void *loadLib(char id) {
    PRINT_MSG_0();
    char libNameFormat[] = "lib%c.so";
    char libName[8];
    sprintf(libName, libNameFormat, id);
    int load_flags = RTLD_LAZY | RTLD_GLOBAL; // !!! @TODO - @CristiFati: Note RTLD_LAZY: if RTLD_NOW would be here instead, there would be nothing left to do. Same thing if RTLD_GLOBAL wouldn't be specified. !!!
    void *ret = dlopen(libName, load_flags);
    if (ret == NULL) {
    char *err = dlerror();
    printf("Error loading lib (%s): %s\n", libName, (err != NULL) ? err : "(null)");
    }
    return ret;
    }

下面是libB.c的修改版本。请注意,相同的模式也应应用于原始的libA.c

  • libB.c:

    #include "defines.h"
    #include "libB.h"
    #include "libC.h"
    #include "utils.h"


    size_t funcB() {
    PRINT_MSG_0();
    void *mod = loadLib('C');
    size_t ret = funcC();
    dlclose(mod);
    return ret;
    }

输出:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ls
code.py defines.h libA.c libA.h libB.c libB.h libC.c libC.h utils.c utils.h
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libC.so libC.c utils.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libB.so libB.c utils.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> gcc -fPIC -shared -o libA.so libA.c utils.c
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ls
code.py defines.h libA.c libA.h libA.so libB.c libB.h libB.so libC.c libC.h libC.so utils.c utils.h
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ldd libA.so
linux-vdso.so.1 => (0x00007ffe5748c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d9e3f6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4d9e9c2000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ldd libB.so
linux-vdso.so.1 => (0x00007ffe22fe3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe93ce8a000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe93d456000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> ldd libC.so
linux-vdso.so.1 => (0x00007fffe85c3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2d47453000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2d47a1f000)
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> nm -S libC.so | grep charArray
U charArray
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> nm -S libA.so | grep charArray
0000000000201060 0000000000000003 D charArray
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. python3 code.py
Python 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609] on linux

Traceback (most recent call last):
File "code.py", line 22, in <module>
main()
File "code.py", line 12, in main
lib_a = CDLL(DLL)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
self._handle = _dlopen(self._name, mode)
OSError: ./libA.so: undefined symbol: funcB

我相信这会重现该问题。现在,如果您将code.py(的第1st部分)修改为:

#!/usr/bin/env python3

import sys
from ctypes import CDLL, \
RTLD_GLOBAL, \
c_size_t


RTLD_LAZY = 0x0001

DLL = "./libA.so"


def main():
lib_a = CDLL(DLL, RTLD_LAZY | RTLD_GLOBAL)
func_a = lib_a.funcA
func_a.restype = c_size_t

ret = func_a()
print("{:s} returned {:d}".format(func_a.__name__, ret))


if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()

您将得到以下输出:

[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q053327620]> LD_LIBRARY_PATH=. python3 code.py
Python 3.5.2 (default, Nov 12 2018, 13:43:14)
[GCC 5.4.0 20160609] on linux

From C: [libA.c] (11) - [funcA]
From C: [utils.c] (6) - [loadLib]
From C: [libB.c] (8) - [funcB]
From C: [utils.c] (6) - [loadLib]
From C: [libC.c] (7) - [funcC]
0 - A
1 - B
2 - C

funcA returned 3

注释:

  • C中,RTLD_LAZY | RTLD_GLOBAL 的存在非常重要。如果将 RTLD_LAZY 替换为 RTLD_NOW它将不起作用
    • 此外,如果未指定RTLD_GLOBAL,它也将不起作用。我没有检查是否可以指定其他 RTLD_ 标志来代替 RTLD_GLOBAL 以使事情仍然有效
  • 创建处理所有库加载和初始化的包装器库将是一件好事(解决方法),特别是如果您计划从多个位置使用它们(这样,整个过程只会在一个地方发生)。但是,之前的要点仍然适用
  • 出于某种原因,ctypes 不会公开 RTLD_LAZY(事实上,还有许多其他相关标志)。在code.py中定义它是一种解决方法,并且在不同的(Nix)平台(风格)上,它的值可能会有所不同

关于python - 使用 ctypes/cffi 解决循环共享对象依赖关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53327620/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com