Разрешение циклических зависимостей общих объектов с помощью ctypes / cffi - PullRequest
0 голосов
/ 15 ноября 2018

Я хотел бы использовать cffi (или даже ctypes, если нужно) для доступа к C ABI из Python 3 в Linux. API реализуется несколькими .so файлами (назовем их libA.so, libB.so и libC.so), так что libA содержит основные экспортируемые функции, а другие библиотеки обеспечивают поддержку libA .

Теперь libA зависит от libB, а libB зависит от libC. Тем не менее, есть проблема. Существует глобальный массив, определенный libA, который libC ожидает присутствия. Так что libC на самом деле зависит от libA - круговой зависимости. Попытка использовать cffi или ctags, эквивалентные dlopen для загрузки libA, приводит к отсутствию символов из libB и libC, но попытка загрузить libC сначала приводит к ошибке об отсутствующем массиве (который находится в libA).

Поскольку это переменная, а не функция, опция RTLD_LAZY здесь не применима.

Как ни странно, ldd libA.so не показывает libB или libC как зависимости, поэтому я не уверен, является ли это частью проблемы. Я полагаю, что полагается на любую программу, которая связывается с этими библиотеками, чтобы явно указать их все.

Есть ли способ обойти это? Одной из идей было создание нового общего объекта (скажем, «all.so»), который зависит от libA, libB и libC, чтобы dlopen("all.so") мог загрузить все, что ему нужно, за один раз, но я могу тоже не получится.

Какова лучшая стратегия для решения этой ситуации? На самом деле ABI, к которому я пытаюсь получить доступ, довольно большой, с 20-30 общими объектными файлами.

1 Ответ

0 голосов
/ 16 ноября 2018

Это (если я правильно понял проблему) совершенно нормальный вариант использования на Nix , и он должен работать без проблем.

При решении проблем, связанных с ctypes ( [Python 3]: ctypes - библиотека внешних функций для Python ), лучший (универсальный) способ их решения:

  • Написать (маленькое) C приложение, которое выполняет требуемую работу (и, конечно, работает)
  • Только затем перейдите к ctypes (в основном это перевод вышеуказанного приложения)

Я подготовил небольшой (и фиктивный) пример:

  • define.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: статические ) (и, следовательно, как следствие, он не может быть 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

Я считаю, что это воспроизводит проблему.Теперь, если вы измените (1 st часть) code.py на:

#!/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()

, вы получите следующий output :

[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 ) платформах (разновидностях) его значение может отличаться
...