Python如何调用dll:用于加载和调用共享库的Python Ctypes

2021年9月12日03:22:52 发表评论 1,005 次浏览

我喜欢使用 CPython 和 Dynamsoft C/C++ Barcode SDK 将条码检测 API 引入 Python,这要求我们学会Python调用第三方dll的相关技术。但是,Ctypes也值得探索。它允许开发人员在纯 Python 代码中调用共享库的 C 函数。Python如何调用dll?本文介绍了如何通过 Python Ctypes 调用 Dynamsoft C/C++ Barcode APIs 的步骤,通过这个实例了解Python调用动态链接库的方法和原理。

尝试在纯Python中调用Dynamsoft Barcode SDK的C函数

Python调用dll实例:假设您已经从 Dynamsoft 网站下载了C/C++ 包,其中包含适用于 Windows 和 Linux 的 *.dll 和 *.so 文件。我们将 *.dll 和 *.so 文件复制到 Python 项目的同一文件夹中。

Python调用第三方dll:在 Python 文件中,我们导入 Ctypes 库,然后加载 Dynamsoft 共享库:

import os
import platform
from ctypes import *

dbr = None
if 'Windows' in system:         
    os.environ['path'] += ';' + os.path.join(os.path.abspath('.'), r'<subfolder path>')
    dbr = windll.LoadLibrary('DynamsoftBarcodeReaderx64.dll')
else:
    dbr = CDLL(os.path.join(os.path.abspath('.'), '<libDynamsoftBarcodeReader.so path>'))

DynamsoftBarcodeReaderx64.dll取决于vcom110.dll。如果vcom110.dll错过,您将收到以下错误:

Traceback (most recent call last):
  File ".\failure.py", line 80, in <module>
    dbr = windll.LoadLibrary('DynamsoftBarcodeReaderx64.dll')
  File "C:\Python37\lib\ctypes\__init__.py", line 442, in LoadLibrary
    return self._dlltype(name)
  File "C:\Python37\lib\ctypes\__init__.py", line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 126] The specified module could not be found

对于Linux,路径libDynamsoftBarcodeReader.so就足够了。

使用 Ctypes 解码条码图像的步骤

Python如何调用dll?要了解调用函数,您可以参考该DynamsoftBarcodeReader.h文件。

步骤 1:初始化 Dynamsoft Barcode Reader

DBR_CreateInstance = dbr.DBR_CreateInstance
DBR_CreateInstance.restype = c_void_p
instance = dbr.DBR_CreateInstance()

步骤 2:设置许可证密钥

DBR_InitLicense = dbr.DBR_InitLicense
DBR_InitLicense.argtypes = [c_void_p, c_char_p] 
DBR_InitLicense.restype = c_int
ret = DBR_InitLicense(instance, c_char_p('LICENSE-KEY'.encode('utf-8')))

从 Dynamsoft 网站获取有效的许可证密钥,然后更新c_char_p('LICENSE-KEY'.encode('utf-8')).

将 Python 转换string为 时char *,您需要utf-8先将其编码为。否则,您将获得TypeError: bytes or integer address expected instead of str instance.

步骤3:Python调用dll实例:调用解码API

DBR_DecodeFile = dbr.DBR_DecodeFile
DBR_DecodeFile.argtypes = [c_void_p, c_char_p, c_char_p]
DBR_DecodeFile.restype = c_int
ret = DBR_DecodeFile(instance, c_char_p('test.png'.encode('utf-8')), c_char_p(''.encode('utf-8')))

步骤4:获取条码结果

pResults = POINTER(TextResultArray)()
DBR_GetAllTextResults = dbr.DBR_GetAllTextResults
DBR_GetAllTextResults.argtypes = [c_void_p, POINTER(POINTER(TextResultArray))]
DBR_GetAllTextResults.restype = c_int
ret = DBR_GetAllTextResults(instance, byref(pResults))
print(ret)

resultsCount = pResults.contents.resultsCount
print(resultsCount)
results = pResults.contents.results
print(results)

if bool(results):
    for i in range(resultsCount):
        result = results[i] 
        if bool(result):
            print(result)
            print('Format: %s' % result.contents.barcodeFormatString.decode('utf-8'))                   
            print('Text: %s' % result.contents.barcodeText.decode('utf-8'))

Python调用第三方dll:这是整个过程中最艰难的部分。结果是一个TextResultArray结构,其中包含一个TextResult结构列表。每个TextResult结构都包含条码格式、条码文本和其他信息。要访问条码解码结果,我们需要在 Python 中定义所有相关的 C 结构:

class SamplingImageData(Structure):
    _fields_ = [("bytes", POINTER(c_byte)), ("width", c_int), ("height", c_int)]

class ExtendedResult(Structure):
    _fields_ = [("resultType", c_uint),
                ("barcodeFormat", c_uint),
                ("barcodeFormatString", c_char_p),
                ("barcodeFormat_2", c_uint),
                ("barcodeFormatString_2", c_char_p),
                ("confidence", c_int),
                ("bytes", POINTER(c_byte)),
                ("bytesLength", c_int),
                ("accompanyingTextBytes", POINTER(c_byte)),
                ("accompanyingTextBytesLength", c_int),
                ("deformation", c_int),
                ("detailedResult", c_void_p),
                ("samplingImage", SamplingImageData),
                ("clarity", c_int),
                ("reserved", c_char * 40),
                ]

class LocalizationResult(Structure):
    _fields_ = [("terminatePhase", c_uint), 
    ("barcodeFormat", c_uint),
    ("barcodeFormatString", c_char_p),
    ("barcodeFormat_2", c_uint),
    ("barcodeFormatString_2", c_char_p),
    ("x1", c_int),
    ("y1", c_int),
    ("x2", c_int),
    ("y2", c_int),
    ("x3", c_int),
    ("y3", c_int),
    ("x4", c_int),
    ("y4", c_int),
    ("angle", c_int),
    ("moduleSize", c_int),
    ("pageNumber", c_int),
    ("regionName", c_char_p),
    ("documentName", c_char_p),
    ("resultCoordinateType", c_uint),
    ("accompanyingTextBytes", POINTER(c_byte)),
    ("accompanyingTextBytesLength", c_int),
    ("confidence", c_int),
    ("reserved", c_char * 52),
    ]

class TextResult(Structure):
    _fields_ = [("barcodeFormat", c_uint), 
    ("barcodeFormatString", c_char_p), 
    ("barcodeFormat_2", c_uint), 
    ("barcodeFormatString_2", c_char_p), 
    ("barcodeText", c_char_p), 
    ("barcodeBytes", POINTER(c_byte)),
    ("barcodeBytesLength", c_int),
    ("localizationResult", POINTER(LocalizationResult)),
    ("detailedResult", c_void_p),
    ("resultsCount", c_int),
    ("results", POINTER(POINTER(ExtendedResult))),
    ("exception", c_char_p),
    ("isDPM", c_int),
    ("isMirrored", c_int),
    ("reserved", c_char * 44),
    ]


class TextResultArray(Structure):
    _fields_= [("resultsCount",c_int),
              ("results", POINTER(POINTER(TextResult)))]

步骤5:释放 Barcode Result 的内存

DBR_FreeTextResults = dbr.DBR_FreeTextResults
DBR_FreeTextResults.argtypes = [POINTER(POINTER(TextResultArray))]
if bool(pResults):
    DBR_FreeTextResults(byref(pResults)) 

步骤6:销毁 Dynamsoft 条码阅读器

DBR_DestroyInstance = dbr.DBR_DestroyInstance
DBR_DestroyInstance.argtypes = [c_void_p]
DBR_DestroyInstance(instance)

Python调用动态链接库:测试程序

Python如何调用dll?到目前为止,我们已经成功地实现了一个带有 Ctypes 的 Python 条码阅读器。然而,运行脚本不会输出任何内容。结果计数大于零,但bool(results)返回false,表示条码解码结果为空。显然是不可能的。但不幸的是,我还没有弄清楚错误在哪里。可能是由嵌套的 C 结构的内存副本引起的。为了避免分段违规,最好编写一些 C 代码来简化 Python 中定义的 C 结构。

编写一些 C 代码来简化 Ctypes 调用

Python调用dll实例:我们创建了一个 CMake 库项目,命名bridge为实现两个方法dbr_get_resultsdbr_free_results而不是DBR_GetAllTextResultsDBR_FreeTextResults。C 代码是用bridge.c.

CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 3.0.0)

project(bridge VERSION 0.1.0)

INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include")
if (CMAKE_HOST_WIN32)
    LINK_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib/Windows")
else()
    LINK_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/lib/Linux")
endif()
add_library(${PROJECT_NAME} SHARED bridge.c)

if(CMAKE_HOST_WIN32)
    target_link_libraries (${PROJECT_NAME} "DBRx64")
else()
    target_link_libraries (${PROJECT_NAME} "DynamsoftBarcodeReader")
endif()

为了导出 Windows 和 Linux 的函数,我们在 中定义了一个宏bridge.h

#if !defined(_WIN32) && !defined(_WIN64)
#define EXPORT_API
#else
#define EXPORT_API __declspec(dllexport)
#endif

此外,我们还定义了 Python Ctypes 将使用的 C 结构和函数:

typedef struct {
    char* format;
    char* text;
} ResultInfo;

typedef struct {
    int size;
    ResultInfo** pResultInfo;
} ResultList;

EXPORT_API ResultList* dbr_get_results(void* barcodeReader);
EXPORT_API void dbr_free_results(ResultList* resultList);

在 中bridge.c,我们添加了dbr_get_results和的实现dbr_free_results

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "bridge.h"

ResultList* dbr_get_results(void* barcodeReader)
{
    TextResultArray *pResults;
    int ret = DBR_GetAllTextResults(barcodeReader, &pResults);
    int count = pResults->resultsCount;
    TextResult **results = pResults->results;

    ResultInfo** pResultInfo = (ResultInfo**)malloc(sizeof(ResultInfo*) * count);
    ResultList* resultList = (ResultList*)malloc(sizeof(ResultList));
    resultList->size = count;
    resultList->pResultInfo = pResultInfo;

    for (int i = 0; i < count; i++)
    {
        TextResult* pResult = results[i];
        ResultInfo* pInfo = (ResultInfo*)malloc(sizeof(ResultInfo));
        pInfo->format = NULL;
        pInfo->text = NULL;
        pResultInfo[i] = pInfo;
        pInfo->format = (char *)calloc(strlen(pResult->barcodeFormatString) + 1, sizeof(char));
        strncpy(pInfo->format, pResult->barcodeFormatString, strlen(pResult->barcodeFormatString));
        pInfo->text = (char *)calloc(strlen(pResult->barcodeText) + 1, sizeof(char));
        strncpy(pInfo->text, pResult->barcodeText, strlen(pResult->barcodeText));
    }

    DBR_FreeTextResults(&pResults);

    return resultList;
}

void dbr_free_results(ResultList* resultList)
{
    int count = resultList->size;
    ResultInfo** pResultInfo = resultList->pResultInfo;

    for (int i = 0; i < count; i++) 
    {   
        ResultInfo* resultList = pResultInfo[i];
        if (resultList) 
        {
            if (resultList->format)
                free(resultList->format);
            if (resultList->text)
                free(resultList->text);
            
            free(resultList);
        }
    }

    if (pResultInfo)
        free(pResultInfo);
}

桥库构建完成后,我们回到 Python 脚本。一个新的库文件bridge.dll/bridge.so已准备好加载:

dbr = None
bridge = None
if 'Windows' in system:                   
    os.environ['path'] += ';' + os.path.join(os.path.abspath('.'), r'bridge\lib\Windows')
    dbr = windll.LoadLibrary('DynamsoftBarcodeReaderx64.dll')
    bridge = windll.LoadLibrary(os.path.join(os.path.abspath('.'), r'bridge\build\Debug\bridge.dll'))
else:
    dbr = CDLL(os.path.join(os.path.abspath('.'), 'bridge/lib/Linux/libDynamsoftBarcodeReader.so'))
    bridge = CDLL(os.path.join(os.path.abspath('.'), 'bridge/build/libbridge.so'))

Python如何调用dll?库加载顺序对 Linux 至关重要:libDynamsoftBarcodeReader.so首先,然后libbridge.so. 如果库以错误的顺序加载,Python 代码将无法在 Linux 上运行。

C 结构现在比上一步中定义的结构更简单、更清晰:

class ResultInfo(Structure):
    _fields_ = [("format", c_char_p), ("text", c_char_p)]

class ResultList(Structure):
    _fields_ = [("size", c_int), ("pResultInfo", POINTER(POINTER(ResultInfo)))]

因此获取和销毁条码解码结果的Python代码改为:

# dbr_get_results  
dbr_get_results = bridge.dbr_get_results
dbr_get_results.argtypes = [c_void_p]
dbr_get_results.restype = c_void_p
address = dbr_get_results(instance)
data = cast(address, POINTER(ResultList))
size = data.contents.size
results = data.contents.pResultInfo

for i in range(size):   
    result = results[i]                
    print('Format: %s' % result.contents.format.decode('utf-8'))                   
    print('Text: %s' % result.contents.text.decode('utf-8'))

# dbr_free_results
dbr_free_results = bridge.dbr_free_results
dbr_free_results.argtypes = [c_void_p]
if bool(address):
    dbr_free_results(address)  

最后,我们可以成功运行 Python 条码解码应用程序,Python调用第三方dll的基本操作步骤到此就完成了。

Python调用dll实例源代码

https://github.com/yushulx/python-ctypes-barcode-shared-library

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: