python调用c++的动态库,实现结构体类型、数组之间的参数传递

22 篇文章 0 订阅
10 篇文章 0 订阅

ctypes库介绍

  • python调用C++编译生成的动态链接库,包括DLL或者.so,需要用到ctypes库的方法
  • ctypes是一个用于Python的外部函数库,它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。

python调用的windows 的DLL

  • 在python中ctypes只能调用C函数接口以及参数,不能直接传输和调用c++中的数据类型,如string类型;因此,在导出的头文件中,需指定C编译器来编译,且输入输出参数类型必须是C语言所支持的,因此c++头文件中导出函数名前面要加关键字extern "C" { }

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

    ctypes 类型C 类型Python 类型
    c_bool_Boolbool (1)
    c_charchar单字符字节对象
    c_wcharwchar_t单字符字符串
    c_bytecharint
    c_ubyteunsigned charint
    c_shortshortint
    c_ushortunsigned shortint
    c_intintint
    c_uintunsigned intint
    c_longlongint
    c_ulongunsigned longint
    c_longlong__int64long longint
    c_ulonglongunsigned __int64unsigned long longint
    c_size_tsize_tint
    c_ssize_tssize_tPy_ssize_tint
    c_floatfloatfloat
    c_doubledoublefloat
    c_longdoublelong doublefloat
    c_char_pchar * (以 NUL 结尾)字节串对象或 None
    c_wchar_pwchar_t * (以 NUL 结尾)字符串或 None
    c_void_pvoid *int 或 None

基础类型之间的传参,int、float、char*

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

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

    #pragma once
    #include <string.h>
    
    
    #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)
    
复杂类型的传参
  • 接口如何实现复杂类型参数的传递?如字符串指针一维数组结构体
  • 字符串指针参数传递示例代码
    # 通过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'))
        arg3 = 0.25
        # 输出返回参数
        # 512可根据返回的字节数进行调整
        resArray = create_string_buffer(512) 
        libc.wm_detect_api.restype = c_int
        res = libc.wm_detect_api(arg1, arg2, arg3 , 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
    
  • 字节流数组传参,示例代码如下
    	# 函数encodeBitsBchApi前3个参数均为输入参数,最后一个参数为输出返回参数,类型为unsigned char*, 字节类型
    	def BCHEncode(input_stream, m, t):
    	    libc.encodeBitsBchApi.argtypes = [c_char_p, c_int, c_int , c_void_p]
    	    arg1 = c_char_p(input_stream.encode('utf-8'))
    	    arg2 = m
    	    arg3 = t
    	    
    	    # arg4 = create_string_buffer(128) 该方式也可用
    	    buftype = c_ubyte * 128 # 定义字节类型变量
    	    arg4 = buftype()
    	
    	    code = libc.encodeBitsBchApi(arg1, arg2, arg3, arg4)
    	    # result = arg4.value
    	    bits = []
    	    for i in range(64):
    	        bits.append(str(arg4[i]))
    	    return code, "".join(bits)
    
  • 一维数组传参,示例代码如下
    #coding=utf-8
    import os
    import sys
    import ctypes
    from ctypes import *
    
    libc = cdll.LoadLibrary(os.getcwd() + "/imgPropreccess.so")
    
    # 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
    
  • 结构体类型传参,示例代码如下
    class Infos(ctypes.Structure):
        _fields_ = [
            ('appid', c_char * 32),
            ('uuid', c_char * 128),
            ('num', c_int ),
            ('taskId', c_int),        
        ]
    
    def pdf_extract_api(infile, licenseCode, salt, pAppid, traceId):
        # int licenseExtractPdfWmApi(const char* inputPath, const char * licenseCode, const char * salt, const char * appIds, int traceId, ExtractInfo *res);
        lib_embed_so.licenseExtractPdfWmApi.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p, c_int, c_void_p]
        arg1 = c_char_p(infile.encode('gbk'))
        arg2 = c_char_p(licenseCode.encode('utf-8'))
        arg3 = c_char_p(salt.encode('utf-8'))
        arg4 = c_char_p(pAppid.encode('utf-8'))
        arg5 = traceId
        buftype = Infos * 1
        result = buftype()
        res = lib_embed_so.licenseExtractPdfWmApi(arg1, arg2, arg3, arg4, arg5, result)
        print(res)
        print(result[0].appid, result[0].uuid, result[0].num, result[0].taskId)
    

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出现多个,系统反而不知道找哪个,删除其中一个变量的路径即可
参考文献
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值