1. 程式人生 > >python如何呼叫C, 如何註冊成C的回撥函式(python後臺程式常用方法)

python如何呼叫C, 如何註冊成C的回撥函式(python後臺程式常用方法)

最近做一個專案,分析視訊內容,用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.