【问题标题】:How to overload weak declared function of a C library using ctypes in python?如何在 python 中使用 ctypes 重载 C 库的弱声明函数?
【发布时间】:2019-03-26 12:42:47
【问题描述】:

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

我的回调函数在库中定义为“弱”。我想知道是否可以使用 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))

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

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

我希望输出是py redifinition 5

我做错了吗?还是 ctypes 根本无法重新定义一个弱声明的 C 函数?

【问题讨论】:

  • 弱链接由动态链接器介导。据我所知,在实例化库的 Python 包装器后,您对它所做的任何操作都不会以任何方式调节动态链接。

标签: python c ctypes


【解决方案1】:

您搞错了:符号在链接时间处理,而您在“操作” 运行时间。链接器不可能知道您的 Python 回调函数。虽然它不是官方来源,但请查看[Wikipedia]: Weak symbol 了解更多详细信息。
另外,[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/StackOverflow/q055357490]> ~/sopr.sh
*** Set shorter prompt to better fit when pasted in StackOverflow (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__((weak)) 效果可见的。此外,关于注释代码(试图设置 .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.

【讨论】:

  • 感谢 CristiFati 提供详细且记录在案的答案。我想我会从库中删除方便的“弱”属性,这可能会在未来导致可移植性问题,并且不是面向 ctypes python 测试的。使用函数指针和 setter 似乎是一种更简洁的解决方案。
  • 是的,这最适合您的方案。 __attribute__((weak)) 是一个不错且强大的功能(由 一些 编译器支持),但在这里不太适用。
猜你喜欢
  • 2015-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多