Windows 系统级个人文件夹 vs OneDrive简析
本文最后更新于:2024年4月4日 凌晨
前情提要
众所周知,Windows
的系统级个人文件夹包括:Documents
, Music
, Desktop
, Pictures
等等
这些文件夹默认情况下,是在C:\Users\{name}\
下
那么由于一些妇孺皆知的原因,C盘空间是永远捉襟见肘的
有没有办法将这些文件夹移动到其他驱动器上呢?
Move
伟大的巨硬已经帮我们想到了
打开个人文件夹(如:音乐)的属性-位置标签,我们就可以看到移动按钮
Tip: 不知道大家有没有发现,这个“位置”标签只有这些个人文件夹才有,普通文件夹是没有的
例如选择新位置为:E:\Music,移动,大功告成
有的人可能会说了,直接Ctrl+X
剪切不行吗?
听起来有点粗暴,其实经过我的实验,也是可以的,操作系统仍然能够跟踪这些文件夹的位置
:等等,什么叫跟踪,为什么要跟踪
Search
这次我们拿文档(Documents
)来举例吧
QQ
都知道吧,默认情况下,QQ
的消息记录默认是保存在“我的文档”下的
那么问题来了,“我的文档”是可以被移动的(如前文所述),那么QQ
如何准确查找“我的文档”
总不能是User
目录(C:\Users\{name}\
) + Documents
吧
// 那也太捞了,不会有人这么写吧,不会吧不会吧
用半月板想都知道,那肯定是有系统API
的ya
SHGetKnownFolderPath
(更现代) or SHGetFolderPath
(前者的包装器)
1 |
|
1 |
|
在Qt
中,可以用QStandardPaths
获取:
1 |
|
话说这些API
是如何知道“我的文档”的当前位置的?
当然是记在注册表啊,见微软文档
“我的文档”文件夹的路径存储在以下注册表项中,其中的 <存储位置的完整路径> 是存储位置的路径:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
数值名称:Personal
数值类型:REG_SZ
数值数据:存储位置的完整路径
也就是说,Windows
会通过注册表来跟踪个人文件夹的当前位置
同时,我们也可以通过查看注册表 来 判断个人文件夹是否移动成功
如果移动后注册表没有更新 或者 “位置”标签消失,说明寄了
坑:编码
这里其实有一个非常大的坑,和Windows API
交互经常会有这种问题
就是如何输出TCHAR
数组,和PWSTR
指针
如果直接
1 |
|
那么估计会死得很惨
看起来用wcout
输出PWSTR
非常正确(W代表宽字符)
事实也确实如此,但是结果却是:
1 |
|
?为什么PWSTR
的结果不正确,是SHGetKnownFolderPath
出bug了吗,是OneDrive重定向了吗
在阅读了大量文档和Qt源码后(Qt API正确输出,且内部使用了SHGetKnownFolderPath
)
我发现:居然是打印过程出现了问题!中文没有正常显示(通过调试模式打断点可以看到内存里显示是正常的)
aaaaa,这谁想得到啊,编码不正确不应该是乱码吗,怎么会直接没了!
其实说实话,好好读SHGetKnownFolderPath文档的程序员应该一眼就初见端倪了
1 |
|
人家都说了,返回的路径不会以'\'
结尾的
你看看E:\OneDrive\
正常吗?!盯————
为了正常显示中文,需要加入这一行:
1 |
|
然后就正常了
因为默认情况下,locale
是”C”(听说是为了可移植性,所以不支持中文)
可通过以下代码获取默认locale
:
1 |
|
你以为这就完了?太年轻了兄弟
TCHAR
怎么办呢
难道你想说你看了眼TCAHR
的定义:typedef char TCHAR
,然后发现很合理,很正常
随便用个cout
、 printf
都能正常输出中文
那你有没有想过TCHAR
的T是什么意思,_T
有什么用,L
有什么用
T
可以理解为TEXT
,意为文本,也就是会根据UNICODE
宏定义自动处理宽窄字符
1 |
|
_T
也是如此
1 |
|
那么L
前缀呢,就是把字面量标记为宽字符
だから,我们需要同时考虑宽窄字符(UNICODE
宏是否定义)
1 |
|
可以用_tprintf
宏来自动选择printf
和wprintf
// 宏真神奇
// 顺便说一下,Windows API也常会提供两个版本,以A结尾(ANSI,单字节字符)和以W结尾(宽字符),同时还会提供一个宏(不带后缀)来自动选择
啊,我不得不吐槽一下,原生C++的编码太离谱了,不会真有人用得来吧
看看远方的Qt
吧,家人们,QString
直接就是Unicode
编码,舒服
你以为这就完了?仍旧年轻了兄弟
如果你用的是Windows Clion + MSVC
,此时你只要:
1 |
|
就会得到这一坨,还有一个Waring:
1 |
|
乍一看,文件编码明明就是UTF-8
呀,怎么不是Unicode
了
不会有人不知道UTF-8
分为两个版本吧:UTF-8
& UTF-8 with BOM
BOM(byte order mark):用于标记字节序,微软在 UTF-8 中使用 BOM 是因为这样可以把 UTF-8 和 ASCII 等编码明确区分开
两个版本的区别就是:文件开头有没有 U+FEFF
MSVC
编译器默认编码是UTF-8 with BOM
,如果没有BOM
,MSVC
编译器就不会认为这是Unicode
编码,导致编译后打印乱码
Solution:
- 在IDE的文件编码设置中改为
UTF-8 with BOM
- 或在
cmake
中强制采用UTF
-8编译:add_compile_options(/utf-8)
还有个坑:
Clion
正常运行没问题,但是调试模式 还是会乱码,寄
OneDrive
事情到这里就结束了吗,其实才刚刚开始
我发现,当个人文件夹遇上OneDrive,问题就大发了
OneDrive有一个备份个人文件夹的功能
如果我们打开某文件夹的备份按钮
那么该文件夹就会被移动到OneDrive文件夹中
这个移动同样会被注册表跟踪,和手动剪切进来没有什么两样
// 有时候你可能会发现,OneDrive自动备份导致文件夹移动后,缺少了“位置”标签页,别慌,重启一下资源管理器就好了
异变
不过,一旦个人文件夹进入了OneDrive,性质就发生了改变
此时如果我们移动这个 个人文件夹(如:音乐)(通过“位置”标签),就会报错
如果此时强行用Ctrl+X
剪切该文件夹,是可以成功的
但是,该文件夹(如:音乐)就会退化为一个普通文件夹,且名称从”Music”变为了“音乐”
// 原本的“音乐文件夹”虽然看起来叫”音乐“,其实地址栏名称是Music(什么中英混合体)
此时你会发现,注册表中的音乐文件夹依然指向OneDrive\Music
过了一会儿(等资源管理器反应过来),就会在下重新生成一个新的“音乐”文件夹来取代之
// 可能会看到两个一样的“音乐”文件夹,别担心,重启一下资源管理器
这种情况下,你原来的音乐文件夹就废了,只能手动剪切内容了
异变-2
第二种情况,如果你没有手动将OneDrive下的个人文件夹移动走,而是通过刚刚OneDrive的备份开关(关闭备份)
那么,令人震惊的事情发生了,OneDrive中的“音乐”文件夹没有任何变化(甚至图标还在)
而C:\Users\{name}\
下生成了一个新的“音乐”文件夹(正宫),且内置快捷方式指向OneDrive中的“音乐”文件夹
这波操作,陈独秀你坐下
// 因此,一旦将个人文件夹移动至OneDrive,将很难正常移出去
注释
如果你在OneDrive文件夹内部移动个人文件夹(如:音乐),那么情况将大不相同
情况1:通过“位置”标签页将 OneDrive\Music 移动到 OneDrive\Other\Music
那么神奇的事情将会发生,OneDrive\Music
文件夹依然存在,且内容完好,但是缺少了图标
而OneDrive\Other\Music
空有图标(正宫标志),却没有内容
啊这——,分裂了是吧
情况2:直接Ctrl+X剪切
那么一切都很正常
移动OneDrive
你以为这又又完了,太年轻了兄弟
有没有想过,OneDrive也是在C盘的,很占空间的
那假如我要移动OneDrive文件夹,其中的“音乐”文件夹会怎么样?
首先,我们要解决一个问题,要如何移动OneDrive(这玩意儿是个网盘啊,可不是一般的文件夹)
需要参考一下官方文档:更改 OneDrive 文件夹的位置 - Microsoft 支持
- 取消此电脑链接
- 移动(剪切)OneDrive文件夹
- 重新初始化OneDrive,并选择OneDrive文件夹位置(移动后)
好的,那么OneDrive中的Music文件夹会怎么样,能保持正宫位置吗
请看一段广告,我们稍后回来——
好的,没有赞助商,揭晓答案:OneDrive\Music
守住了宝座,非常正常,甚至注册表也同步更新了
为什么呢,究竟是被包装在OneDrive中的原因,还是OneDrive取消链接,从网盘退化为普通文件夹的原因!
为了验证,我们先取消OneDrive链接,使其退化为普通文件夹,然后剪切Music
文件夹
发现:还是没有卵用,Music
文件夹依旧退化
寄,OneDrive文件夹真神奇
Rebuild
如果我们把OneDrive\Music
删除会怎么样呢?
此时在user
文件夹下生成了一个新的Music文件夹
那么假如我们把这个C:\Users\{name}\Music
继续删除呢
emmm,那就没了
此时,SHGetKnownFolderPath
的返回Error
(hr != S_OK
),但是注册表中记录的还是默认位置 //看来会判断是否存在
如何找回呢?
OneDrive备份
如果我们直接打开OneDrive同步与备份中的Music开关
那么OneDrive中会奇迹般地出现“音乐”文件夹,同时注册表也更新了
API
其实我们也可以通过SHGetKnownFolderPath
手动新建系统文件夹
只要将dwFlags
设置为该值:
1 |
|
1 |
|
这样,Music
文件夹就会在默认位置重新被创建了(Brand-New)!
// 如果想要获取某文件夹的默认存储位置(移动前的)可以这样:
1 |
|
1 |
|
静观其变
既然我们可以通过API去创建个人文件夹,那么同理,其他软件如果需要往这些文件夹写入数据但是发现Not Found,可能大概应该会重新帮我们创建
所以,放几天大概就好了吧,啊哈哈
// 例如:打开网易云音乐就会自动创建
手动新建
或者,我们可以观察一下注册表中对应文件夹的位置,然后手动新建一个一毛一样的文件夹,然后重启资源管理器,大概就会被认为是正宫了
Just没有图标,那么文件夹图标是哪来的呢?
其实是文件夹下的一个隐藏文件(且被系统保护):desktop.ini
对于Music
文件夹,desktop.ini
内容如下:
1 |
|
第一行设置了文件夹的本地化名称,即在不同语言环境下显示的名称
这也就解释了为什么音乐文件夹显示为”音乐“,但实际路径中是”Music“
仔细看可以发现,实际上是从windows.storage.dll
中提取ID为21790的资源(字符串)
我们可以验证一下:
1 |
|
输出为:Resource string: 音乐
iconTip
设置了当你将鼠标悬停在文件夹图标上时显示的提示信息IconResource
是图标资源,和IconFile & IconIndex
应该是重复的,大概是为了兼容性考虑
所以其实都是不是很重要的信息,如果我们自己新建Music
文件的话,大概可能把这个desktop.ini
建出来,外表就大差不大了
疑点
现在还剩下一个最大的疑点,为什么个人文件夹(如:音乐)一旦进入OneDrive,再移动(剪切)出来,就会失去系统属性,堕落为普通文件夹呢
是因为desktop.ini
吗?
一般情况下,我们移动文件夹,desktop.ini
作为文件夹内的一个文件肯定也会被移动的
不过在OneDrive中的Music比较特殊,如果在文件夹选项中勾选了“隐藏受保护的操作系统文件(推荐)
”
那么desktop.ini
会被隐藏,此时剪切Music文件夹,移出OneDrive,会发现,desktop.ini
的内容发生了变化
只剩下更改文件夹名称的这一行了(但其实真是文件名也变成了”音乐”)
1 |
|
这就很奇怪了,一般文件夹的剪切应该保留desktop.ini
才对
第二种情况,如果我们没有勾选“隐藏受保护的操作系统文件(推荐)
”,那就能看到desktop.ini
此时会有几个警告,问你要不要转移和覆盖desktop.ini
会弹出很多次警告,还有一次覆盖4个文件?挺奇怪的(里面总共就俩desktop.ini
)
然后呢,转移成功,会发现,图标什么的都保留了,desktop.ini
也是正确的
但是,一看注册表,哎呀,又fallback到了C:\Users\{name}\
下,而且还生成了一个新的正宫
寄
世界十大未解之谜:计算机中的幽灵
Peace
我不干了,等我入职微软再说吧
Ref
SHGetKnownFolderPath 函数 (shlobj_core.h) - Win32 apps | Microsoft Learn
SHGetFolderPathA 函数 (shlobj_core.h) - Win32 apps | Microsoft Learn
Qt获取windows文档、下载、图片等目录路径_qt获取download目录-CSDN博客
c++ - 获取我的文档的路径 - SegmentFault 思否
更改 OneDrive 文件夹的位置 - Microsoft 支持
移动Onedrive文件夹至D盘目录 - 知乎 (zhihu.com)
「带 BOM 的 UTF-8」和「无 BOM 的 UTF-8」有什么区别?网页代码一般使用哪个? - 知乎 (zhihu.com)
解决 C++ printf 汉字问号。含 _tprintf(), printf(), wprintf() 详解_c++汉字变成问号-CSDN博客
[C++] cout、wcout无法正常输出中文字符问题的深入调查(1):各种编译器测试 - zyl910 - 博客园 (cnblogs.com)