有时候调用一些SDK,但是人家又是封装成dll文件形式调用的,这时没法看源码,也不想看其对应的开发文档(尤其有些开发文档写得还很难懂,或者你从某个开源社区拿过来,就根本没找到开发文档),
一.动态链接库之为啥你看不到源码
DLL(Dynamic Link Library)和 SO(Shared Object)都是可执行文件的一种形式,用于在运行时动态链接到程序中。它们的意义是为了实现代码的模块化和共享,提高代码的复用性和可维护性。
也就是说他们相当于打包好了的模块,你是看不到内部的代码的(当然逆向或许可以)
他所暴露的就只有调用的python接口,当然这个暴露的接口实际上也是通过python的ctypes模块调用编写的py文件中来看的,不过有些项目写得很混乱或易读性不够好,不论是结构还是命名,因此,本文是给出通过python的dir()模块来获取对象中可用的属性和方法并讨论说明在实际开发过程中如何玄学使用。
DLL(Windows 操作系统):
DLL 是 Windows 操作系统中的动态链接库文件,以 .dll 扩展名结尾。 DLL
文件包含函数、数据和资源等可供程序在运行时动态链接的代码。 多个程序可以共享使用同一个 DLL 文件,避免了重复编写和存储相同的代码。
DLL 的优势在于实现代码的动态链接,程序在运行时才将所需的函数和资源链接到程序中,而不是在编译时静态链接。 SO(Linux 操作系统):
SO 是 Linux 操作系统中的共享对象文件,以 .so 扩展名结尾。 SO 文件也是包含可供程序在运行时动态链接的代码、数据和资源等。
在 Linux 系统中,SO 文件可以被多个程序共享使用,实现代码的模块化和共享。 SO 文件的概念和作用与 DLL 文件相似,但在
Linux 系统下使用。
1.1简单dir()例子(不用dll的情况)
下面我给出一个简单的例子,这个例子我们可以清晰的看到 名为A的类的结构
# 定义一个类
class A():a_num = 1111a_string = "AAAAAAAAA"def A_fun(self):print("这是A函数")def A_add_1(self, num):return num+1# 创建一个对象
a = A()# 使用 dir() 显示当前作用域中的所有名称列表
name_list = dir(a)
print(name_list)
输出:
['A_add_1', 'A_fun', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a_num', 'a_string']
这里可以看到,‘A_add_1’, ‘A_fun’,‘a_num’, ‘a_string’ 这几个我们自定义的类函数与变量都存在了
1.2 简单dir()例子(调用DLL的情况)
A.dll 零积分下载链接
或者看 下文 四章节 自己将打包一个等效刚才A类功能的dll文件,
import ctypes# 加载 DLL 文件
dll = ctypes.CDLL("A.dll")# 定义函数的返回类型和参数类型
dll.A_add_1.restype = ctypes.c_int
dll.A_add_1.argtypes = [ctypes.c_void_p, ctypes.c_int]# 创建 A 类的实例
class A(ctypes.Structure):_fields_ = [("a_num", ctypes.c_int),("a_string", ctypes.c_char * 10)]# 调用 A_new 函数创建实例
dll.A_new.restype = ctypes.POINTER(A)
a_ptr = dll.A_new()
a = a_ptr.contents# 调用 A_add_1 函数
num = 5
result = dll.A_add_1(ctypes.byref(a), num)
print("Result:", result)# 获取 a_num 的值
a_num = a.a_num
print("a_num:", a_num)# 释放实例
dll.A_del(a_ptr)print(dir(A))
['__class__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_b_base_', '_b_needsfree_', '_fields_', '_objects', 'a_num', 'a_string']
这里实现的功能是和之前的A类是相同的,不过这里只能看到 ‘a_num’, ‘a_string’*,
至于为什么,因为python没法直接与获得dll文件的数据,只能得到一堆指向某个函数或者某个变量的指针,所以你想使用dll调用的方法复刻 A类 的各个函数(方法),变量。
那你可以下面这样写(当然这个,存python开发者不用掌握,我都用python了还要管变量类型与输出定义?能看懂已经很不错了)
import ctypes# 加载 DLL 文件
dll = ctypes.CDLL("A.dll")# 定义函数的返回类型和参数类型
dll.A_add_1.restype = ctypes.c_int
dll.A_add_1.argtypes = [ctypes.c_void_p, ctypes.c_int]dll.A_fun.restype = ctypes.c_char
dll.A_fun.argtypes = [ctypes.c_void_p]# dll.not_exist.restype = ctypes.c_char
# dll.not_exist.argtypes = [ctypes.c_void_p]# 创建 A 类的实例
class A(ctypes.Structure):_fields_ = [("a_num", ctypes.c_int),("a_string", ctypes.c_char * 10),("not_exist_val", ctypes.c_char * 10),]def A_add_1(self, num):return dll.A_add_1(ctypes.byref(self), num)def A_fun(self):return dll.A_fun(ctypes.byref(self))# 调用 A_new 函数创建实例
dll.A_new.restype = ctypes.POINTER(A)
a_ptr = dll.A_new()
a = a_ptr.contents# 调用 A_fun方法
result = a.A_fun()print("*"*10)
# 调用 A_add_1 方法
num = 5
result = a.A_add_1(num)
print("A_add_1 Result:", result)# 获取 a_num 的值
a_num = a.a_num
print("a_num:", a_num)# 获取 not_exist_val 的值(实际上该值并不存在,但是程序依然后随机分配一个数据地址指针给你,
# 你可以看到每次运行的结果都是空,但不会报错)
a_not_exist_val= a.not_exist_val
print("a_not_exist_val:", a_not_exist_val)# 释放实例
dll.A_del(a_ptr)print(dir(A))
输出
**********
A_add_1 Result: 6
a_num: 1111
a_not_exist_val: b''
['A_add_1', 'A_fun', '__class__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_b_base_', '_b_needsfree_', '_fields_', '_objects', 'a_num', 'a_string', 'not_exist_val']
这是A函数
提示: 在上面代码中我还多加了一个 名叫 not_exist_val 的值,这个值在A.dll文件中是不存在的,但是作为变量依然不会报错,只是返回了个空(也有可能随机一个十六进制数),这里主要是想说明,如果看到一个从dll调用的变量值是空或者乱码,极有可能这个调用的 名字错了,比如我这里的 not_exist_val ,而不是这个dll文件中真的存在 not_exist_val 这个变量。
二.实例分析
尤其是针对一些硬件的sdk的开发,要么没技术支持,要么连手册都没有,要么有手册但是和python没关系,但是又想用python来进行调用。
比如下面的例子,我要使用海康的MV-DLS600P深度相机做手眼标定的开发, 我想获取深度相机的视频流,就2023年而言海康给了一部分调用代码,但又没有完全给,成功运行上了示例代码,相机是亮了,但是只得到了一个 stFrameData.stImageData[i] 的变量,然后你就可以从这个变量上把深度图和点云图求出来了。如下图:
开发手册是c++的,看起来只能所有定义参考作用,鉴定为不如直接用dir()自己看,而且总感觉代码更新了,手册没更新…
但是不知道视频流的返回函数是什么(即stFrameData.stImageData[i]这个对象到底应该调用什么才能返回视频流),例如下面代码中 stFrameData.stImageData[i].nWidth是返回宽度,那么返回视频流是什么呢?
def work_thread(camera=0,pdata=0,nDataSize=0):while True:stFrameData=MV3D_RGBD_FRAME_DATA()ret=camera.MV3D_RGBD_FetchFrame(pointer(stFrameData), 1000)if ret==0:for i in range(0, stFrameData.nImageCount):# print("MV3D_RGBD_FetchFrame[%d]:nFrameNum[%d],nDataLen[%d],nWidth[%d],nHeight[%d]" % (# i, stFrameData.stImageData[i].nFrameNum, stFrameData.stImageData[i].nDataLen, stFrameData.stImageData[i].nWidth, stFrameData.stImageData[i].nHeight))print("MV3D_RGBD_FetchFrame[%d]:帧号[%d],数据长度[%d],宽度[%d],高度[%d]" % (i, stFrameData.stImageData[i].nFrameNum, stFrameData.stImageData[i].nDataLen,stFrameData.stImageData[i].nWidth, stFrameData.stImageData[i].nHeight))
1.通过dir查看
dir() 是一个内置函数,用于获取对象的所有属性和方法的列表。它返回一个包含字符串的列表,这些字符串表示对象拥有的属性和方法的名称。
dir() 函数有以下两种常见的用法:
无参数使用:当不传入任何参数时,dir() 返回当前作用域中的所有名称列表,包括内置的名称。
python Copy print(dir()) # 返回当前作用域中的所有名称列表 有参数使用:当传入一个对象作为参数时,dir()
返回该对象的属性和方法列表。python Copy my_list = [1, 2, 3] print(dir(my_list)) # 返回 my_list
对象的属性和方法列表 注意:参数可以是任何对象,包括内置对象(如列表、字典、字符串等)和自定义对象。dir()
函数返回的列表中的字符串代表对象的属性和方法名称。属性名称以字符串的形式表示,而方法名称则以函数对象的形式表示。您可以通过访问对象的属性(如
obj.attribute)或调用对象的方法(如 obj.method())来使用它们。需要注意的是,dir()
函数只返回对象中可见的属性和方法名称。有些属性和方法可能以双下划线开头,表示为特殊属性或私有属性,这些在列表中是不可见的。但是,您仍然可以通过直接访问这些属性和方法来使用它们。
加上下面,代码即可看到
print("对象中可用的属性和方法名称: " ,dir(stFrameData.stImageData[i]))
输出:
对象中可用的属性和方法名称:
[‘class’, ‘ctypes_from_outparam’, ‘delattr’, ‘dict’, ‘dir’, ‘doc’, ‘eq’, ‘format’, ‘ge’, ‘getattribute’, ‘gt’, ‘hash’, ‘init’, ‘init_subclass’, ‘le’, ‘lt’, ‘module’, ‘ne’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘setstate’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘b_base’, ‘b_needsfree’, ‘fields’, ‘_objects’, ‘enImageType’, ‘nDataLen’, ‘nFrameNum’, ‘nHeight’, ‘nReserved’, ‘nTimeStamp’, ‘nWidth’, ‘pData’]
这里通过英文名判断大概就是,pData ,这里加上去然后调用看看是什么类型
print("en图像类型",stFrameData.stImageData[i].enImageType)print("en图像类型十六进制:", hex(stFrameData.stImageData[i].enImageType))print("n数据长度", stFrameData.stImageData[i].nDataLen)print("n帧数", stFrameData.stImageData[i].nFrameNum)print("n高度", stFrameData.stImageData[i].nHeight)print("n保留(类型)", type(stFrameData.stImageData[i].nReserved))print("n保留", stFrameData.stImageData[i].nReserved)print("n时间戳", stFrameData.stImageData[i].nTimeStamp)print("n宽度", stFrameData.stImageData[i].nWidth)print( "p数据(类型)",type(stFrameData.stImageData[i].pData))print("p数据", stFrameData.stImageData[i].pData)
输出:
en图像类型 35127329
en图像类型十六进制: 0x2180021
n数据长度 18874368
n帧数 1
n高度 2048
n保留(类型) <class 'Mv3dRgbdImport.Mv3dRgbdDefine.c_byte_Array_16'>
n保留 <Mv3dRgbdImport.Mv3dRgbdDefine.c_byte_Array_16 object at 0x000001F6F8ED8AC8>
n时间戳 1057938808
n宽度 3072
p数据(类型) <class 'Mv3dRgbdImport.Mv3dRgbdDefine.LP_c_ubyte'>
p数据 <Mv3dRgbdImport.Mv3dRgbdDefine.LP_c_ubyte object at 0x000001F6F8ED8AC8>
2.根据类型进行分析
基础知识:
C语言中有多种数据类型,以下是一些常见的C类型的定义示例:
整数类型:
int: 用于表示整数,通常为32位或64位(取决于编译器和平台)。 short: 用于表示短整数,通常为16位。 long:
用于表示长整数,通常为32位或64位。 char: 用于表示字符,通常为8位。 浮点数类型:float: 用于表示单精度浮点数,通常为32位。 double: 用于表示双精度浮点数,通常为64位。 指针类型:
int*: 用于表示指向整数的指针。 char*: 用于表示指向字符的指针。 void*: 用于表示通用指针,可以指向任意类型的数据。
结构体类型:struct: 用于定义自定义的结构体类型,可以包含多个成员变量,每个成员变量可以是任意类型。 枚举类型:
enum: 用于定义枚举类型,可以列出一组具名的常数值。
根据打印输出结果,stFrameData.stImageData[i]的成员变量(例如enImageType、nDataLen、nFrameNum等)的值和类型都表明它们是C类型的数据。C语言中的数据类型,例如整数、枚举、指针等(通常在Python中使用ctypes库进行封装和访问)
而我要获得点云图和深度图,那么根据stFrameData.stImageData[i] 对象打印的结果,以及给出的英文名来判断,深度相机返回数据最有可能出现的位置应该是 nReserved或者 pData的函数(方法)的返回值中。(有些类型的深度相机返回值有可能直接是一个很长的字符串,海康则一般是一个 C类型数据 ,反正就是一个结构体变量(一个指向储存着特定二维结构信息的指针))
print("en图像类型",stFrameData.stImageData[i].enImageType)
print("en图像类型十六进制:", hex(stFrameData.stImageData[i].enImageType))
print("n数据长度", stFrameData.stImageData[i].nDataLen)
print("n帧数", stFrameData.stImageData[i].nFrameNum)
print("n高度", stFrameData.stImageData[i].nHeight)
print("n保留(类型)", type(stFrameData.stImageData[i].nReserved))
print("n保留", stFrameData.stImageData[i].nReserved)
print("n时间戳", stFrameData.stImageData[i].nTimeStamp)
print("n宽度", stFrameData.stImageData[i].nWidth)
print( "p数据(类型)",type(stFrameData.stImageData[i].pData))
print("p数据", stFrameData.stImageData[i].pData)
现在让我们来看看这两个的返回值分别是什么
2.1nReserved:
stFrameData.stImageData[i].nReserved
n保留(类型) <class ‘Mv3dRgbdImport.Mv3dRgbdDefine.c_byte_Array_16’>
n保留 <Mv3dRgbdImport.Mv3dRgbdDefine.c_byte_Array_16 object at 0x00000253BF6A8BC8>
可观察到关键词 Array 和 16,那么可能就是 可能是一个长度为 16 的字节数组类型,至于前面的 c 和 byte,那可能是c语言开发,返回字节。
因此以解析字节的方式进行类型解析
nReserved_data = np.array(stFrameData.stImageData[i].nReserved)
print("nReserved_data:" ,nReserved_data)
nReserved_data = list(stFrameData.stImageData[i].nReserved)
print("nReserved_data2 :", nReserved_data)
输出如下
nReserved_data: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
nReserved_data2 : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
可以看见还真是16个字节的数组,但是很明显不是我要的深度图数据
2.2pData:
stFrameData.stImageData[i].pData
p数据(类型) <class ‘Mv3dRgbdImport.Mv3dRgbdDefine.LP_c_ubyte’>
p数据 <Mv3dRgbdImport.Mv3dRgbdDefine.LP_c_ubyte object at 0x00000253BF6A8BC8>
关于LP_c_ubyte 是什么类型资料如下
LP_c_ubyte 是一个指针类型,通常在与 C 语言交互的过程中使用。它表示指向 c_ubyte 类型数据的指针。
c_ubyte 是 ctypes 库中定义的一种数据类型,它对应于 C 语言中的 unsigned char 类型,即无符号字节类型。
LP_c_ubyte 是 ctypes 库中的一个别名,它表示一个指向 c_ubyte 类型数据的指针。在与 C 语言进行交互时,可以使用
LP_c_ubyte 类型来表示指向字节数组的指针。
翻译一些,说人话就是 stFrameData.stImageData[i].pData 是个指针(不了解指针的同学,可以理解成 是一个储存有目标数据储存地址号的一个特殊变量,我们可以通过该变量找到目标数据 )
于是我们可以这样写,使用 contents 方法
在Python中,contents 是ctypes库中指针对象的属性之一。contents属性用于访问指针所指向的内存区域中的值。
pData_data = np.array(stFrameData.stImageData[i].pData.contents)
print("pData_data:",pData_data)
输出:
pData_data: 0
然后很明显,0 是个屁的深度相机的返回数据,根据经验,返回的深度数据应该是一串字符有或者是一个很大的二维矩阵,然后才能处理成深度图或者点云图,所以这里应该是使用 **np.ctypeslib.as_array()**方法
我这里说下两者的区别: contents 方法只返回指针变量下的数据区第一个地址的数据,而np.ctypeslib.as_array方法会顺着第一个往下遍历,遍历的长度则由之前获得的 width 和 height 决定,如下
width = stFrameData.stImageData[i].nWidth
height = stFrameData.stImageData[i].nHeight
print("-"*20)
print(type(stFrameData.stImageData[i].pData))
print(stFrameData.stImageData[i].pData)
data = np.ctypeslib.as_array(stFrameData.stImageData[i].pData, shape=(height, width))
print("获得转换后的图像数据 data : ", data)
image = cv2.cvtColor(data, cv2.COLOR_GRAY2BGR)
# 保存图像
now = datetime.now()
timestamp = now.strftime("%Y_%m_%d_%H_%M_%S")
timestamp = timestamp + "_" + str(i)
cv2.imwrite("img_out/" + timestamp + ".jpg", image)
print("保存图像名称: ", timestamp)
print("当前图像类型(十六进制格式): ", hex(stFrameData.stImageData[i].enImageType) )print("*"*50)
输出:
深度图和rgb图还都是这个接口一起发出来的,只能说易读性很不好。
np.ctypeslib.as_array()
函数将指针转换为NumPy数组时,它会根据指针所指向的地址找到NumPy数组的起始位置,并从该地址开始遍历,将连续的内存块解释为NumPy数组的元素。而 stFrameData.stImageData[i].pData.contents 返回的是指针
stFrameData.stImageData[i].pData 指向的地址上存储的数据。它提供了指针所指向的内存位置的内容,通常是一个
C/C++ 数据类型的对象。因此,在正确配置和使用的情况下,stFrameData.stImageData[i].pData.contents
应该返回指针所指向的内存位置上存储的数据。通过使用 np.ctypeslib.as_array() 函数,我们可以将指针直接转换为NumPy数组,并正确解释指针所指向的内存数据。
四.其他
4.1 dll文件的生成
通过将C++文件或者C文件编译后获得即可,例如之前的
class A():a_num = 1111a_string = "AAAAAAAAA"def A_fun(self):print("这是A函数")def A_add_1(self, num):return num+1
等效上面的在C语言下的 A.c 文件代码如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endiftypedef struct {int a_num;char a_string[10];
} A;DLL_EXPORT void A_fun(A* self) {printf("这是A函数\n");
}DLL_EXPORT int A_add_1(A* self, int num) {return num + 1;
}DLL_EXPORT A* A_new() {A* self = (A*)malloc(sizeof(A));self->a_num = 1111;strcpy(self->a_string, "AAAAAAAAA");return self;
}DLL_EXPORT void A_del(A* self) {free(self);
}
然后使用gcc编译成dll或者so文件
gcc -shared -o A.dll A.c
然后再调用即可
import ctypes# 加载 DLL 文件
dll = ctypes.CDLL("A.dll")# 定义函数的返回类型和参数类型
dll.A_add_1.restype = ctypes.c_int
dll.A_add_1.argtypes = [ctypes.c_void_p, ctypes.c_int]# 创建 A 类的实例
class A(ctypes.Structure):_fields_ = [("a_num", ctypes.c_int),("a_string", ctypes.c_char * 10)]# 调用 A_new 函数创建实例
dll.A_new.restype = ctypes.POINTER(A)
a_ptr = dll.A_new()
a = a_ptr.contents# 调用 A_add_1 函数
num = 5
result = dll.A_add_1(ctypes.byref(a), num)
print("Result:", result)# 获取 a_num 的值
a_num = a.a_num
print("a_num:", a_num)# 释放实例
dll.A_del(a_ptr)
4.2 海康MV-DLS600P 深度图与黑白图采集主代码
# -- coding: utf-8 --
import threading
from Mv3dRgbdImport.Mv3dRgbdApi import *
from Mv3dRgbdImport.Mv3dRgbdDefine import *
import msvcrt
import ctypes
import time
import os
import numpy as np
from Mv3dRgbdImport.Mv3dRgbdDefine import DeviceType_Ethernet, DeviceType_USB, MV3D_RGBD_FLOAT_EXPOSURETIME, \ParamType_Float, CoordinateType_Depth
import cv2
from datetime import datetimeg_bExit = False
def work_thread(camera=0,pdata=0,nDataSize=0):while True:stFrameData=MV3D_RGBD_FRAME_DATA()ret=camera.MV3D_RGBD_FetchFrame(pointer(stFrameData), 1000)if ret == 0:for i in range(0, stFrameData.nImageCount):# if stFrameData.stImageData[i].enImageType == MV3D_RGBD_ImageType.Normal:width = stFrameData.stImageData[i].nWidthheight = stFrameData.stImageData[i].nHeightprint("-"*20)print(type(stFrameData.stImageData[i].pData))print(stFrameData.stImageData[i].pData)data = np.ctypeslib.as_array(stFrameData.stImageData[i].pData, shape=(height, width))print("获得转换后的图像数据 data : ", data)image = cv2.cvtColor(data, cv2.COLOR_GRAY2BGR)# 保存图像now = datetime.now()timestamp = now.strftime("%Y_%m_%d_%H_%M_%S")timestamp = timestamp + "_" + str(i)cv2.imwrite("img_out/" + timestamp + ".jpg", image)print("保存图像名称: ", timestamp)print("当前图像类型(十六进制格式): ", hex(stFrameData.stImageData[i].enImageType) )print("*"*50)# cv2.imshow("Image", image)# cv2.waitKey(1)else:print("no data[0x%x]" % ret)if g_bExit == True:break# 触发线程 5s触发一次
def work_thread_trigger(cam=0, pData=0, nDataSize=0):while True:time.sleep(5)ret = cam.MV3D_RGBD_SoftTrigger()if 0 == ret:print ("MV3D_RGBD_SoftTrigger success")else:print ("MV3D_RGBD_SoftTrigger failed[0x%x]" % ret)if g_bExit == True:breakif __name__ == "__main__":nDeviceNum=ctypes.c_uint(0)nDeviceNum_p=byref(nDeviceNum)ret=Mv3dRgbd.MV3D_RGBD_GetDeviceNumber(DeviceType_Ethernet | DeviceType_USB, nDeviceNum_p) #获取设备数量if ret!=0:print("MV3D_RGBD_GetDeviceNumber fail! ret[0x%x]" % ret)os.system('pause')sys.exit()if nDeviceNum==0:print("find no device!")os.system('pause')sys.exit()print("Find devices numbers:", nDeviceNum.value)stDeviceList = MV3D_RGBD_DEVICE_INFO_LIST();net = Mv3dRgbd.MV3D_RGBD_GetDeviceList(DeviceType_Ethernet | DeviceType_USB, pointer(stDeviceList.DeviceInfo[0]), 20, nDeviceNum_p)for i in range(0, nDeviceNum.value):print("\ndevice: [%d]" % i)strModeName = ""for per in stDeviceList.DeviceInfo[i].chModelName:strModeName = strModeName + chr(per)print("device model name: %s" % strModeName)strSerialNumber = ""for per in stDeviceList.DeviceInfo[i].chSerialNumber:strSerialNumber = strSerialNumber + chr(per)print("device SerialNumber: %s" % strSerialNumber)# 创建相机示例camera=Mv3dRgbd()nConnectionNum = 0# 打开设备ret = camera.MV3D_RGBD_OpenDevice(pointer(stDeviceList.DeviceInfo[int(nConnectionNum)]))if ret != 0:print ("MV3D_RGBD_OpenDevice fail! ret[0x%x]" % ret)os.system('pause')sys.exit()# 开始取流ret=camera.MV3D_RGBD_Start()if ret != 0:print ("start fail! ret[0x%x]" % ret)camera.MV3D_RGBD_CloseDevice()os.system('pause')sys.exit()# 获取图像线程try:hthreadhandle=threading.Thread(target=work_thread,args=(camera,None,None))hthreadhandle.start()except:print("error: unable to start thread")try:hthreadhandle_trigger= threading.Thread(target=work_thread_trigger, args=(camera, None, None))hthreadhandle_trigger.start()except:print("error: unable to start thread")#msvcrt.getch()os.system('pause')g_bExit = Truehthreadhandle.join()hthreadhandle_trigger.join()# 停止取流ret=camera.MV3D_RGBD_Stop()if ret != 0:print ("stop fail! ret[0x%x]" % ret)os.system('pause')sys.exit()# 销毁句柄ret=camera.MV3D_RGBD_CloseDevice()if ret != 0:print ("CloseDevice fail! ret[0x%x]" % ret)os.system('pause')sys.exit()sys.exit()