1. windows 下磁盘文件读写
下面是读取D:\磁盘上的第0扇区 512 Bytes
CreateFile()打开磁盘,获取文件句柄;
SetFilePointer()设置读写的位置;
ReadFile()读取磁盘扇区数据。
HANDLE hFile;
char drive[] = "\\\\.\\D:"; //------- \\.\D: ----- //------- 创建文件句柄 ------ hFile = CreateFile(drive, // 还可以为硬盘"\\\\.\\physicalDrive0"绝对读写, 或类似"D:\\abc\\fileName.txt"文件形式, GENERIC_READ, // 打开方式 FILE_SHARE_WRITE|FILE_SHARE_READ, // 前者表示之后打开该文件的程序 only write, 后者为only read NULL, // 安全属性 LPSECURITY_ATTRIBUTES OPEN_EXISTING, // how to Create 0, NULL); // if(hFile == INVALID_HANDLE_VALUE) return;
// 中间两个参数分别是偏移字节数的低32Bit数值,和高32Bit的地址, 高32Bit不用则为零; 此处表示D:\从首扇区起,偏移字节数为0
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
DWORD lenRead;
ReadFile(hFile, buf, 512, &lenRead, NULL); // 在设定的位置起,读取512Byte到缓存buf; 这里读取的是分区引导扇区
2. 获取磁盘所有驱动器
DWORD allDrive = GetLogicalDrive(). // 返回值共有 32 Bits,从低位到高位分别表示A,B,C,D,E,..... // 比如0x0000 007C, 即 01111100, 表示有C,D,E,F,G 共5个驱动器;
3. NTFS 文件系统
一个NTFS系统是由引导扇区,主文件表MFT,和数据区组成;另外MFT有一部分重要备份在数据区。
(1) 引导扇区
若果D:\盘是NTFS文件系统,那么上面得到的第0个扇区数据 buf 偏移0x03开始的8个Bytes就是"NTFS ",表示这个扇区就是NTFS
的引导记录。这第0个扇区也就是$Boot扇区,这个扇区包含了该卷的 BPB 和扩展BPB参数,可以得到该卷的卷大小,磁头数,扇区大小,簇
大小等等参数;要解析一个NTFS卷的文件结构也是从这里的BPB参数开始的。
解析时有用到的是这个扇区的前 88 (0x58) 个Bytes,剩余的是引导代码和结束标志"55 AA",前88个字节具体结构如下:
1 typedef struct NTFS_BPB{ // 在cmd 输入 fsutil fsinfo ntfsinfo d: 查询 NTFS 信息 2 UCHAR jmpCmd[3]; 3 UCHAR s_ntfs[8]; // "NTFS " 标志 4 // 0x0B 5 UCHAR bytesPerSec[2]; // 0x0200 扇区大小,512B 6 UCHAR SecsPerClu; // 0x08 每簇扇区数,4KB 7 UCHAR rsvSecs[2]; // 保留扇区 8 UCHAR noUse01[5]; //
9 // 0x15 10 UCHAR driveDscrp; // 0xF8 磁盘介质 -- 硬盘 11 UCHAR noUse02[2]; //
12 // 0x18 13 UCHAR SecsPerTrack[2]; // 0x003F 每道扇区数 63 14 UCHAR Headers[2]; // 0x00FF 磁头数 15 UCHAR secsHide[4]; // 0x3F 隐藏扇区 16 UCHAR noUse03[8]; // 17 // 0x28 18 UCHAR allSecsNum[8]; // 卷总扇区数, 高位在前, 低位在后 19 // 0x30 20 UCHAR MFT_startClu[8]; // MFT 起始簇 21 UCHAR MFTMirr_startClu[8]; // MTF 备份 MFTMirr 位置 22 //0x40 23 UCHAR cluPerMFT[4]; // 每记录簇数 0xF6 24 UCHAR cluPerIdx[4]; // 每索引簇数 25 //0x48 26 UCHAR SerialNum[8]; // 卷序列号
27 UCHAR checkSum[8]; // 校验和 28 }Ntfs_Bpb,*pNtfs_Bpb;
(2) 关于簇
在一个分区中引导记录扇区所在的簇编号为0,往后的簇编号1,2,3等等一直到卷尾,这就是一个分区的逻辑簇号(LCN);计算
逻辑扇区号:LCN * 簇大小,簇的大小在BPB参数中找到,一般为8个扇区4KB;以此可以由 MFT 起始簇 MFT_startClu 计算
出第一个 MFT 项(记录)的位置。
VCN,虚拟簇号,给一个文件从它的首簇开始编号,为0,依次递增一直到文件的尾簇,在物理上不一定连续。
(3) 主文件表 (Master File Table, MFT)
MFT 是由一条条 MFT 项(记录)所组成的,而且每项大小是固定的(一般为1KB),MFT保留了前16项用于特殊文件记录,称为元数据,
元数据在磁盘上是物理连续的,编号为0~15;如果$MFT的偏移为0x0C0000000, 那么下一项的偏移就是0x0C0000400,在下一项就是
0x0C0000800等等;
MFT记录了整个卷的所有文件 (包括MFT本身、数据文件、文件夹等等) 信息,包括空间占用,文件基本属性,文件位置索引,创建时
间用户权限,加密信息等等,每一个文件在 MFT 中都有一个或多个 MFT 项记录文件属性信息,这里的属性包括数据,如果这个文件很小
在 MFT 项中就可以放下,那么这条属性就定义为常驻属性,常驻标志位记为1,如果是非常驻,则有一个索引指向另一条记录(称为一个运行)。
(3) 第一条 MFT 项: $MFT
MFT 的第一项记录$MFT描述的是主分区表MFT本身,它的编号为0,MFT项的头部都是如下结构:
1 typedef struct MFT_HEADER{ 2 UCHAR mark[4]; // "FILE" 标志 3 UCHAR UsnOffset[2]; // 更新序列号偏移 30 00 4 UCHAR usnSize[2]; // 更新序列数组大小+1 03 00 5 UCHAR LSN[8]; // 日志文件序列号(每次记录修改后改变) 58 8E 0F 34 00 00 00 00 6 // 0x10 7 UCHAR SN[2]; // 序列号 随主文件表记录重用次数而增加 8 UCHAR linkNum[2]; // 硬连接数 (多少目录指向该文件) 01 00 9 UCHAR firstAttr[2]; // 第一个属性的偏移 38 00 10 UCHAR flags[2]; // 0已删除 1正常文件 2已删除目录 3目录正使用 11 // 0x18 12 UCHAR MftUseLen[4]; // 记录有效长度 A8 01 00 00 13 UCHAR maxLen[4]; // 记录占用长度 00 04 00 00 14 // 0x20 15 UCHAR baseRecordNum[8]; // 索引基本记录, 如果是基本记录则为0 16 UCHAR nextAttrId[2]; // 下一属性Id 07 00 17 UCHAR border[2]; // 18 UCHAR xpRecordNum[4]; // 用于xp, 记录号 19 // 0x30 20 UCHAR USN[8]; // 更新序列号(2B) 和 更新序列数组 21 }Mft_Header, *pMft_Header;
上面的头部结构体在扇区的数据偏移 0x00 ~0x38;
在0x38之后的4大块颜色数据是4条属性,描述名称,时间,索引等等信息,最后以"FF FF FF FF"结束。它们分别以0x10,0x30,
0x80, 0xB0作为标志;这里的四种属性所描述的的信息类型可以由下表查得,对照数据和结构体可以把这4条属性解析出来。
1 //------------------ 属性头通用结构 ---- 2 typedef struct NTFSAttribute //所有偏移量均为相对于属性类型 Type 的偏移量 3 { 4 UCHAR Type[4]; // 属性类型 0x10, 0x20, 0x30, 0x40,...,0xF0,0x100 5 UCHAR Length[4]; // 属性的长度 6 UCHAR NonResidentFiag; // 是否是非常驻属性,l 为非常驻属性,0 为常驻属性 00 7 UCHAR NameLength; // 属性名称长度,如果无属性名称,该值为 00 8 UCHAR ContentOffset[2]; // 属性内容的偏移量 18 00 9 UCHAR CompressedFiag[2]; // 该文件记录表示的文件数据是否被压缩过 00 00 10 UCHAR Identify[2]; // 识别标志 00 00 11 //--- 0ffset: 0x10 --- 12 //-------- 常驻属性和非常驻属性的公共部分 ---- 13 union CCommon 14 { 15 //---- 如果该属性为 常驻 属性时使用该结构 ---- 16 struct CResident 17 { 18 UCHAR StreamLength[4]; // 属性值的长度, 即属性具体内容的长度。"48 00 00 00" 19 UCHAR StreamOffset[2]; // 属性值起始偏移量 "18 00" 20 UCHAR IndexFiag[2]; // 属性是否被索引项所索引,索引项是一个索引(如目录)的基本组成 00 00 21 }; 22 //------- 如果该属性为 非常驻 属性时使用该结构 ---- 23 struct CNonResident 24 { 25 UCHAR StartVCN[8]; // 起始的 VCN 值(虚拟簇号:在一个文件中的内部簇编号,0起) 26 UCHAR LastVCN[8]; // 最后的 VCN 值 27 UCHAR RunListOffset[2]; // 运行列表的偏移量 28 UCHAR CompressEngineIndex[2]; // 压缩引擎的索引值,指压缩时使用的具体引擎。 29 UCHAR Reserved[4]; 30 UCHAR StreamAiiocSize[8]; // 为属性值分配的空间 ,单位为B,压缩文件分配值小于实际值 31 UCHAR StreamRealSize[8]; // 属性值实际使用的空间,单位为B 32 UCHAR StreamCompressedSize[8]; // 属性值经过压缩后的大小, 如未压缩, 其值为实际值 33 }; 34 }; 35 };
由这个结构体可以知道,属性头的长度取决于是否有属性名,属性名长度是多少;是否常驻,如果常驻,属性内容长度是多少,如果非常驻,运行列表有多长。
(0x08)日志文件序列号,它又叫文件参考号、文件引用号,一共 8Byte,前6个字节是文件称为文件号;后2个字节是文件顺序号,文件顺序号随重用而增加。
10H 类型:10H属性$STANDART_INFORMATION,描述的是文件的创建、访问、修改时间,传统属性,以及版本信息等等。
1 struct Value0x10 2 { 3 UCHAR fileCreateTime[8]; // 文件创建时间 4 UCHAR fileChangeTime[8]; // 文件修改时间 5 UCHAR MFTChangeTime[8]; // MFT修改时间 6 UCHAR fileLatVisited[8]; // 文件最后访问时间 7 UCHAR tranAtrribute[4]; // 文件传统属性 8 UCHAR otherInfo[28]; // 版本,所有者,配额,安全等等信息(详细略) 9 UCHAR updataNum[8]; // 文件更新序列号 10 };
下面的偏移都是相对于属性首字节,其值加上0x38 就是实际偏移(图中的offset)。
0x00 4B: (0x10) 类型标志
0x04 4B: (0x60) 整条10H属性的长度
0x08 1B: (0x00) 非常驻
0x09 1B: (0x00) 无属性名称
0x0A 2B: (0x18) 属性内容偏移位置
0x18 8B: (ED 46 39 6B 6B 93 CF 01) 8个字节是文件创建时间,紧随其后的3x8个字节分别是文件最后修改时间,MFT修改
时间,文件最后访问时间。64位数值是相对于1601-01-01零点整的千万分之一秒数。可以用FileTimeToSystemTime()转换成
我们通常见到的形式。
0x38 8B: (06 00 00 00 00 00 00 00)传统属性,这里是系统隐含文件,位描述:
后面还有0x28个字节是版本和管理信息等等。
20H类型 $ATTRIBUTE_LIST
当一个文件需要多个MFT项来记录,20H是用来描述属性的属性列表;当非常驻属性依然不够空间,则需要属性列表。20H类属性也有
可能为常驻或非常驻,可以应用上面的通用属性头。以此结构体得到属性值的偏移地址,进而得到属性内容。
//------- 这个结构只是数据内容部分,不包括属性头 NTFSAttribute ----
//------- 由属性头的属性值偏移量确定属性值的起始位置 ---
1 struct Value0x20{ 2 UCHAR type[4]; // 类型 3 UCHAR recordType[2]; // 记录类型 4 UCHAR nameLen[2]; // 属性名长度 5 UCHAR nameOffset; // 属性名偏移 6 UCHAR startVCN[8]; // 起始 VCN 7 UCHAR baseRecordNum[8]; // 基本文件记录索引号 8 UCHAR attributeId[2]; // 属性 id 9 //---- 属性名(Unicode) 长度取决于 nameLen 的值 --- 10 };
30H 类型 $FILE_NAME
30H 类型属性描述的是文件或文件夹的名字和创建基本信息,属性头不在赘述,属性值的结构如下:
1 struct Value0x30 2 { 3 UCHAR parentFileNum[8]; // 父目录的文件参考号,前6B 4 UCHAR createTime[8]; // 文件创建时间 5 UCHAR changeTime[8]; // 文件修改时间 6 UCHAR MFTchangeTime[8]; // MFT 修改时间 7 UCHAR lastVisitTime[8] // 最后一次访问时间 8 UCHAR AllocSize[8]; // 文件分配大小 9 UCHAR realSize[8]; // 实际大小 10 UCHAR fileFlag[4]; // 文件标志,系统 隐藏 压缩等等 11 UCHAR EAflags[4] // EA扩展属性和重解析点 12 UCHAR nameLength; // 文件名长 13 UCHAR nameSpace; // 文件命名空间 14 //----- Name (Unicode编码) 长度为 2 * nameLength ---- 15 }
NTFS通过为一个文件创建多个30H属性实现POSIX (Portable Operating System Interface, 可移植操作系统接口) 式硬连接,
每个30H属性都有自己的详细资料和父目录;一个硬连接删除时,就从MFT中删除这个文件名,最后一个硬连接被删除时,这个文件就算是
真正被删除了。
文件参考号:包括前 6B 的文件记录号,后 2B 的文件引用计数;当文件记录号为0x05时,是根目录。
命名空间:0 --- POSIX, 1 -- Win32, 2 --- DOS, 3 --- Win32 & DOS
40H 属性 $OBJECT_ID
MTFS统一给所有 MFT 记录分配的一个标识 --- 对象ID,即结构体第一个16B,可能只有一个全局对象ID,后面的3个ID可能没有。
1 struct Value0x40 2 { 3 UCHAR GObjectId[16]; // 全局对象ID 给文件的唯一ID号 4 UCHAR GOriginalVolumeId[16]; // 全局原始卷ID 永不改变 5 UCHAR GOriginalObjectId[16]; // 全局原始对象ID 派给本MFT记录的第一个对象ID 6 UCHAR GDomain[16]; // 全局域ID (未使用) 7 };
50H 属性 $SECURITY_DESCRIPTOR ( 安全描述符) 略。
60H 属性 $VOLUME_NAME 卷名属性
1 struct Value0x60 2 { 3 //---- 通用属性头 -- 4 UCHAR VolumeName[N]; //(Unicode) N 最大为 127 外加一个结束符'\0' 5 };
70H 属性 $VOLUME_INFORMATION 卷版本、状态
1 struct Value0x70 2 { //----- 通用属性头 --- 3 UCHAR noUsed1[8]; // 00 4 UCHAR mainVersion; // 主版本号 1--winNT, 3--Win2000/XP 5 UCHAR SecVersion; // 次版本号 当主为3, 0--win2000, 1--WinXP/7 6 UCHAR flag[2]; // 标志 7 UCHAR noUsed2[4]; // 00 8 }; 9 /* flag: 10 * 0x0001 坏区标志 下次重启时chkdsk/f进行修复 11 * 0x0002 调整日志文件大小 12 * 0x0004 更新装载 13 * 0x0008 装载到 NT4 14 * 0x0010 删除进行中的USN 15 * 0x0020 修复对象 Ids 16 * 0x8000 用 chkdsk 修正卷 17 */
80H 属性 $DATA 容纳文件数据(未命名数据流),文件的大小一般指是未命名数据流的大小,没有长度限制,当它为常驻时,数据
长度最小。它的结构为属性头加上数据流,如果数据流太大,则标记为非常驻,以运行的方式索引到外部。例如找一个MP3文件,从它的MFT
项中0x80属性中可以看到它一定是非常驻,它的运行所指向的一系列簇就是音乐文件数据流;
一个80H属性实例:
0x00~0x37 是属性头;运行列表偏移是紫色和橙色区域,0x40开始,可以得到运行列表 32 40 34 00 00 0C 32 80 31 07 54 16 ;分析如下:
首先0x32,低4位是2,表示紧随其后的2Byte 0x3440作为运行簇大小,高4位是3,表示簇大小之后的3个Byte 0x0C0000 是起始簇,这
是一个运行结束;接下来的0x32同理得簇起始号0x165407,运行大小为0x3180簇;一个运行的结束后跟0x00 为列表结束,之后填充无效字符。
90H 属性 $INDEX_ROOT 索引根。实现NTFS的B+树索引的根节点,总是常驻。索引根属性由属性头、索引根和索引项组成。属性头是通用
属性头的常驻部分。结构体如下(可能有些偏差):
1 struct indexHeader 2 { 3 UCHAR type[4]; // 属性类型 4 UCHAR checkRule[4]; // 校对规则 5 UCHAR allocSize[4]; // 索引项分配大小 6 UCHAR CluPerIdx; // 每索引记录的簇数 7 UCHAR noUse01[3]; // 填充 8 9 UCHAR firstIdxOffset[4]; // 第一个索引项偏移 10 UCHAR idxTotalSize[4]; // 索引项总大小 11 UCHAR indxAllocSize[4]; // 索引项分配大小 12 UCHAR flag; // 标志 13 UCHAR noUse02[3]; 14 }; 15 // 一般小目录在90属性中有常驻目录项,目录项的结构与INDX缓存中的目录项一样 16 struct indexItem 17 { 18 UCHAR MFTnum[8] // 文件的 MFT 记录号,前6B是MFT编号,用于定位此文件记录 19 UCHAR ItemLen:[2]; // 索引项大小 20 UCHAR IndexIdentifier:[2]; // 索引标志 R 21 UCHAR isNode[2]; // 1---此索引包含一个子节点,0---此为最后一项 22 UCHAR noUse03[2]; // 填充 23 UCHAR FMFTnum[8]; // 父目录 MFT文件参考号 0x05表示根目录 24 UCHAR CreateTime[8]; //文件创建时间 25 UCHAR fileModifyTime[8]; //文件修改时间 26 UCHAR MFTModifyTime[8]; //文件记录最后修改时间 27 UCHAR LastVisitTime[8]; //文件最后访问时间 28 UCHAR FileAllocSize[8]; //文件分配大小 (B) 29 UCHAR FileRealSize[8]; //文件实际大小 (B) 30 UCHAR FileIdentifier[8]; //文件标志 31 UCHAR FileNameLen; //文件名长度 32 UCHAR FileNameSpace; //文件名命名空间 33 //---- 0x52 --- 34 //FileName; // 文件名 (末尾填满 8 字节) 35 UCHAR nextItemVCN[8]; // 下一项的 VCN (有子节点才有) 36 };
A0H 属性 $INDEX_ALLOCATION 索引分配属性,也是索引,由属性头和运行列表组成,一般指向一个或多个目录文件(INDX文件,即4K缓存);
A0H属性和90H属性共同描述了磁盘文件和目录的 MFT 记录的位置。第5项MFT的A0H属性记录根目录的位置。
B0H 属性 $BITMAP 位图属性,虚拟簇使用(占用)情况,这条属性用在$MFT和索引中;在Bitmap文件中,每一个 Bit 代表分区的一个簇,置1代表其已使用;
第0个字节的第0位表示分区第0簇,之后依次递增。
C0H 属性 $REPARSE_POINT 重解析点。使应用程序为文件或目录关联一个应用程序数据块,详细略。
D0H $EA_INFORMATION 扩充信息属性。为在NTFS下实现HPFS的OS/2子系统信息,及WinNT服务器的OS/2客户端应用而设置的,一般为非常驻;
E0H $EA 扩充属性 也是为了实现NTFS下的 HPFS,一般为非常驻;
100H $LOGGED_UTILITY_STREAM,EFS加密属性,存储用于实现EFS加密有关的信息,合法用户列表,解密密钥等等
(4) 解析一个磁盘分区的文件目录的顺序:
引导扇区($Boot) ----> 第0项记录($MFT) ----> 根目录记录(第5项,90H,A0H) ----> 根目录(INDX)
1 struct indxHeader // A0H外部缓存文件结构,最大长度一般为4K 2 { 3 UCHAR mark[4]; // 标志 "INDX" 4 UCHAR usnOffset[2]; // 更新序列偏移 5 UCHAR usnSize[2]; // 更新序列数组大小S 6 UCHAR LSN[8]; // 日志文件序列号 7 UCHAR indxVCN[8]; // 本索引缓存在分配索引中的VCN 8 UCHAR itemOffset[4]; // 第一项索引的偏移(从这里开始计算) 9 UCAHR itemSize[4]; // 索引项实际大小(B) 10 UCHAR itemAlloc[4]; // 索引项分配大小(B)(不包括头部) 略小于4K 11 UCHAR isNode; // 是结点置1,表示有子节点 12 UCHAR noUse[3]; 13 //UCHAR USN[2S]; // 更新序列号和数组 14 };
在文件头之后就是目录项了,项的结构就是在上面90H的介绍里定义的indexItem,每一个项代表一个文件或目录的MFT项,通过
项的 MFT 记录号可以计算出MFT项的磁盘地址,它等于$MFT 的偏移地址 + 编号*0x400,以此可以找到该索引项对应的文件或子目录
的MFT项。
(5) 搜索一个已删除的文件或目录的MFT项
上面说了,一个文件的MFT项的地址等于$MFT的地址+MFT编号*0x400,如果目录中的对应项删除了,那么可以从MFT的首部开始检索,
因为MFT一般是连续的,而一个MFT项的大小又是固定的,一项项读取,找到各自的0x30属性,解析出文件名,进行比较 (MFT中有一些
空白区域需要跳过)。
(6) 关于文件名
一般在文件名的前一个字节是文件名的命名空间,不管是INDX文件中,还是0x30属性中。
0x00 ---- POSI ,最大的命名空间,大小写敏感,支持除 '\0' 和 '/' 所有Unicode字符,最大程度255个字符;
0x01 ---- Win32,是POSI的子集,不支持字符:* / < > | \ : ? ,不能用句点或空格结束;
0x02 ---- DOS , 是Win32的子集,字符必须比空格0x20大,文件名1~8个字符,然后句点分割接后缀扩展名1~3个字符;
0x03 --- DOS&Win32,必须兼容Win32和DOS命名方式
在INDX文件中,经常可以看到含有0x02和0x03或者0x01的两个不同命名空间、相同MFT编号的项,也就是说这两个目录项指向同一个记录,同样的
在这个文件的MFT项中也有两个0X30属性,其中一个是0x01或0x03,表示的是完整的文件名;另一个是0x02,DOS命名方式,它是一个短文件名,它在
我们命名的基础上,截断 '.' 之前的超出6个字符的所有字符,只剩前6个,之后接上"~1" ,这样正好8个字符,当然后面的句点和扩展名保留。另外,它必须
满足DOS命名规则,必须大写,删除禁止使用的字符等等。如果文件名重复了,在 "~1" 基础上递增,"~2","~3"等等。检索比对时,我们自然要使用前者。
(7) 关于字符集
字符集是字符在计算机上的编码方式,可以看成一种协议,一种约定规则,我们处理一串二进制数所代表的字符时,必须清楚它用的是哪一种编码方式;
在windows系统中文件的命名是固定用两个字节表示一个字符,在MFT中可以发现英文文件名字符之间都填充一个 '\0' ,这是宽字符集与变长字符集兼容,
在宽字符集中,小于128的字符数值上是等于ASCII码;我们的文件数据一般用的是变长字符集(GB2312等等);
为了比较输入的文件名和NTFS中的文件名,我们必须要先转换;
两个WinAPI 函数,用于宽字符和变长字符转换
// 函数原型
int WideCharToMultiByte( UINT CodePage, // code page DWORD dwFlags, // performance and mapping flags LPCWSTR lpWideCharStr, // address of wide-character string int cchWideChar, // number of characters in string LPSTR lpMultiByteStr, // address of buffer for new string int cchMultiByte, // size of buffer LPCSTR lpDefaultChar, // address of default for unmappable // characters LPBOOL lpUsedDefaultChar // address of flag set when default // char. used ); int MultiByteToWideChar( UINT CodePage, // code page DWORD dwFlags, // character-type options LPCSTR lpMultiByteStr, // address of string to map int cchMultiByte, // number of bytes in string LPWSTR lpWideCharStr, // address of wide-character buffer int cchWideChar // size of buffer ); //--- WCHAR 定义在tchar.h中 ---- void charTest() { TCHAR tc1[16] ; //= _T("后来"); WCHAR tc2[8] = {0x540E, 0x6765, 0, 0, 0, 0, 0, 0}; // memset(tc2, 0, 20); // MultiByteToWideChar(CP_ACP, 0, tc1, 4, (LPWSTR)tc2, 4); WideCharToMultiByte(CP_ACP, 0 ,(WCHAR*)tc2, 2, tc1, sizeof(tc1), 0, 0); cout<<"tc1 "<<tc1<<sizeof(tc1)<<" "<<strlen(tc1)<<endl; PrintHex(tc1); cout<<endl; cout<<"tc2 "<<sizeof(tc2)<<" "<<wcslen((LPWSTR)tc2)<<endl; PrintHex(tc2); cout<<endl; }