对于C语言中的循环提前中断没有明显的原因 - 可能与函数指针(回调)有关

时间:2018-03-14 16:21:51

标签: python c for-loop callback ctypes

我正在试验用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个元素......通常&#34;中断&#34;在8个元素之后,有时甚至只在5或6之后。我可以确认DATA的长度是否正确地传递到DLL例程中。我很困惑。

以防这是编译器(标志)问题,这是我的makefile的摘录:

CC      = i686-w64-mingw32-gcc
CFLAGS  = -Wall -Wl,-add-stdcall-alias -shared -std=c99
LDFLAGS = -lm

我在64位Linux上使用mingw(32位):

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脚本,我在32位Wine上使用官方32位版本的CPython 3.5.3 for Windows:

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);
}

1 个答案:

答案 0 :(得分:2)

您的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不会。)