USB 传输的 LibUSB C++ 格式不同
LibUSB C++ Format of USB transfer differs
我使用stack overflow已经很长时间了,大部分问题都已经有了解决方案。这是我第一次实际上无法通过网络弄清楚。我希望有人能回答以下问题。
简介
我目前正在从事一个项目,该项目应该能够执行命令并根据其响应采取行动。这个项目 运行s 在基于 debian 的系统上,在 c++ 控制台应用程序中。为了能够执行此类命令,我尝试使用 LibUSB 库。
问题
每当发送数据包时,它都不会 return 硬件文档中描述的有效响应。有一个默认工具可以触发校准命令,我用 Wireshark 嗅探了这些数据包,但是校准工具的 OUT 中断调用的结构与 LibUSB 生成的不同,因此(我认为)导致命令不被执行。
文档提供了以下命令之一,该命令应该运行 诊断程序检查 return 5 个字节的数据。
[0] Header: 0x02
[1] Command: 0x4C
[2] Byte to send: 0x02 (N bytes to send, Argument + data size)
[3] Argument: 0x09
[4] Data: 0x00
响应应具有以下格式:
[0] Header: 0x02
[1] Command: 0x4C
[2] Byte to send: 0x03 (N bytes to send, Argument + data size)
[3] Argument: 0x09
[4] Processing result: D-1
[5] Diagnostic result: D-2
D-1:0x01:正常或 0x00 错误 D-2:0x00:正常或不正常 0x00,链接错误代码。
到目前为止尝试过的东西
- T运行类型:
- 同步:
- Libusb_bulk_transfer
- Libusb_control_transfer
- libusb_interrupt_transfer
- 异步:
- Libusb_fill_bulk_transfer
- Libusb_fill_control_transfer
- Libusb_fill_interrupt_transfer
我尝试将异步作为 LibUSB 库的同步实现。控件 t运行sfer 我尝试 运行domly 在填充变量的最合乎逻辑的方法已经 运行 之后切换变量,但没有成功,正如预期的那样。由于在数据包嗅探中发现的结果清楚地表明正在进行中断调用。
接口:硬件有两个接口。接口 0 包含 OUT 0x02 和 IN 0x81,接口 1 包含 OUT 0x04 和 IN 0x83。如果接口 1 用于诊断命令,则嗅探由工具触发的对设备的 USB 中断调用。 (还尝试了接口 0 的 IN 和 OUT,无法正常工作。
使用 Wireshark 嗅探数据包
数据包嗅探结果
使用工具生成的请求和响应:IMG: Interrupt OUT (I marked the bit where to command is actually provided) IMG: Interrupt IN response 此代码实际有效并且 return 是其数据槽中的预期数据集。 (如上所述,return格式是正确的,0x01和0x00)。
通过 LibUSB 使用代码生成的请求和响应:IMG: Interrupt OUT IMG: Interrupt IN 响应
是的,我还尝试将缓冲区大小设置为 64,这是硬件的最大缓冲区大小。可惜没用。可以清楚地看到,两个请求相差很大,我使用了错误的 t运行sfer 方法吗?它是另一种可以发送命令的受支持格式吗?
使用的代码片段:
代码片段有点过时,我尝试重写/编辑了几次,最后的实现是从在线示例中使用的。
#define USB_VENDOR_ID <VENDOR_ID>/* USB vendor ID used by the device
* 0x0483 is STMs ID
*/
#define USB_PRODUCT_ID <PRODUCT_ID> /* USB product ID used by the device */
#define USB_ENDPOINT_IN (LIBUSB_ENDPOINT_IN | 0x83) /* endpoint address */
#define USB_ENDPOINT_OUT (LIBUSB_ENDPOINT_OUT | 0x04) /* endpoint address */
#define USB_TIMEOUT 3000 /* Connection timeout (in ms) */
#define INTERFACE_NO 1
static libusb_context *ctx = NULL;
static libusb_device_handle *handle;
static uint8_t receiveBuf[64];
uint8_t transferBuf[64];
uint16_t counter=0;
int main(int argc, char **argv) {
libusb_device **devs; //pointer to pointer of device, used to retrieve a list of devices
libusb_device_handle *dev_handle; //a device handle
libusb_context *ctx = NULL; //a libusb session
int r; //for return values
ssize_t cnt; //holding number of devices in list
r = libusb_init(&ctx); //initialize the library for the session we just declared
if(r < 0) {
qDebug()<<"Init Error "<<r<<endl; //there was an error
return 1;
}
libusb_set_debug(ctx, 4); //set verbosity level to 3, as suggested in the documentation
cnt = libusb_get_device_list(ctx, &devs); //get the list of devices
if(cnt < 0) {
qDebug()<<"Get Device Error"<<endl; //there was an error
return 1;
}
qDebug()<<cnt<<" Devices in list."<<endl;
dev_handle = libusb_open_device_with_vid_pid(ctx, 0x0AFA, 0x7D3); //these are vendorID and productID I found for my usb device
if(dev_handle == NULL)
qDebug()<<"Cannot open device"<<endl;
else
qDebug()<<"Device Opened"<<endl;
libusb_free_device_list(devs, 1); //free the list, unref the devices in it
unsigned char *data = new unsigned char[5] { 0x02, 0x4C, 0x02, 0x09, 0 }; //data to write
data[0]= 0x02;data[1]= 0x4C;data[2]=0x02;data[3]=0x09; data[4]= 0; //some dummy values
int actual; //used to find out how many bytes were written
if(libusb_kernel_driver_active(dev_handle, INTERFACE_NO) == 1) { //find out if kernel driver is attached
qDebug()<<"Kernel Driver Active"<<endl;
if(libusb_detach_kernel_driver(dev_handle, INTERFACE_NO) == 0) //detach it
qDebug()<<"Kernel Driver Detached!"<<endl;
}
r = libusb_claim_interface(dev_handle, INTERFACE_NO); //claim interface 0 (the first) of device (mine had jsut 1)
if(r < 0) {
qDebug()<<"Cannot Claim Interface"<<endl;
return 1;
}
qDebug()<<"Claimed Interface"<<endl;
for(int i = 0; i != sizeof(data); i++) {
fprintf(stderr, "[%d] - %02x\n", i, data[i]);
}
qDebug()<<"Writing Data..."<<endl;
r = libusb_bulk_transfer(dev_handle, (USB_ENDPOINT_OUT | LIBUSB_ENDPOINT_OUT), data, sizeof(data), &actual, 0); //my device's out endpoint was 2, found with trial- the device had 2 endpoints: 2 and 129
if(r == 0 && actual == sizeof(data)) //we wrote the 4 bytes successfully
qDebug()<<"Writing Successful!"<<endl;
else
qDebug()<<"Write Error"<<endl;
fprintf(stderr, "Error Writing: %s", libusb_strerror(static_cast<libusb_error>(r)));
r = libusb_release_interface(dev_handle, INTERFACE_NO); //release the claimed interface
if(r!=0) {
qDebug()<<"Cannot Release Interface"<<endl;
return 1;
}
qDebug()<<"Released Interface"<<endl;
libusb_close(dev_handle); //close the device we opened
libusb_exit(ctx); //needs to be called to end the
delete[] data; //delete the allocated memory for data
return 0;
}
我希望有人有能力并愿意在这里帮助我,因为我已经连续三天为此工作,但仍然没有找到解决这个问题的合理方法。
提前致谢!
~马克
感谢您的回复!我目前找到了解决问题的方法!它与同时使用 C/C++ 无关。抱歉代码有点乱。我写了好几次所以整洁不是我的首要任务,尽管我会在 Whosebug 上记住它以备将来 post。即使解决了,我还是添加了嗅探进出数据包的结果,希望它可以帮助其他人解决可能的相同问题。
嗯,问题是什么?
因此,该工具的捕获表明最后 64 位是请求及其数据的有效负载,这适用于 OUT 和 IN。 (正如现在实际提供的图像所示)正如我之前所说,我尝试分配大小为 64 的数组,并使用操作所需的数据设置前几个槽。至于其他插槽,它们被分配的内存地址上的剩余空间填满了。
我做了什么来修复它
所以,我所做的是以下内容。在初始化一个数组并将它的大小分配为 64 之后,我使用 memset 命令将所有分配的插槽设置为 0,这样数组中的剩余数据将被完全清除。这给我留下了一个干净的数组,我可以在其中设置我想要发送的命令所需的变量。 (见以下片段)
// Initialize array of 64 bytes.
uint8_t *data = new uint8_t[64];
memset(data, 0x00, 64);
data[0] = 0x02; data[1] = 0x4C; data[2] = 0x01; data[3] = 0x17;
我整理了一些代码以提供更好的可读性,这是我使用的有效代码!希望其他人发现此信息有用。
//*** DEPENDENCIES *************************************************************
// QT
#include <QCoreApplication>
#include <QtCore/QDebug>
// Others
#include <libusb.h>
#include <iostream>
//*** VARIABLES ****************************************************************
#define USB_VENDOR_ID <VENDOR_ID_GOES_HERE>
#define USB_PRODUCT_ID <PRODUCT_ID_GOES_HERE>
#define USB_ENDPOINT_OUT 0x04
#define USB_ENDPOINT_IN 0x83
#define INTERFACE_NO 0x01
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
libusb_device *dev;
libusb_device_handle *dev_handle;
libusb_context *ctx = NULL;
//*** INITIALIZATION *******************************************************
uint r = libusb_init(&ctx);
// Check if initiated succesfully
if ( r < 0 ) { qDebug() << "Init error."; return 1; }
libusb_set_debug(ctx, 4);
dev_handle = libusb_open_device_with_vid_pid(ctx, USB_VENDOR_ID, USB_PRODUCT_ID);
if (dev_handle == NULL) { qDebug() << "Could not open device."; return 1;}
qDebug() << "Device opened succesfully!";
// Check if kernel driver, detach
if(libusb_kernel_driver_active(dev_handle, INTERFACE_NO) == 1) {
qDebug() << "Kernel Driver Active";
if(libusb_detach_kernel_driver(dev_handle, INTERFACE_NO) == 0) {
qDebug() << "Kernel Driver Detached";
}
}
// Claim interface
r = libusb_claim_interface(dev_handle, INTERFACE_NO);
if ( r < 0 ) {
qDebug() << "Could not claim interface.";
return 1;
}
qDebug() << "Interface claimed.";
//*** EXECUTION OF USB TRANSFERS *******************************************
// Prepare command
int actual_written;
// Initialize array of 64 bytes.
uint8_t *data = new uint8_t[64];
memset(data, 0x00, 64);
data[0] = 0x02; data[1] = 0x4C; data[2] = 0x01; data[3] = 0x17;
qDebug() << "================= OUT ==============================";
//*** ATTEMPT TO WRITE COMMAND *********************************************
r = libusb_bulk_transfer(dev_handle,
USB_ENDPOINT_OUT,
data, 64,
&actual_written,
10000);
qDebug() << "OUT status: " << libusb_strerror(static_cast<libusb_error>(r));
if (r == 0 && actual_written == 64) {
qDebug() << "Succesfully written!";
} else {
qDebug() << "||" << r << "||"<< actual_written << "||"
<< "Could not write.";
}
qDebug() << "================== IN ===============================";
//*** ATTEMPT TO READ FEEDBACK *********************************************
// Initialize array of 64 bytes.
uint8_t *feedback = new uint8_t[64];
memset(feedback, 0x00, 64);
int actual_received;
r = libusb_bulk_transfer(
dev_handle,
USB_ENDPOINT_IN,
feedback,
64,
&actual_received,
0);
qDebug() << "IN status: " << libusb_strerror(static_cast<libusb_error>(r));
if(r == 0 && actual_received == 64) {
qDebug("\nRetrieval successful!");
qDebug("\nSent %d bytes with string: %s\n", actual_received, feedback);
} else {
qDebug() << actual_received << "||" <<feedback << "||"
<< "Could not read incoming data. ||";
}
for( int m = 0; m < 64; m++)
{
fprintf(stderr, "[%d] - %02x\n", m, feedback[m]);
}
if (feedback[4] != 0x01) {
qDebug() << "Unsuccesful offset adjustment.";
return -1;
}
// Further code should go here.
//*** FREEING USB **********************************************************
// Releasing interface
r = libusb_release_interface(dev_handle, INTERFACE_NO);
if ( r < 0 ) { qDebug() << "Could not release interface."; return 1; }
qDebug() << "Interface released.";
libusb_close(dev_handle);
libusb_exit(ctx);
delete[] data;
delete[] feedback;
qDebug() << "End of main";
return 0;
}
Thomas 和 David,非常感谢!
~马克
我使用stack overflow已经很长时间了,大部分问题都已经有了解决方案。这是我第一次实际上无法通过网络弄清楚。我希望有人能回答以下问题。
简介 我目前正在从事一个项目,该项目应该能够执行命令并根据其响应采取行动。这个项目 运行s 在基于 debian 的系统上,在 c++ 控制台应用程序中。为了能够执行此类命令,我尝试使用 LibUSB 库。
问题 每当发送数据包时,它都不会 return 硬件文档中描述的有效响应。有一个默认工具可以触发校准命令,我用 Wireshark 嗅探了这些数据包,但是校准工具的 OUT 中断调用的结构与 LibUSB 生成的不同,因此(我认为)导致命令不被执行。
文档提供了以下命令之一,该命令应该运行 诊断程序检查 return 5 个字节的数据。
[0] Header: 0x02
[1] Command: 0x4C
[2] Byte to send: 0x02 (N bytes to send, Argument + data size)
[3] Argument: 0x09
[4] Data: 0x00
响应应具有以下格式:
[0] Header: 0x02
[1] Command: 0x4C
[2] Byte to send: 0x03 (N bytes to send, Argument + data size)
[3] Argument: 0x09
[4] Processing result: D-1
[5] Diagnostic result: D-2
D-1:0x01:正常或 0x00 错误 D-2:0x00:正常或不正常 0x00,链接错误代码。
到目前为止尝试过的东西
- T运行类型:
- 同步:
- Libusb_bulk_transfer
- Libusb_control_transfer
- libusb_interrupt_transfer
- 异步:
- Libusb_fill_bulk_transfer
- Libusb_fill_control_transfer
- Libusb_fill_interrupt_transfer
我尝试将异步作为 LibUSB 库的同步实现。控件 t运行sfer 我尝试 运行domly 在填充变量的最合乎逻辑的方法已经 运行 之后切换变量,但没有成功,正如预期的那样。由于在数据包嗅探中发现的结果清楚地表明正在进行中断调用。
接口:硬件有两个接口。接口 0 包含 OUT 0x02 和 IN 0x81,接口 1 包含 OUT 0x04 和 IN 0x83。如果接口 1 用于诊断命令,则嗅探由工具触发的对设备的 USB 中断调用。 (还尝试了接口 0 的 IN 和 OUT,无法正常工作。
使用 Wireshark 嗅探数据包
数据包嗅探结果 使用工具生成的请求和响应:IMG: Interrupt OUT (I marked the bit where to command is actually provided) IMG: Interrupt IN response 此代码实际有效并且 return 是其数据槽中的预期数据集。 (如上所述,return格式是正确的,0x01和0x00)。
通过 LibUSB 使用代码生成的请求和响应:IMG: Interrupt OUT IMG: Interrupt IN 响应
是的,我还尝试将缓冲区大小设置为 64,这是硬件的最大缓冲区大小。可惜没用。可以清楚地看到,两个请求相差很大,我使用了错误的 t运行sfer 方法吗?它是另一种可以发送命令的受支持格式吗?
使用的代码片段: 代码片段有点过时,我尝试重写/编辑了几次,最后的实现是从在线示例中使用的。
#define USB_VENDOR_ID <VENDOR_ID>/* USB vendor ID used by the device
* 0x0483 is STMs ID
*/
#define USB_PRODUCT_ID <PRODUCT_ID> /* USB product ID used by the device */
#define USB_ENDPOINT_IN (LIBUSB_ENDPOINT_IN | 0x83) /* endpoint address */
#define USB_ENDPOINT_OUT (LIBUSB_ENDPOINT_OUT | 0x04) /* endpoint address */
#define USB_TIMEOUT 3000 /* Connection timeout (in ms) */
#define INTERFACE_NO 1
static libusb_context *ctx = NULL;
static libusb_device_handle *handle;
static uint8_t receiveBuf[64];
uint8_t transferBuf[64];
uint16_t counter=0;
int main(int argc, char **argv) {
libusb_device **devs; //pointer to pointer of device, used to retrieve a list of devices
libusb_device_handle *dev_handle; //a device handle
libusb_context *ctx = NULL; //a libusb session
int r; //for return values
ssize_t cnt; //holding number of devices in list
r = libusb_init(&ctx); //initialize the library for the session we just declared
if(r < 0) {
qDebug()<<"Init Error "<<r<<endl; //there was an error
return 1;
}
libusb_set_debug(ctx, 4); //set verbosity level to 3, as suggested in the documentation
cnt = libusb_get_device_list(ctx, &devs); //get the list of devices
if(cnt < 0) {
qDebug()<<"Get Device Error"<<endl; //there was an error
return 1;
}
qDebug()<<cnt<<" Devices in list."<<endl;
dev_handle = libusb_open_device_with_vid_pid(ctx, 0x0AFA, 0x7D3); //these are vendorID and productID I found for my usb device
if(dev_handle == NULL)
qDebug()<<"Cannot open device"<<endl;
else
qDebug()<<"Device Opened"<<endl;
libusb_free_device_list(devs, 1); //free the list, unref the devices in it
unsigned char *data = new unsigned char[5] { 0x02, 0x4C, 0x02, 0x09, 0 }; //data to write
data[0]= 0x02;data[1]= 0x4C;data[2]=0x02;data[3]=0x09; data[4]= 0; //some dummy values
int actual; //used to find out how many bytes were written
if(libusb_kernel_driver_active(dev_handle, INTERFACE_NO) == 1) { //find out if kernel driver is attached
qDebug()<<"Kernel Driver Active"<<endl;
if(libusb_detach_kernel_driver(dev_handle, INTERFACE_NO) == 0) //detach it
qDebug()<<"Kernel Driver Detached!"<<endl;
}
r = libusb_claim_interface(dev_handle, INTERFACE_NO); //claim interface 0 (the first) of device (mine had jsut 1)
if(r < 0) {
qDebug()<<"Cannot Claim Interface"<<endl;
return 1;
}
qDebug()<<"Claimed Interface"<<endl;
for(int i = 0; i != sizeof(data); i++) {
fprintf(stderr, "[%d] - %02x\n", i, data[i]);
}
qDebug()<<"Writing Data..."<<endl;
r = libusb_bulk_transfer(dev_handle, (USB_ENDPOINT_OUT | LIBUSB_ENDPOINT_OUT), data, sizeof(data), &actual, 0); //my device's out endpoint was 2, found with trial- the device had 2 endpoints: 2 and 129
if(r == 0 && actual == sizeof(data)) //we wrote the 4 bytes successfully
qDebug()<<"Writing Successful!"<<endl;
else
qDebug()<<"Write Error"<<endl;
fprintf(stderr, "Error Writing: %s", libusb_strerror(static_cast<libusb_error>(r)));
r = libusb_release_interface(dev_handle, INTERFACE_NO); //release the claimed interface
if(r!=0) {
qDebug()<<"Cannot Release Interface"<<endl;
return 1;
}
qDebug()<<"Released Interface"<<endl;
libusb_close(dev_handle); //close the device we opened
libusb_exit(ctx); //needs to be called to end the
delete[] data; //delete the allocated memory for data
return 0;
}
我希望有人有能力并愿意在这里帮助我,因为我已经连续三天为此工作,但仍然没有找到解决这个问题的合理方法。
提前致谢!
~马克
感谢您的回复!我目前找到了解决问题的方法!它与同时使用 C/C++ 无关。抱歉代码有点乱。我写了好几次所以整洁不是我的首要任务,尽管我会在 Whosebug 上记住它以备将来 post。即使解决了,我还是添加了嗅探进出数据包的结果,希望它可以帮助其他人解决可能的相同问题。
嗯,问题是什么?
因此,该工具的捕获表明最后 64 位是请求及其数据的有效负载,这适用于 OUT 和 IN。 (正如现在实际提供的图像所示)正如我之前所说,我尝试分配大小为 64 的数组,并使用操作所需的数据设置前几个槽。至于其他插槽,它们被分配的内存地址上的剩余空间填满了。
我做了什么来修复它
所以,我所做的是以下内容。在初始化一个数组并将它的大小分配为 64 之后,我使用 memset 命令将所有分配的插槽设置为 0,这样数组中的剩余数据将被完全清除。这给我留下了一个干净的数组,我可以在其中设置我想要发送的命令所需的变量。 (见以下片段)
// Initialize array of 64 bytes.
uint8_t *data = new uint8_t[64];
memset(data, 0x00, 64);
data[0] = 0x02; data[1] = 0x4C; data[2] = 0x01; data[3] = 0x17;
我整理了一些代码以提供更好的可读性,这是我使用的有效代码!希望其他人发现此信息有用。
//*** DEPENDENCIES *************************************************************
// QT
#include <QCoreApplication>
#include <QtCore/QDebug>
// Others
#include <libusb.h>
#include <iostream>
//*** VARIABLES ****************************************************************
#define USB_VENDOR_ID <VENDOR_ID_GOES_HERE>
#define USB_PRODUCT_ID <PRODUCT_ID_GOES_HERE>
#define USB_ENDPOINT_OUT 0x04
#define USB_ENDPOINT_IN 0x83
#define INTERFACE_NO 0x01
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
libusb_device *dev;
libusb_device_handle *dev_handle;
libusb_context *ctx = NULL;
//*** INITIALIZATION *******************************************************
uint r = libusb_init(&ctx);
// Check if initiated succesfully
if ( r < 0 ) { qDebug() << "Init error."; return 1; }
libusb_set_debug(ctx, 4);
dev_handle = libusb_open_device_with_vid_pid(ctx, USB_VENDOR_ID, USB_PRODUCT_ID);
if (dev_handle == NULL) { qDebug() << "Could not open device."; return 1;}
qDebug() << "Device opened succesfully!";
// Check if kernel driver, detach
if(libusb_kernel_driver_active(dev_handle, INTERFACE_NO) == 1) {
qDebug() << "Kernel Driver Active";
if(libusb_detach_kernel_driver(dev_handle, INTERFACE_NO) == 0) {
qDebug() << "Kernel Driver Detached";
}
}
// Claim interface
r = libusb_claim_interface(dev_handle, INTERFACE_NO);
if ( r < 0 ) {
qDebug() << "Could not claim interface.";
return 1;
}
qDebug() << "Interface claimed.";
//*** EXECUTION OF USB TRANSFERS *******************************************
// Prepare command
int actual_written;
// Initialize array of 64 bytes.
uint8_t *data = new uint8_t[64];
memset(data, 0x00, 64);
data[0] = 0x02; data[1] = 0x4C; data[2] = 0x01; data[3] = 0x17;
qDebug() << "================= OUT ==============================";
//*** ATTEMPT TO WRITE COMMAND *********************************************
r = libusb_bulk_transfer(dev_handle,
USB_ENDPOINT_OUT,
data, 64,
&actual_written,
10000);
qDebug() << "OUT status: " << libusb_strerror(static_cast<libusb_error>(r));
if (r == 0 && actual_written == 64) {
qDebug() << "Succesfully written!";
} else {
qDebug() << "||" << r << "||"<< actual_written << "||"
<< "Could not write.";
}
qDebug() << "================== IN ===============================";
//*** ATTEMPT TO READ FEEDBACK *********************************************
// Initialize array of 64 bytes.
uint8_t *feedback = new uint8_t[64];
memset(feedback, 0x00, 64);
int actual_received;
r = libusb_bulk_transfer(
dev_handle,
USB_ENDPOINT_IN,
feedback,
64,
&actual_received,
0);
qDebug() << "IN status: " << libusb_strerror(static_cast<libusb_error>(r));
if(r == 0 && actual_received == 64) {
qDebug("\nRetrieval successful!");
qDebug("\nSent %d bytes with string: %s\n", actual_received, feedback);
} else {
qDebug() << actual_received << "||" <<feedback << "||"
<< "Could not read incoming data. ||";
}
for( int m = 0; m < 64; m++)
{
fprintf(stderr, "[%d] - %02x\n", m, feedback[m]);
}
if (feedback[4] != 0x01) {
qDebug() << "Unsuccesful offset adjustment.";
return -1;
}
// Further code should go here.
//*** FREEING USB **********************************************************
// Releasing interface
r = libusb_release_interface(dev_handle, INTERFACE_NO);
if ( r < 0 ) { qDebug() << "Could not release interface."; return 1; }
qDebug() << "Interface released.";
libusb_close(dev_handle);
libusb_exit(ctx);
delete[] data;
delete[] feedback;
qDebug() << "End of main";
return 0;
}
Thomas 和 David,非常感谢!
~马克