如何在 python 中使用 ctypes 重载 C 库的弱声明函数?

How to overload weak declared function of a C library using ctypes in python?

我正在尝试设置一个测试环境来验证我正在使用的 C 库。该库嵌入在设备 运行 上,并定制了 linux。在网上阅读了一些内容后,我决定使用 python 和 ctypes 从 python 调用我的库函数。它几乎适用于我的所有功能,但在使用回调函数时我会卡住。

我的回调函数在库中定义为 "weak"。我想知道我是否可以使用 python ctypes 重载我的弱 C 函数?

看看我试过的例子:

libtest.c

#include <stdio.h>
#include <stdlib.h>
#include "libtest.h"

int multiply(int a, int b)
{
    custom_callback(a);
    return a*b;
}

int __attribute__((weak)) custom_callback(int a)
{
    printf("Callback not redefined, a = %d\n", a);
    return 0;
}

main.py

from ctypes import *

lib = cdll.LoadLibrary("libtest.so")

CALLBACKFUNC = CFUNCTYPE(c_int, c_int)

def py_callback_func(a):
    print("py redifinition", a)
    return 0


lib.custom_callback = CALLBACKFUNC(py_callback_func)

lib.multiply(c_int(5), c_int(10))

然后我 运行 我的 main.py 和 python3 :

$ python3 main.py 
Callback not redefined, a = 5

我希望输出为 py redifinition 5

我是不是做错了什么?还是 ctypes 根本无法重新定义弱声明的 C 函数?

你弄糊涂了:weak 符号在 link[=62 处处理=] 时间,而你在 运行 时间“操作”。 linker 不可能知道你的 Python 回调函数。虽然这不是官方来源,但请查看 [Wikipedia]: Weak symbol for more details.
Also, [Python.Docs]: ctypes - A foreign function library for Python
这是一个小演示。

libtest00.c:

#include <stdio.h>


int __attribute__((weak)) custom_callback(int a) {
    printf("Callback not redefined, a = %d\n", a);
    return 0;
}


int multiply(int a, int b) {
    custom_callback(a);
    return a * b;
}

callback00.c:

#include <stdio.h>


int custom_callback(int a) {
    printf("C callback redefinition, a = %d\n", a);
    return 0;
}

code00.py:

#!/usr/bin/env python

import sys
import ctypes as ct


DLL_NAME = "./libtest000.so"
CALLBACKFUNCTYPE = ct.CFUNCTYPE(ct.c_int, ct.c_int)


def py_callback_func(a):
    print("PY callback redefinition", a)
    return 0


def main(*argv):
    dll_name = argv[0] if argv else DLL_NAME
    dll = ct.CDLL(dll_name)
    multiply = dll.multiply
    multiply.argtypes = [ct.c_int, ct.c_int]
    multiply.restype = ct.c_int
    #dll.custom_callback(3)
    #dll.custom_callback = CALLBACKFUNCTYPE(py_callback_func)
    #dll.custom_callback(3)
    res = multiply(5, 10)
    print("{:} returned {:d}".format(multiply.__name__, res))


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出:

[cfati@cfati-5510-0:/mnt/e/Work/Dev/Whosebug/q055357490]> ~/sopr.sh
*** Set shorter prompt to better fit when pasted in Whosebug (or other) pages ***

[064bit prompt]> ls
callback00.c  code00.py  code01.py  libtest00.c  libtest01.c
[064bit prompt]> gcc -o libtest000.so -fPIC -shared libtest00.c
[064bit prompt]> gcc -o libtest001.so -fPIC -shared libtest00.c callback00.c
[064bit prompt]> ls
callback00.c  code00.py  code01.py  libtest00.c  libtest000.so  libtest001.so  libtest01.c
[064bit prompt]>
[064bit prompt]> python code00.py ./libtest000.so
Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] 64bit on linux

Callback not redefined, a = 5
multiply returned 50

Done.
[064bit prompt]>
[064bit prompt]> python code00.py ./libtest001.so
Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] 64bit on linux

C callback redefinition, a = 5
multiply returned 50

Done.

如图所示,__attribute__((弱))效果 可见的。此外,关于注释代码(试图设置 .dll 函数):它仅在 CTypes 代理级别运行,它不会更改 .dll 代码(那将是荒谬的)。

关于您的问题,有几种方法可以解决。
这里有一个使用指针来保存这样一个回调函数。可以通过 setter 函数(也导出)从外部设置指针。

libtest01.c:

#include <stdio.h>

#define EXEC_CALLBACK(X) \
    if (pCallback) { \
        pCallback(X); \
    } else { \
        fallbackCallback(X); \
    }


typedef int (*CustomCallbackPtr)(int a);

static CustomCallbackPtr pCallback = NULL;


static int fallbackCallback(int a) {
    printf("Callback not redefined, a = %d\n", a);
    return 0;
}


int multiply(int a, int b) {
    EXEC_CALLBACK(a);
    return a * b;
}


void setCallback(CustomCallbackPtr ptr) {
    pCallback = ptr;
}

code01.py:

#!/usr/bin/env python

import sys
import ctypes as ct


DLL_NAME = "./libtest01.so"
CALLBACKFUNCTYPE = ct.CFUNCTYPE(ct.c_int, ct.c_int)


def py_callback_func(a):
    print("PY callback redefinition", a)
    return 0


def main(*argv):
    dll = ct.CDLL(DLL_NAME)
    multiply = dll.multiply
    multiply.argtypes = [ct.c_int, ct.c_int]
    multiply.restype = ct.c_int
    set_callback = dll.setCallback
    set_callback.argtypes = [CALLBACKFUNCTYPE]
    multiply(5, 10)
    set_callback(CALLBACKFUNCTYPE(py_callback_func))
    multiply(5, 10)
    set_callback(ct.cast(ct.c_void_p(), CALLBACKFUNCTYPE))
    multiply(5, 10)


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出:

[064bit prompt]> ls  # Files from previous step
callback00.c  code00.py  code01.py  libtest00.c  libtest000.so  libtest001.so  libtest01.c
[064bit prompt]> gcc -o libtest01.so -fPIC -shared libtest01.c
[064bit prompt]>
[064bit prompt]> ls
callback00.c  code00.py  code01.py  libtest00.c  libtest000.so  libtest001.so  libtest01.c  libtest01.so
[064bit prompt]>
[064bit prompt]> python code01.py
Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] 64bit on linux

Callback not redefined, a = 5
PY callback redefinition 5
Callback not redefined, a = 5

Done.