python调用DLL和so

ctypes库介绍

  • python调用C++编译生成的动态链接库,包括DLL或者.so,需要用到ctypes库的方法
  • ctypes是一个用于Python的外部函数库,它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。
  • 解决调用过程中出现OSError: exception: access violation reading 0x0000000000000030问题,报错原因是c并不直接支持python中常用数据类型

windows 生成python可调用的DLL

  • 这里需要注意的是python中ctypes只能调用C函数接口以及参数,不能直接传输和调用c++中的数据类型,如string类型;因此,在导出的头文件中,需指定C编译器来编译,且输入输出参数类型必须是C语言所支持的

  • 注意:导出函数名前面要加关键字extern "C" { }

  • ctype库函数接口支持的类型

    ctypes 类型 C 类型 Python 类型
    c_bool _Bool bool (1)
    c_char char 单字符字节对象
    c_wchar wchar_t 单字符字符串
    c_byte char int
    c_ubyte unsigned char int
    c_short short int
    c_ushort unsigned short int
    c_int int int
    c_uint unsigned int int
    c_long long int
    c_ulong unsigned long int
    c_longlong __int64long long int
    c_ulonglong unsigned __int64unsigned long long int
    c_size_t size_t int
    c_ssize_t ssize_tPy_ssize_t int
    c_float float float
    c_double double float
    c_longdouble long double float
    c_char_p char * (以 NUL 结尾) 字节串对象或 None
    c_wchar_p wchar_t * (以 NUL 结尾) 字符串或 None
    c_void_p void * int 或 None

Python调用dll或者so

  • Python 默认函数的输入和返回值类型为 int 型,如果C的函数接口导出函数的输入和返回值类型不是int型,就需要argtypesrestype指定函数形参和返回类型

  • 输入参数有多种类型,输出为整型(通常表示错误码),其接口头文件如下xxx.h,输入为2个char* 和2个int以及一个int*,返回int,其中1个char*和int*作为c++处理后的结果返回给python,类似java的jni层作用

    #pragma once
    #include 
    
    
    #ifdef _WIN32
    #ifdef _WINDLL
    #define DLL_EXPORT __declspec(dllexport)
    #else  
    #define DLL_EXPORT __declspec(dllimport)  
    #endif  
    #else  
    #define DLL_EXPORT 
    #endif
    
      extern "C" {
      	DLL_EXPORT int  sum( int a, int b);
      	DLL_EXPORT int str_add_sum(const char* p0, int a, int b, int *c, char* result);
      }
    
  • python调用接口示例,建议使用python3,python2可能会出现解码报错

    from ctypes import *
    
    # pDLL = WinDLL("./Test.dll") ## for windows xxx.dll
    # pSo = cdll.LoadLibrary("./xxx.so") ## for linux xxx.so 
    pDll = CDLL("./Test.dll")  ## 均可
    
    ## case 1: 接口函数输出输入均为int型, 无需指定参数
    #最简单的形式
    res = pDll.sum(1,2)
    print(res)
    
    ## case 2: 指定参数类型,对应上面头文件的类型
    # str_add_sum接口调用示例
    str_input = "1010"
    
    ##根据上述的ctype库函数接口支持表格,用argtypes指定函数的输入参数类型
    pDll.str_add_sum.argtypes = [c_char_p, c_int, c_int, c_void_p, c_char_p] # c_void_p 对应接受int*
    
    # Use b'xxxxx' for byte strings that are translated to char*
    arg1 = c_char_p(str_input.encode('utf-8')) 
    arg2 = 2
    arg3 = 3
    buftype = ctypes.c_int * 1
    arg4 = buftype()
    arg5 = create_string_buffer(64) # 定义接受字节数的长度为64
    ## restype指定接口的返回类型
    pDll.str_add_sum.restype = c_int
    ##call api
    res = pDll.str_add_sum(arg1, arg2, arg3, arg4,arg5) # type is bytes #返回的是utf-8编码的数据,需要解码
    print(arg4[0])  # 2+3=5
    print(arg5.value.decode("utf-8"))
    print(res)
    

接口调用如何实现参数的传递?如字符串,整数,浮点数等结构体

  • python中通过byte字节类型创建内存空间,可避免内存泄漏问题,代码如下
    import ctypes
    from ctypes import *
    
    libc = CDLL("./detect.so") 
    
    def detect_api(input_path, model):
        libc.wm_detect_api.argtypes = [c_char_p, c_char_p, c_float, c_char_p]
        arg1 = c_char_p(input_path.encode('utf-8'))
        arg2 = c_char_p(input_model.encode('utf-8'))
        
        #resbyte = bytes(" ", "utf-8")# 注意是空格字符,必须要占一个字符,否则异常报错
        #resline = ctypes.c_char_p(resbyte * 64) # 预留返回字符串不超过64个字符,根据需求自己定义
        resArray = create_string_buffer(64) 
        # libc.wm_detect_api.restype = c_int
    
        res = libc.wm_detect_api(arg1, arg2, 0.25, resArray)
       	# resline is c_char_p object , so call value method convert to bytes type
        # type is bytes , using utf-8 keys parameters decode
        print(resArray.value.decode("utf-8"))
        return res
    

接口的一维数组参数的相互传递

  • 通过byref实现,直接上代码
#coding=utf-8
import os
import sys
import ctypes
from ctypes import *

libc = cdll.LoadLibrary(os.getcwd() + "/imgPropreccess.dll")

# int imgSiftMatch(const char* inputMatchPath, const char* refSourcePath, float* points);
def img_sift_match(inputPath, referencePath):
	# c接口的基础数据类型指针,在python指定类型时,可用v_void_p表示
    libc.imgSiftMatch.argtypes = [c_char_p, c_char_p, c_void_p]
    # p = create_string_buffer(" ", 128)
    arg1 = c_char_p(inputPath.encode('utf-8'))
    arg2 = c_char_p(referencePath.encode('utf-8'))
    float_array = c_float * 8
    float_data = float_array() # convert to class '__main__.c_float_Array_8'
    
    # c_float_Array_8对象接受c接口返回的浮点型数组各个值
    code = libc.imgSiftMatch(arg1, arg2, byref(float_data))#返回的是utf-8编码的数据,需要解码

    points_list = []
    # c_float_Array_8转换为列表返回
    for i in range(8):
        points_list.append(int(0.5+float_data[i]))

    return code, points_list

# int cropImageRoi(const char* pInputPath, const char* pSavePath, int* pts);
def crop_to_Roi(inputPath, outPath, pts):
    ext_image_list = ['.png','.PNG','.jpg','jpeg','.bmp','.JPG']
    if len(pts) !=8:
        print("input points is wrong!\n")
        return -1
    dirPath, extname = os.path.splitext(outPath)

    if extname not in ext_image_list:
        print("input save path  is unvalid!\n")
        return -1
    libc.cropImageRoi.argtypes = [c_char_p, c_char_p, c_void_p]
    int_array = c_int * 8
    int_data = int_array() # convert to class '__main__.c_long_Array_8'

    # class '__main__.c_long_Array_8' object must be using for seq assign value
    for i in range(len(pts)):
        int_data[i] = pts[i]
    arg1 = c_char_p(inputPath.encode('utf-8'))
    arg2 = c_char_p(outPath.encode('utf-8'))
    code = libc.cropImageRoi(arg1, arg2, byref(int_data))

    return code

if __name__ == "__main__":
    ret, pts = img_sift_match("../src.png", "../ref.jpg")
    print(pts)
    crop_to_Roi("../ref.jpg", "./result.jpg", pts)
    

windows 下常见的报错

  • 通过ctypes.cdll.LoadLibrary可以调用指定路径下的dll,但如果被调用的dll又依赖于其他的dll,系统路径或当前路径搜索不到所依赖的,则会报错,OSError: [WinError 126] 找不到指定的模块,或报其他的溢出错误码
    • 解决办法:利用VS 自带的window依赖可执行程序查看,然后查看所依赖的dll是否在系统变量路径中,可以加入系统路径或者python脚本当前目录
      • 切换至路径D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC
      • 执行dumpbin /dependents xxx.dll,xxx.dll换成绝对路径
    • 若依赖的dll存在多个不同目录中,且依然报上述错误,则有可能是依赖的dll出现多个,系统反而不知道找哪个,删除其中一个变量的路径即可

参考文献

  • https://stackoverflow.com/questions/14883853/ctypes-return-a-string-from-c-function
  • OSError: exception: access violation reading
  • ctypes库学习
  • https://zhuanlan.zhihu.com/p/143026186

你可能感兴趣的:(python,C++,C,ctypes,python调用dll)