VS与Win7共舞:UAC与数据重定向

2023-10-18 05:10
文章标签 数据 vs 重定向 win7 uac 共舞

本文主要是介绍VS与Win7共舞:UAC与数据重定向,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

回想当年微软高调发布Windows Vista的时候,突出的兼容性问题成为其在推广时遇到的最大阻力,让Windows Vista“出师未捷身先死”,从而成为继Windows Me之后微软最失败的操作系统。有鉴于此,微软在进行Windows 7的开发的时候,将应用程序兼容性放在了前所未有的高度,提前两年就开始为Windows 7进行各种兼容性测试,同时在Windows 7上提供了Windows XP虚拟模式,最大程度地保证应用程序可以平滑地过渡到Windows 7,从这些我们都可以看出微软的良苦用心。

  但是,操作系统的改变,必然会带来一些应用程序兼容性的问题,保持应用程序的良好兼容新,不仅仅是微软自家的事情,作为应用程序的开发者,我们也有不可推卸的责任。你的应用程序是否能够与Windows 7良好兼容?这是摆在每个程序员面前的一个问题。下面我们就以一系列文章,来介绍一下Windows 7有了什么新的变化?这些变化会带来那些应用程序兼容性问题?如何让应用程序与Windows 7保持兼容?

  很多程序员在设计和实现应用程序的时候,为了图方便和省事,都有向应用程序所在的目录“Program Files”,Windows目录或者操作系统根目录(尤其是C:\)写入数据文件的习惯。另外,这些人也习惯于用注册表HKLM/Software下的键值来保存一些数据,比如应用程序的配置参数等等。应用程序一直都工作的很好,直到万恶的UAC的出现。在Windows 7中,这些人会发现他们所要创建的文件没有相应的位置被创建,注册表键值没有被修改。他们问“到底怎么回事?我的应用程序运行正常并没有报错,但是所创建的文件怎么就不见了呢?在其他操作系统上都工作的好好的啊?”

都是UAC Virtualization惹的祸

  从Windows Vista开始,当然也包括Windows 7,因为UAC机制的引入,操作系统的标准普通用户被限制访问一些核心文件,文件夹和注册表键值。当我们开发的应用程序试图向这些地方写入数据的时候,会被重新定向到其他操作系统认为比较安全的位置。大多数时候,对于普通用户和应用程序开发人员来说这都是透明的,并没有给我们带来什么不便。但是在有些时候,事情并非如此。数据重新定向可能会导致一些奇怪的现象:

  • 在应用程序中,你写入数据到“Program Files”目录,虽然应用程序执行正确,没有错误值返回,但是在这个目录下你却找不到你刚刚写入的文件。
  • 你的应用程序修改了注册表键值,但是你在注册表相应的位置却看不到更新。
  • 当你关闭或者启用UAC后,你的应用程序找不到某些文件了,原来存在的文件凭空消失了。

  在Windows Vista之前,我们通常都是以管理员身份来运行应用程序的。这样,应用程序就可以自由地对操作系统相关的目录或者注册表键值进行读写。当我们以普通用户身份运行这些应用程序时,就会出现这样或者那样无法访问的错误。Windows Vista为了减少这种错误,改善对于普通用户的应用程序的兼容性,同时又不失去其安全性,就利用UAC Virtualization(UAC 虚拟化访问)这种机制,将应用程序的写操作(包括文件和注册表操作)重新定向到了一个预先在用户的配置文件中定义的目录。UAC Virtualization分为文件Virtualization和注册表Virtualization。例如,当一个普通用户运行一个应用程序尝试写入数据到 C:\Program Files\Contoso\Settings.ini时,这个写入操作将被重新定向到C:\Users\Username\AppData\Local\VirtualStore\Program Files\Contoso\settings.ini。这就是为什么我们在写入的时候没有任何错误,但是在相应的目录下找不到我们创建的文件。同样的,对注册表HKLM\Software的写入操作也会被重新定向到HKCU\Software\Classes\VirtualStore。下图1展示了整个UAC Virtualization的流程。

图1  UAC Virtualization的流程

图1  UAC Virtualization的流程

  这里需要注意的是,UAC Virtualization仅作用于32位的应用程序对系统文件/目录、注册表的读写。64位程序、非交互式程序、模拟程序(Processes that impersonate)、内核调用者、Manifest中含有requestedExecutionLevel属性的可执行文件不包含在Virtualization的作用范围内。

如何获取正确的文件路径

  UAC Virtualization (UAC虚拟化访问) 只是为了帮助现有的应用程序与Windows Vista或者Windows 7保持兼容,减少应用程序错误而设计的。为Windows 7而全新设计的应用程序,不应该再向一些敏感的系统目录或者注册表位置写入数据。同时也不应该借助虚拟化技术为一些不正确的应用程序行为提供补救方案,这无异于饮鸩止渴。当更多的应用程序移植到Windows 7之后,微软可能在未来版本的Windows取消对UAC虚拟存储的支持。例如,64位应用程序是禁用虚拟存储的。

  在为Windows 7新开发应用程序时,我们应该始终开发与标准用户权限相适应的应用程序,而不要指望总是在管理员权限下运行你所设计的应用程序。同时,更多地在普通用户权限下测试你的应用程序,而不是在管理员权限下测试你的应用程序。

  当我们那些在Windows 7之前设计的应用程序遇到UAC Virtualization问题的时候,我们需要从新设计我们的代码,将文件写入到合适的位置。在改善既有代码,使之可以与Windows 7兼容的时候,我们应该确保以下几点:

  • 在运行的时候,应用程序只会将数据保存到每个用户预先定义的位置或者是%alluserprofile% 中定义的普通用户拥有访问权限的位置。
  • 确定你要写入数据的“已知文件夹”(Knownfolders)。通常,所有用户共用的公共数据文件应该写入到一个全局的公共的位置,这样所有用户都可以访问到。而其它数据则应该写入每个用户自己的文件夹。
  1. 公共数据文件包括日志文件,配置文件(通常是INI或者XML文件),应用程序状态文件,比如保存的游戏进程等等。
  2. 而属于每个用户的文档,则应该保持在文档目录下,或者是用户自己指定的目录。
  • 当你确定合适的文件保存位置后,不要在代码中明文写出(Hard-code)你选择的路径。为了更好地保持兼容性,我们应该采用下面这些API来获得操作系统“已知文件夹(Knownfolders)”的正确路径。

一、C/C++非托管代码: 使用SHGetKnownFolderPath函数,通过指定“已知文件夹”的KNOWNFOLDERID作为参数来获得正确的文件夹路径。

  • FOLDERID_ProgramData –所有用户都可以访问的应用程序数据适合放置在这个目录下。
  • FOLDERID_LocalAppData – 每个用户单独访问的应用程序数据适合放置在这个目录下。
  • FOLDERID_RoamingAppData – 每个用户单独访问的应用程序数据适合放置在这个目录下。 与上面一个目录不同的是,放置在这个目录下的文件会随着用户迁移,当一个用户在同一个域中的其他计算机登录的时候,这些文件会被复制到当前登录的机器上,就像用户随身携带的公文包一样。

  下面这段代码演示了在非托管代码中如何调用SHGetKnownFolderPath函数获得正确的文件保存路径:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "shlobj.h"
#include "shlwapi.h"
//…
#define AppFolderName _T("YourApp")
#define DataFileName _T("SomeFile.txt")
// 构造一个数据文件路径
// dataFilePath指向一个长度为MAX_PATH,类型为TCHAR的字符串数值
// hwndDlg是消息对话框的父窗口句柄
// 当有错误发生的时候用于显示错误提示
// includeFileName用于表示是否在路径后面扩展文件名
BOOL MakeDataFilePath( TCHAR *dataFilePath,
                       HWND hwndDlg,  BOOL includeFileName)
{
     // 初始化工作
     memset (dataFilePath, 0, MAX_PATH *  sizeof ( TCHAR ));
     PWSTR pszPath = NULL;
     // SHGetKnownFolderPath函数可以返回一个已知文件见的路径,
     // 例如我的文档(My Documents),桌面(Desktop),
        // 应用程序文件夹(Program Files)等等。
     // 对于数据文件来说,FOLDERID_ProgramFiles并不是一个合适的位置
     // 使用FOLDERID_ProgramFiles保存所有用户共享的数据文件
     // 使用FOLDERID_LocalAppData保存属于每个用户自己的文件(non-roaming).
     // 使用FOLDERID_RoamingAppData保存属于每个用户自己的文件(roaming).
// 对于“随身文件”(Roaming files),
// 当一个用户在一个域中的其他计算机登陆的时候,
     // 这些文件会被复制到当前登录的机器上,就像用户随身携带的公文包一样   
     // 获取文件夹路径
     if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData,
                0, NULL, &pszPath)))
     // 错误的做法: if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFiles,
        // 0, NULL, &pszPath)))
     {
         // 提示错误
         MessageBox(hwndDlg, _T( "SHGetKnownFolderPath无法获取文件路径" ),
             _T( "Error" ), MB_OK | MB_ICONERROR);
         return FALSE;
     }
     // 复制路径到目标变量
     _tcscpy_s(dataFilePath, MAX_PATH, pszPath);
     ::CoTaskMemFree(pszPath);
     //错误的做法: _tcscpy_s(dataFilePath, MAX_PATH, _T("C:\\"));
     // 在路径后面扩展应用程序所在文件夹
     if (!::PathAppend(dataFilePath, AppFolderName))
     {
         // 提示错误
         MessageBox(hwndDlg, _T( "PathAppend无法扩展路径" ),
             _T( "Error" ), MB_OK | MB_ICONERROR);
         return FALSE;
     }
     // 是否添加文件名
     if (includeFileName)
     {
         // 在路径后扩展文件名
         if (!::PathAppend(dataFilePath, DataFileName))
         {
             // 提示错误
             MessageBox(hwndDlg, _T( "PathAppend无法扩展文件名" ),
                 _T( "Error" ), MB_OK | MB_ICONERROR);
             return FALSE;
         }
     }
     return TRUE;
}

二、托管代码: 使用System.Environment.GetFolderPath函数,通过指定我们想要获取的“已知文件夹”为参数,从而获取相应的文件夹的正确路径。

  • Environment.SpecialFolder.CommonApplicationData – 所有用户都可以访问的应用程序数据适合放置在这个目录下。
  • Environment.SpecialFolder.LocalApplicationData – 每个用户单独访问的应用程序数据适合放置在这个目录下。
  • Environment.SpecialFolder.ApplicationData – 每个用户单独访问的应用程序数据适合放置在这个目录下。这是“随身文件夹”。

  下面这段代码展示了如何在托管代码中获取正确的文件路径:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
internal  class FileIO
     {
         private const string AppFolderName =  "YourApp" ;
         private const string DataFileName =  "SomeFile.txt" ;
         private static string _dataFilePath;
         /// <summary>
         /// 构建路径
         /// </summary>
         static FileIO()
         {
             // Environment.GetFolderPath返回一个“已知文件夹”的路径
             // Path.Combine可以合并两个路径成一个合法的路径
             // …
             
             _dataFilePath = Path.Combine(Environment.GetFolderPath(
                   Environment.SpecialFolder.ProgramFiles), AppFolderName);
             //错误的做法:
             //_dataFilePath = Path.Combine(Environment.GetFolderPath(
              Environment.SpecialFolder.CommonApplicationData), AppFolderName);
             
             // 扩展文件名
             _dataFilePath = Path.Combine(_dataFilePath, DataFileName);
         }
          public static void Save(string text)
         {
             // 检查要保存的字符串是否为空
             if (String.IsNullOrEmpty(text))
             {
                 MessageBox.Show( "字符串为空,无法保持." "空字符串" ,
                      MessageBoxButtons.OK, MessageBoxIcon.Error);
                 return ;
             }
             try
             {
                 // 获取文件保存的路径
                 string dirPath = Path.GetDirectoryName(_dataFilePath);
                 // 检查文件夹是否存在
                 if (!Directory.Exists(dirPath))
                     Directory.CreateDirectory(dirPath);  // 创建文件夹
             }
             catch (Exception ex)
             {
                 MessageBox.Show(ex.Message,  "文件夹创建失败" ,
                     MessageBoxButtons.OK, MessageBoxIcon.Error);
                 return ;
             }
             try
             {
                 // 保存字符串到文件
                 StreamWriter sw =  new StreamWriter(_dataFilePath);
                 try
                 {
                     sw.Write(text);
                 }
                 finally
                 {
                     // 关闭文件
                     sw.Close();
                 }
             }
             catch (Exception ex)
             {
                 MessageBox.Show(ex.Message,  "文件写入失败" ,
                     MessageBoxButtons.OK, MessageBoxIcon.Error);
             }
         }
         // …
    }
}

三、如果上面的方法都不适合你,你还可以使用环境变量获取相应的文件夹路径:

  • %ALLUSERSPROFILE% – 所有用户都可以访问的应用程序数据适合放置在这个目录下。
  • %LOCALAPPDATA% – 每个用户单独访问的应用程序数据适合放置在这个目录下。 – (Windows Vista 或者Windows 7)
  • %APPDATA% – 每个用户单独访问的应用程序数据适合放置在这个目录下。这是“随身文件夹”。- (Windows Vista 或者Windows 7)

禁用UAC Virtualization

  凡事都没有绝对。如果因为一些特殊的要求(众所周知,客户的要求千奇百怪,无奇不有),我们一定要向“Program Files”目录写入数据,这时该怎么办呢?面对这种极其特殊的情况,我们可以在应用程序的Manifest禁用UAC Virtualization,取消其对数据写操作的重定向。在项目属性中,我们设置启用UAC(Enable User Account Control),并且在UAC Execution Level中设置请求管理员权限。这样,应用程序在启动的时候,就会向用户请求管理员权限,当应用程序获得管理员执行权限后,当然可以向任意目录写入数据,UAC Virtualization也就不会起作用了。

图2  通过Manifest禁用UAC Virtualization

图2  通过Manifest禁用UAC Virtualization

  对于64位应用程序,本身是不具备UAC Virtualization机制的,所以根本不存在禁用的问题。当我们在64位应用程序中尝试向“Program Files”等敏感目录写入数据时,就会遇到一个“拒绝访问”的错误:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 测试文件夹是否存在
BOOL IsDirectoryExists( TCHAR *dirName)
{
     WIN32_FILE_ATTRIBUTE_DATA dataDirAttrData;
     if (!::GetFileAttributesEx(dirName, GetFileExInfoStandard, &dataDirAttrData))
     {
         DWORD lastError = ::GetLastError();
         if (lastError == ERROR_PATH_NOT_FOUND || lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_NOT_READY)
             return FALSE;
     }
     return (dataDirAttrData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
// …
     // 获取文件夹路径
     //if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData,
     //           0, NULL, &pszPath)))
     // 错误的做法:
     if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFiles,
         0, NULL, &pszPath)))
     {
         // 提示错误
         MessageBox(hwndDlg, _T( "SHGetKnownFolderPath无法获取文件路径" ),
             _T( "Error" ), MB_OK | MB_ICONERROR);
         return FALSE;
     }
//…
        // 检查文件夹是否存在
     if (::IsDirectoryExists(dataFilePath))
     {
         // 如果文件夹不存在,则创建文件夹
         if (!::CreateDirectory(dataFilePath, NULL))
         {
             DWORD dwErrorCode = ::GetLastError();
             LPCWSTR lpBuffer;
             // 获取错误信息
             FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER   |
                    FORMAT_MESSAGE_IGNORE_INSERTS  |
                  FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  dwErrorCode,  //  错误代码
                  LANG_NEUTRAL,
                  ( LPTSTR )&lpBuffer,
                  0 ,
                  NULL );
              
             // 显示错误对话框
             MessageBox(hwndDlg, lpBuffer, _T( "创建文件夹错误" ), MB_OK | MB_ICONERROR);
             LocalFree(( HLOCAL )lpBuffer);
             
             return FALSE;
         }
     }

  当这段代码执行到创建文件夹的时候,会遇到一个“拒绝访问”错误:

图3  创建文件夹的“拒绝访问”错误

图3  创建文件夹的“拒绝访问”错误

  为了避免这个错误,同样的,我们可以通过在项目属性中设置,使得Manifest中嵌入UAC相关的信息,在应用程序启动的时候请求管理员权限,就像我们在运行其他大多数需要管理器权限的应用程序一样。当应用程序获得管理员权限后,这个错误就不存在了。但是这里必须要指出,这种做法是不太安全的,能够避免尽量避免。

这篇关于VS与Win7共舞:UAC与数据重定向的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/230380

相关文章

Windwos +vs 2022 编译openssl 1.0.2 库

一 前言 先说 结论,编译64位报错,查了一圈没找到解决方案,最后换了32位的。 使用qt访问web接口,因为是https,没有openssl库会报错 QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());if (reply){if (reply->error() == QNetworkReply::NoError

【服务器运维】MySQL数据存储至数据盘

查看磁盘及分区 [root@MySQL tmp]# fdisk -lDisk /dev/sda: 21.5 GB, 21474836480 bytes255 heads, 63 sectors/track, 2610 cylindersUnits = cylinders of 16065 * 512 = 8225280 bytesSector size (logical/physical)

SQL Server中,查询数据库中有多少个表,以及数据库其余类型数据统计查询

sqlserver查询数据库中有多少个表 sql server 数表:select count(1) from sysobjects where xtype='U'数视图:select count(1) from sysobjects where xtype='V'数存储过程select count(1) from sysobjects where xtype='P' SE

数据时代的数字企业

1.写在前面 讨论数据治理在数字企业中的影响和必要性,并介绍数据治理的核心内容和实践方法。作者强调了数据质量、数据安全、数据隐私和数据合规等方面是数据治理的核心内容,并介绍了具体的实践措施和案例分析。企业需要重视这些方面以实现数字化转型和业务增长。 数字化转型行业小伙伴可以加入我的星球,初衷成为各位数字化转型参考库,星球内容每周更新 个人工作经验资料全部放在这里,包含数据治理、数据要

如何在Java中处理JSON数据?

如何在Java中处理JSON数据? 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨在Java中如何处理JSON数据。JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,在现代应用程序中被广泛使用。Java通过多种库和API提供了处理JSON的能力,我们将深入了解其用法和最佳

两个基因相关性CPTAC蛋白组数据

目录 蛋白数据下载 ①蛋白数据下载 1,TCGA-选择泛癌数据  2,TCGA-TCPA 3,CPTAC(非TCGA) ②蛋白相关性分析 1,数据整理 2,蛋白相关性分析 PCAS在线分析 蛋白数据下载 CPTAC蛋白组学数据库介绍及数据下载分析 – 王进的个人网站 (jingege.wang) ①蛋白数据下载 可以下载泛癌蛋白数据:UCSC Xena (xena

中国341城市生态系统服务价值数据集(2000-2020年)

生态系统服务反映了人类直接或者间接从自然生态系统中获得的各种惠益,对支撑和维持人类生存和福祉起着重要基础作用。目前针对全国城市尺度的生态系统服务价值的长期评估还相对较少。我们在Xie等(2017)的静态生态系统服务当量因子表基础上,选取净初级生产力,降水量,生物迁移阻力,土壤侵蚀度和道路密度五个变量,对生态系统供给服务、调节服务、支持服务和文化服务共4大类和11小类的当量因子进行了时空调整,计算了

【计算机网络篇】数据链路层(12)交换机式以太网___以太网交换机

文章目录 🍔交换式以太网🛸以太网交换机 🍔交换式以太网 仅使用交换机(不使用集线器)的以太网就是交换式以太网 🛸以太网交换机 以太网交换机本质上就是一个多接口的网桥: 交换机的每个接口考研连接计算机,也可以理解集线器或另一个交换机 当交换机的接口与计算机或交换机连接时,可以工作在全双工方式,并能在自身内部同时连通多对接口,使每一对相互通信的计算机都能像

使用Jsoup抓取数据

问题 最近公司的市场部分布了一个问题,到一个网站截取一下医院的数据。刚好我也被安排做。后来,我发现为何不用脚本去抓取呢? 抓取的数据如下: Jsoup的使用实战代码 结构 Created with Raphaël 2.1.0 开始 创建线程池 jsoup读取网页 解析Element 写入sqlite 结束

Excel实用技巧——二级下拉菜单、数据验证

EXCEL系列文章目录   Excel系列文章是本人亲身经历职场之后萌发的想法,为什么Excel覆盖如此之广,几乎每个公司、学校、家庭都在使用,但是它深藏的宝藏功能却很少被人使用,PQ、BI这些功能同样适用于数据分析;并且在一些需要简单及时的数据分析项目前,Excel是完胜python、R、SPSS这些科学专业的软件的。因此决心开启Excel篇章。 数据分析为什么要学Excel Excel图表