C 中的 For 循环没有明显原因提前中断 - 可能与函数指针(回调)有关
For loop in C breaks early for no obvious reason - possibly related to a function pointer (call back)
我正在试验用 C 编写的 DLL 和用 Python 编写的回调函数。我的 DLL 包含以下定义和例程:
typedef int16_t (*conveyor_belt)(int16_t index);
int16_t __stdcall DEMODLL sum_elements_from_callback(
int16_t len,
conveyor_belt get_data
)
{
int16_t sum = 0;
int i;
for(i = 0; i < len; i++)
{
sum += get_data(i);
}
return sum;
}
我使用 ctypes
从 Python 脚本中调用上面的代码:
import ctypes
DATA = [1, 6, 8, 4, 9, 7, 4, 2, 5, 2]
conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16)
@conveyor_belt
def get_data(index):
print((index, DATA[index]))
return DATA[index]
dll = ctypes.windll.LoadLibrary('demo_dll.dll')
sum_elements_from_callback = dll.sum_elements_from_callback
sum_elements_from_callback.argtypes = (ctypes.c_int16, conveyor_belt)
sum_elements_from_callback.restype = ctypes.c_int16
test_sum = sum_elements_from_callback(len(DATA), get_data)
print(('sum', 48, test_sum))
我得到的输出看起来像这样(大部分时间):
(0, 1)
(1, 6)
(2, 8)
(3, 4)
(4, 9)
(5, 7)
(6, 4)
(7, 2)
('sum', 48, 41)
据我所知,for 循环并没有像我期望的那样遍历 DATA 的所有 10 个元素...它通常 "breaks" 在 8 个元素之后,有时甚至只在 5 个元素之后或 6. 我可以确认 DATA 的长度已正确传递到 DLL 例程中。我很纳闷。
以防万一这是一个编译器(标志)问题,这里是我的 makefile 的摘录:
CC = i686-w64-mingw32-gcc
CFLAGS = -Wall -Wl,-add-stdcall-alias -shared -std=c99
LDFLAGS = -lm
我在 64 位之上使用 mingw(32 位)Linux:
user@box:~> i686-w64-mingw32-gcc --version
i686-w64-mingw32-gcc (GCC) 7.2.0
user@box:~> uname -s -r -p
Linux 4.4.114-42-default x86_64
对于 运行 DLL 和 Python 脚本,我在 Windows 之上使用 CPython 3.5.3 的官方 32 位发布版本32 位葡萄酒:
user@box:~> wine --version
wine-2.18
这是一个强大的组合,我已经大量使用了一段时间。
在 C 代码中使用回调会有所不同。以下代码将在大约 5 到 8 次迭代后停止(没有错误):
int16_t sum = 0;
int16_t sum_index = 0;
int16_t i;
for(i = 0; i < len; i++)
{
sum_index += i;
sum += get_data(i);
}
下面的代码将按照我的预期迭代到最后。它只是不调用回调函数:
int16_t sum = 0;
int16_t sum_index = 0;
int16_t i;
for(i = 0; i < len; i++)
{
sum_index += i;
// sum += get_data(i);
}
您的 conveyor_belt 函数在 C 和 Python 之间的定义方式不同。
typedef int16_t (*conveyor_belt)(int16_t index);
这声明您的函数指针类型以使用 cdecl 调用约定(除非被特殊的编译器标志覆盖)。
conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16)
而这声明您的 Python 函数使用 stdcall 调用约定。
使用错误的调用约定调用函数每次都会使您的堆栈指针偏移一点。根据您的编译器生成的堆栈布局,这可能最终会覆盖局部变量或参数(即 i 或 len 导致循环中断)。
您应该从 WINFUNCTYPE 切换到 CFUNCTYPE 以指示 cdecl,或者更改 typedef 以添加 __stdcall:
typedef int16_t (__stdcall *conveyor_belt)(int16_t index);
(请注意,调用约定位于括号内星号之前。一些编译器在其他地方接受它,但 MSVC 不接受。)
我正在试验用 C 编写的 DLL 和用 Python 编写的回调函数。我的 DLL 包含以下定义和例程:
typedef int16_t (*conveyor_belt)(int16_t index);
int16_t __stdcall DEMODLL sum_elements_from_callback(
int16_t len,
conveyor_belt get_data
)
{
int16_t sum = 0;
int i;
for(i = 0; i < len; i++)
{
sum += get_data(i);
}
return sum;
}
我使用 ctypes
从 Python 脚本中调用上面的代码:
import ctypes
DATA = [1, 6, 8, 4, 9, 7, 4, 2, 5, 2]
conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16)
@conveyor_belt
def get_data(index):
print((index, DATA[index]))
return DATA[index]
dll = ctypes.windll.LoadLibrary('demo_dll.dll')
sum_elements_from_callback = dll.sum_elements_from_callback
sum_elements_from_callback.argtypes = (ctypes.c_int16, conveyor_belt)
sum_elements_from_callback.restype = ctypes.c_int16
test_sum = sum_elements_from_callback(len(DATA), get_data)
print(('sum', 48, test_sum))
我得到的输出看起来像这样(大部分时间):
(0, 1)
(1, 6)
(2, 8)
(3, 4)
(4, 9)
(5, 7)
(6, 4)
(7, 2)
('sum', 48, 41)
据我所知,for 循环并没有像我期望的那样遍历 DATA 的所有 10 个元素...它通常 "breaks" 在 8 个元素之后,有时甚至只在 5 个元素之后或 6. 我可以确认 DATA 的长度已正确传递到 DLL 例程中。我很纳闷。
以防万一这是一个编译器(标志)问题,这里是我的 makefile 的摘录:
CC = i686-w64-mingw32-gcc
CFLAGS = -Wall -Wl,-add-stdcall-alias -shared -std=c99
LDFLAGS = -lm
我在 64 位之上使用 mingw(32 位)Linux:
user@box:~> i686-w64-mingw32-gcc --version
i686-w64-mingw32-gcc (GCC) 7.2.0
user@box:~> uname -s -r -p
Linux 4.4.114-42-default x86_64
对于 运行 DLL 和 Python 脚本,我在 Windows 之上使用 CPython 3.5.3 的官方 32 位发布版本32 位葡萄酒:
user@box:~> wine --version
wine-2.18
这是一个强大的组合,我已经大量使用了一段时间。
在 C 代码中使用回调会有所不同。以下代码将在大约 5 到 8 次迭代后停止(没有错误):
int16_t sum = 0;
int16_t sum_index = 0;
int16_t i;
for(i = 0; i < len; i++)
{
sum_index += i;
sum += get_data(i);
}
下面的代码将按照我的预期迭代到最后。它只是不调用回调函数:
int16_t sum = 0;
int16_t sum_index = 0;
int16_t i;
for(i = 0; i < len; i++)
{
sum_index += i;
// sum += get_data(i);
}
您的 conveyor_belt 函数在 C 和 Python 之间的定义方式不同。
typedef int16_t (*conveyor_belt)(int16_t index);
这声明您的函数指针类型以使用 cdecl 调用约定(除非被特殊的编译器标志覆盖)。
conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16)
而这声明您的 Python 函数使用 stdcall 调用约定。
使用错误的调用约定调用函数每次都会使您的堆栈指针偏移一点。根据您的编译器生成的堆栈布局,这可能最终会覆盖局部变量或参数(即 i 或 len 导致循环中断)。
您应该从 WINFUNCTYPE 切换到 CFUNCTYPE 以指示 cdecl,或者更改 typedef 以添加 __stdcall:
typedef int16_t (__stdcall *conveyor_belt)(int16_t index);
(请注意,调用约定位于括号内星号之前。一些编译器在其他地方接受它,但 MSVC 不接受。)