在 Python 中定义 C++ 的回调函数

Defining callback functions for C++in Python

提问人:肉蛋充肌 提问时间:7/5/2023 最后编辑:肉蛋充肌 更新时间:7/20/2023 访问量:54

问:

我正在尝试编写对使用回调函数的 C++ 函数的绑定。回调函数如下所示。

typedef enum
{
    CB_Event_TermRegister = 101,
    ...
}enSdkCbType;
typedef enum
{
    TSDK_DEV_TYPE_UNDEF  = 0,
    ...
}enSdkDevType;
typedef struct
{
    enSdkDevType eTermType;
    ...
}TSdkEventTermRegister;

typedef int (CALLBACK* ON_CTS_SDK_CALL_BACK)(enSdkCbType eCbType, LPVOID pParam, DWORD dwSize, int usr_data);

其中,为定制型。 根据 而指向不同的点。
我在 python 中定义这个回调是这样的:
enSdkCbTypeenumpParamstructeCbType

from ctypes import *
class enSdkCbType(c_int):
    CB_Event_TermRegister = 101
    ...
class enSdkDevType(c_int):
    TSDK_DEV_TYPE_UNDEF = 0
    ...
class TSdkEventTermRegister(Structure):
    _fields_ = [
        ("eTermType", enSdkDevType),
        ...
    ]

def callback(eCbType, pParam, dwParamLen, usr_data):
    if eCbType == enSdkCbType.CB_Asw_OpenTermAudVid:
        assert dwParamLen == sizeof(TSdkEventTermRegister)
        p = POINTER(TSdkEventTermRegister).from_address(pParam)
        print(p.contents.eTermType)
    return 0

dll = CDLL('my_dll')
CallbackType = CFUNCTYPE(c_int, enSdkCbType, c_void_p, c_ulong)
dll.func(CallbackType(callback))

但是不起作用,因为在这个回调中发送的参数是这样的:它们的类型是
显然,参数的格式与我定义它们时不同。此外,似乎又向回调发送了一个 parameter(),因为根据定义,参数应该是这样的。
我不知道我的 Python 代码哪里出错了,但我可以提供运行良好的 C++ 版本的回调函数。
if eCbType == enSdkCbType.CB_Asw_OpenTermAudVid<enSdkCbType object at 0x01F10F80>, 101, 159492072, 72<class '__main__.enSdkCbType'> <class 'int'> <class 'int'> <class 'int'> <enSdkCbType object at 0x01F10F80>101(eCbType), 159492072(pParam), 72(dwSize), usr_data

int CALLBACK CSdkMp3Dlg::OnTzlSdkCallback(enSdkCbType eCbType, LPVOID pParam, DWORD dwSize, int usr_data)
{
    switch(eCbType)
    {
        case CB_Event_TermRegister:
        {
            ASSERT(dwSize == sizeof(TSdkEventTermRegister));
            TSdkEventTermRegister * pEventTermRegister = (TSdkEventTermRegister*)pParam;
            ...
        }
        ...
    }
    return 0;
}
Python C++ 回调 ctypes

评论


答:

1赞 Mark Tolonen 7/20/2023 #1

主要错误是回调定义不正确:

CallbackType = CFUNCTYPE(c_int, enSdkCbType, c_void_p, c_ulong)

应该是:

CALLBACK = ct.WINFUNCTYPE(c_int, enSdkCbType, c_void_p, c_ulong, c_int)

决赛不见了。也是在 Windows 中匹配的类型,定义为 。它实际上只对 32 位版本很重要,但应该纠正可移植性。c_intWINFUNCTYPECALLBACK__stdcall

过去之后,回调中出现了一些错误。以下是我测试的 C 代码和更正后的 Python 代码:

测试.c

#include <windows.h>
#include <stdio.h>

#define API __declspec(dllexport)

typedef enum {
    CB_Event_TermRegister = 101,
} enSdkCbType;

typedef enum {
    TSDK_DEV_TYPE_UNDEF  = 0,
} enSdkDevType;

typedef struct {
    enSdkDevType eTermType;
} TSdkEventTermRegister;

typedef int (CALLBACK* ON_CTS_SDK_CALL_BACK)(enSdkCbType eCbType, LPVOID pParam, DWORD dwSize, int usr_data);

API void func(ON_CTS_SDK_CALL_BACK cb) {
    TSdkEventTermRegister t = {5};
    cb(CB_Event_TermRegister, &t, sizeof t, 0);
}

test.py

import ctypes as ct
import ctypes.wintypes as w  # Defines Windows types like LPVOID and DWORD
                             # for closer matching of types as documented.
class enSdkCbType(ct.c_int):
    CB_Event_TermRegister = 101

class enSdkDevType(ct.c_int):
    TSDK_DEV_TYPE_UNDEF = 0

class TSdkEventTermRegister(ct.Structure):
    _fields_ = ("eTermType", enSdkDevType),

dll = ct.CDLL('./test')
CALLBACK = ct.WINFUNCTYPE(ct.c_int, enSdkCbType, w.LPVOID, w.DWORD, ct.c_int)
# Good practice to explicitly declare function arguments and return type.
dll.func.argtypes = CALLBACK,
dll.func.restype = None

# This decorator means "callback = CALLBACK(callback)" so
# the function can be directly passed without wrapping it
# each time.  Also safer if the callback is stored by the
# C code for later use, as wrapping it when called means
# the wrapper is destroyed when the call returns.
# It's not needed in this case, but another good practice.
@CALLBACK
def callback(eCbType, pParam, dwParamLen, usr_data):
    # Get the value as a Python integer otherwise it doesn't
    # match the type being compared to.
    if eCbType.value == enSdkCbType.CB_Event_TermRegister:
        assert dwParamLen == ct.sizeof(TSdkEventTermRegister)
        # The address is of the structure, not a pointer to structure.
        p = TSdkEventTermRegister.from_address(pParam)
        print(p.eTermType.value)
    return 0

dll.func(callback)

输出:

5

评论

0赞 肉蛋充肌 7/20/2023
它有效!我可以在类中定义这个回调函数吗?这样我就可以在调用函数时将信息传递给我的类。我正在尝试做这样的事情,但该过程以代码 -1073741819 (0xC0000005) 退出。还是我应该将回调函数定义为一个类并覆盖它的函数?def callback(self, eCbType, pParam, dwParamLen, usr_data):__call__
0赞 肉蛋充肌 7/20/2023
我有一个使用此 dll 的 java 演示。他们将此回调函数定义为一个类,并在其中实现目标业务逻辑,并且该类扩展callback.invokecallbackwin32.StdCallLibrary.StdCallCallback