【问题标题】:Python ctypes LoadLibrary: does it really load a new instance, e.g. so different global variables?Python ctypes LoadLibrary:它是否真的加载了一个新实例,例如如此不同的全局变量?
【发布时间】:2021-08-30 07:00:44
【问题描述】:

来自https://docs.python.org/3/library/ctypes.html关于cdll.LoadLibrary

此方法始终返回库的新实例。

这是否意味着如果库在内部使用了一些全局变量,那么每次调用cdll.LoadLibrary 时,它都会返回一个使用一组新的独立全局变量的库?

(这可能说明对此类共享库的工作方式存在一些误解……很高兴得到纠正)

【问题讨论】:

    标签: python python-3.x shared-libraries ctypes


    【解决方案1】:

    这是一个 ctypes 包装器类型的新 Python 实例,但引用了相同的 DLL。这很容易测试:

    test.c:

    __declspec(dllexport)
    int global = 7;
    
    __declspec(dllexport)
    int increment() {
        global += 1;
        return global;
    }
    

    test.py

    from ctypes import *
    
    dll = CDLL('./test')
    print(dll)
    print(c_int.in_dll(dll,'global'))
    print(dll.increment())
    print(c_int.in_dll(dll,'global'))
    
    dll2 = CDLL('./test')
    print(dll2)
    print(c_int.in_dll(dll2,'global'))
    print(dll2.increment())
    print(c_int.in_dll(dll2,'global'))
    

    下面的输出。注意全局变量不断增加,实例地址不同但句柄(实际上是加载的DLL的虚拟基地址)是相同的。

    <CDLL 'C:\test', handle 7ff81aad0000 at 0x2362f0dbee0>
    c_long(7)
    8
    c_long(8)
    <CDLL 'C:\test', handle 7ff81aad0000 at 0x2362f0dbeb0>
    c_long(8)
    9
    c_long(9)
    

    【讨论】:

      【解决方案2】:

      cdll.LoadLibrary 的行为不受 Python 控制,但它取决于运行 Python 的操作系统。 @MarkTolonen 答案显示了 Windows 上的行为,此答案集中在 Linux(和 MacO)上。

      cdll.LoadLibrary 使用dlopen 加载共享对象,因此我们需要分析dlopen 的行为。

      让我们看看下面的 C 代码 (foo.c):

      #include <stdio.h>
      static int init();
      
      //global, initialized when so is loaded:
      int my_global = init();
      
      static int init(){
          printf("initializing address %p\n", (void*)&my_global);
          return 42;
          
      }
      
      extern "C" {
      void set(int new_val){ my_global = new_val;}
      int get() {return my_global;}
      }
      

      g++ --shared -fPIC foo.c -o foo.so 编译。我使用 C++ 而不是 C,所以每次初始化全局变量 my_global 时,它都会记录到标准输出(这不会是 that straight forward in C)。

      经过一番准备:

      import ctypes
      
      def init_functions(dll):
          get = dll.get
          get.argtypes = []
          get.restype = ctypes.c_int
          set = dll.set
          set.argtypes = [ctypes.c_int]
          set.restype = None
          return get, set
      

      我们观察到以下行为:

      #first load:
      get, set = init_functions(ctypes.CDLL("./foo.so"))
      # initializing address 0x7f5ca4a8102c
      print(get())   # 42
      set(21)
      print(get())   # 21
      

      到目前为止:全局变量已初始化,我们可以读/写它。现在第二次加载:

      get2, set2 = init_functions(ctypes.CDLL("./foo.so"))
      

      Ups,我们没有看到初始化记录,这意味着......

      print(get2())  # 21
      

      全局变量没有重新初始化。这是dlopen 的预期行为:一旦共享对象被加载,它就不会被重新加载而是被重用。这就是为什么例如pyximport%%cython-magic 使用不同的names for resulting shared-objects

      要真正创建新版本,我们将共享对象foo.so 复制到foo.so.1,现在:

      # load copied shared object:
      get3, set3 = init_functions(ctypes.CDLL("./foo.so.1"))
      # initializing address 0x7f5ca487102c
      print(get3())    # 42
      

      我们可以看到,全局变量被初始化了,但它是另一个地址,即不是旧变量,这很容易检查:

      print(get())    # 21 - still the old value.
      

      到目前为止,Windows 和 Linux 上的行为大致相同,但在 Linux 上,我们可以使用符号插入来确保使用相同的全局变量。

      普通 CPython 使用 dlopenRTLD_LOCAL,即不使用符号插入,通过使用 RTLD_GLOBAL,即

      ...
      #first load:
      get, set = init_functions(ctypes.CDLL("./foo.so", mode=ctypes.RTLD_GLOBAL))
      # initializing address 0x7fd01efc102c
      ...
      

      mode=ctypes.RTLD_GLOBAL 在 Windows 上将被忽略。

      第一个区别可见

      ...
      # load copied shared object:
      get3, set3 = init_functions(ctypes.CDLL("./foo.so.1"))
      # initializing address 0x7fd01efc102c
      ...
      

      也使用与“foo.so”相同的地址“foo.so.1” - 由于RTLD_GLOBAL 插入了来自两个共享对象的符号n

      现在:

      print(get3())  # 42
      print(get())   # 42
      

      也就是说,旧的全局变量被重新初始化了。


      虽然很有趣,但我不建议依赖这些细节 - 它太聪明、太脆弱且不可移植:很容易引入内存泄漏或崩溃。

      人们应该接受,一般来说,全局变量不能通过重新加载共享对象来重新初始化(以可移植的方式)。

      通常,人们希望避免插入全局变量并确保它们在共享对象中具有内部链接(例如,通过使它们成为静态或在编译时使用hidden-attribute),因此即使加载也不会被插入RTLD_GLOBAL.

      【讨论】:

        猜你喜欢
        • 2011-03-30
        • 1970-01-01
        • 2011-11-16
        • 2011-07-24
        • 1970-01-01
        • 2013-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多