对硬盘的操作一般有:读,写,查询,分区,格式化。在Linux系统对硬盘进行自定义开发是比较容易的,因为在Linux系统,所有的设备都可以当做文件来处理。但是在万恶的Windows上就没有那么的便利了,简直就是人间地狱。最近在Linux上做了个硬盘的自定义分区硬盘,自定义格式化硬盘的程序,需要将它们移植到Windows上,遇到了很多的问题,记录下来留作备忘,也可以给后来者做个参考。

(一)基本概念

先看一个磁盘容量的计算公式:

磁盘容量=磁头数(盘面号) * 柱面数  * 扇区数 * 扇区大小

也就是说,如果需要计算出一个磁盘大的大小,我们需要先知道磁头数柱面数扇区数等信息。基本概念如下:

1)  磁头号:

磁头号也叫盘面(Side)号,硬盘有数个盘片,每盘片两个面,每个面一个磁头 。

2)  磁道:

    磁盘在格式化时被划分成许多同心圆,这些同心圆轨迹叫做磁道(Track)

3)  柱面:

    所有盘面上的同一磁道构成一个圆柱,通常称做柱面(Cylinder)

4)  扇区:

    操作系统以扇区(Sector)形式将信息存储在硬盘上,每个扇区包括512个字节的数据和一些其他信息

5)  簇:

    将物理相邻的若干个扇区称为了一个簇。

(二)磁盘信息查询

在Windows系统,硬盘操作需要通过驱动才能进行,Windows系统提供了函数DeviceIoControl 用来查询和设置设备信息。下面是一个简单得获取磁盘容量的实例:

#include<Windows.h>
#include<stdio.h>#define DISK_NAME    "\\\\.\\PhysicalDrive2"int GetDiskSize(void)
{HANDLE handle;int l_s32Ret = 0;long long l_s64Offset = 0;long l_s64OffsetH = 0;long long l_s64Ret = 0;unsigned long l_u64Type = 0;DISK_GEOMETRY   DiskGeometry;DWORD junk;                  handle = CreateFile(TEXT(DISK_NAME),GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);if (handle == INVALID_HANDLE_VALUE){fprintf(stderr, "open device Error: %ld\n", GetLastError());CloseHandle(handle);return -1;}l_s32Ret = DeviceIoControl(handle, // device to be queriedIOCTL_DISK_GET_DRIVE_GEOMETRY, // operation to performNULL, 0,                     // no input buffer&DiskGeometry, sizeof(DISK_GEOMETRY),             // output buffer&junk,                          // # bytes returned(LPOVERLAPPED)NULL);         // synchronous I/Oif (l_s32Ret){l_s64Ret = DiskGeometry.Cylinders.QuadPart * DiskGeometry.TracksPerCylinder* DiskGeometry.SectorsPerTrack * DiskGeometry.BytesPerSector;printf("l_s64Ret = %lld  l_s64Ret = %d G \n", l_s64Ret, l_s64Ret / (1024 * 1024 * 1024));}else{fprintf(stderr, "DeviceIoControl Error: %ld\n", GetLastError());}CloseHandle(handle);
}int main(void)
{GetDiskSize();getchar();return 0;
}

在Windows打开一个物理存储设备,需要使用目录:\\\\.\\PhysicalDrive,后面的数值就是在电脑中的物流设备号,系统从0开始分配。我这里打开的是一个SD卡,在我电脑中给它分的设备号是2,因为我电脑中有接两个硬盘,0,1 分配给了硬盘。上面代码执行结果如下:

在Windows中不能直接获取到当前总共有多少个物理存储设备,比如,系统并没有接口可以直接知道当前有多少个存储设备接入到了电脑中。需要开发者自己依次打开各物理设备判断是否存在,如果不能打开就表示设备不在。如果你要知道Windows上哪些逻辑分区是属于哪个分区,同样也需要自己依次去查询。下面的程序分别是通过磁盘物理通道和逻辑通道获取分区信息。所谓逻辑分区也就是我们电脑中可以看到的C盘,D盘,E盘等等。

#include<Windows.h>
#include<stdio.h>#define DISK_NAME    "\\\\.\\PhysicalDrive2"
#define DISK_PATH_LEN   128/**根据物理磁盘号获取磁盘信息**/
//IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
/**取磁盘的详细信息(包括柱面、磁道、扇区等统计信息)**/
/******************************************************************************
* Function: get the disk's drive geometry information
* input: diskPath, disk path name
* output: pdg,disk geometry information
* return: Succeed, 0-
*         Fail, -1
******************************************************************************/
BOOL GetDriveGeometry(unsigned char Disknum, DISK_GEOMETRY *pdg)
{HANDLE hDevice;               // handle to the drive to be examinedBOOL bResult;                 // results flagDWORD junk;                   // discard resultsULONGLONG DiskSize;           // size of the drive, in byteschar diskPath[64] = { 0 };sprintf_s(diskPath, "\\\\.\\PhysicalDrive%d", Disknum);//printf("Partition path = %s \n", diskPath);hDevice = CreateFile(TEXT(diskPath), // drive0,                        // no access to the driveFILE_SHARE_READ |      // share modeFILE_SHARE_WRITE,NULL,                     // default security attributesOPEN_EXISTING,            // disposition0,                        // file attributesNULL);                    // do not copy file attributesif (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive{return (FALSE);}bResult = DeviceIoControl(hDevice, // device to be queriedIOCTL_DISK_GET_DRIVE_GEOMETRY, // operation to performNULL, 0,                       // no input bufferpdg, sizeof(*pdg),            // output buffer&junk,                          // # bytes returned(LPOVERLAPPED)NULL);         // synchronous I/Oif (bResult){printf("Cylinders = %I64d\n", pdg->Cylinders);printf("Tracks/cylinder = %ld\n", (ULONG)pdg->TracksPerCylinder);printf("Sectors/track = %ld\n", (ULONG)pdg->SectorsPerTrack);printf("Bytes/sector = %ld\n", (ULONG)pdg->BytesPerSector);DiskSize = pdg->Cylinders.QuadPart * (ULONG)pdg->TracksPerCylinder *(ULONG)pdg->SectorsPerTrack * (ULONG)pdg->BytesPerSector;printf("Disk size = %I64d (Bytes) = %I64d (Gb)\n", DiskSize,DiskSize / (1024 * 1024 * 1024));}else{printf("GetDriveGeometry failed. Error %ld.\n", GetLastError());}CloseHandle(hDevice);return (bResult);
}/**根据逻辑分区获取分区信息**/
//IOCTL_DISK_GET_PARTITION_INFO_EX
DWORD DiskGetPartitionInfoEX(CHAR letter)
{HANDLE hDevice;                 // handle to the drive to be examinedBOOL result;                    // results flagDWORD readed;                   // discard resultsPARTITION_INFORMATION_EX pInformation;CHAR path[DISK_PATH_LEN];sprintf_s(path, "\\\\.\\%c:", letter);printf("Partition path = %s \n", path);hDevice = CreateFile(path, // drive to openGENERIC_READ | GENERIC_WRITE,   // access to the driveFILE_SHARE_READ | FILE_SHARE_WRITE,    //share modeNULL,             // default security attributesOPEN_EXISTING,    // disposition0,                // file attributesNULL);            // do not copy file attributeif (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive{fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());return DWORD(-1);}result = DeviceIoControl(hDevice,                // handle to deviceIOCTL_DISK_GET_PARTITION_INFO_EX, // dwIoControlCodeNULL,                            // lpInBuffer0,                               // nInBufferSize&pInformation,           // output buffersizeof(pInformation),         // size of output buffer&readed,       // number of bytes returnedNULL      // OVERLAPPED structure);printf("buffer len = %d readlen = %zd \n", sizeof(pInformation), readed);if (!result) // fail{fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());(void)CloseHandle(hDevice);return (DWORD)-1;}printf("PartitionStyle = %d \n", pInformation.PartitionStyle);printf("StartingOffset = 0x%x \n", pInformation.StartingOffset.QuadPart);printf("PartitionLength = 0x%x\n = %d G\n", pInformation.PartitionLength.QuadPart, (pInformation.PartitionLength.QuadPart) / (1024 * 1024 * 1024));printf("PartitionNumber = %d \n", pInformation.PartitionNumber);printf("RewritePartition = %ld \n", pInformation.RewritePartition);(void)CloseHandle(hDevice);return 0;
}int main(void)
{DISK_GEOMETRY pdg;            // disk drive geometry structureBOOL bResult;                 // generic results flagbResult = GetDriveGeometry(0, &pdg);printf("\n\n\n");DiskGetPartitionInfoEX('D');getchar();return 0;}

执行结果如下,打开的是物理设备0,也就是我电脑中的一个硬盘。然后是通过逻辑分区号D,查询了我电脑D盘的信息。

如果要通过分区号去查询磁盘的物理设备号等信息,可以使用下面的程序:

#include<Windows.h>
#include<stdio.h>#define DISK_NAME    "\\\\.\\PhysicalDrive2"
#define DISK_PATH_LEN   64/********************************************************
Function:    DiskGetPartitionInfoEX
Description: Query the physical disk number based on the partition number
Input:  letter
OutPut: none
Return: 0 success
none 0 error
Others:
Author: Caibiao Lee
Date:   2018-05-03
*********************************************************/
DWORD GetDiskPhyChnFromPartitionLetter(CHAR letter)
{HANDLE hDevice;                 // handle to the drive to be examinedBOOL result;                    // results flagDWORD readed;                   // discard resultsSTORAGE_DEVICE_NUMBER number;   //use this to get disk numbersCHAR path[DISK_PATH_LEN];sprintf_s(path, "\\\\.\\%c:", letter);hDevice = CreateFile(path, // drive to openGENERIC_READ | GENERIC_WRITE,    // access to the driveFILE_SHARE_READ | FILE_SHARE_WRITE,    //share modeNULL,             // default security attributesOPEN_EXISTING,    // disposition0,                // file attributesNULL);            // do not copy file attributeif (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive{fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());return DWORD(-1);}result = DeviceIoControl(hDevice,                // handle to deviceIOCTL_STORAGE_GET_DEVICE_NUMBER, // dwIoControlCodeNULL,                            // lpInBuffer0,                               // nInBufferSize&number,           // output buffersizeof(number),         // size of output buffer&readed,       // number of bytes returnedNULL      // OVERLAPPED structure);if (!result) // fail{fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());(void)CloseHandle(hDevice);return (DWORD)-1;}/**#define FILE_DEVICE_DISK                0x00000007#define FILE_DEVICE_CD_ROM              0x00000002**/printf("DeviceType = %d DeviceNumber = %d PartitionNumber = %d\n\n",number.DeviceType, number.DeviceNumber, number.PartitionNumber);(void)CloseHandle(hDevice);return number.DeviceNumber;
}

执行结果如下图,第一个是设备类型,我们可以通过这个判断是是属于哪种存储设备,比如,U盘,硬盘,CD等。DeviceNumber 是物理设备号,PartitionNumber是表示该物理设备总共有多少个分区。

如果要通过DeviceIoControl查询更多的磁盘系统信息,可以参考官方资料:DeviceIoControl function

(三)磁盘位置偏移

磁盘位置偏移,在Windows系统,它有提供两个函数来实现,SetFilePointer 和SetFilePointerEx。对于习惯了Linux编程的人来说,这两个接口都是非常的不友好,函数详细内容可见官方文档。

DWORD WINAPI SetFilePointer(_In_        HANDLE hFile,_In_        LONG   lDistanceToMove,_Inout_opt_ PLONG  lpDistanceToMoveHigh,_In_        DWORD  dwMoveMethod
);

lDistanceToMove 参数是需要偏移位置的低32位,lpDistanceToMoveHigh是需要偏移位置的高32位的地址。对于最大地址不大于4G的情况,可以直接这样使用,高位的地址就直接赋值为空。

res = SetFilePointer(handle, offset, NULL, FILE_BEGIN);

这样的接口函数,简直就是想砸电脑,又是低位值,又是高位地址。SetFilePointerEx这个函数相对比较好用些

BOOL WINAPI SetFilePointerEx(_In_      HANDLE         hFile,_In_      LARGE_INTEGER  liDistanceToMove,_Out_opt_ PLARGE_INTEGER lpNewFilePointer,_In_      DWORD          dwMoveMethod
);

liDistanceToMove 是需要偏移到的位置,lpNewFilePointer这个是用来接收接收偏移到位置的地址,不知道这个值具体有什么作用。对于大容量存储设备,比如几T大小的硬盘,可以这样进行偏移:

int SetFilePoint(void)
{HANDLE handle;unsigned long long l_u64Ret = 0;unsigned long long l_u64In = 0;long l_s64Offset = 0;long l_s64OffsetH = 0;long long l_s64Ret = 0;LARGE_INTEGER  liDistanceToMove = { 0 };LARGE_INTEGER  lNewFilePointer = { 0 };PLARGE_INTEGER lpNewFilePointer = &lNewFilePointer;handle = CreateFile(TEXT(DISK_NAME),GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);if (handle == INVALID_HANDLE_VALUE){fprintf(stderr, "open device Error: %ld\n", GetLastError());CloseHandle(handle);return -1;}liDistanceToMove.QuadPart = 5500105249280;l_u64Ret = SetFilePointerEx(handle, liDistanceToMove, lpNewFilePointer, FILE_BEGIN);if (l_u64Ret){printf("Set Point l_s32Ret = %lld \n", l_u64Ret);if (NULL != lpNewFilePointer){printf("Set Point  l_s64Offset = %lld \n", lpNewFilePointer->QuadPart);}else{printf("Set Point  l_s64Offset = %lld \n");}}else{fprintf(stderr, "%s %d set file pointer Error: %ld\n", __FILE__, __LINE__, GetLastError());}CloseHandle(handle);
}

注意:对于dwMoveMethod,与我们文件操作一样,它有三个标志,用来表示相对于开始位置,当前位置和结束位置。这里需要注意的是,在对于存储设备的操作中,偏移并不能直接偏移到设备的结尾处,偏移到先对与结尾为0的位置,实际在执行的时候会报错,不知道为什么。

(四)磁盘数据直接读写

在Linux系统我们操作一个存储设备,可以向操作一个文件一样直接操作,比如我要修改第二扇区的第5个字节,在Linux系统我们可以直接打开设备,将设备描述符偏移到第二扇区的第五个字节,直接写一个字节的数据就可以修改该位置的数据了。但是,对于Windows系统则不是这样了,如果要直接对存储设备进行读写操作,它是以扇区为单位进行操作的。还是修改第二扇区的第5个字节的数据,我首先需要将第二扇区的所有数据都读取出来缓存,然后在缓存中修改第二扇区的第5个字节的数据,然后再将缓存的数据都写回到第二扇区。我是刚接触Windows系统编程,不知道有没有其便捷的方式写入数据,总觉得在Windows下做设备文件编程,迟早是要被气疯的。下面是一个直接读写数据的实例:

#include<Windows.h>
#include<stdio.h>#define DISK_NAME    "\\\\.\\PhysicalDrive2"
#define DISK_PATH_LEN   64int SetFilePoint(HANDLE handle, long offset)
{int res;res = SetFilePointer(handle, offset, NULL, FILE_BEGIN);if (INVALID_SET_FILE_POINTER == res){fprintf(stderr, "set file pointer Error: %ld\n", GetLastError());CloseHandle(handle);return -1;}else{printf("set file point = %d \n", res);}return 0;
}int SetFilePointEx(HANDLE handle, LARGE_INTEGER  liDistanceToMove)
{unsigned long long l_u64Ret = 0;LARGE_INTEGER  lNewFilePointer = { 0 };PLARGE_INTEGER lpNewFilePointer = &lNewFilePointer;l_u64Ret = SetFilePointerEx(handle, liDistanceToMove, lpNewFilePointer, FILE_BEGIN);if (l_u64Ret){printf("Set Point l_s32Ret = %lld \n", l_u64Ret);if (NULL != lpNewFilePointer){printf("Set Point  l_s64Offset = %lld \n", lpNewFilePointer->QuadPart);}else{printf("Set Point  l_s64Offset = %lld \n");}}else{fprintf(stderr, "%s %d set file pointer Error: %ld\n", __FILE__, __LINE__, GetLastError());}return 0;
}int ReadDataFromDevice(void)
{HANDLE handle;int res = 0;unsigned char arru8Buffer[1024] = { 0 };unsigned long size = 0;unsigned long offset = 0;unsigned long  readsize = 0;handle = CreateFile(TEXT(DISK_NAME),GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);if (handle == INVALID_HANDLE_VALUE){fprintf(stderr, "open device Error: %ld\n", GetLastError());return -1;}offset = 512;SetFilePoint(handle, offset);size = 512;res = ReadFile(handle, arru8Buffer, size, &readsize, NULL);if (0 != res){if (size == readsize){for (unsigned int i = 0; i < readsize; ){printf("0x%x ", arru8Buffer[i++]);if (0 == i % 16){printf("\n");}}}}else{fprintf(stderr, "Read data  Error: %ld\n", GetLastError());}CloseHandle(handle);return 0;
}int WriteDataToDevice(void)
{HANDLE handle;int res = 0;unsigned char arru8Buffer[1024] = { 0 };unsigned long size = 0;unsigned long offset = 0;unsigned long  writesize = 0;offset = 512 ;writesize = 36;handle = CreateFile(TEXT(DISK_NAME),GENERIC_READ| GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);if (handle == INVALID_HANDLE_VALUE){fprintf(stderr, "open device Error: %ld\n", GetLastError());CloseHandle(handle);return -1;}res = SetFilePoint(handle, offset);res = WriteFile(handle, arru8Buffer, 512, &writesize, NULL);if (0 == res){fprintf(stderr, "Write File Error: %ld\n", GetLastError());CloseHandle(handle);return -1;}else{}CloseHandle(handle);return 0;
}int main(void)
{ReadDataFromDevice();WriteDataToDevice();ReadDataFromDevice();getchar();return 0;
}

上面代码操作的物理设备2,是我的一个SD卡。注意:不要对自己系统的硬盘低扇区进行写入操作,低扇区是一些分区信息,写错了就得重新分区格式化了,如果是系统盘,那就得重新安装系统了。

上面我是先将整个第二扇区的数据读取出来并打印显示,然后再将第二扇区的开始36个字节写为0。从下面的结果图可以看出,它实际上是把整个扇区都擦除了。

注意,关于数据的直接读取,读取的最小单位是一个扇区,我电脑是512字节。如果读取的数据长度不是512的整数倍,读取接口会报错,错误码为87。

(五)注意事项

(1)对硬盘的所有操作都需要管理员权限,因此需要将自己的IDE设置配置合适的权限,不然运行会提示错误码5。VS设置方法可以参考我之前博客:VS2017中设置程序以管理员身份运行

(2)在对存储设备位置进行偏移的时候,不能相对于结尾做偏移,不知道为什么。

(3)直接数据读写的时候,是以扇区的形式对数据进行读写,如果只写几个字节数据,系统并不会只修改这几个字节数据,而是怎个扇区擦除然后再写入需要写入的几个字节数据。就算你先把句柄偏移到需要写入的位置,实际写入的时候它也是按扇区对齐的,不一定是从你偏移到的位置读写。

官方文档链接:

System Error Codes

DeviceIoControl function

CreateFile function

SetFilePointer function

国内对于Windows磁盘操作的资料比较少比较乱,建议多看官方文档,不然还是比较容易掉坑里。

如果有时间,后面在写 存储设备的分区和格式化操作

 

===============================2022.08.28===============================

博客附件资料下载及博客内容更新,可以通过下面博客获取

li_wen01嵌入式开发相关博客更新目录及资料下载说明

liwen01 2022.08.28 日更新 

===============================2022.08.28===============================

Windows硬盘等存储设备读写查询分区格式化(一)相关推荐

  1. pe读linux硬盘分区工具_已经成功把pe安装到linux分区,并能读写linux分区

    优点:Windows中一般是不能读写linux分区的,除非用了相应的工具.因此把pe安装到linux分区是比较安全的,而且pe可以读写linux分区,这样你的系统也可以备份到linux分区了. 步骤: ...

  2. solaris下使用USB 海量存储设备

    第8 章• 使用USB 设备(任务) 139 从Solaris 10 1/06 发行版开始,vold 可自动挂载热插拔设备.有关更多信息,请参见第 124 页中的"vold 用于识别热插拔U ...

  3. 10款存储设备测试软件

    http://www.dostor.com/article/2014-01-18/7180637.shtml [导读]随着云存储.大数据的持续升温,存储的地位也水涨船高,存储设备的性能也越来越重要.存 ...

  4. 把Hyper-V Server 2008R2装进USB存储设备

    把Hyper-V Server 2008R2装进USB存储设备 之前在发布了windows8的时候,有一个比较吸引人的feature就是windows to go,借助该feature可以把Windo ...

  5. 硬盘由于io设备错误无法运行此项请求_移动硬盘坏了真倒霉,祸不单行U盘也坏了,数据怎么存储才安全?...

    许多人在使用电脑的存储设备时,一般都觉得存储设备比较不容易损坏,但是恰恰就是这种思想让很多人碰到了存储设备发生损坏后造成数据丢失,或是误操作后将许多的重要数据弄丢,这时才觉得存储设备并不是不容易出问题 ...

  6. Linux和Windows硬盘分区设置

    Linux和Windows硬盘分区设置 现在的Linux都带了图形界面的分区工具,所以老的fdisk命令使用就不在这再写了. 一.系统引导过程简介 系统引导过程主要由以下几个步骤组成(以硬盘启动为例) ...

  7. linux双系统内存分配,Linux和Windows硬盘分区设置(二)

    三.关于硬盘分区划分标准及合理分区结构 1.硬盘分区划分标准 硬盘的分区由主分区.扩展分区和逻辑分区组成:所以我们在对硬盘分区时要遵循这个标准:主分区(包括扩展分区)的最大个数是四个,主分区(包含扩展 ...

  8. windows下能读写linux分区的软件

    windows下能读写linux分区的软件 1. ext2ifs 这个工具与explore2fs都是John Newbigin使用Delphi写的,explore2fs Copyright (C) 2 ...

  9. 【Android 文件管理】分区存储 ( 创建与查询图片文件 )

    文章目录 一.分区存储模式下使用 MediaStore 插入图片 二.分区存储模式下使用 MediaStore 查询图片 三.相关文档资料 Android 分区存储系列博客 : [Android 文件 ...

最新文章

  1. 对话IT:火狐4.0正式版发布 庆功会上听宫博士“酒后真言”
  2. nag在逆向中是什么意思_OD 实验(四) - 去除 NAG 窗口的几种方法
  3. Computer:MediaPreview的简介、安装、使用方法之详细攻略
  4. 如果沟通有范式,它会是怎么样子?
  5. 解决FastJson中“$ref 循环引用检测”的问题的几种方式
  6. Zebra项目:分析、实施与测试
  7. Java NIO群聊系统
  8. VB 字符串续行符最多25行…………
  9. 基于JAVA+SpringMVC+MYSQL的校园宿舍管理系统
  10. 《矩阵与变换》教学中的几个“务必”
  11. Android5.1/7.1 Selinux JNI访问新增/dev/xxx设备节点
  12. C#强制关闭Excel进程(通过COM使用Excel时)
  13. 今天没白过之《Linux的变量》
  14. DSP 程序远程升级 / Bootloader设计指南(五)—— FLASH擦写操作
  15. msfvenom 生成php木马,利用msfvenom生成木马程序
  16. 树莓派做BT下载器:transmission
  17. iec104点号_IEC104规约报文说明(104报文解释的较好的文本)
  18. ajax 与 php 简单聊天室
  19. 可以写一个表白代码吗
  20. 怎样规划自己的研究生生活?

热门文章

  1. python云计算主要是干嘛的_阿里巴巴python 云计算是干什么的
  2. 找不到模块“@/....”或其相应的类型声明。
  3. xml在u3d的使用[u3d_rpg游戏开发之物品管理(四)]
  4. python学习日记(2)——变量|字符串|数字布尔类型
  5. [2021-09-10] 【入门1】顺序结构——多行字符串的打印
  6. C++实现鼠标手写+自绘按钮
  7. 华为手机设置屏幕常亮
  8. Roxio MyDVD(DVD制作软件)v3.0.0.8中文版
  9. python中数字逐个递增_Python中递增和递减运算符的行为
  10. 通过商业智能(BI)可视化数据分析了解布洛芬的产销情况