C和Python调用C的动态库的调查

最近在实现一个C程序的扩展的时候,需要使用Python调用到C的动态库,但是实验了很多方法都不能把我们的动态库调用起来,要么是缺少symbol,要么是编译不过。
所以有了以下几个实验。

  1. case 1(函数声明在主函数里面,但是需要在libbar库中用到)
    gcc -shared -o libbar.so bar.c
    gcc -o prog main.c -L. -lbar -ldl
# ./prog
./libbar.so: undefined symbol: foo

动态库中调用到了主函数中定义的变量或方法,必须使用rdynamic编译选项。
gcc -rdynamic -o prog2 main.c -L. -lbar -ldl

# ./prog2
Hello world
  1. bar.c中的函数在foo.c中实现了,把两个c文件打包成一个so文件
    gcc -shared -o libfoobar.so bar.c foo.c
    gcc -o prog3 main_foobar.c -L. -lfoobar -ldl
# ./prog3
Hello world
  1. bar.c中的函数在foo.c中实现了,但是把foo.c bar.c分别打成不同的so文件
    gcc -shared -o libfoo.so foo.c
    gcc -shared -o libbar.so bar.c
# ldd libfoo.so
        linux-vdso.so.1 (0x00007ffc4a5e1000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f56865a6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f56867bb000)
# ldd libbar.so
        statically linked
# cat lib.py
# lib.py
import ctypes

if __name__ == "__main__":
    # Load the shared library into ctypes
    c_lib = ctypes.CDLL("./libbar.so")
    c_lib.bar()
# python3 lib.py
Traceback (most recent call last):
  File "lib.py", line 6, in <module>
    c_lib = ctypes.CDLL("./libbar.so")
  File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: ./libbar.so: undefined symbol: foo
# LD_PRELOAD=./libfoo.so python3 lib.py <-- 加上LD_PRELOAD就可以解决这个问题了
Hello world
  1. 把两个动态链接库合并
    gcc -shared -o libfoo.so foo.c
    gcc -shared -o libbar.so bar.c
    gcc -shared -o libfoobar2.so -L. -lbar -lfoo -ldl
# ldd libfoobar2.so
        statically linked <-- 这里好像都不对了
# cat lib3.py
# lib3.py
import ctypes

if __name__ == "__main__":
    # Load the shared library into ctypes
    c_lib = ctypes.CDLL("./libfoobar2.so")
    c_lib.bar()
# LD_PRELOAD=./libbar.so:./libfoo.so python3 lib3.py <--这种方式不行,不能把两个so文件合并
Traceback (most recent call last):
  File "lib3.py", line 7, in <module>
    c_lib.bar()
  File "/usr/lib/python3.8/ctypes/__init__.py", line 386, in __getattr__
    func = self.__getitem__(name)
  File "/usr/lib/python3.8/ctypes/__init__.py", line 391, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: ./libfoobar2.so: undefined symbol: bar

In practice it is not possible.
From linker point of view, a SO library is a final product that does not contain relocation information required for linking.
If you have access to either source or object files for both libraries, it is straightforward to compile/link a combined SO from them.
can’t merge multi .so file into a new one.

https://stackoverflow.com/questions/915128/merge-multiple-so-shared-libraries

  1. 从Python中加载tp_nvme.so报错
# python3 lib5.py
Traceback (most recent call last):
  File "lib5.py", line 6, in <module>
    c_lib = ctypes.CDLL("./tp_nvme.so")
  File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: ./tp_nvme.so: undefined symbol: tp_TrayIndex
# LD_PRELOAD=./libfoo.so python3 lib5.py <-- 在libfoo.so中加了tp_TrayIndex,可以继续往下走
Traceback (most recent call last):
  File "lib5.py", line 6, in <module>
    c_lib = ctypes.CDLL("./tp_nvme.so")
  File "/usr/lib/python3.8/ctypes/__init__.py", line 373, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: ./tp_nvme.so: undefined symbol: xx_uint32
  1. 编译是编译的过程,链接是链接的过程,链接就是把需要的库放到你ELF中的固定位置
# gcc -shared -o libfoo foo.c
# ldd libfoo
        linux-vdso.so.1 (0x00007ffc7bb1d000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcbaa3cb000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fcbaa5e0000)

# gcc -shared -o libfoo foo.c  -lm
# ldd libfoo
        linux-vdso.so.1 (0x00007fff39764000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5063f65000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f506417a000)

不过我在生成so文件的时候,使用-l链接什么东西,最后生成的so文件都没有相应的依赖

可以使用readelf查看最后生成的可执行文件中依赖的动态链接库;当然也可以使用ldd或者objdump;
需要注意的是“ldd does not work on a.out shared libraries.”
和我上面的观察一直

# readelf -d prog3

Dynamic section at offset 0x2d98 contains 28 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
# ldd prog3
        linux-vdso.so.1 (0x00007ffceafb1000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f49c4195000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f49c3fa3000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f49c41be000)

# objdump -p prog2|grep NEEDED
  NEEDED               libdl.so.2
  NEEDED               libc.so.6
  1. LD_LIBRARY_PATH
# strace ./prog3
execve("./prog3", ["./prog3"], 0x7ffd4ebaef10 /* 34 vars */) = 0
brk(NULL)                               = 0x55a48688f000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd47e59ee0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=113513, ...}) = 0
mmap(NULL, 113513, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f812af98000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \22\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=18848, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f812af96000
mmap(NULL, 20752, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f812af90000
mmap(0x7f812af91000, 8192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f812af91000
mmap(0x7f812af93000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f812af93000
mmap(0x7f812af94000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f812af94000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f812ad9e000
mmap(0x7f812adc0000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f812adc0000
mmap(0x7f812af38000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7f812af38000
mmap(0x7f812af86000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f812af86000
mmap(0x7f812af8c000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f812af8c000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f812ad9b000
arch_prctl(ARCH_SET_FS, 0x7f812ad9b740) = 0
mprotect(0x7f812af86000, 16384, PROT_READ) = 0
mprotect(0x7f812af94000, 4096, PROT_READ) = 0
mprotect(0x55a486050000, 4096, PROT_READ) = 0
mprotect(0x7f812afe1000, 4096, PROT_READ) = 0
munmap(0x7f812af98000, 113513)          = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=113513, ...}) = 0
mmap(NULL, 113513, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f812af98000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=131072, ...}) = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=131072, ...}) = 0
openat(AT_FDCWD, "/lib/tls/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls", 0x7ffd47e59580)        = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64", 0x7ffd47e59580)     = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64", 0x7ffd47e59580)     = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
openat(AT_FDCWD, "/usr/lib/tls/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls", 0x7ffd47e59580)    = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64", 0x7ffd47e59580) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/libfoobar.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
brk(NULL)                               = 0x55a48688f000
brk(0x55a4868b0000)                     = 0x55a4868b0000
munmap(0x7f812af98000, 113513)          = 0
write(2, "libfoobar.so: cannot open shared"..., 72libfoobar.so: cannot open shared object file: No such file or directory
) = 72
exit_group(1)                           = ?
+++ exited with 1 +++

从上面可以看出,Linux的加载器在自己的默认目录下拼命的寻找libfoobar.so,都没有找到后,就歇菜了,说不好意思,打开libfoobar.so识别了
这个时候我们可以使用LD_LIBRARY_PATH来添加我们自己的寻找路径

# LD_LIBRARY_PATH=./ ./prog3
Hello world
  1. 那怎么在编译的时候,把你需要的so文件信息写入到ELF文件中去呢?
    使用dlopen肯定是不行的,因为使用到的so文件是你自己在代码里面指定的,链接器是不知道的;但是后面查找so文件的过程是一样的;
    代码引用,编译的时候使用-L指定查找目录,-l指定具体的so文件名;
    需要注意的是,如果代码没有引用,但是指定了-l,是没有用的;
# gcc  -o prog3 main_foobar.c -L. -lfoobar -ldl
# ldd prog3
        linux-vdso.so.1 (0x00007ffd12180000)
        libfoobar.so => not found 
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5a552a8000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a550b6000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f5a552d1000)
# LD_LIBRARY_PATH=./ ldd prog3
        linux-vdso.so.1 (0x00007fff847fb000)
        libfoobar.so => ./libfoobar.so (0x00007f6a11fce000) <--找到了
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f6a11fac000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6a11dba000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6a11fda000)
  1. Do not use the standard system libraries when linking.
    -nodefaultlibs

  2. dlopen的第二个参数flags中有:
    RTLD_LAZY
    Perform lazy binding.
    Resolve symbols only as the code that references them is executed. If the symbol is never referenced, then it is never resolved.
    (Lazy binding is performed only for function references; references to variables are always immediately bound when the shared object is loaded.)
    意思就是只是针对函数可以做LAZY,变量不行。

https://www.baeldung.com/linux/ld_preload-trick-what-is
https://gist.github.com/mbohun/4191988
https://www.hpc.dtu.dk/?page_id=1180
https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
https://man7.org/linux/man-pages/man3/dlopen.3.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值