我喜欢使用 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_results
,dbr_free_results
而不是DBR_GetAllTextResults
和DBR_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