生成C++动态库

生成C++ 动态库需要注意以下几点:

1.编译成64位的dll库

如果64位的python调用32位的dll会报下面的错误:

OSError: [WinError 193] %1 不是有效的 Win32 应用程序。

这种错误的原因基本上都是64为的python调用了32位的动态库。
解决的方法是生成64位的动态库。安装了visual studio可以使用:
在这里插入图片描述
x64的命令行窗口进行编译。编译命令是:

cl /LD hello.cpp

2. C++ 模板

头文件的模板如下:

//dll.h
#ifndef DLL_EXPORT
#define DECLDIR __declspec(dllimport)
#else
#define DECLDIR __declspec(dllexport)
#endif

c++源文件的模板如下:

//hello.cpp
#include "stdio.h"
#include <iostream>
#include <string.h>

#ifdef __cplusplus
extern "C"
{
#endif
#define DLL_EXPORT 
#include "dll.h"

using namespace std;

#pragma execution_character_set("utf-8")

DECLDIR void hello(void){
	printf("hello world");
	}
DECLDIR int add_int(int a,int b){
	return a+b;
}
DECLDIR float add_float(float a,float b){
	return a+b;
}
DECLDIR double add_double(double a,double b){
	return a+b;
}
DECLDIR char pass_char(char c){
	printf("%c\n",char(c));
	return c;
}
DECLDIR wchar_t pass_wchar(wchar_t  wc){
	wcout.imbue(locale("chs"));
	wcout<< wc;
	return wc;
}
DECLDIR char* pass_str(char *s){
	printf("%s\n",s);
	return strcat(s,"return");
}
DECLDIR wchar_t* pass_wstr(wchar_t*s,wchar_t wc){
	wcout<< s;
	wchar_t *p = wcschr(s,wc);
	return p;
	
}

DECLDIR int* get_memory(int n)
{
    int *p = new int[n];
	for(int i = 0; i < n; i++)
	{
			p[i] = i;
	}
	return p;
}
 
DECLDIR void free_memory(int *p)
{
	if(p)
		delete [] p;
}
DECLDIR int  find_num(int target,int *p,int n){
	int i;
	for (i=0;i<n;i++){
		if (p[i]== target)
			return i;
	}
	return -1;
	}

#ifdef __cplusplus
}
#endif

3. 示例

c++程序见第二节
python程序如下:

from ctypes import *
lib_path = './hello.dll'
dlllib = cdll.LoadLibrary(lib_path)
dlllib.hello()

Python调用带参数的动态库

第一节的示例是没有任何参数的,属于最简单的情况。如何传递以及返回参数,才是python调用动态库的要点。

python与c数据类型

在ctypes中定义python互相对应的参数类型。

类型pythonc
布尔c_boolbool
整型c_intint
长整型c_longlong
浮点c_floatfloat
双精度c_doubledouble
字符c_charchar
宽字符c_wcharwchar_t
字符串c_char_pchar*
宽字符串c_wchar_pwchar_t *
指针c_void_pvoid *

int、long、float、double参数

标准的dll调用需要明确传入参数和返回参数,否则系统会默认为所有参数为整型。比如:

//hello.cpp
#include "stdio.h"
#include <iostream>
 
#ifdef __cplusplus
extern "C"
{
#endif
#define DLL_EXPORT 
#include "dll.h"

using namespace std;
 
DECLDIR void hello(void){
	printf("hello world");
	}
DECLDIR int add_int(int a,int b){
	return a+b;
}
DECLDIR float add_float(float a,float b){
	return a+b;
}
DECLDIR double add_double(double a,double b){
	return a+b;
}

#ifdef __cplusplus
}
#endif
from ctypes import *
lib_path = './hello.dll'
dlllib = cdll.LoadLibrary(lib_path)
ret_i =dlllib.add_int(1,200)
ret_f = dlllib.add_float(c_float(1.1),c_float(2.1))
print(ret_i,ret_f)

此时程序返回的结果为:
201 1094533517

参数声明

解决办法是显式声明传入和返回参数。

from ctypes import *
lib_path = './hello.dll'
dlllib = cdll.LoadLibrary(lib_path)
ret_i =dlllib.add_int(1,200)
dlllib.add_float.argtypes=[c_float,c_float]
dlllib.add_float.restype = c_float
ret_f = dlllib.add_float(c_float(1.1),c_float(2.1))
print(ret_i,ret_f)

此时结果为:

201 3.1999998092651367

字符与字符串参数

传递单个字符,可以使用c_char或者c_wchar,如果要传递字符串则需要c_char_p和c_wchar_p:

from ctypes import *
lib_path = './hello.dll'
dlllib = cdll.LoadLibrary(lib_path)
dlllib.pass_char.argtypes=[c_char]
dlllib.pass_char.restype = c_char
ret_c = dlllib.pass_char(b"A")
dlllib.pass_wchar.argtypes=[c_wchar]
dlllib.pass_wchar.restype = c_wchar
ret_w = dlllib.pass_wchar("学")

dlllib.pass_str.argtypes=[c_char_p]
dlllib.pass_str.restype = c_char_p

ret_s = dlllib.pass_str(b"test string ")
dlllib.pass_wstr.argtypes=[c_wchar_p,c_wchar]
dlllib.pass_wstr.restype = c_wchar_p
ret_sw = dlllib.pass_wstr("世界真奇妙","好")

print("------")
print("pass_char",str(ret_c.decode("utf8")))
print("pass_wchar",ret_w)
print("pass_str",str(c_char_p(ret_s).value.decode("utf8")))
print("pass_wstr",ret_sw)

需要注意的是,c++对宽字符的处理比较麻烦,如果返回的字符串被修改,需要特别注意,因为返回的可能是乱码。

------
pass_char A
pass_wchar 学
pass_str test string return
pass_wstr 真奇妙
A
学test string 
世界真奇妙

C分配内存与释放内存

分配内存后,返回的是一个指针。

from ctypes import *
lib_path = './hello.dll'
dlllib = cdll.LoadLibrary(lib_path)
dlllib.get_memory.argtypes=[c_int]
dlllib.get_memory.restype = POINTER(c_int)
ret_int = dlllib.get_memory(20)
print("get:",ret_int)
int_array = [ret_int[i] for i in range(20)]
print(int_array)

dlllib.free_memory.restype = c_void_p
dlllib.free_memory(ret_int)
get: <__main__.LP_c_long object at 0x00000272E0718040>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

python与c的效率

使用python与C混合编程的意义在于C的处理速度快,毕竟python是解释型语音。比如通过循环查找数组里面的数值:

from ctypes import *
import line_profiler
import sys

def c_find(a):
    lib_path = './hello.dll'
    dlllib = cdll.LoadLibrary(lib_path)
    dlllib.find_num.argtypes=[c_int,POINTER(c_int),c_int]
    dlllib.find_num.restype = c_int

    ret_f = dlllib.find_num(5,a,len(a))
    return ret_f

def p_find(a):
    for i in range(len(a)):
        if a[i]==5:
            return i
    return -1

def main():
    a = [20]*100000
    a.append(5)
    arr_d = (c_int * len(a))(*(i for i in a))
    lib_path = './hello.dll'
    dlllib = cdll.LoadLibrary(lib_path)
    dlllib.find_num.argtypes=[c_int,POINTER(c_int),c_int]
    dlllib.find_num.restype = c_int
    dlllib.find_num(5,arr_d,len(arr_d))
    p_find(a)

profile = line_profiler.LineProfiler(main)  
profile.enable()  
main()
profile.disable() 
profile.print_stats(sys.stdout)  

结果:

Timer unit: 1e-07 s

Total time: 0.0549094 s
File: p1.py
Function: main at line 21

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    21                                           def main():
    22         1       3110.0   3110.0      0.6      a = [20]*100000
    23         1       2200.0   2200.0      0.4      a.append(5)
    24         1     299069.0 299069.0     54.5      arr_d = (c_int * len(a))(*(i for i in a))
    25         1         11.0     11.0      0.0      lib_path = './hello.dll'
    26         1       9016.0   9016.0      1.6      dlllib = cdll.LoadLibrary(lib_path)
    27         1        428.0    428.0      0.1      dlllib.find_num.argtypes=[c_int,POINTER(c_int),c_int]
    28         1         31.0     31.0      0.0      dlllib.find_num.restype = c_int
    29         1       1576.0   1576.0      0.3      dlllib.find_num(5,arr_d,len(arr_d))
    30         1     233653.0 233653.0     42.6      p_find(a)


Process finished with exit code 0

数组的大小是100001个,要选择的元素在最后一个。纯查找时间python为233653个时间单位,而c为1576。当然传递数组需要很长的时间。这是额外的花销。需要综合考虑是否采用动态库,因为参数准备与传递需要很大的花销。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐