python如何调用C, 如何注册成C的回调函数(python后台程序常用方法)

本文介绍了一种利用C语言处理视频内容并通过Python调用的方法。重点讲解了如何使用ctypes模块让Python调用C语言编写的.so库,并注册Python回调函数以获取实时进度和状态更新。
Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

最近做一个项目,分析视频内容,用C语言开发,需要运行在linux服务器后台,被python调用,并且实时返回进度信息;

其实是python后台程序常用方法:

C开发完成底层的功能,python直接把C当做python模块进行调用。


需要做两个工作:

  • python能调用C语言的函数;
  • python通过调用C函数,并注册python的回调函数,C代码通过python回调函数告诉Python当前实时进度和状态;

1,python如何调用C语言

主要就是应用ctypes这个模块,too simple too naive。

python代码是这样滴:

from ctypes import *
dynamicLibString = './libcheckVideoFile.so'
mylib = cdll.LoadLibrary(dynamicLibString)
ulHandle = mylib.VideoAnalyzeInit(videoFilename)
    if ulHandle == 0:
        print 'VideoAnalyzeInit error.'
        print ''
        
    mylib.EnableBlackDetected(ulHandle)
    mylib.EnablePureColorDetected(ulHandle)
    mylib.EnableFrozenDetected(ulHandle)


    mylib.EnableMuteVoiceDetected(ulHandle)


C代码是这样滴:

unsigned long VideoAnalyzeInit(char* szFilename)
{
    VideoAnalyzeManage* pManager = new VideoAnalyzeManage(szFilename);
    if(pManager)
    {
        int iRet = pManager->Init();
        if(iRet != 0)
        {
            delete pManager;
            return 0;
        }
    }
    return (unsigned long)pManager;
}
void EnableBlackDetected(unsigned long ulHandle)
{
    VideoAnalyzeManage* pManager = (VideoAnalyzeManage*)ulHandle;
    if(pManager)
    {
        pManager->EnableBlackDetected();
    }
}


就像C语言编译出来的.so库只是python的一个模块,直接调用就可以了。


2,python注册C语言的回调函数

其实也不难,python的函数本身也是python的对象,实现也就简单了:


python的回调函数:

def OnPyVideoAnalyzeResultCallback(ulStartTS, ulEndTS, ulDetectedType, ulParam):
    fStartTS = ulStartTS/1000.0
    fEndTS   = ulEndTS/1000.0
    outputString = ''
    
    if ulDetectedType == ALL_BLACK_DETECTED :
        outputString = videoFilename + ': All black color detected: start(' + str(fStartTS) + ') end(' + str(fEndTS) + ')'
    elif ulDetectedType == SIMPLE_COLOR_DETECTED :
        outputString = videoFilename + ': All pure color detected: start(' + str(fStartTS) + ') end(' + str(fEndTS) + ')'
    elif ulDetectedType == FROZEN_VIDEO_DETECTED :
        outputString = videoFilename + ': Frozen image detected: start(' + str(fStartTS) + ') end(' + str(fEndTS) + ')'
    elif ulDetectedType == AUDIO_MUTE_DETECTED :
        outputString = videoFilename + ': Mute voice detected: start(' + str(fStartTS) + ') end(' + str(fEndTS) + ')'
    print outputString
    WriteLog(logFilename, outputString)

def OnPyVideoStateCallback(uiProgress, uiState, ulParam):
    global videoFilename
    outputString = ''
    if uiState == DECODE_START :
        outputString = '\r\n' + videoFilename + ': video analyze is starting......'
        WriteLog(logFilename, outputString)
    elif uiState == DECODE_RUNNING :
        outputString = videoFilename + ': video analyze is running, progress: ' + str(uiProgress) + '%'
    elif uiState == DECODE_END :
        outputString = videoFilename + ': video analyze is ended'
        WriteLog(logFilename, outputString)
    elif uiState == DECODE_ERROR :
        outputString = videoFilename + ': video analyze is error'
        WriteLog(logFilename, outputString)
    print outputString
python 两个回调函数:OnPyVideoAnalyzeResultCallback和OnPyVideoStateCallback。

如何把这两个python函数注册成C代码的回调函数呢?

python部分是这样注册滴:

    CMPRESULTFUNC = CFUNCTYPE(None, c_ulong, c_ulong, c_ulong, c_ulong)
    CMPSTATEFUNC = CFUNCTYPE(None, c_ulong, c_ulong, c_ulong)

    iRet = mylib.VideoAnalyzeStart(ulHandle, CMPRESULTFUNC(OnPyVideoAnalyzeResultCallback), CMPSTATEFUNC(OnPyVideoStateCallback))
应用这个来设置:CFUNCTYPE
第一个参数是python回调函数的返回值,如果没有就是None。

第二个及其以后的就是python回调函数的参数类型了。

CMPRESULTFUNC = CFUNCTYPE(None, c_ulong, c_ulong, c_ulong, c_ulong)//创建一个c函数类型的对象工厂,该函数返回值为None,有三个入参,都为unsigned long。

CMPRESULTFUNC(OnPyVideoAnalyzeResultCallback)根据Python可调用对象生成函数。


mylib.VideoAnalyzeStart(ulHandle, CMPRESULTFUNC(OnPyVideoAnalyzeResultCallback), CMPSTATEFUNC(OnPyVideoStateCallback))//设置回调函数

C部分是这样的:

int VideoAnalyzeStart(unsigned long ulHandle, AnalyzeDetectedCallback resultCallback, AnalyzeStateCallback stateCallback)
{
    VideoAnalyzeManage* pManager = (VideoAnalyzeManage*)ulHandle;
    if(pManager)
    {
        pManager->SetAnalyzeResultCallback(resultCallback, 0);
        pManager->SetStateNotifyCallback(stateCallback, 0);
        int iRet = pManager->Start();
        return iRet;
    }
    return -1;
}
C部分不用管。

但是如何确定python函数参数与C函数参数的对应关系呢?

python函数参数与C函数参数的对应表(其实也可以叫ctypes类型表):


一个大坑:需要注意CMPRESULTFUNC(OnPyVideoAnalyzeResultCallback)这个指针函数是有自己的生存空间的,如果生存空间已过,会被释放,C代码再回调的时候,就会使用一个过期指针。

这里建议使用一个全局的python指针。

CMPRESULTFUNC = CFUNCTYPE(c_int, c_ulong, c_ulong, c_ulong, c_ulong)
CMPSTATEFUNC = CFUNCTYPE(c_int, c_ulong, c_ulong, c_ulong)

pResutFunc = CMPRESULTFUNC(OnPyVideoAnalyzeResultCallback)
pStateFunc = CMPSTATEFUNC(OnPyVideoStateCallback)


def main():
    global pResutFunc
    global pStateFunc
    ....
    iRet = mylib.VideoAnalyzeStart(ulHandle, pResutFunc, pStateFunc)
见官网的解释: https://docs.python.org/3/library/ctypes.html#ctypes.c_long

Note

 

Make sure you keep references to CFUNCTYPE() objects as long as they are used from C code. ctypes doesn’t, and if you don’t, they may be garbage collected, crashing your program when a callback is made.

Also, note that if the callback function is called in a thread created outside of Python’s control (e.g. by the foreign code that calls the callback), ctypes creates a new dummy Python thread on every invocation. This behavior is correct for most purposes, but it means that values stored with threading.local will not survive across different callbacks, even when those calls are made from the same C thread.

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>首先,用户的问题是关于在Python中使用回调函数实现按键中断和恢复主程序进程。用户提到了回调函数,并引用了两个引用,但引用是关于文件写入和回调函数的一般概念,不是直接针对Python的按键中断。 用户的关键点是:"我想在Python中使用回调函数实现按键中断和恢复主程序进程"。所以,我需要提供一个清晰的解决方案。 回想一下,在Python中,处理按键中断通常涉及信号处理或事件监听。回调函数函数作为参数传递给其他函数或系统,在特定事件发生时被调用。 在Python中,常见的方式是使用`signal`模块来处理信号,如SIGINT(由Ctrl+C触发),或使用像`keyboard`这样的第三方库来监听按键事件。 由于用户指定了"回调函数",我应该设计一个使用回调的解决方案。 步骤: 1. **理解需求**:用户想要在按键时中断主程序,然后恢复它。这类似于暂停和继续执行。 2. **可能的Python机制**: - **信号处理**:Python的`signal`模块允许设置信号处理器(回调函数)来处理信号如SIGINT。但SIGINT通常用于终止程序,而不是暂停和恢复。 - **线程**:使用多线程,主线程运行程序,另一个线程监听按键事件。 - **第三方库**:如`keyboard`库,专门用于监听键盘事件。 3. **推荐方法**: - 对于按键监听,`keyboard`库是一个不错的选择,因为它跨平台且易于使用。 - 使用回调函数来处理按键事件。 4. **实现思路**: - 主程序运行一些任务。 - 当按下特定键(如空格键)时,中断主程序(暂停)。 - 当再次按下该键时,恢复主程序。 - 这需要某种同步机制,如事件或标志。 5. **代码结构**: - 导入必要的模块。 - 定义一个回调函数来处理按键事件。 - 设置按键监听器。 - 主程序循环,检查暂停标志。 6. **考虑恢复**:中断后恢复主程序进程。这可能意味着保存状态或使用线程控制。 由于Python的全局解释器锁(GIL),使用线程可能不是最佳,但对于I/O绑定任务或简单事件监听是可行的。 更好的方式是使用`keyboard`库,因为它支持异步按键监听。 安装:用户可能需要安装`keyboard`库,使用`pip install keyboard`。 现在,构建一个示例代码。 伪代码: - 定义一个全局变量,如`paused = False`。 - 定义一个回调函数,当按键按下时切换`paused`状态。 - 启动一个线程来监听按键。 - 在主循环中,检查`paused`,如果暂停,则等待或休眠,直到恢复。 使用`threading.Event`来同步。 示例代码: ```python import threading import time import keyboard # 需要安装 # 全局事件来控制暂停 pause_event = threading.Event() def on_key_event(e): if e.name == 'space' and e.event_type == 'down': # 当按下空格键 if pause_event.is_set(): print("恢复程序") pause_event.clear() # 清除事件,恢复 else: print("暂停程序") pause_event.set() # 设置事件,暂停 # 设置按键监听 keyboard.on_press_key('space', on_key_event) # 使用空格键触发 # 主程序 def main(): print("主程序开始。按空格键暂停/恢复。") while True: # 如果暂停事件被设置,等待 pause_event.wait() # 如果事件设置,wait会阻塞;否则继续 # 但这里需要反转逻辑,因为当事件设置时,我们应该暂停 # 更好的方式:使用一个循环检查暂停状态 pass # 重新设计 # 重新设计主循环 def main(): print("主程序开始。按空格键暂停/恢复。") while True: if not pause_event.is_set(): # 如果没有暂停,执行任务 print("执行任务...") time.sleep(1) # 模拟工作 else: # 暂停状态,等待 time.sleep(0.1) # 避免CPU忙等待 # 启动主程序 if __name__ == "__main__": main() ``` 在上面的代码中: - `keyboard.on_press_key`设置按键监听器,当按下空格键时调用`on_key_event`回调。 - `pause_event`是一个`threading.Event`。当设置时,表示暂停;清除时,表示运行。 - 主循环检查`pause_event.is_set()`:如果设置了,就跳过任务执行(进入休眠);否则执行任务。 但`keyboard.on_press_key`在后台启动一个线程,所以不需要额外线程。 现在,关于恢复:当用户再次按键时,回调函数清除事件,主循环自动恢复执行。 用户说“恢复主程序进程”,在这个上下文中,是恢复主线程的执行。 为了更精确,使用事件来同步。 最后,确保回答结构清晰: - 解释概念。 - 提供代码示例。 - 讨论注意事项。 根据系统指令: - 行内数学表达式用$...$,但这里可能不需要数学。 - 独立公式用$$...$$,但同样,可能不适用。 - 使用中文回答。 - 生相关问题。 - 引用标识:在回答中,如果引用用户提供的引用,添加[^1]等。但用户引用不是直接相关,所以可能不需要。 用户引用: - [^1] 是关于文件写入的Python代码。 - [^2] 是关于回调函数的定义。 在回答中,我可以提到回调函数的概念,引用[^2]。 生相关问题。 回答结构: 1. 介绍回调函数和按键中断。 2. 提供Python实现方案。 3. 代码示例。 4. 注意事项。 5. 相关问题。</think>### Python 中使用回调函数实现按键中断和恢复主程序进程 在 Python 中,通过回调函数实现按键中断和恢复主程序进程的核心思路是:**使用事件监听机制捕获按键事件,并通过回调函数修改全局状态来控制主程序的执行流程**。这通常涉及多线程或异步编程,其中主线程运行核心任务,另一个线程监听按键事件。当按键触发时,回调函数修改标志位(如暂停/恢复状态),主程序根据标志位暂停或继续执行。以下是详细实现步骤和代码示例。 #### 关键概念 - **回调函数**:应用程序提供给系统调用函数,用于响应特定事件(如按键)。回调函数需遵守预定义的参数格式[^2]。 - **中断机制**:按键中断时,回调函数修改全局状态(如 `paused` 标志),主程序通过轮询或事件等待响应变化。 - **恢复机制**:再次按键时,回调函数恢复状态,主程序继续执行。 #### 实现方案(使用 `keyboard` 第三方库) 推荐使用 `keyboard` 库(跨平台支持),因为它简化了按键监听和回调绑定。首先安装库: ```bash pip install keyboard ``` ##### 代码示例 以下代码实现: - 主程序模拟耗时任务(如数据处理)。 - 按 **空格键** 触发中断(暂停主程序)。 - 再次按 **空格键** 恢复主程序。 - 使用 `threading.Event` 同步状态,避免忙等待(减少 CPU 占用)。 ```python import threading import time import keyboard # 用于按键监听 # 全局事件控制暂停/恢复 pause_event = threading.Event() # 回调函数:处理按键中断 def on_key_press(event): if event.name == 'space': # 空格键触发 if pause_event.is_set(): print("回调: 恢复主程序") pause_event.clear() # 清除事件,恢复执行 else: print("回调: 中断主程序") pause_event.set() # 设置事件,暂停执行 # 绑定按键回调 keyboard.on_press(on_key_press) # 主程序任务 def main_task(): print("主程序启动。按空格键中断/恢复...") task_id = 0 while True: if not pause_event.is_set(): # 未暂停时执行任务 task_id += 1 print(f"执行任务 #{task_id}") time.sleep(1) # 模拟耗时操作 else: # 暂停时,等待事件清除(避免忙等待) pause_event.wait() print("主程序恢复执行") if __name__ == "__main__": # 启动主程序 main_task_thread = threading.Thread(target=main_task) main_task_thread.daemon = True # 设为守护线程,随主线程结束 main_task_thread.start() # 保持主线程运行(监听按键) keyboard.wait('esc') # 按ESC键退出程序 ``` #### 代码说明 1. **回调函数 `on_key_press`**: - 参数 `event` 包含按键信息(如 `event.name`)。 - 当按下空格键时,切换 `pause_event` 状态(设置/清除事件)。 2. **全局同步 `pause_event`**: - `threading.Event` 提供线程安全的标志管理。 - `pause_event.set()`:暂停主程序。 - `pause_event.clear()`:恢复主程序。 3. **主程序逻辑**: - 未暂停时执行任务(如打印、计算)。 - 暂停时调用 `pause_event.wait()`,阻塞线程直到事件清除。 4. **退出机制**: - `keyboard.wait('esc')` 监听 ESC 键退出程序。 #### 注意事项 1. **线程安全**:使用 `threading.Event` 确保状态同步,避免竞态条件。 2. **性能优化**:暂停时通过 `wait()` 减少 CPU 占用(替代忙循环)。 3. **跨平台限制**:`keyboard` 库在 Linux/macOS 可能需要管理员权限。 4. **替代方案**:如果不用第三方库,可用 `signal` 模块处理系统信号(如 Ctrl+C),但灵活性较低: ```python import signal paused = False def signal_handler(sig, frame): global paused paused = not paused signal.signal(signal.SIGINT, signal_handler) # 绑定Ctrl+C ``` #### 应用场景 此方法适用于: - 长时间运行任务的暂停/恢复(如数据处理、爬虫)。 - 交互式调试(临时中断程序检查状态)。 - 实时控制系统(通过按键手动干预)。 [^1]: 引用[1]展示了文件写入操作,但本方案通过回调直接控制程序状态,无需额外写入。 [^2]: 引用[2]解释了回调函数的通用原理,本实现遵循参数格式规范。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值