Python调用C语言动态库(DLL)中的结构体、指针与变量操作指南

文章目录

  • 前言
  • 一、如何生成C语言动态库DLL
  • 第一步:安装编译工具
  • 第二步: 设计C代码
  • 第三步:编译成C语言动态库DLL
  • 二、如何使用C语言动态库
  • 第一步:python/pytorch调入DLL接口
  • 第二步:Python调用DLL函数
  • 第三步:Python测试函数
  • 三、完整程序与测试结果
  • 总结

  • 前言

    在使用python等进行数据处理时,有时需要使用C语言生成的动态库进行数据处理,比如有些算法已经用C语言实现,或有些函数处理python没有现成函数或速度很慢,这时使用C语言生成动态库DLL的方法比较容易解决,在一些前期算法验证或测试验证中(可以隐藏核心代码)比较实用。


    一、如何生成C语言动态库DLL

    第一步:安装编译工具

    生成C语言动态库DLL需要使用编译工具,Visual Studio(VS)是比较方便使用的工具,当然还可以使用GCC、intel C Compiler等。下面以VS作为示例来说明使用方法。
    安装VS请在**这里**下载并安装,安装过程较为简单,没有特殊设置。

    第二步: 设计C代码

    为了说明Python如何调用C代码中的结构体(带指针)、指针、变量已经返回值处理等问题,C代码构造了如下的程序。
    首先建立一个test.h头文件,定义结构体和DLL库函数结构:

    #ifndef __TEST_H__
    #define __TEST_H__
    
    #define __EXTERN__ __declspec(dllexport)
    
    typedef struct _struct_my_data
    {
        int x;
        int y;
        float *data;
    }stru_my_data;
    
    
    // test dll c function: bias + beta * din.data
    __EXTERN__ stru_my_data Test(stru_my_data din, float* bias, float beta);
    
    #endif // __TEST_H__
    

    再建立一个test.c的函数实现文件如下:

    // all function should be accessible after compiled to dll
    
    #include <stdio.h>
    #include <stdlib.h>
    #include "test.h"
    
    
    // test dll c function: bias + beta * din.data
    stru_my_data Test(stru_my_data din, float* bias, float beta)
    {
        int i = 0, j = 0;
        stru_my_data dout = {0};
        dout.x = din.x;
        dout.y = din.y;  
        dout.data = (float *)malloc(sizeof(float) * din.x * din.y);
        
        // bias + beta * din.data
        for (i = 0; i < din.y; i++)
        {
            for (j = 0; j < din.x; j++)
            {            
                dout.data[i * dout.x + j] = bias[i * din.x + j] + beta * din.data[i * din.x + j];
            }
        }          
    
        return dout;
    }
    
    

    第三步:编译成C语言动态库DLL

    使用VS的cl直接编译即可,可以建立一个bat文件,方便后续直接运行调用,bat文件为:

    @ call "d:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
    @ cl /O2 /LD test.c -o .\dll\test.dll
    @ del test.obj 
    @ pause
    

    二、如何使用C语言动态库

    第一步:python/pytorch调入DLL接口

    使用C DLL首先需要使用ctypes读入DLL库

    import ctypes
    test_dll = ctypes.CDLL("./dll/test.dll")
    

    为了能解析和使用C DLL的结构体,需要使用ctypes.Structure,可以建立一个Class用于此目的:

    class StructData(ctypes.Structure):
        _fields_ = [ ("x", ctypes.c_int), ("y", ctypes.c_int), ("data", ctypes.POINTER(ctypes.c_float))]
    

    同时需要设置参数和返回值

    test_dll.Test.argtypes = [StructData, ctypes.POINTER(ctypes.c_float), ctypes.c_float]
    test_dll.Test.restype = StructData
    

    第二步:Python调用DLL函数

    为了方便Python进行方便调用,可以建立一个C DLL的Python封装函数

    def DllTest(data_in, data_bias, beta):
        [height, width]     = data_in.shape
        [height_b, width_b] = data_bias.shape
        # check
        if data_in.shape != data_in.shape:
            print('Input Data Size != Bias Data Size.')
            exit(-1)
        # din
        din_c = StructData()
        din_c.x = height
        din_c.y = width
        din_c.data = data_in.reshape(-1).ctypes.data_as(ctypes.POINTER(ctypes.c_float))
        # bias
        bias_c = data_bias.reshape(-1).ctypes.data_as(ctypes.POINTER(ctypes.c_float))
        # C DLL Function
        dout_c = test_dll.Test(din_c, bias_c, beta)
        # output reshape                                               
        dout = np.ctypeslib.as_array(dout_c.data, shape = (dout_c.x, dout_c.y))
        return dout
    

    第三步:Python测试函数

    为了验证C代码正确性,可以通过python进行正确性验证

    def CheckDllTest(height = 64, width = 64, err_esp = 1.0e-6, sim_num = 100):    
        for i in range(sim_num):
            # random data for test
            data_in = np.random.rand(height, width).astype('float32')
            bias_in = np.random.rand(height, width).astype('float32')
            beta_in = np.random.rand(1).astype('float32')
            # numpy result
            dout = bias_in + beta_in * data_in;       
            # C DLL result    
            dout_c = DllTest(data_in, bias_in, beta_in[0])
            # compare
            error = np.allclose(dout, dout_c, rtol=1.e-5, atol = err_esp, equal_nan=False)
            if error is False:
                error_max = np.max(dout - dout_c)
                print("Test Error: max error = %0.8f > %0.8f(err_esp)" \
                        % (error_max, err_esp))
                return 1
            else:
               print("Test %d Successful" % (i))
        return 0
    

    三、完整程序与测试结果

    完整代码如下:

    # This is a program for C DLL functions test
    import ctypes
    import numpy as np
    import time
    
    test_dll = ctypes.CDLL("./dll/test.dll")
    
    class StructData(ctypes.Structure):
        _fields_ = [ ("x", ctypes.c_int), ("y", ctypes.c_int), ("data", ctypes.POINTER(ctypes.c_float))]
        
    ############################################################################
    # DLL
    test_dll.Test.argtypes = [StructData, ctypes.POINTER(ctypes.c_float), ctypes.c_float]
    test_dll.Test.restype = StructData
    
    def DllTest(data_in, data_bias, beta):
        [height, width]     = data_in.shape
        [height_b, width_b] = data_bias.shape
        # check
        if data_in.shape != data_in.shape:
            print('Input Data Size != Bias Data Size.')
            exit(-1)
        # din
        din_c = StructData()
        din_c.x = height
        din_c.y = width
        din_c.data = data_in.reshape(-1).ctypes.data_as(ctypes.POINTER(ctypes.c_float))
        # bias
        bias_c = data_bias.reshape(-1).ctypes.data_as(ctypes.POINTER(ctypes.c_float))
        # C DLL Function
        dout_c = test_dll.Test(din_c, bias_c, beta)
        # output reshape                                               
        dout = np.ctypeslib.as_array(dout_c.data, shape = (dout_c.x, dout_c.y))
        return dout
        
    def CheckDllTest(height = 64, width = 64, err_esp = 1.0e-6, sim_num = 100):    
        for i in range(sim_num):
            # random data for test
            data_in = np.random.rand(height, width).astype('float32')
            bias_in = np.random.rand(height, width).astype('float32')
            beta_in = np.random.rand(1).astype('float32')
            # numpy result
            dout = bias_in + beta_in * data_in;       
            # C DLL result    
            dout_c = DllTest(data_in, bias_in, beta_in[0])
            # compare
            error = np.allclose(dout, dout_c, rtol=1.e-5, atol = err_esp, equal_nan=False)
            if error is False:
                error_max = np.max(dout - dout_c)
                print("Test Error: max error = %0.8f > %0.8f(err_esp)" \
                        % (error_max, err_esp))
                return 1
            else:
               print("Test %d Successful" % (i))
        return 0
    
    
    ############################################################################
    # MAIN
    if __name__ == "__main__":
        height  = 16
        width   = 16
        sim_num = 10
        
        print("Python USE C DLL Functions Testing ...")    
        ################################################
        st = time.time()
        if CheckDllTest(height = height, width = width, err_esp = 1.0e-8, sim_num = sim_num):
            print("Test Error!")
            exit(-1)
        print("Test Done.[T] %.3f" % (time.time() - st))
        
    

    测试结果正确


    总结

    以上Python调用C语言动态库(DLL)结构体/指针/变量的方法,基本可以满足一般的需求,对于一些应用即可以借助C语言的代码执行高效性和通用性,又可以使用Python的便利性等特性何乐而不为。特别是有些AI神经网络inference应用方面,可以借助C/CUDA/C++,再结合pytorch C扩展等加速开发周期。

    作者:ENOCH_Q

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python调用C语言动态库(DLL)中的结构体、指针与变量操作指南

    发表回复