vhd 转换为img源码,由VS2010 C++编译

简介:

//51CTO首发此文,转载请保留完整信息

本工程主要由vhd2img.cpp layout.h组成,C代码实现。

vhd2img.cpp:

 
 
  1. // vhd2img.cpp : 定义控制台应用程序的入口点。 
  2. //by www.frombyte.cn zhangyu
  3. //北亚数据恢复中心(www.sjhf.net)张宇 2012/1/6 发表于51cto
  4.  
  5. #include "stdafx.h" 
  6. #include <windows.h> 
  7. #include "layout.h" 
  8. #define VHD_OPENED 1 
  9. #define VHD_UNOPEN 0 
  10. #define BIT_MASK 0x80 
  11. static inline int test_bit (byte *addr, u32 nr) 
  12.     return ((addr[nr >> 3] << (nr & 7)) & BIT_MASK) != 0; 
  13. }; 
  14. static inline void set_bit (byte *addr, u32 nr) 
  15.     addr[nr >> 3] |= (BIT_MASK >> (nr & 7)); 
  16.  
  17. HANDLE hIn,hOut; 
  18.  
  19. struct vhd_info 
  20.     u64 uSize; 
  21.     u32 *bat; 
  22.     byte *batmap; 
  23.     u64 uBlkSize; 
  24.     u64 uBatNum; 
  25.     u32 spb; //secters per block 
  26.     u32 log2spb; 
  27.     u32 vhd_status; 
  28.     bool is_have_batmap; 
  29. }; 
  30.  
  31.  
  32. int open_vhd(TCHAR *name,vhd_info &vhd) 
  33.     hd_ftr vhd_head; 
  34.     hIn = CreateFile(name,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); 
  35.     if(hIn == INVALID_HANDLE_VALUE) 
  36.     { 
  37.         _tprintf(_T("ERROR%d:source VHD file open error!\n"),GetLastError()); 
  38.         return -1; 
  39.     } 
  40.     DWORD bRead; 
  41.     ReadFile(hIn,(char*)&vhd_head,sizeof(hd_ftr),&bRead,NULL); 
  42.     if(bRead != sizeof(hd_ftr)) 
  43.         return -2; 
  44.     if(strncmp(vhd_head.cookie,HD_COOKIE,8) != 0) 
  45.     { 
  46.         _tprintf(_T("the source file is not a valid VHD format!\n")); 
  47.         return -3; 
  48.     } 
  49.     if( (LE32(vhd_head.type) != HD_TYPE_DYNAMIC) && (LE32(vhd_head.type) != HD_TYPE_DIFF) ) 
  50.         return -4; 
  51.  
  52.     dd_hdr vhd_sparce; 
  53.     ReadFile(hIn,(char*)&vhd_sparce,sizeof(dd_hdr),&bRead,NULL); 
  54.     if(bRead != sizeof(dd_hdr)) 
  55.         return -5; 
  56.     if(strncmp(vhd_sparce.cookie,DD_COOKIE,8) != 0) 
  57.         return -6; 
  58.     vhd.uBlkSize = LE32(vhd_sparce.block_size); 
  59.     vhd.uBatNum = LE32(vhd_sparce.max_bat_size); 
  60.     vhd.bat = new u32 [vhd.uBatNum]; 
  61.     LARGE_INTEGER liPoi,liNew; 
  62.     liPoi.QuadPart = LE64(vhd_sparce.table_offset); 
  63.     SetFilePointerEx(hIn,liPoi,&liNew,FILE_BEGIN); 
  64.     ReadFile(hIn,vhd.bat,vhd.uBatNum * sizeof(u32),&bRead,NULL); 
  65.     if(bRead != vhd.uBatNum * sizeof(u32)) 
  66.         return -7; 
  67.  
  68.     dd_batmap_hdr batmap_head; 
  69.     ReadFile(hIn,(char*)&batmap_head,sizeof(dd_batmap_hdr),&bRead,NULL); 
  70.     if(bRead != sizeof(dd_batmap_hdr)) 
  71.         return -8; 
  72.     if(strncmp(batmap_head.cookie,VHD_BATMAP_COOKIE,8) != 0) 
  73.         vhd.is_have_batmap = FALSE; 
  74.     else 
  75.         vhd.is_have_batmap = TRUE; 
  76.  
  77.     vhd.spb = vhd.uBlkSize >> VHD_SECTOR_SHIFT; 
  78.     vhd.vhd_status = VHD_OPENED; 
  79.     return 1; 
  80. int read_vhd(vhd_info &vhd,byte* buf,u32 blkno) 
  81.     byte spb_bitmap[VHD_SECTOR_SIZE];  
  82.     DWORD bRead; 
  83.     if(vhd.bat[blkno] == 0xFFFFFFFF) 
  84.         return 2; 
  85.     byte *tbuf = new byte [vhd.uBlkSize]; 
  86.     LARGE_INTEGER liPoi,liNew; 
  87.     liPoi.QuadPart = (u64)(LE32(vhd.bat[blkno])) << VHD_SECTOR_SHIFT; 
  88.     SetFilePointerEx(hIn,liPoi,&liNew,FILE_BEGIN); 
  89.     ReadFile(hIn,spb_bitmap,VHD_SECTOR_SIZE,&bRead,NULL); 
  90.     if(bRead != VHD_SECTOR_SIZE) 
  91.     { 
  92.         delete [] tbuf; 
  93.         return -2; 
  94.     } 
  95.     ReadFile(hIn,tbuf,vhd.uBlkSize,&bRead,NULL); 
  96.     if(bRead != vhd.uBlkSize) 
  97.     { 
  98.         delete [] tbuf; 
  99.         return -3; 
  100.     } 
  101.     for(u32 i=0;i<vhd.spb;i++) 
  102.     { 
  103.         if(test_bit(spb_bitmap,i)) //位为1,表示磁盘上有数据,需要拷贝 
  104.         { 
  105.             memcpy(buf + i * VHD_SECTOR_SIZE,tbuf + i*VHD_SECTOR_SIZE,VHD_SECTOR_SIZE); 
  106.         } 
  107.     } 
  108.     delete [] tbuf; 
  109.     return 1; 
  110.  
  111. int _tmain(int argc, _TCHAR* argv[]) 
  112.     if(argc != 3)  
  113.     {        
  114.         _tprintf(_T("vhd2img.exe <input vhd name> <output file/disk name>\n")); 
  115.         _tprintf(_T("eg. vhd2img.exe my.vhd d:\\my.img \n")); 
  116.         _tprintf(_T("    vhd2img.exe my.vhd \"\\\\.\\physicaldrive1\" (write to hardisk 1)\n")); 
  117.         return 0; 
  118.     } 
  119.  
  120.     vhd_info vhd; 
  121.  
  122.     //打开输入vhd 
  123.     if(open_vhd(argv[1],vhd) != 1) 
  124.         return -1; 
  125.  
  126.     //生成目标文件 
  127.     HANDLE hOut = CreateFile(argv[2],GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL); 
  128.     if(hOut == INVALID_HANDLE_VALUE) 
  129.     { 
  130.         hOut = CreateFile(argv[2],GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,0,NULL); 
  131.         if(hOut == INVALID_HANDLE_VALUE) 
  132.         { 
  133.             _tprintf(_T("ERROR%d:the dest disk/file open error!\n"),GetLastError()); 
  134.             return -1; 
  135.         } 
  136.     } 
  137.     //u32 nBlkSize = vhd.uBlkSize; 
  138.     byte* buf = new byte [vhd.uBlkSize]; 
  139.     u32 nBitmapSize = (vhd.spb >> 3 ); 
  140.     byte* bitmap = new byte [nBitmapSize]; 
  141.     DWORD bWrite; 
  142.     LARGE_INTEGER liPos,liNew; 
  143.  
  144.     for(int i=0;i<vhd.uBatNum;i++)  
  145.     { 
  146.         memset(buf,0,vhd.uBlkSize); 
  147.         memset(bitmap,0,nBitmapSize); 
  148.         if(read_vhd(vhd,buf,i) != 1) //读错误或属于稀疏空间 
  149.             continue
  150.         liPos.QuadPart = (u64)i * (u64)vhd.uBlkSize; 
  151.         SetFilePointerEx(hOut,liPos,&liNew,FILE_BEGIN); 
  152.         WriteFile(hOut,buf,vhd.uBlkSize,&bWrite,NULL); 
  153.         if(bWrite != vhd.uBlkSize) 
  154.         { 
  155.             _tprintf(_T("ERROR%d:#%dblk (2MB) write error!\n\n"),GetLastError(),i); 
  156.             return -1; 
  157.         } 
  158.     } 
  159.  
  160.     liPos.QuadPart = (u64)vhd.uBatNum * (u64)vhd.uBlkSize; 
  161.     SetFilePointerEx(hOut,liPos,&liNew,FILE_BEGIN); 
  162.     SetEndOfFile(hOut); 
  163.  
  164.     //释放 
  165.     delete [] buf; 
  166.     delete [] bitmap; 
  167.     CloseHandle(hIn); 
  168.     CloseHandle(hOut); 
  169.  
  170.     return 1; 
  171.  
  172.  

layout.h:

 

 
 
  1. /* Copyright (c) 2008, XenSource Inc. 
  2.  * All rights reserved. 
  3.  * 
  4.  * Redistribution and use in source and binary forms, with or without 
  5.  * modification, are permitted provided that the following conditions are met: 
  6.  *     * Redistributions of source code must retain the above copyright 
  7.  *       notice, this list of conditions and the following disclaimer. 
  8.  *     * Redistributions in binary form must reproduce the above copyright 
  9.  *       notice, this list of conditions and the following disclaimer in the 
  10.  *       documentation and/or other materials provided with the distribution. 
  11.  *     * Neither the name of XenSource Inc. nor the names of its contributors 
  12.  *       may be used to endorse or promote products derived from this software 
  13.  *       without specific prior written permission. 
  14.  * 
  15.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  16.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  17.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
  18.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
  19.  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  20.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  21.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  22.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  23.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  24.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  25.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  26. */ 
  27. typedef UINT32 u32; 
  28. typedef UINT64 u64; 
  29. typedef unsigned char   vhd_uuid_t[16]; 
  30. #ifndef __VHD_H__ 
  31. #define __VHD_H__ 
  32.  
  33.  
  34. #define DEBUG 1 
  35.  
  36. /* ---------------------------------------------------------------------- */ 
  37. /* General definitions.                                                   */ 
  38. /* ---------------------------------------------------------------------- */ 
  39.  
  40. #define VHD_SECTOR_SIZE  512 
  41. #define VHD_SECTOR_SHIFT   9 
  42.  
  43. /* ---------------------------------------------------------------------- */ 
  44. /* This is the generic disk footer, used by all disks.                    */ 
  45. /* ---------------------------------------------------------------------- */ 
  46.  
  47. struct hd_ftr { 
  48.   char   cookie[8];       /* Identifies original creator of the disk      */ 
  49.   u32    features;        /* Feature Support -- see below                 */ 
  50.   u32    ff_version;      /* (major,minor) version of disk file           */ 
  51.   u64    data_offset;     /* Abs. offset from SOF to next structure       */ 
  52.   u32    timestamp;       /* Creation time.  secs since 1/1/2000GMT       */ 
  53.   char   crtr_app[4];     /* Creator application                          */ 
  54.   u32    crtr_ver;        /* Creator version (major,minor)                */ 
  55.   u32    crtr_os;         /* Creator host OS                              */ 
  56.   u64    orig_size;       /* Size at creation (bytes)                     */ 
  57.   u64    curr_size;       /* Current size of disk (bytes)                 */ 
  58.   u32    geometry;        /* Disk geometry                                */ 
  59.   u32    type;            /* Disk type                                    */ 
  60.   u32    checksum;        /* 1's comp sum of this struct.                 */ 
  61.   vhd_uuid_t uuid;        /* Unique disk ID, used for naming parents      */ 
  62.   char   saved;           /* one-bit -- is this disk/VM in a saved state? */ 
  63.   char   hidden;          /* tapdisk-specific field: is this vdi hidden?  */ 
  64.   char   reserved[426];   /* padding                                      */ 
  65. }; 
  66.  
  67. /* VHD cookie string. */ 
  68. static const char HD_COOKIE[9]  =  "conectix"
  69.  
  70. /* Feature fields in hd_ftr */ 
  71. #define HD_NO_FEATURES     0x00000000 
  72. #define HD_TEMPORARY       0x00000001 /* disk can be deleted on shutdown */ 
  73. #define HD_RESERVED        0x00000002 /* NOTE: must always be set        */ 
  74.  
  75. /* Version field in hd_ftr */ 
  76. #define HD_FF_VERSION      0x00010000 
  77.  
  78. /* Known creator OS type fields in hd_ftr.crtr_os */ 
  79. #define HD_CR_OS_WINDOWS   0x5769326B /* (Wi2k) */ 
  80. #define HD_CR_OS_MACINTOSH 0x4D616320 /* (Mac ) */ 
  81.  
  82. /* 
  83.  * version 0.1:  little endian bitmaps 
  84.  * version 1.1:  big endian bitmaps; batmap 
  85.  * version 1.2:  libvhd 
  86.  * version 1.3:  batmap version bump to 1.2 
  87.  */ 
  88. #define VHD_VERSION(major, minor)  (((major) << 16) | ((minor) & 0x0000FFFF)) 
  89. #define VHD_CURRENT_VERSION        VHD_VERSION(1, 3) 
  90.  
  91. /* Disk geometry accessor macros. */ 
  92. /* Geometry is a triple of (cylinders (2 bytes), tracks (1 byte), and  
  93.  * secotrs-per-track (1 byte))  
  94.  */ 
  95. #define GEOM_GET_CYLS(_g)  (((_g) >> 16) & 0xffff) 
  96. #define GEOM_GET_HEADS(_g) (((_g) >> 8)  & 0xff) 
  97. #define GEOM_GET_SPT(_g)   ((_g) & 0xff) 
  98.  
  99. #define GEOM_ENCODE(_c, _h, _s) (((_c) << 16) | ((_h) << 8) | (_s)) 
  100.  
  101. /* type field in hd_ftr */ 
  102. #define HD_TYPE_NONE       0 
  103. #define HD_TYPE_FIXED      2  /* fixed-allocation disk */ 
  104. #define HD_TYPE_DYNAMIC    3  /* dynamic disk */ 
  105. #define HD_TYPE_DIFF       4  /* differencing disk */ 
  106.  
  107. /* String table for hd.type */ 
  108. static const char *HD_TYPE_STR[7] = { 
  109.         "None",                    /* 0 */ 
  110.         "Reserved (deprecated)",   /* 1 */ 
  111.         "Fixed hard disk",         /* 2 */ 
  112.         "Dynamic hard disk",       /* 3 */ 
  113.         "Differencing hard disk",  /* 4 */ 
  114.         "Reserved (deprecated)",   /* 5 */ 
  115.         "Reserved (deprecated)"    /* 6 */ 
  116. }; 
  117.  
  118. #define HD_TYPE_MAX 6 
  119.  
  120. struct prt_loc { 
  121.   u32    code;            /* Platform code -- see defines below.          */ 
  122.   u32    data_space;      /* Number of 512-byte sectors to store locator  */ 
  123.   u32    data_len;        /* Actual length of parent locator in bytes     */ 
  124.   u32    res;             /* Must be zero                                 */ 
  125.   u64    data_offset;     /* Absolute offset of locator data (bytes)      */ 
  126. }; 
  127.  
  128. /* Platform Codes */ 
  129. #define PLAT_CODE_NONE  0x0 
  130. #define PLAT_CODE_WI2R  0x57693272  /* deprecated                         */ 
  131. #define PLAT_CODE_WI2K  0x5769326B  /* deprecated                         */ 
  132. #define PLAT_CODE_W2RU  0x57327275  /* Windows relative path (UTF-16)     */ 
  133. #define PLAT_CODE_W2KU  0x57326B75  /* Windows absolute path (UTF-16)     */ 
  134. #define PLAT_CODE_MAC   0x4D616320  /* MacOS alias stored as a blob.      */ 
  135. #define PLAT_CODE_MACX  0x4D616358  /* File URL (UTF-8), see RFC 2396.    */ 
  136.  
  137. /* ---------------------------------------------------------------------- */ 
  138. /* This is the dynamic disk header.                                       */ 
  139. /* ---------------------------------------------------------------------- */ 
  140.  
  141. struct dd_hdr { 
  142.   char   cookie[8];       /* Should contain "cxsparse"                    */ 
  143.   u64    data_offset;     /* Byte offset of next record. (Unused) 0xffs   */ 
  144.   u64    table_offset;    /* Absolute offset to the BAT.                  */ 
  145.   u32    hdr_ver;         /* Version of the dd_hdr (major,minor)          */ 
  146.   u32    max_bat_size;    /* Maximum number of entries in the BAT         */ 
  147.   u32    block_size;      /* Block size in bytes. Must be power of 2.     */ 
  148.   u32    checksum;        /* Header checksum.  1's comp of all fields.    */ 
  149.   vhd_uuid_t prt_uuid;    /* ID of the parent disk.                       */ 
  150.   u32    prt_ts;          /* Modification time of the parent disk         */ 
  151.   u32    res1;            /* Reserved.                                    */ 
  152.   char   prt_name[512];   /* Parent unicode name.                         */ 
  153.   struct prt_loc loc[8];  /* Parent locator entries.                      */ 
  154.   char   res2[256];       /* Reserved.                                    */ 
  155. }; 
  156.  
  157. /* VHD cookie string. */ 
  158. static const char DD_COOKIE[9]  =  "cxsparse"
  159.  
  160. /* Version field in hd_ftr */ 
  161. #define DD_VERSION 0x00010000 
  162.  
  163. /* Default blocksize is 2 meg. */ 
  164. #define DD_BLOCKSIZE_DEFAULT 0x00200000 
  165.  
  166. #define DD_BLK_UNUSED 0xFFFFFFFF 
  167.  
  168. struct dd_batmap_hdr { 
  169.   char   cookie[8];       /* should contain "tdbatmap"                    */ 
  170.   u64    batmap_offset;   /* byte offset to batmap                        */ 
  171.   u32    batmap_size;     /* batmap size in sectors                       */ 
  172.   u32    batmap_version;  /* version of batmap                            */ 
  173.   u32    checksum;        /* batmap checksum -- 1's complement of batmap  */ 
  174. }; 
  175.  
  176. static const char VHD_BATMAP_COOKIE[9] = "tdbatmap"
  177.  
  178. /* 
  179.  * version 1.1: signed char checksum 
  180.  */ 
  181. #define VHD_BATMAP_VERSION(major, minor)  (((major) << 16) | ((minor) & 0x0000FFFF)) 
  182. #define VHD_BATMAP_CURRENT_VERSION        VHD_BATMAP_VERSION(1, 2) 
  183.  
  184. /* Layout of a dynamic disk: 
  185.  * 
  186.  * +-------------------------------------------------+ 
  187.  * | Mirror image of HD footer (hd_ftr) (512 bytes)  | 
  188.  * +-------------------------------------------------+ 
  189.  * | Sparse drive header (dd_hdr) (1024 bytes)       | 
  190.  * +-------------------------------------------------+ 
  191.  * | BAT (Block allocation table)                    | 
  192.  * |   - Array of absolute sector offsets into the   | 
  193.  * |     file (u32).                                 | 
  194.  * |   - Rounded up to a sector boundary.            | 
  195.  * |   - Unused entries are marked as 0xFFFFFFFF     | 
  196.  * |   - max entries in dd_hdr->max_bat_size         | 
  197.  * +-------------------------------------------------+ 
  198.  * | Data Block 0                                    | 
  199.  * | Bitmap (padded to 512 byte sector boundary)     | 
  200.  * |   - each bit indicates whether the associated   | 
  201.  * |     sector within this block is used.           | 
  202.  * | Data                                            | 
  203.  * |   - power-of-two multiple of sectors.           | 
  204.  * |   - default 2MB (4096 * 512)                    | 
  205.  * |   - Any entries with zero in bitmap should be   | 
  206.  * |     zero on disk                                | 
  207.  * +-------------------------------------------------+ 
  208.  * | Data Block 1                                    | 
  209.  * +-------------------------------------------------+ 
  210.  * | ...                                             | 
  211.  * +-------------------------------------------------+ 
  212.  * | Data Block n                                    | 
  213.  * +-------------------------------------------------+ 
  214.  * | HD Footer (511 bytes)                           | 
  215.  * +-------------------------------------------------+ 
  216.  */ 
  217. #define LE32(x)  (( (u32)x >>24) ^ ((u32)x <<8 >>24 <<8) ^ ((u32)x <<16 >>24 <<16) ^ ((u32)x <<24)) 
  218. #define LE64(x)  (((u64)LE32((u32)x)) <<32 ) ^ ( LE32((u32)((u64)x >>32)) )  
  219.  
  220.  
  221. #endif 

 





本文转自 张宇 51CTO博客,原文链接:http://blog.51cto.com/zhangyu/1109742,如需转载请自行联系原作者
目录
相关文章
|
30天前
|
安全 编译器 C++
【C++20概念】编译时多态性的力量
【C++20概念】编译时多态性的力量
46 0
|
16天前
|
存储 人工智能 机器人
【C++面向对象】C++图书管理系统 (源码)【独一无二】
【C++面向对象】C++图书管理系统 (源码)【独一无二】
|
16天前
|
存储 人工智能 机器人
【C/C++】C++学籍信息管理系统(源码+报告)【独一无二】
【C/C++】C++学籍信息管理系统(源码+报告)【独一无二】
|
21天前
|
人工智能 机器人 测试技术
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
|
21天前
|
存储 人工智能 BI
【C++面向对象】C++银行卡管理系统(源码+论文)【独一无二】
【C++面向对象】C++银行卡管理系统(源码+论文)【独一无二】
|
28天前
|
算法 编译器 程序员
深入理解C++编译模式:了解Debug和Release的区别
深入理解C++编译模式:了解Debug和Release的区别
62 2
|
30天前
|
算法 编译器 C++
【C++ 泛型编程 中级篇】C++ 编译时技术:探索 if constexpr 和 std::enable_if
【C++ 泛型编程 中级篇】C++ 编译时技术:探索 if constexpr 和 std::enable_if
40 0
|
30天前
|
安全 算法 编译器
【C++ 静态断言的技巧】掌握C++中static_assert的力量:深入探讨编译时检查
【C++ 静态断言的技巧】掌握C++中static_assert的力量:深入探讨编译时检查
42 1
|
30天前
|
存储 算法 编译器
【C++ 关键字 static_assert 相关问题】C++ 关于静态断言的编译问题 ,深入了解静态断言
【C++ 关键字 static_assert 相关问题】C++ 关于静态断言的编译问题 ,深入了解静态断言
28 0
|
1月前
|
编解码 算法 程序员
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南(三)
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南
27 0

热门文章

最新文章