python调用c语言代码

原本的机器学习项目都用python写好,老板这边又出了个新需求,将部分核心代码抽出来,用C编写,然后通过dll的方式引用,目的就是为了隐藏部分核心代码。

1. 编写C代码

使用codeblocks建dll库

  1. 选择ddl

image-20211228161228492

  1. main.c编写要导出的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "main.h"
#include <stdio.h>

int DLL_EXPORT add_int(int a, int b)
{
return a+b;
}

double* DLL_EXPORT Kernel( double* x, double* y, int row, int col,double gamma, double* w)
{
double *res = NULL;
res =(double*) malloc(sizeof(double)*row*col);
printf("%d %d\n",row,col); // c 数组空间创建
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res[i*col + j] = x[i*col + j] + y[i*col + j]; // 访问二维指针数组
}
}
return res;
}
  1. main.h 编写导出头文件(默认)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef __MAIN_H__
#define __MAIN_H__

#include <windows.h>

/* To use this exported function of dll, include this header
* in your project.
*/

#ifdef BUILD_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif


#ifdef __cplusplus
extern "C"
{
#endif
// 这边编写导出的函数名称
int DLL_EXPORT add_int(int,int);
double* DLL_EXPORT Kernel(double*,double*,int,int,double,double*);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__
  1. 编译
    编译成功后在对应文件夹bin/Debug生成dll文件

2 C调用dl

  1. 新建Console Application

  2. main.c代码编写

注意点:

  • 导入头文件”windows.h”
  • 函数定义 typedef int (*addInt)(int,int);
  • 导入DLL: HMODULE LoadLibrary
  • 获得dll的函数:add_int = (addInt)GetProcAddress(hDll, “add_int”);
  • FreeLibrary
  • 若c参数为二维指针,需要两次malloc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>
#include "windows.h"
typedef int (*addInt)(int,int);
typedef float** (*myKernel)(float**,float**,int,int,float,float*);
addInt add_int;
myKernel kernel;

int main()
{
//asm("int3");
HMODULE hDll = LoadLibrary("dll路径");
if (hDll != NULL) {
printf("Hello world!\n");
add_int = (addInt)GetProcAddress(hDll, "add_int");
int res_add = add_int(3,3);
printf("%d\n",res_add);

kernel = (myKernel)GetProcAddress(hDll,"Kernel");

float arr1[2][3]={{1.1,2.0,3.0},{1.1,2,3.0}};
float arr2[2][3]={{1,2,3},{1,2,3}};
float (*temp1)[3] = arr1;
float (*temp2)[3] = arr2;

float **x = NULL;
x = (float**)malloc(sizeof(float*)*2);

for (int i = 0; i < 2; i++) {
x[i]=(float*)malloc(3*sizeof(float));

}

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
*(*(x+i)+j) = 5;
}
}


float arr3[3] ={1,1,1};
float *w = arr3;
float** res = kernel(x,x,2,3,0.2,w);

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d %d %f\n ", i,j,*(*(res+i)+j));
}
}

}
FreeLibrary(hDll);
return 0;
}

3 Python调用Dll

  1. 下载与导入ctypes包
1
2
3
4
from ctypes import cdll,c_float,c_int
import numpy.ctypeslib as npct
import numpy as np
import win32api
  1. 导入dll

    1
    mykernel = cdll.LoadLibrary('dll路径')
  2. 要调用的C函数参数(argtypes)与返回值(restype)都有二维指针,因此需要预先设置

    npct.ndpointer表示是指针

    1
    2
    3
    4
    5
    6
    7
    mykernel.Kernel.argtypes = [npct.ndpointer(dtype=np.float64,ndim=2,shape = (2, 3),flags='C_CONTIGUOUS'),
    npct.ndpointer(dtype=np.float64,ndim=2,shape = (2, 3),flags='C_CONTIGUOUS'),
    c_int,
    c_int,
    c_float,
    npct.ndpointer(dtype=np.float64,ndim=1,flags='C_CONTIGUOUS')]
    mykernel.Kernel.restype = npct.ndpointer(dtype=np.float64,ndim=2,shape = (2, 3),flags='C_CONTIGUOUS')
  3. numpy数组

1
2
3
4
5
6
7
x=[[1,2,3],[4,5,6]]
x=np.array(x,dtype=np.float64)
w=[1,1,1]
w=np.array(w,dtype=np.float64)

print('addreess',k)

  1. 调用函数
1
2
res = mykernel.Kernel(x,x,x.shape[0],x.shape[1],c_float(0.1),w)
# res就是返回的二维数组numpy
  1. Free
1
win32api.FreeLibrary(mykernel._handle)

一个大坑:

python的float32要对应C的float类型,float64对应 double类型,否则会造成地址访问冲突
具体报错:exception: access violation reading 0xFFFFFFFFFFFFFFFF

参考

使用ctypes调用C函数库加速Numpy运算

Codeblocks上dll的创建和使用

Python如何调用DLL函数:C数组与numpy数组传递

Ctypes和NumPy