我想使用cffi
(如果需要的话甚至可以使用ctypes
)从Linux上的Python 3访问C ABI。该API由许多.so
文件(我们将其称为libA.so
,libB.so
和libC.so
)实现,因此libA
包含主要的导出函数,其他库为libA
提供支持。
现在,libA
取决于libB
,而libB
取决于libC
。但是,有一个问题。 libA
定义了一个libC
期望存在的全局数组。因此libC
实际上取决于libA
-循环依赖。尝试使用相当于dlopen
的cffi或ctags加载libA
会导致libB
和libC
中缺少符号,但是首先尝试加载libC
会导致关于缺少的数组(位于libA
中的错误)。
由于它是变量而不是函数,因此RTLD_LAZY选项似乎在这里不适用。
奇怪的是,ldd libA.so
并没有将libB
或libC
显示为依赖项,因此我不确定这是否是问题的一部分。我想这依赖于与这些库链接的任何程序来明确指定它们。
有没有办法解决这个问题?一种想法是创建一个依赖于libA
,libB
和libC
的新共享对象(例如“ all.so”),以便dlopen("all.so")
可以加载它的所有内容一口气需要,但我也无法使它正常工作。
处理这种情况的最佳策略是什么?实际上,我要访问的ABI很大,可能有20-30个共享对象文件。
答案 0 :(得分:2)
(如果我正确理解了问题,)这是 Nix 上的一个非常正常的用例,应该可以正常运行。
在处理与 ctypes ([Python 3]: ctypes - A foreign function library for Python)相关的问题时,解决这些问题的最佳(通用)方法是:
我准备了一个小的(和虚拟的)示例:
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 的1 st 部分修改为:
#!/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
注释:
RTLD_LAZY | RTLD_GLOBAL
中非常重要。如果将 RTLD_LAZY 替换为 RTLD_NOW ,则它将不起作用