使用Python的ctypes库来调用C/C++的动态链接库
ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
一、加载
加载库的方式
from ctypes import * #用到类CDLL
mydll = CDLL("/xx/xx/libxxxx.dll") # windows平台
或者mydll = cdll.LoadLibrary("/xx/xx/libxxxx.dll")
mydll = CDLL("/xx/xx/libxxxx.so") # linux平台
#调用其函数func
result = mydll.func(arg1)
from ctypes import * #用到类CDLL
mydll = CDLL("/xx/xx/libxxxx.dll") # windows平台
或者mydll = cdll.LoadLibrary("/xx/xx/libxxxx.dll")
mydll = CDLL("/xx/xx/libxxxx.so") # linux平台
#调用其函数func
result = mydll.func(arg1)
根据当前平台分别加载Windows和Linux上的C的标准动态库msvcrt.dll和libc.so.6。
注意这里我们使用的ctypes.cdll来load动态库,实际上ctypes中总共有以下四种方式加载动态库:
-
class
ctypes.
CDLL
(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)此类的实例即已加载的动态链接库。库中的函数使用标准 C 调用约定,并假定返回
int
.在 Windows 上创建 CDLL 实例可能会失败,即使 DLL 名称确实存在 -
class
ctypes.
OleDLL
(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)仅 Windows : 此类的实例即加载好的动态链接库,其中的函数使用
stdcall
调用约定,并且假定返回 windows 指定的 HRESULT 返回码。 HRESULT 的值包含的信息说明函数调用成功还是失败,以及额外错误码。 如果返回值表示失败,会自动抛出 OSError 异常。 -
class
ctypes.
WinDLL
(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)仅 Windows: 此类的实例即加载好的动态链接库,其中的函数使用
stdcall
调用约定,并假定默认返回int
-
class
ctypes.
PyDLL
(name, mode=DEFAULT_MODE, handle=None)这个类实例的行为与 CDLL 类似,只不过 不会 在调用函数的时候释放 GIL 锁,且调用结束后会检查 Python 错误码。 如果错误码被设置,会抛出一个 Python 异常。所以,它只在直接调用 Python C 接口函数的时候有用
通过使用至少一个参数(共享库的路径名)调用它们,可以实例化所有这些类。也可以传入一个已加载的动态链接库作为handler
参数,其他情况会调用系统底层的dlopen
或LoadLibrary
函数将库加载到进程,并获取其句柄。如cdll.LoadLibrary()、oledll.LoadLibrary()、windll.LoadLibrary()、pydll.LoadLibrary()
WinDll虽然是可以应用于windows平台上,但是其只能加载标准函数调用约定为__stdcall的动态库;
msvcrt.dll中函数调用约定是C/C++默认的调用约定__cdecl,就不能用WinDll,得用CDLL
其中OleDLL对数据类型比较严格要求, 比如C代码中,如果让int跟float相加,返回不能是float,只能是int,而且结果还是错的.
方法/属性访问
这些类的实例没有共用方法。动态链接库的导出函数可以通过属性或者索引的方式访问。注意,通过属性的方式访问会缓存这个函数,因而每次访问它时返回的都是同一个对象。另一方面,通过索引访问,每次都会返回一个新的对象:
>>> from ctypes import CDLL
>>> libc = CDLL("libc.so.6") # On Linux
>>> libc.time == libc.time
True
>>> libc['time'] == libc['time']
False
寻找动态库
ctypes.util.find_library
(name)
尝试寻找一个库然后返回其路径名, name 是库名称, 且去除了 lib 等前缀和 .so
、 .dylib
、版本号等后缀(这是 posix 连接器 -l
选项使用的格式)。如果没有找到对应的库,则返回 None
。
在 Linux 上, find_library()
会尝试运行外部程序(/sbin/ldconfig
, gcc
, objdump
以及 ld
) 来寻找库文件。返回库文件的文件名。
在 3.6 版更改: 在Linux 上,如果其他方式找不到的话,会使用环境变量 LD_LIBRARY_PATH
搜索动态链接库。
在 Windows 上, find_library()
在系统路径中搜索,然后返回全路径,但是如果没有预定义的命名方案, find_library("c")
调用会返回 None
使用 ctypes 包装动态链接库,更好的方式 可能 是在开发的时候就确定名称,然后硬编码到包装模块中去,而不是在运行时使用 find_library()
寻找库。
把动态库设置环境变量
windows:只需要把关联动态库复制到加载的动态库同级目录下;
linux:需要添加环境变量
sudo echo /home/seetaFace6Python/seetaface/lib/centos > /etc/ld.so.conf.d/seetaface6.conf
sudo ldconfig
可能遇到ldconfig: /lib64/libstdc++.so.6 不是符号连接
解决办法
[root@localhost lib64]# ln -sf /usr/lib64/libstdc++.so.6.0.19 /usr/lib64/libstdc++.so.6
[root@localhost lib64]# sudo ldconfig
容器设置方法:
直接在Dockefile中 利用COPY把动态库文件复制进镜像中,并且设置动态库环境变量.但是这种通用性不好
-
在Dockerfile中,新建lib文件夹,可以新建多个如/home/lib/lib1、/home/lib/lib2.并且设置环境变量,然后再docker run中利用-v进行挂载即可
二、数据类型
ctypes 类型 | C 类型 | Python 数据类型 |
---|---|---|
c_bool | _Bool |
bool (1) |
c_char | char |
单字符字节串对象 |
c_wchar | wchar_t |
单字符字符串 |
c_byte | char |
int |
c_ubyte | unsigned char |
int |
POINTER(c_ubyte) | uchar* | int |
c_short | short |
int |
c_ushort | unsigned short |
int |
c_int | int |
int |
c_uint | unsigned int |
int |
c_long | long |
int |
c_ulong | unsigned long |
int |
c_longlong |
__int64 或 long long
|
int |
c_ulonglong |
unsigned __int64 或 unsigned long long
|
int |
c_size_t | size_t |
int |
c_ssize_t |
ssize_t 或 Py_ssize_t
|
int |
c_float | float |
float |
c_double | double |
float |
c_longdouble | long double |
float |
c_char_p |
char * (NUL terminated) |
字节串对象或 None
|
c_wchar_p |
wchar_t * (NUL terminated) |
字符串或 None
|
c_void_p | void * |
int 或 None
|
该表格列举了ctypes、c和python之间基本数据的对应关系,在定义函数的参数和返回值时,需记住几点:
必须使用ctypes的数据类型
参数类型用关键字argtypes定义,argtypes必须是一个序列,如tuple或list,否则会报错
返回类型用restype定义,使用 None
表示 void
,即不返回任何结果的函数
若没有显式定义参数类型和返回类型,python默认为int型
cast() 函数可以将一个指针实例强制转换为另一种 ctypes 类型。 cast() 接收两个参数,一个 ctypes 指针对象或者可以被转换为指针的其他类型对象,和一个 ctypes 指针类型。 返回第二个类型的一个实例,该返回实例和第一个参数指向同一片内存空间:
>>> a = (c_byte * 4)()
>>> cast(a, POINTER(c_int))
<ctypes.LP_c_long object at ...>
所以 cast() 可以用来给结构体 Bar
的 values
字段赋值:
>>> bar = Bar()
>>> bar.values = cast((c_byte * 4)(), POINTER(c_int))
>>> print(bar.values[0])
0
cast应用:获取numpy数组指针
a = np.asarray(range(16), dtype=np.int32).reshape([4,4])
if not a.flags['C_CONTIGUOUS']:
a = np.ascontiguous(a, dtype=a.dtype) # 如果不是C连续的内存,必须强制转换
a_ctypes_ptr = cast(a.ctypes.data, POINTER(c_int)) #转换为ctypes,这里转换后的可以直接利用ctypes转换为c语言中的int*,然后在c中使用
for i in range(16):
print(a_ctypes_ptr[i])
三、C代码编译
如下c代码,deme.c
/******C端代码*********/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int add(int a,float b){
printf("a=%d\n", a);
printf("b=%f\n", b);
return a+b;
}
int hello()
{
printf("Hello world\n");
return 0;
}
编译
gcc -fPIC -shared -o libdeme.so deme.c
四、C++代码编译
由于ctypes是与C兼容的数据类型,也就是针对C进行编译后进行调用,所以直接对C++代码编译,在python调用时,会提示找不到函数
Traceback (most recent call last):
File "d:\AI\C++_study\Test\demo.py", line 7, in <module>
dll.hello()
File "D:\Anaconda3\envs\py36\lib\ctypes\__init__.py", line 361, in __getattr__
func = self.__getitem__(name)
File "D:\Anaconda3\envs\py36\lib\ctypes\__init__.py", line 366, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: function 'hello' not found
特别注意在调用C++函数需要在函数声明时,加入前缀“ extern "C" ”,这是由于C++支持函数重载功能,在编译时会更改函数名。在函数声明时,前缀extern "C"则确保按C的方式编译。
c++需要demo.cpp,如下
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define DLLEXPORT extern "C" __declspec(dllexport)
DLLEXPORT float __stdcall add(int a,float b){
printf("a=%d\n", a);
printf("b=%f\n", b);
return a+b;
}
DLLEXPORT int __stdcall hello()
{
printf("Hello world\n");
return 0;
}
__declspec(dllexport)可以省略,其他都不可以
或者如下简单书写:
extern "C" float add(int a,float b){
printf("a=%d\n", a);
printf("b=%f\n", b);
return a+b;
}
四、python代码
demo.py
# -*- coding: utf-8 -*-
from ctypes import *
# dll =CDLL("./libdemo.so")
# dll = cdll.LoadLibrary("./libdemo.so")
# dll = windll.LoadLibrary("./libdemo.so")
dll = PyDLL("./libdemo.so")
dll.hello()
dll.add.argtypes=[c_int,c_float]
dll.add.restype=c_float
a=c_int(10)
b=c_float(20.5)
res= dll.add(a,b)
print("res=",res)
结果如下
Hello world
a=10
b=20.500000
res= 30.5
五、指针的使用
5.1 创建指针
函数 | 说明 |
---|---|
byref(x [, offset]) | 返回 x 的地址,x 必须为 ctypes 类型的一个实例。相当于 c 的 &x 。 offset 表示偏移量。 |
pointer(x) | 创建并返回一个指向 x 的指针实例, x 是一个实例对象。 |
POINTER(type) | 返回一个类型,这个类型是指向 type 类型的指针类型, type 是 ctypes 的一个类型。 |
byref 很好理解,传递参数的时候就用这个,用 pointer 创建一个指针变量也行,不过 byref 更快。
而 pointer 和 POINTER 的区别是,pointer 返回一个实例,POINTER 返回一个类型。甚至你可以用 POINTER 来做 pointer 的工作:
>>> a = c_int(66) # 创建一个 c_int 实例
>>> b = pointer(a) # 创建指针
>>> c = POINTER(c_int)(a) # 创建指针
>>> b
<__main__.LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents # 输出 a 的值
c_long(66)
>>> c.contents # 输出 a 的值
c_long(66)
可以将 ctypes 类型数据传入 pointer() 函数创建指针:
>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
指针实例拥有 contents 属性,它返回指针指向的真实对象,如上面的 i
对象:
>>> pi.contents
c_long(42)
注意 ctypes 并没有 OOR (返回原始对象), 每次访问这个属性时都会构造返回一个新的相同对象:
>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
将这个指针的 contents 属性赋值为另一个 c_int 实例将会导致该指针指向该实例的内存地址
指针对象也可以通过整数下标进行访问和赋值,赋值会把原来的值内容覆盖
>>> print(i)
c_long(99)
>>> pi[0] = 22
>>> print(i)
c_long(22)
无参调用指针类型可以创建一个 NULL
指针。 NULL
指针的布尔值是 False
>>> null_ptr = POINTER(c_int)()
>>> print(bool(null_ptr))
False
有时候 C 函数接口可能由于要往某个地址写入值,或者数据太大不适合作为值传递,从而希望接收一个 指针 作为数据参数类型。
使用bytef()来引用传递参数
5.2 指针传递值:
用byref()
C/C++:
DLLEXPORT void __stdcall add_point(float* a, float* b, float* c)
{
*c = *a + *b;
*a = 129.7;
}
python
x1 = ctypes.c_float(1.9)
x2 = ctypes.c_float(10.1)
x3 = ctypes.c_float(0)
dll.add_point(byref(x1),byref(x2),byref(x3))
print("x1=",x1)
print("x2=",x2)
print("x3=",x3)
结果为:
x1= 129.6999969482422
x2= 10.100000381469727
x3= 12.0
值随着指针进行改变,另外在小数位上会进行值变动.小数位保留7位的话,基本一致
5.3 接收指针数据:
利用POINT()来接收指针数据,在接收类型中声明
C/C++
DLLEXPORT int* __stdcall point(int* x)
{
int* y=NULL;
y = x;
return y;
}
PYTHON:
x = ctypes.c_int(2560)
Cfun.point.restype = ctypes.POINTER(ctypes.c_int) ##声明函数返回值为int*
y = Cfun.point(ctypes.byref(x))
print("y is %d" % y[0])
不可接收返回数组,因为返回的为内存地址
5.4 数组的使用
在C中创建array函数:
DLLEXPORT void __stdcall get_array(int x[])
{
printf("x[0]= %d x[1]=%d x[2]=%d x[3]=%d \n", *x,x[1],x[2],x[3]);
*x = 100;
}
python:
Array = ctypes.c_int * 4; ##声明一维数组,数组长度为4
a = Array(0, 1, 2, 3) ##初始化数组
dll.get_array(a)
print(a[0], a[1], a[2], a[3]) # 数组没办法打印整体
# 这一把数组转为列表再打印
a_list=[]
for i in range(4):
a_list.append(a[i])
print(a_list)
结果:
x[0]= 0 x[1]=1 x[2]=2 x[3]=3
100 1 2 3
还可以初始化一个空,再赋值
Array = c_int * 4
func_list = Array()
for i in range(4):
func_list[i] = i
还可以初始化一个列表再转为数组
pyarr=[1,2,3,4]
arr = (ctypes.c_int * len(pyarr))(*pyarr)
在C中修改数组的值,在python中确实被修改。
声明二维数组的方法:
Array = (ctypes.c_int * 4)*5 ##声明二维数组
a=Array()
###使用循环对其进行赋值
for i in range(5):
for j in range(4):
a[i][j]=i*j
三维:
list3d = [
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0]],
[[0.2, 1.2, 2.2, 3.2], [4.2, 5.2, 6.2, 7.2]],
[[0.4, 1.4, 2.4, 3.4], [4.4, 5.4, 6.4, 7.4]],
]
arr = (c_double * 4 * 2 * 3)(*(tuple(tuple(j) for j in i) for i in list3d))
检查它是否以行优先顺序正确初始化:
>>> (c_double * 24).from_buffer(arr)[:]
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0,
0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2,
0.4, 1.4, 2.4, 3.4, 4.4, 5.4, 6.4, 7.4]
不可接收返回数组,因为范围的为内存地址
数组长度问题,python数组传到c++后,使用sizeof(arr) / sizeof(arr[0]) 的结果是错的,所以需要把数组的长度当做参数传入
5.5 字符串传递
字符串
C/C++接收的类型为char* ,即byte类型,需要对字符串进行编码,,利用b"内容",或者内容.encode()后进行传输,或者用bytes("nihao", 'utf-8')返回值需要decode,
C++:
DLLEXPORT char* __stdcall get_str(char * path)
{
cout<<"path:"<<path<<endl;
char* ret;
ret = (char *)malloc(10);
strcpy(ret, "你好hello123,./");
return ret;
}
python:
dll.get_str.argtypes=[c_char_p]
tex= "你好呀dsf123,./" #或者b"你好呀dsf123,./"
texd=tex.encode() #或者bytes("nihao", 'utf-8')
text = c_char_p(texd)
dll.get_str.restype=c_char_p
rt_str=dll.get_str(text)
print(rt_str.decode())
字符串传输给C/C++,如果有中文会乱码,但返回有中文不会有乱码
不用了要free释放,否则会造成内存泄漏
字符串列表
c/c++
//构建字符串数组,2个元素
struct struct_str_arr
{
char* str_ptr[2];
};
struct_str_arr str_arr;
struct_str_arr* str_arr_ptr = (struct_str_arr*)malloc(sizeof(str_arr));
DLLEXPORT struct_str_arr* __stdcall get_str_list(int n, char *b[2])
{
for(int i=0;i<n;i++)
{
printf("%s", *(b+i));
printf("\n");
}
str_arr_ptr->str_ptr[0]="你好";
str_arr_ptr->str_ptr[1]="hell";
return str_arr_ptr;
}
python:
# 返回的数据
class StructPointer(ctypes.Structure):
_fields_ = [("str_ptr", ctypes.c_char_p * 2)]
dll.test_str_arr.restype = ctypes.POINTER(StructPointer)
str1 = c_char_p(bytes("nihao", 'utf-8'))
str2 = c_char_p(bytes("shijie", 'utf-8'))
a = (c_char_p*2)(str1, str2)
ret_ptr =lib.get_str_list(2, a)
#ret_ptr =lib.get_str_list(2, pointer(a))
ret_ptr.contents.str_ptr[1].decode()
5.6 结构体传递
5.6.1 cvMat传递
python中opencv存储一幅图像的数据类型是array,而在C++中opencv存储一幅图像的数据类型是Mat,这两者之间的转换需要通过unsigned char * 来完成。
unsigned char*等价于uchar*
数据类型对应关系
python: ctypes.POINTER(ctypes.c_ubyte) 或者ctypes.c_char_p
C++: unsigned char *
python中将array转换成ctypes.POINTER(ctypes.c_ubyte)
import ctypes as C
import cv2
img = cv2.imread('ROI0.png')
#将img转换成可被传入dll的数据类型
input = img.ctypes.data_as(C.POINTER(C.c_ubyte))
C++ 中将uchar 转为cvMat*
Mat src = Mat(rows,cols,CV_8UC3,src_data);
//或者分为两步,利用Mat.data
// Mat src = Mat(Size(cols, rows), CV_8UC3, Scalar(255, 255, 255)); //建立空图
// src.data = src_data;
C++中将uchar 复制的方法*
ret_data在入参中作为指针传递:
memcp(ret_data,src.data,rows*cols*3);
ret_data作为返回结果传递:
vector<uchar> data_encode;
imencode(".png", dst, data_encode); //把图片dst信息保存到缓存data_encode中
std::string str_encode(data_encode.begin(), data_encode.end());
uchar* char_r = new uchar[str_encode.size() + 10];
memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
return char_r;
python中将uchar转为array*
#a为uchar*的数据
b =string_at(a,cols*rows*channels) # 类似于base64
nparr = np.frombuffer(b, np.uint8)
img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR)
完整代码
C++:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
//作为返回值返回
extern "C" uchar* mattostring(uchar* src_data,int rows,int cols){
Mat dst = Mat(Size(cols, rows), CV_8UC3, Scalar(255, 255, 255)); //建立空图
dst.data = mat_data;
circle(dst, Point(60, 60), 10, Scalar(255, 0, 0)); //画图
vector<uchar> data_encode;
imencode(".png", dst, data_encode);
std::string str_encode(data_encode.begin(), data_encode.end());
uchar* char_r = new uchar[str_encode.size() + 10];
memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
return char_r;
}
//作为入参指针传递
extern "C" void draw_circle(int rows, int cols, unsigned char *src_data , unsigned char *ret_data)
{
Mat src = Mat(rows, cols, CV_8UC3, src_data); //uchar* 转cvMat
circle(src, Point(60, 60), 10, Scalar(255, 0, 0)); //画图
//将Mat转换成unsigned char
memcpy(ret_data, src.data, rows*cols*3);
}
python:
from ctypes import *
import cv2
import numpy as np
from PIL import Image
MYDLL= CDLL("./build/libhello.dll")
MYDLL.hello()
image=cv2.imread("./images/ch1.jpg")
rows = image.shape[0]
cols = image.shape[1]
channels =3
MYDLL.mattostring.argtypes = (POINTER(c_ubyte), c_int,c_int) #c_char_p也可以
MYDLL.mattostring.restype = c_void_p # POINTER(c_ubyte) 跟c_void_p都可以
MYDLL.draw_circle.argtypes=[c_int,c_int,POINTER(c_ubyte),POINTER(c_ubyte)]
MYDLL.draw_circle.restype=c_void_p
ret_img = np.zeros(dtype=np.uint8, shape=(rows, cols, 3))
srcPointer=image.ctypes.data_as(POINTER(c_ubyte)) #方式1.1
#srcPointer=image.ctypes.data_as(c_char_p) #方式1.2
# srcPointer = image.astype(np.uint8).tostring() #方式2
a=MYDLL.mattostring(srcPointer,rows,cols)
b =string_at(a,cols*rows*channels) # 类似于base64
nparr = np.frombuffer(b, np.uint8) # 转array,但是维度不是图片
img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR) #转cvMat
img_decode=Image.fromarray(img_decode[:,:,::-1]) # 由于直接cv2.imshow()显示出来的图是错误的,保存或者转为Image格式,显示正确
img_decode.show()
retPoint = ret_img.ctypes.data_as(POINTER(c_ubyte))
MYDLL.draw_circle(rows, cols, srcPointer, retPoint)
ret_img_out = Image.fromarray(ret_img[:,:,::-1]) # 参数指针传递,不需要从uchar*转换,只需要取他的源头数据即可.
ret_img_out.show()
第二种借助numpy来进行转换,两者的区别就是,第一种传的是指针,如果参数进去,在mattostring函数内对变量srcPointer进行修改则会影响最终输出的内容,第二种方式不会有影响。方式2 的argtypes注释掉如上面的代码,在最后加上
cv2.imshow("image",image) #方式1有画圈
cv2.waitKey(0) # 方式2还是原图,
这里用到了opencv,所以编译还需要把opencv的头文件跟库文件加入
CMakeLists.txt:
cmake_minimum_required (VERSION 2.6)
project(hello)
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(OpenCV_DIR D:/opencv/opencv-4.5.2) # 该地址为OpenCVConfig.cmake所在的目录地址
find_package(OpenCV REQUIRED)
set(SRC_LIST hello.cpp)
# 添加头文件
include_directories( ${OpenCV_INCLUDE_DIRS} ) # 可省略,在find_package中已经实现
message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
add_library( hello SHARED hello.cpp)
# 链接OpenCV库
target_link_libraries( hello ${OpenCV_LIBS} )
上述方法把图片的尺寸写死了,但一般图片都是动态的,所以需要把尺寸信息也要传递回来.这时采用dtypes的结构体方式
C++结构体及使用代码:
struct CvMatImage{
//cv图片结构体
int rows;
int cols;
int channels;
uchar *data;
};
extern "C" CvMatImage mattostring(uchar* src_data,int rows,int cols){
Mat dst = Mat(rows, cols, CV_8UC3, src_data);
circle(dst, Point(60, 60), 10, Scalar(255, 0, 0)); //画图
vector<uchar> data_encode;
imencode(".png", dst, data_encode);
std::string str_encode(data_encode.begin(), data_encode.end());
uchar* char_r = new uchar[str_encode.size() + 10];
memcpy(char_r, str_encode.data(), sizeof(char) * (str_encode.size()));
CvMatImage cvimage{310,310,3,char_r};
return cvimage;
}
python结构体及使用代码:(名称、属性名要一致,类型要对应上)
from ctypes import *
import cv2
import numpy as np
from PIL import Image
from typing import List
class CvMatImage(Structure):
# cvMatImage的结构体
rows:int
cols:int
channels:int
data:(List[c_ubyte])
_fields_ = [("rows",c_int32),("cols",c_int32),("channels",c_int32),("data",POINTER(c_ubyte))]
def __str__(self):
return "CvImageData(rows={},cols={},channels={},data:{})".format(self.rows,self.cols,self.channels,List[c_ubyte])
def get_numpy_by_cvImage(cvimage):
"""
结构体转为numpy图片
param cvimage:cvimage的结构体,包含rows,cols,channels,data
return :numpy图片
"""
data = cvimage.data
cv_rows = cvimage.rows
cv_cols =cvimage.cols
cv_channels = cvimage.channels
b =string_at(data,cv_cols*cv_rows*cv_channels) # 类似于base64
nparr = np.frombuffer(b, np.uint8)
img_decode= cv2.imdecode(nparr,cv2.IMREAD_COLOR)
return img_decode
MYDLL = CDLL("./build/libhello.dll")
image = cv2.imread("./images/ch1.jpg")
(rows,cols,channels) = image.shape
MYDLL.mattostring.argtypes = (POINTER(c_ubyte), c_int, c_int)
MYDLL.mattostring.restype = CvMatImage # todo 这里设置非常重要
srcPointer = image.ctypes.data_as(POINTER(c_ubyte))
cvimage = MYDLL.mattostring(srcPointer, rows, cols)
img_decode = get_numpy_by_cvImage(cvimage)
img_decode = Image.fromarray(img_decode[:, :, ::-1])
img_decode.show()
5.6.2 seetaImageData传递
seetaImageData为seetaface的图片数据格式,本身有头文件#include <seeta/Common/Struct.h>
struct SeetaImageData
{
int width; // 图像宽度
int height; // 图像高度
int channels; // 图像通道
unsigned char *data; // 图像数据
};
故在python中定义一样的结构体
class SeetaImageData(Structure):
width: int
height: int
channels: int
data:List[c_ubyte]
_fields_=[('width',c_int32),('height',c_int32),('channels',c_int32),("data",POINTER(c_ubyte))]
def __str__(self):
return "SeetaImageData(width={},height={},channels={},data:{})".format(self.width,self.height,self.channels,List[c_ubyte])
C++代码(函数部分):
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <seeta/Common/Struct.h>
#include <seeta/FaceDetector.h>
using namespace std;
using namespace cv;
seeta::FaceDetector* new_fd() {
seeta::ModelSetting setting;
setting.device = SEETA_DEVICE_GPU; //GPU CPU AUTO
setting.id = 0;
setting.append("./models/face_detector.csta");
//按原始cpp文件所在的路径为参考,而不是动态库所在路径.但如果是执行文件,那么按执行文件的位置
return new seeta::FaceDetector(setting);
}
extern "C" SeetaFaceInfoArray Detect(SeetaImageData simage){
seeta::FaceDetector *faceDetector = new_fd();
SeetaFaceInfoArray faces = faceDetector->detect(simage);
return faces;
}
注意: C++代码所涉及的文件的相对地址:
- 生成动态库时,,参考为原始cpp文件,
- 生成执行文件,参考编译后的执行文件
python代码(调用部分):
def get_seetaImageData_by_numpy(image_np: np.array) -> SeetaImageData:
"""
param image_np:numpy数组
return :seetaImageData结构体
"""
seetaImageData = SeetaImageData()
height, width, channels = image_np.shape
seetaImageData.height = int(height)
seetaImageData.width = int(width)
seetaImageData.channels = int(channels)
seetaImageData.data = image_np.ctypes.data_as(POINTER(c_ubyte))
return seetaImageData
MYDLL = CDLL("./lib/centos/libFaceAPI.so")
image = cv2.imread("./images/ch1.jpg")
(rows,cols,channels) = image.shape
simage = get_seetaImageData_by_numpy(image)
MYDLL.Detect.argtypes = (SeetaImageData,)
MYDLL.Detect.restype = SeetaFaceInfoArray
detect_result = MYDLL.Detect(simage)
#打印结果为SeetaFaceInfoArray(data:[SeetaFaceInfo(pos=SeetaRect(x=101,y=50,width=96,height=126),score=0.9995892643928528)],size:1)
rect_list = detect_result.data
注:关于入参说明
- MYDLL.Detect.argtypes = (SeetaImageData,) 传的为数据,则在C++中,入参为SeetaImageData
- MYDLL.Detect.argtypes = (POINT(SeetaImageData),) 传的为地址,则C++中,入参为SeetaImageData&
5.7 结构体数组传递
SeetaPointF *5的数组传递
SeetaPointF 的结构体为:
# python
class SeetaPointF(Structure):
x: int
y: int
_fields_=[('x',c_double),('y',c_double)]
// c++
struct SeetaPointF
{
double x;
double y;
};
作为参数指针传入:
C++:
extern "C" int mark5(SeetaImageData &simage, SeetaRect &box, SeetaPointF points5[5])
{
std::vector<SeetaPointF> points = landDetector5->mark(simage, box);
int size = points.size();
for (int i = 0; i < size; i++)
points5[i] = points[i]; //由于points为vecter,需要转化为数组.
return 1;
}
python:
MYDLL.mark5.argtypes = (POINTER(SeetaImageData),POINTER(SeetaRect),POINTER(SeetaPointF))
# 或者
# MYDLL.mark5.argtypes = (POINTER(SeetaImageData),POINTER(SeetaRect),SeetaPointF*5)
detect_result = MYDLL.Detect(simage)
rect_list = detect_result.data
if detect_result.size > 0:
_face = detect_result.data[0].pos
points = (SeetaPointF * 5)() #初始化一个长度为5的空数组
MYDLL.mark5(simage, _face,points) #作为参数以地址传递,那么在c代码中points改变了,python中的point也会改变
关于结构体数组传入后,队员原始函数的入参为结构体* 的使用方法.
说明:入参结构体* 即传入结构体数组的起始也就是第一个数据的地址.
方法一: 把数组转为vector,那么入参就可以改为 .data()
extern "C" int Predict(SeetaImageData &simage, const SeetaRect &box, SeetaPointF points5[5])
{
std::vector<SeetaPointF> points;
for (int i = 0; i < 5; i++)
{
points.push_back(points5[i]);
}
auto status = liveDetector->Predict(simage, box, points.data());
}
方法二: 直接提取结构体数组的第一个数据的地址作为入参即可
extern "C" int Predict(SeetaImageData &simage, const SeetaRect &box, SeetaPointF points5[5])
{
auto status = liveDetector->Predict(simage, box, &points5[0]); //&poinst5[0]为提取第一个元素的地址
}
返回结构体数组
<font color='red'>放弃吧,因为C代码返回的为数组地址.C可以调用的时候取值,但python没办法去通过内存取值</font>
六、指导说明
工具函数
-
ctypes.addressof
(obj)
以整数形式返回内存缓冲区地址。 obj 必须为一个 ctypes 类型的实例。引发一个 审计事件 ctypes.addressof
,附带参数 obj
。
-
ctypes.alignment
(obj_or_type)
返回一个 ctypes 类型的对齐要求。 obj_or_type 必须为一个 ctypes 类型或实例。
-
ctypes.byref
(obj[, offset])
返回指向 obj 的轻量指针,该对象必须为一个 ctypes 类型的实例。 offset 默认值为零,且必须为一个将被添加到内部指针值的整数。byref(obj, offset)
对应于这段 C 代码:(((char *)&obj) + offset)
返回的对象只能被用作外部函数调用形参。 它的行为类似于 pointer(obj)
,但构造起来要快很多。
-
ctypes.cast
(obj, type)
此函数类似于 C 的强制转换运算符。 它返回一个 type 的新实例,该实例指向与 obj 相同的内存块。 type 必须为指针类型,而 obj 必须为可以被作为指针来解读的对象。
-
ctypes.create_string_buffer
(init_or_size, size=None)
此函数会创建一个可变的字符缓冲区。 返回的对象是一个 c_char 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字节串对象。如果将一个字节串对象指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字节串长度的情况下指定数组大小。引发一个 审计事件 ctypes.create_string_buffer
,附带参数 init
, size
。
-
ctypes.create_unicode_buffer
(init_or_size, size=None)
此函数会创建一个可变的 unicode 字符缓冲区。 返回的对象是一个 c_wchar 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字符串。如果将一个字符串指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字符串长度的情况下指定数组大小。引发一个 审计事件 ctypes.create_unicode_buffer
,附带参数 init
, size
。
-
ctypes.DllCanUnloadNow
()
仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes 扩展 dll 所导出的 DllCanUnloadNow 函数来调用。
-
ctypes.DllGetClassObject
()
仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes
扩展 dll 所导出的 DllGetClassObject 函数来调用。
-
ctypes.util.find_library
(name)
尝试寻找一个库并返回路径名称。 name 是库名称并且不带任何前缀如 lib
以及后缀如 .so
,.dylib
或版本号(形式与 posix 链接器选项 -l
所用的一致)。 如果找不到库,则返回 None
。确切的功能取决于系统。
-
ctypes.util.find_msvcrt
()
仅限 Windows:返回 Python 以及扩展模块所使用的 VC 运行时库的文件名。 如果无法确定库名称,则返回 None
。如果你需要通过调用 free(void *)
来释放内存,例如某个扩展模块所分配的内存,重要的一点是你应当使用分配内存的库中的函数。
-
ctypes.FormatError
([code])
仅限 Windows:返回错误码 code 的文本描述。 如果未指定错误码,则会通过调用 Windows api 函数 GetLastError 来获得最新的错误码。
-
ctypes.GetLastError
()
仅限 Windows:返回 Windows 在调用线程中设置的最新错误码。 此函数会直接调用 Windows GetLastError() 函数,它并不返回错误码的 ctypes 私有副本。
-
ctypes.get_errno
()
返回调用线程中系统 errno 变量的 ctypes 私有副本的当前值。引发一个 审计事件 ctypes.get_errno
,不附带任何参数。
-
ctypes.get_last_error
()
仅限 Windows:返回调用线程中系统 LastError
变量的 ctypes 私有副本的当前值。引发一个 审计事件 ctypes.get_last_error
,不附带任何参数。
-
ctypes.memmove
(dst, src, count)
与标准 C memmove 库函数相同:将 count 个字节从 src 拷贝到 dst。 dst 和 src 必须为整数或可被转换为指针的 ctypes 实例。
-
ctypes.memset
(dst, c, count)
与标准 C memset 库函数相同:将位于地址 dst 的内存块用 count 个字节的 c 值填充。 dst 必须为指定地址的整数或 ctypes 实例。
-
ctypes.POINTER
(type)
这个工厂函数创建并返回一个新的 ctypes 指针类型。 指针类型会被缓存并在内部重用,因此重复调用此函数耗费不大。 type 必须为 ctypes 类型。
-
ctypes.pointer
(obj)
此函数会创建一个新的指向 obj 的指针实例。 返回的对象类型为 POINTER(type(obj))
。注意:如果你只是想向外部函数调用传递一个对象指针,你应当使用更为快速的 byref(obj)
。
-
ctypes.``resize
(obj, size)
此函数可改变 obj 的内部内存缓冲区大小,其参数必须为 ctypes 类型的实例。 没有可能将缓冲区设为小于对象类型的本机大小值,该值由 sizeof(type(obj))
给出,但将缓冲区加大则是可能的。
-
ctypes.``set_errno
(value)
设置调用线程中系统 errno 变量的 ctypes 私有副本的当前值为 value 并返回原来的值。引发一个 审计事件 ctypes.set_errno
附带参数 errno
。
-
ctypes.set_last_error
(value)
仅限 Windows:设置调用线程中系统 LastError
变量的 ctypes 私有副本的当前值为 value 并返回原来的值。引发一个 审计事件 ctypes.set_last_error
,附带参数 error
。
-
ctypes.sizeof
(obj_or_type)
返回 ctypes 类型或实例的内存缓冲区以字节表示的大小。 其功能与 C sizeof
运算符相同。
-
ctypes.string_at
(address, size=-1)
此函数返回从内存地址 address 开始的以字节串表示的 C 字符串。 如果指定了 size,则将其用作长度,否则将假定字符串以零值结尾。引发一个 审计事件 ctypes.string_at
,附带参数 address
, size
。
-
ctypes.WinError
(code=None, descr=None)
仅限 Windows:此函数可能是 ctypes 中名字起得最差的函数。 它会创建一个 OSError 的实例。 如果未指定 code,则会调用 GetLastError
来确定错误码。 如果未指定 descr,则会调用 FormatError() 来获取错误的文本描述。在 3.3 版更改: 以前是会创建一个 WindowsError 的实例。
-
ctypes.wstring_at
(address, size=-1)
此函数返回从内存地址 address 开始的以字符串表示的宽字节字符串。 如果指定了 size,则将其用作字符串中的字符数量,否则将假定字符串以零值结尾。引发一个 审计事件 ctypes.wstring_at
,附带参数 address
, size
。
ctypes.addressof
(obj)
以整数形式返回内存缓冲区地址。 obj 必须为一个 ctypes 类型的实例。引发一个 审计事件 ctypes.addressof
,附带参数 obj
。
ctypes.alignment
(obj_or_type)
返回一个 ctypes 类型的对齐要求。 obj_or_type 必须为一个 ctypes 类型或实例。
ctypes.byref
(obj[, offset])
返回指向 obj 的轻量指针,该对象必须为一个 ctypes 类型的实例。 offset 默认值为零,且必须为一个将被添加到内部指针值的整数。byref(obj, offset)
对应于这段 C 代码:(((char *)&obj) + offset)
返回的对象只能被用作外部函数调用形参。 它的行为类似于 pointer(obj)
,但构造起来要快很多。
ctypes.cast
(obj, type)
此函数类似于 C 的强制转换运算符。 它返回一个 type 的新实例,该实例指向与 obj 相同的内存块。 type 必须为指针类型,而 obj 必须为可以被作为指针来解读的对象。
ctypes.create_string_buffer
(init_or_size, size=None)
此函数会创建一个可变的字符缓冲区。 返回的对象是一个 c_char 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字节串对象。如果将一个字节串对象指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字节串长度的情况下指定数组大小。引发一个 审计事件 ctypes.create_string_buffer
,附带参数 init
, size
。
ctypes.create_unicode_buffer
(init_or_size, size=None)
此函数会创建一个可变的 unicode 字符缓冲区。 返回的对象是一个 c_wchar 的 ctypes 数组。init_or_size 必须是一个指明数组大小的整数,或者是一个将被用来初始化数组条目的字符串。如果将一个字符串指定为第一个参数,则将使缓冲区大小比其长度多一项以便数组的最后一项为一个 NUL 终结符。 可以传入一个整数作为第二个参数以允许在不使用字符串长度的情况下指定数组大小。引发一个 审计事件 ctypes.create_unicode_buffer
,附带参数 init
, size
。
ctypes.DllCanUnloadNow
()
仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes 扩展 dll 所导出的 DllCanUnloadNow 函数来调用。
ctypes.DllGetClassObject
()
仅限 Windows:此函数是一个允许使用 ctypes 实现进程内 COM 服务的钩子。 它将由 _ctypes
扩展 dll 所导出的 DllGetClassObject 函数来调用。
ctypes.util.find_library
(name)
尝试寻找一个库并返回路径名称。 name 是库名称并且不带任何前缀如 lib
以及后缀如 .so
,.dylib
或版本号(形式与 posix 链接器选项 -l
所用的一致)。 如果找不到库,则返回 None
。确切的功能取决于系统。
ctypes.util.find_msvcrt
()
仅限 Windows:返回 Python 以及扩展模块所使用的 VC 运行时库的文件名。 如果无法确定库名称,则返回 None
。如果你需要通过调用 free(void *)
来释放内存,例如某个扩展模块所分配的内存,重要的一点是你应当使用分配内存的库中的函数。
ctypes.FormatError
([code])
仅限 Windows:返回错误码 code 的文本描述。 如果未指定错误码,则会通过调用 Windows api 函数 GetLastError 来获得最新的错误码。
ctypes.GetLastError
()
仅限 Windows:返回 Windows 在调用线程中设置的最新错误码。 此函数会直接调用 Windows GetLastError() 函数,它并不返回错误码的 ctypes 私有副本。
ctypes.get_errno
()
返回调用线程中系统 errno 变量的 ctypes 私有副本的当前值。引发一个 审计事件 ctypes.get_errno
,不附带任何参数。
ctypes.get_last_error
()
仅限 Windows:返回调用线程中系统 LastError
变量的 ctypes 私有副本的当前值。引发一个 审计事件 ctypes.get_last_error
,不附带任何参数。
ctypes.memmove
(dst, src, count)
与标准 C memmove 库函数相同:将 count 个字节从 src 拷贝到 dst。 dst 和 src 必须为整数或可被转换为指针的 ctypes 实例。
ctypes.memset
(dst, c, count)
与标准 C memset 库函数相同:将位于地址 dst 的内存块用 count 个字节的 c 值填充。 dst 必须为指定地址的整数或 ctypes 实例。
ctypes.POINTER
(type)
这个工厂函数创建并返回一个新的 ctypes 指针类型。 指针类型会被缓存并在内部重用,因此重复调用此函数耗费不大。 type 必须为 ctypes 类型。
ctypes.pointer
(obj)
此函数会创建一个新的指向 obj 的指针实例。 返回的对象类型为 POINTER(type(obj))
。注意:如果你只是想向外部函数调用传递一个对象指针,你应当使用更为快速的 byref(obj)
。
ctypes.``resize
(obj, size)
此函数可改变 obj 的内部内存缓冲区大小,其参数必须为 ctypes 类型的实例。 没有可能将缓冲区设为小于对象类型的本机大小值,该值由 sizeof(type(obj))
给出,但将缓冲区加大则是可能的。
ctypes.``set_errno
(value)
设置调用线程中系统 errno 变量的 ctypes 私有副本的当前值为 value 并返回原来的值。引发一个 审计事件 ctypes.set_errno
附带参数 errno
。
ctypes.set_last_error
(value)
仅限 Windows:设置调用线程中系统 LastError
变量的 ctypes 私有副本的当前值为 value 并返回原来的值。引发一个 审计事件 ctypes.set_last_error
,附带参数 error
。
ctypes.sizeof
(obj_or_type)
返回 ctypes 类型或实例的内存缓冲区以字节表示的大小。 其功能与 C sizeof
运算符相同。
ctypes.string_at
(address, size=-1)
此函数返回从内存地址 address 开始的以字节串表示的 C 字符串。 如果指定了 size,则将其用作长度,否则将假定字符串以零值结尾。引发一个 审计事件 ctypes.string_at
,附带参数 address
, size
。
ctypes.WinError
(code=None, descr=None)
仅限 Windows:此函数可能是 ctypes 中名字起得最差的函数。 它会创建一个 OSError 的实例。 如果未指定 code,则会调用 GetLastError
来确定错误码。 如果未指定 descr,则会调用 FormatError() 来获取错误的文本描述。在 3.3 版更改: 以前是会创建一个 WindowsError 的实例。
ctypes.wstring_at
(address, size=-1)
此函数返回从内存地址 address 开始的以字符串表示的宽字节字符串。 如果指定了 size,则将其用作字符串中的字符数量,否则将假定字符串以零值结尾。引发一个 审计事件 ctypes.wstring_at
,附带参数 address
, size
。
数据类型
-
class
ctypes._CData
这个非公有类是所有 ctypes 数据类型的共同基类。 另外,所有 ctypes 类型的实例都包含一个存放 C 兼容数据的内存块;该内存块的地址可由 addressof() 辅助函数返回。 还有一个实例变量被公开为 _objects;此变量包含其他在内存块包含指针的情况下需要保持存活的 Python 对象。ctypes 数据类型的通用方法,它们都是类方法(严谨地说,它们是 metaclass 的方法):
from_buffer
(source[, offset])此方法返回一个共享 source 对象缓冲区的 ctypes 实例。 source 对象必须支持可写缓冲区接口。 可选的 offset 形参指定以字节表示的源缓冲区内偏移量;默认值为零。 如果源缓冲区不够大则会引发 ValueError。引发一个 审计事件ctypes.cdata/buffer
附带参数pointer
,size
,offset
。from_buffer_copy
(source[, offset])此方法创建一个 ctypes 实例,从 source 对象缓冲区拷贝缓冲区,该对象必须是可读的。 可选的 offset 形参指定以字节表示的源缓冲区内偏移量;默认值为零。 如果源缓冲区不够大则会引发 ValueError。引发一个 审计事件ctypes.cdata/buffer
附带参数pointer
,size
,offset
。from_address
(address)此方法会使用 address 所指定的内存返回一个 ctypes 类型的实例,该参数必须为一个整数。引发一个 审计事件ctypes.cdata
,附带参数address
。from_param
(obj)此方法会将 obj 适配为一个 ctypes 类型。 它调用时会在当该类型存在于外部函数的argtypes
元组时传入外部函数调用所使用的实际对象;它必须返回一个可被用作函数调用参数的对象。所有 ctypes 数据类型都带有这个类方法的默认实现,它通常会返回 obj,如果该对象是此类型的实例的话。 某些类型也能接受其他对象。in_dll
(library, name)此方法返回一个由共享库导出的 ctypes 类型。 name 为导出数据的符号名称,library 为所加载的共享库。ctypes 数据类型的通用实例变量:
_b_base_
有时 ctypes 数据实例并不拥有它们所包含的内存块,它们只是共享了某个基对象的部分内存块。 _b_base_ 只读成员是拥有内存块的根 ctypes 对象。_b_needsfree_
这个只读变量在 ctypes 数据实例自身已分配了内存块时为真值,否则为假值。_objects
这个成员或者为None
,或者为一个包含需要保持存活以使内存块的内存保持有效的 Python 对象的字典。 这个对象只是出于调试目的而对外公开;绝对不要修改此字典的内容。
基础数据类型
-
class
ctypes.``_SimpleCData
这个非公有类是所有基本 ctypes 数据类型的基类。 它在这里被提及是因为它包含基本 ctypes 数据类型共有的属性。 _SimpleCData 是 _CData 的子类,因此继承了其方法和属性。 非指针及不包含指针的 ctypes 数据类型现在将可以被封存。实例拥有一个属性:
value
这个属性包含实例的实际值。 对于整数和指针类型,它是一个整数,对于字符类型,它是一个单字符字符串对象或字符串,对于字符指针类型,它是一个 Python 字节串对象或字符串。当从 ctypes 实例提取value
属性时,通常每次会返回一个新的对象。 ctypes 并 没有 实现原始对象返回,它总是会构造一个新的对象。 所有其他 ctypes 对象实例也同样如此。
基本数据类型当作为外部函数调用结果被返回或者作为结构字段成员或数组项被提取时,会透明地转换为原生 Python 类型。 换句话说,如果某个外部函数具有 c_char_p 的 restype
,你将总是得到一个 Python 字节串对象,而 不是 一个 c_char_p 实例。
基本数据类型的子类并 没有 继续此行为。 因此,如果一个外部函数的 restype
是 c_void_p 的一个子类,你将从函数调用得到一个该子类的实例。 当然,你可以通过访问 value
属性来获取指针的值。
这些是基本 ctypes 数据类型:
-
class
ctypes.c_byte
代表 C
signed char
数据类型,并将值解读为一个小整数。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_char
代表 C
char
数据类型,并将值解读为单个字符。 该构造器接受一个可选的字符串初始化器,字符串的长度必须恰好为一个字符。 -
class
ctypes.c_char_p
当指向一个以零为结束符的字符串时代表 C
char *
数据类型。 对于通用字符指针来说也可能指向二进制数据,必须要使用POINTER(c_char)
。 该构造器接受一个整数地址,或者一个字节串对象。 -
class
ctypes.c_double
代表 C
double
数据类型。 该构造器接受一个可选的浮点数初始化器。 -
class
ctypes.c_longdouble
代表 C
long double
数据类型。 该构造器接受一个可选的浮点数初始化器。 在sizeof(long double) == sizeof(double)
的平台上它是 c_double 的一个别名。 -
class
ctypes.c_float
代表 C
float
数据类型。 该构造器接受一个可选的浮点数初始化器。 -
class
ctypes.c_int
代表 C
signed int
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 在sizeof(int) == sizeof(long)
的平台上它是 c_long 的一个别名。 -
class
ctypes.c_int8
代表 C 8 位
signed int
数据类型。 通常是 c_byte 的一个别名。 -
class
ctypes.c_int16
代表 C 16 位
signed int
数据类型。 通常是 c_short 的一个别名。 -
class
ctypes.c_int32
代表 C 32 位
signed int
数据类型。 通常是 c_int 的一个别名。 -
class
ctypes.c_int64
代表 C 64 位
signed int
数据类型。 通常是 c_longlong 的一个别名。 -
class
ctypes.c_long
代表 C
signed long
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_longlong
代表 C
signed long long
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_short
代表 C
signed short
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_size_t
代表 C
size_t
数据类型。 -
class
ctypes.c_ssize_t
代表 C
ssize_t
数据类型。3.2 新版功能. -
class
ctypes.c_ubyte
代表 C
unsigned char
数据类型,它将值解读为一个小整数。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_uint
代表 C
unsigned int
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 在sizeof(int) == sizeof(long)
的平台上它是 c_ulong 的一个别名。 -
class
ctypes.c_uint8
代表 C 8 位
unsigned int
数据类型。 通常是 c_ubyte 的一个别名。 -
class
ctypes.c_uint16
代表 C 16 位
unsigned int
数据类型。 通常是 c_ushort 的一个别名。 -
class
ctypes.c_uint32
代表 C 32 位
unsigned int
数据类型。 通常是 c_uint 的一个别名。 -
class
ctypes.c_uint64
代表 C 64 位
unsigned int
数据类型。 通常是 c_ulonglong 的一个别名。 -
class
ctypes.c_ulong
代表 C
unsigned long
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_ulonglong
代表 C
unsigned long long
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_ushort
代表 C
unsigned short
数据类型。 该构造器接受一个可选的整数初始化器;不会执行溢出检查。 -
class
ctypes.c_void_p
代表 C
void *
类型。 该值被表示为整数形式。 该构造器接受一个可选的整数初始化器。 -
class
ctypes.c_wchar
代表 C
wchar_t
数据类型,并将值解读为一单个字符的 unicode 字符串。 该构造器接受一个可选的字符串初始化器,字符串的长度必须恰好为一个字符。 -
class
ctypes.c_wchar_p
代表 C
wchar_t *
数据类型,它必须为指向以零为结束符的宽字符串的指针。 该构造器接受一个整数地址或者一个字符串。 -
class
ctypes.c_bool
代表 C
bool
数据类型 (更准确地说是 C99_Bool
)。 它的值可以为True
或False
,并且该构造器接受任何具有逻辑值的对象。 -
class
ctypes.HRESULT
Windows 专属:代表一个
HRESULT
值,它包含某个函数或方法调用的成功或错误信息。 -
class
ctypes.py_object
代表 C PyObject * 数据类型。 不带参数地调用此构造器将创建一个
NULL
PyObject * 指针。
ctypes.wintypes
模块提供了其他许多 Windows 专属的数据类型,例如 HWND
, WPARAM
或 DWORD
。 还定义了一些有用的结构体例如 MSG
或 RECT
。
结构化数据类型
class ctypes.Structure
(*args, **kw)
_fields_
一个定义结构体字段的序列。 其中的条目必须为 2 元组或 3 元组。 元组的第一项是字段名称,第二项指明字段类型;它可以是任何 ctypes 数据类型。
对于整数类型字段例如 c_int,可以给定第三个可选项。 它必须是一个定义字段比特位宽度的小正整数。
字段名称在一个结构体或联合中必须唯一。 不会检查这个唯一性,但当名称出现重复时将只有一个字段可被访问。
可以在定义 Structure 子类的类语句 之后 再定义 _fields_ 类变量,这将允许创建直接或间接引用其自身的数据类型:
class List(Structure):
pass
List._fields_ = [("pnext", POINTER(List)),
...
]
数组与指针
-
class
ctypes.Array
(*args)数组的抽象基类。
创建实际数组类型的推荐方式是通过将任意 ctypes 类型与一个正整数相乘。 作为替代方式,你也可以子类化这个类型并定义 _length_ 和 _type_ 类变量。 数组元素可使用标准的抽取和切片方式来读写;对于切片读取,结果对象本身 并非 一个 Array。
_length_
一个指明数组中元素数量的正整数。 超出范围的抽取会导致 IndexError。 该值将由 len() 返回。_type_
指明数组中每个元素的类型。Array 子类构造器可接受位置参数,用来按顺序初始化元素。 -
class
ctypes._Pointer
私有对象,指针的抽象基类。
实际的指针类型是通过调用 POINTER() 并附带其将指向的类型来创建的;这会由 pointer() 自动完成。
如果一个指针指向的是数组,则其元素可使用标准的抽取和切片方式来读写。 指针对象没有长度,因此 len() 将引发 TypeError。 抽取负值将会从指针 之前 的内存中读取(与 C 一样),并且超出范围的抽取将可能因非法访问而导致崩溃(视你的运气而定)。
_type_
指明所指向的类型。contents
返回指针所指向的对象。 对此属性赋值会使指针改为指向所赋值的对象。
[参考1]https://docs.python.org/zh-cn/3.9/library/ctypes.html#pointers
[参考2] https://zhuanlan.zhihu.com/p/36772947
推荐阅读
-
SSM三大框架基础面试题-一、Spring篇 什么是Spring框架? Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring的6个特征: 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。 数据访问:事务,DAO支持,JDBC,ORM,编组XML。 Web支持:Spring MVC和Spring WebFlux Web框架。 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言:Kotlin,Groovy,动态语言。 列举一些重要的Spring模块? Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。 Spring Aspects:该模块为与AspectJ的集成提供支持。 Spring AOP:提供面向切面的编程实现。 Spring JDBC:Java数据库连接。 Spring JMS:Java消息服务。 Spring ORM:用于支持Hibernate等ORM工具。 Spring Web:为创建Web应用程序提供支持。 Spring Test:提供了对JUnit和TestNG测试的支持。 谈谈自己对于Spring IOC和AOP的理解 IOC(Inversion Of Controll,控制反转)是一种设计思想: 在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。 Spring中的bean的作用域有哪些? 1.singleton:该bean实例为单例 2.prototype:每次请求都会创建一个新的bean实例(多例)。 3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。 5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 Spring中的单例bean的线程安全问题了解吗? 概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 有两种常见的解决方案(用于回答的点): 1.在bean对象中尽量避免定义可变的成员变量(不太现实)。 2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。 ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777 Spring中Bean的生命周期: 1.Bean容器找到配置文件中Spring Bean的定义。 2.Bean容器利用Java Reflection API创建一个Bean的实例。 3.如果涉及到一些属性值,利用set方法设置一些属性值。 4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。 5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。 6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。 7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。 9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。 10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。 12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。 13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 Spring框架中用到了哪些设计模式? 1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。 2.代理设计模式:Spring AOP功能的实现。 3.单例设计模式:Spring中的bean默认都是单例的。 4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。 5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。 还有很多。。。。。。。 @Component和@Bean的区别是什么 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。 @Configuration public class AppConfig { @Bean public TransferService transferService { return new TransferServiceImpl; } } <beans> <bean id="transferService" class="com.kk.TransferServiceImpl"/> </beans> @Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1; when 2: return new serviceImpl2; when 3: return new serviceImpl3; } } 将一个类声明为Spring的bean的注解有哪些? 声明bean的注解: @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller 在展现层使用,控制器的声明 注入bean的注解: @Autowired:由Spring提供 @Inject:由JSR-330提供 @Resource:由JSR-250提供 *扩:JSR 是 java 规范标准 Spring事务管理的方式有几种? 1.编程式事务:在代码中硬编码(不推荐使用)。 2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。 Spring事务中的隔离级别有哪几种? 在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 Spring事务中有哪几种事务传播行为? 在TransactionDefinition接口中定义了八个表示事务传播行为的常量。 支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 二、SpringMVC篇 什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 Spring MVC的工作原理了解嘛? image.png Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 Spring MVC的主要组件? (1)前端控制器 DispatcherServlet(不需要程序员开发) 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 (2)处理器映射器HandlerMapping(不需要程序员开发) 作用:根据请求的URL来查找Handler (3)处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 (4)处理器Handler(需要程序员开发) (5)视图解析器 ViewResolver(不需要程序员开发) 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) (6)视图View(需要程序员开发jsp) View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 Spring MVC的异常处理 ? 统一异常处理: Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; (3)使用@ExceptionHandler注解实现异常处理; 统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103 SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案) SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET。 怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMVC就自动把request对象传入。 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。 SpringMVC中函数的返回值是什么? 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。 SpringMVC用什么对象从后台向前台传递数据的? 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。 怎么样把ModelMap里面的数据放入Session里面? 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池 三、Mybatis篇 什么是MyBatis? MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 讲下MyBatis的缓存 MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有, 二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口, 可在它的映射文件中配置<cache/> Mybatis是如何进行分页的?分页插件的原理是什么? 1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。 2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。 举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 简述Mybatis的插件运行原理,以及如何编写一个插件? 1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,Mybatis通过动态代理, 为需要拦截的接口生成代理对象以实现接口方法拦截功能, 每当执行这4种接口对象的方法时,就会进入拦截方法, 具体就是InvocationHandler的invoke方法,当然, 只会拦截那些你指定需要拦截的方法。 2)实现Mybatis的Interceptor接口并复写intercept方法, 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可, 记住,别忘了在配置文件中配置你编写的插件。 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 1)Mybatis动态sql可以让我们在Xml映射文件内, 以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。 2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。 3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值, 根据表达式的值动态拼接sql,以此来完成动态sql的功能。 #{}和${}的区别是什么? 1)#{}是预编译处理,${}是字符串替换。 2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入); 3)Mybatis在处理${}时,就是把${}替换成变量的值。 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? Hibernate属于全自动ORM映射工具, 使用Hibernate查询关联对象或者关联集合对象时, 可以根据对象关系模型直接获取,所以它是全自动的。 而Mybatis在查询关联对象或关联集合对象时, 需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。 在Mybatis配置文件中, 可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 2)它的原理是,使用CGLIB创建目标对象的代理对象, 当调用目标方法时,进入拦截器方法, 比如调用a.getB.getName, 拦截器invoke方法发现a.getB是null值, 那么就会单独发送事先保存好的查询关联B对象的sql, 把B查询上来,然后调用a.setB(b), 于是a的对象b属性就有值了, 接着完成a.getB.getName方法的调用。 这就是延迟加载的基本原理。 MyBatis与Hibernate有哪些不同? 1)Mybatis和hibernate不同,它不完全是一个ORM框架, 因为MyBatis需要程序员自己编写Sql语句, 不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句, 并将java对象和sql语句映射生成最终执行的sql, 最后将sql执行的结果再映射生成java对象。 2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql, 可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发, 例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁, 一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性, 如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。 3)Hibernate对象/关系映射能力强,数据库无关性好, 对于关系模型要求高的软件(例如需求固定的定制化软件) 如果用hibernate开发可以节省很多代码,提高效率。 但是Hibernate的缺点是学习门槛高,要精通门槛更高, 而且怎么设计O/R映射,在性能和对象模型之间如何权衡, 以及怎样用好Hibernate需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、 扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 MyBatis的好处是什么? 1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写, 给程序的维护带来了很大便利。 2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象, 大大简化了Java数据库编程的重复工作。 3)因为MyBatis需要程序员自己去编写sql语句, 程序员可以结合数据库自身的特点灵活控制sql语句, 因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系? Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。 在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象, 其每个子元素会被解析为ParameterMapping对象。 <resultMap>标签会被解析为ResultMap对象, 其每个子元素会被解析为ResultMapping对象。 每一个<select>、<insert>、<update>、<delete> 标签均会被解析为MappedStatement对象, 标签内的sql会被解析为BoundSql对象。 什么是MyBatis的接口绑定,有什么好处? 接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置. 接口绑定有几种实现方式,分别是怎么实现的? 接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加 上@Select@Update等注解里面包含Sql语句来绑定, 另外一种就是通过xml里面写SQL来绑定,在这种情况下, 要指定xml映射文件里面的namespace必须为接口的全路径名. 什么情况下用注解绑定,什么情况下用xml绑定? 当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多 MyBatis实现一对一有几种方式?具体怎么操作的? 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的外键id, 去再另外一个表里面查询数据,也是通过association配置, 但另外一个表的查询通过select属性配置。 Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 能,Mybatis不仅可以执行一对一、一对多的关联查询, 还可以执行多对一,多对多的关联查询,多对一查询, 其实就是一对一查询,只需要把selectOne修改为selectList即可; 多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。 关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象, 赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询, 一部分列是A对象的属性值,另外一部分列是关联对象B的属性值, 好处是只发一个sql查询,就可以把主对象和其关联对象查出来。 MyBatis里面的动态Sql是怎么设定的?用什么语法? MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现, 但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有 内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or 开始,那么会自动把这个and或者or取掉。 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? 第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。 第二种是使用sql列的别名功能,将列别名书写为对象属性名, 比如T_NAME AS NAME,对象属性名一般是name,小写, 但是列名不区分大小写,Mybatis会忽略列名大小写,
-
SciTech-Python-编译 Python 的 C/C++ 扩展 setup.py 使用 pybind 将 C/C++ 映射到 Python 库
-
Pybind11 使用总结(实现 C++ 和 Python 的相互调用)
-
在 Ubuntu 上使用 Pybind11 调用 Python 接口的 C++ 示例。
-
使用 Pybind11 将 C++ 代码编译成动态库,以便支持 Python 调用。
-
通过在 C 语言中调用 pybind11 生成的动态链接库。
-
封装 Python 和调用 C++ 模块的陷阱(使用 pyinstaller 和 pybind11)
-
在 Windows 中使用 pybind11 的教程(python 调用 C++ 代码)
-
用 python 调用 pybind11 封装的 cuda C++ 动态链接库
-
ubuntu蟒蛇调用C/C++动态链接库的方法详解