Python调用C语言动态库(DLL)结构体/指针/变量的方法


前言

在使用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扩展等加速开发周期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值