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扩展等加速开发周期。
作者:ENOCH_Q