如何在 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.
我正在尝试设置一个测试环境来验证我正在使用的 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.