用自建网站做外贸,网络运维前景怎么样,网络系统定级备案,知名的家居行业网站开发有时候调用一些SDK#xff0c;但是人家又是封装成dll文件形式调用的#xff0c;这时没法看源码#xff0c;也不想看其对应的开发文档#xff08;尤其有些开发文档写得还很难懂#xff0c;或者你从某个开源社区拿过来#xff0c;就根本没找到开发文档#xff09;#xf…有时候调用一些SDK但是人家又是封装成dll文件形式调用的这时没法看源码也不想看其对应的开发文档尤其有些开发文档写得还很难懂或者你从某个开源社区拿过来就根本没找到开发文档
一.动态链接库之为啥你看不到源码
DLLDynamic Link Library和 SOShared Object都是可执行文件的一种形式用于在运行时动态链接到程序中。它们的意义是为了实现代码的模块化和共享提高代码的复用性和可维护性。 也就是说他们相当于打包好了的模块你是看不到内部的代码的当然逆向或许可以
他所暴露的就只有调用的python接口当然这个暴露的接口实际上也是通过python的ctypes模块调用编写的py文件中来看的不过有些项目写得很混乱或易读性不够好不论是结构还是命名因此本文是给出通过python的dir()模块来获取对象中可用的属性和方法并讨论说明在实际开发过程中如何玄学使用。 DLLWindows 操作系统 DLL 是 Windows 操作系统中的动态链接库文件以 .dll 扩展名结尾。 DLL 文件包含函数、数据和资源等可供程序在运行时动态链接的代码。 多个程序可以共享使用同一个 DLL 文件避免了重复编写和存储相同的代码。 DLL 的优势在于实现代码的动态链接程序在运行时才将所需的函数和资源链接到程序中而不是在编译时静态链接。 SOLinux 操作系统 SO 是 Linux 操作系统中的共享对象文件以 .so 扩展名结尾。 SO 文件也是包含可供程序在运行时动态链接的代码、数据和资源等。 在 Linux 系统中SO 文件可以被多个程序共享使用实现代码的模块化和共享。 SO 文件的概念和作用与 DLL 文件相似但在 Linux 系统下使用。 1.1简单dir()例子不用dll的情况
下面我给出一个简单的例子,这个例子我们可以清晰的看到 名为A的类的结构
# 定义一个类
class A():a_num 1111a_string AAAAAAAAAdef A_fun(self):print(这是A函数)def A_add_1(self, num):return num1# 创建一个对象
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(camera0,pdata0,nDataSize0):while True:stFrameDataMV3D_RGBD_FRAME_DATA()retcamera.MV3D_RGBD_FetchFrame(pointer(stFrameData), 1000)if ret0: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 0x000001F6F8ED8AC82.根据类型进行分析
基础知识 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].nReservedn保留(类型) 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].pDatap数据(类型) 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 AAAAAAAAAdef A_fun(self):print(这是A函数)def A_add_1(self, num):return num1等效上面的在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(camera0,pdata0,nDataSize0):while True:stFrameDataMV3D_RGBD_FRAME_DATA()retcamera.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(cam0, pData0, nDataSize0):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__:nDeviceNumctypes.c_uint(0)nDeviceNum_pbyref(nDeviceNum)retMv3dRgbd.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 nDeviceNum0: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)# 创建相机示例cameraMv3dRgbd()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()# 开始取流retcamera.MV3D_RGBD_Start()if ret ! 0:print (start fail! ret[0x%x] % ret)camera.MV3D_RGBD_CloseDevice()os.system(pause)sys.exit()# 获取图像线程try:hthreadhandlethreading.Thread(targetwork_thread,args(camera,None,None))hthreadhandle.start()except:print(error: unable to start thread)try:hthreadhandle_trigger threading.Thread(targetwork_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()# 停止取流retcamera.MV3D_RGBD_Stop()if ret ! 0:print (stop fail! ret[0x%x] % ret)os.system(pause)sys.exit()# 销毁句柄retcamera.MV3D_RGBD_CloseDevice()if ret ! 0:print (CloseDevice fail! ret[0x%x] % ret)os.system(pause)sys.exit()sys.exit()