免责声明:
不支持此处描述的过程:任何事情都可能随时更改,恕不另行通知。
▶ 当 OpenFileDialog 或 SaveFileDialog 用于打开或保存文件时,应用程序访问的最后一个路径,存储在注册表中的 HKEY_CURRENT_USER 分支中,在此键下:
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU
▶ 应用程序打开或保存的最新文件的名称存储 - 按文件扩展名组织 - 在此其他键(相同的注册表分支)下:
Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU
所有信息都存储为二进制值。
第一个 Key - LastVisitedPidlMRU - 存储应用程序访问的最后一个路径,有两种可能的形式:
- 一个父 shell 文件夹项,采用经典 GUID 形式(通常表示桌面文件夹),后跟子
IDlist,其中路径以 Unicode 长路径格式和 DOS 短路径格式存储(以及其他小块此处未描述的信息 - 请参阅进一步阅读链接)。
- 以 null 结尾的 Unicode 字符串,表示可执行文件名称,后跟
IDlist(与之前相同)。
在这里,我将使用这个结构来检索指定可执行文件使用的最后一个路径。
OpenSavePidlMRU键的子键项中包含的二进制值存储为简单的IDlist,因此可以使用SHGetPathFromIDListW()和@直接访问存储的路径987654322@.
在这里,我声明两者(可能会派上用场),但我只使用第二个,因为它稍微灵活一些(好吧,如果需要,它允许检索短路径形式)
进一步阅读或测试:
这里有三种方法:
→ ApplicationGetLastOpenSavePath 用于检索应用程序使用的最后一个路径,给定其可执行文件名称(只是名称,例如 app.exe)。
由于这个问题被标记为WinForms,你可以得到它:
string exeName = Path.GetFileName(Application.ExecutablePath);
→ ApplicationGetLastOpenSaveFileNames 检索应用程序打开的所有文件名,与最后访问的路径相关(仅此一项,因为这是当前问题的内容)。
该方法返回一个命名元组,(string AppPath, List<string> FileNames):
-
AppPath 是最后访问的路径。
-
FileNames 是文件名列表 - 其中路径为 AppPath - 由应用程序访问。
→ 第三个是辅助方法:它的任务是处理对 SHGetPathFromIDListEx() 的调用,这需要一些互操作编组。
方法参数表示从 Registry Key 中提取的字节数组和一个偏移量,它表示 IDlist 的初始位置(如上所述,二进制数据以表示可执行文件名称的以空字符结尾的 Unicode 字符串开头)。
private string ApplicationGetLastOpenSavePath(string executableName)
{
if (string.IsNullOrEmpty(executableName)) return null;
string lastVisitedPath = string.Empty;
var lastVisitedKey = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU", false);
string[] values = lastVisitedKey.GetValueNames();
foreach (string value in values) {
if (value == "MRUListEx") continue;
var keyValue = (byte[])lastVisitedKey.GetValue(value);
string appName = Encoding.Unicode.GetString(keyValue, 0, executableName.Length * 2);
if (!appName.Equals(executableName)) continue;
int offset = executableName.Length * 2 + "\0\0".Length; // clearly null terminated :)
lastVisitedPath = GetPathFromIDList(keyValue, offset);
break;
}
return lastVisitedPath;
}
第二种方法调用第一种方法来检索由string executableName表示的应用程序访问的最后一个路径:
private (string AppPath, List<string> FileNames) ApplicationGetLastOpenSaveFileNames(string executableName)
{
string appPath = ApplicationGetLastOpenSavePath(executableName);
if (string.IsNullOrEmpty(appPath)) return (null, null);
var fileNames = new List<string>();
var extensionMRUKey = Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU", false);
string[] subKeys = extensionMRUKey.GetSubKeyNames().ToArray();
foreach (string key in subKeys) {
var subKey = extensionMRUKey.OpenSubKey(key);
foreach (string keyValue in subKey.GetValueNames()) {
var value = (byte[])subKey.GetValue(keyValue);
string filePath = GetPathFromIDList(value, 0);
if (filePath.Contains(appPath)) {
fileNames.Add(filePath);
}
}
}
return (appPath, fileNames);
}
Helper 方法和 Win32 声明:
private string GetPathFromIDList(byte[] idList, int offset)
{
int buffer = 520; // 520 = MAXPATH * 2
var sb = new StringBuilder(buffer);
IntPtr ptr = Marshal.AllocHGlobal(idList.Length);
Marshal.Copy(idList, offset, ptr, idList.Length - offset);
// or -> bool result = SHGetPathFromIDListW(ptr, sb);
bool result = SHGetPathFromIDListEx(ptr, sb, buffer, GPFIDL_FLAGS.GPFIDL_UNCPRINTER);
Marshal.FreeHGlobal(ptr);
return result ? sb.ToString() : string.Empty;
}
[DllImport("shell32.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
internal static extern bool SHGetPathFromIDListW(
IntPtr pidl,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder pszPath);
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern bool SHGetPathFromIDListEx(
IntPtr pidl,
[MarshalAs(UnmanagedType.LPTStr)]
[In,Out] StringBuilder pszPath,
int cchPath,
GPFIDL_FLAGS uOpts);
internal enum GPFIDL_FLAGS : uint {
GPFIDL_DEFAULT = 0x0000,
GPFIDL_ALTNAME = 0x0001,
GPFIDL_UNCPRINTER = 0x0002
}