我正在与重新启动管理器API:rmgetList()遇到一个奇怪的问题。为了模拟文件锁定方案,我使用以下第三方文件锁定实用程序:
ez文件储物柜-http://www.xoslab.com/efl.html-
文件储物柜http://www.jensscheffler.de/filelelocker
这里的奇怪问题是,这两个实用程序都锁定某个文件,但是,rmgetlist()失败,访问拒绝错误(5),第一个文件锁定实用程序(EZ File lock),而它可与第二个文件一起使用锁定实用程序。
这里可能有什么问题?为什么rmgetlist()会在一个文件锁定实用程序中失败,但可以与另一个文件锁定?
以下是正在使用的代码:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.IO;
using System.Windows.Forms;
namespace RMSession
{
class Program
{
public static void GetProcessesUsingFiles(string[] filePaths)
{
uint sessionHandle;
int error = NativeMethods.RmStartSession(out sessionHandle, 0, Guid.NewGuid().ToString("N"));
if (error == 0)
{
try
{
error = NativeMethods.RmRegisterResources(sessionHandle, (uint)filePaths.Length, filePaths, 0, null, 0, null);
if (error == 0)
{
RM_PROCESS_INFO[] processInfo = null;
uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone;
error = NativeMethods.RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
while (error == ERROR_MORE_DATA)
{
processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = (uint)processInfo.Length;
error = NativeMethods.RmGetList(sessionHandle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
}
if (error == 0 && processInfo != null)
{
for (var i = 0; i < pnProcInfo; i++)
{
RM_PROCESS_INFO procInfo = processInfo[i];
Process proc = null;
try
{
proc = Process.GetProcessById(procInfo.Process.dwProcessId);
}
catch (ArgumentException)
{
// Eat exceptions for processes which are no longer running.
}
if (proc != null)
{
//yield return proc;
}
}
}
}
}
finally
{
NativeMethods.RmEndSession(sessionHandle);
}
}
}
private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;
private const int ERROR_MORE_DATA = 234;
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
private enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[SuppressUnmanagedCodeSecurity]
private static class NativeMethods
{
/// <summary>
/// Starts a new Restart Manager session.
/// </summary>
/// <param name="pSessionHandle">A pointer to the handle of a Restart Manager session. The session handle can be passed in subsequent calls to the Restart Manager API.</param>
/// <param name="dwSessionFlags">Reserved must be 0.</param>
/// <param name="strSessionKey">A null-terminated string that contains the session key to the new session. A GUID will work nicely.</param>
/// <returns>Error code. 0 is successful.</returns>
[DllImport("RSTRTMGR.DLL", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
public static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
/// <summary>
/// Ends the Restart Manager session.
/// </summary>
/// <param name="pSessionHandle">A handle to an existing Restart Manager session.</param>
/// <returns>Error code. 0 is successful.</returns>
[DllImport("RSTRTMGR.DLL")]
public static extern int RmEndSession(uint pSessionHandle);
/// <summary>
/// Registers resources to a Restart Manager session.
/// </summary>
/// <param name="pSessionHandle">A handle to an existing Restart Manager session.</param>
/// <param name="nFiles">The number of files being registered.</param>
/// <param name="rgsFilenames">An array of strings of full filename paths.</param>
/// <param name="nApplications">The number of processes being registered.</param>
/// <param name="rgApplications">An array of RM_UNIQUE_PROCESS structures. </param>
/// <param name="nServices">The number of services to be registered.</param>
/// <param name="rgsServiceNames">An array of null-terminated strings of service short names.</param>
/// <returns>Error code. 0 is successful.</returns>
[DllImport("RSTRTMGR.DLL", CharSet = CharSet.Unicode)]
public static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, uint nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, uint nServices, string[] rgsServiceNames);
/// <summary>
/// Gets a list of all applications and services that are currently using resources that have been registered with the Restart Manager session.
/// </summary>
/// <param name="dwSessionHandle">A handle to an existing Restart Manager session.</param>
/// <param name="pnProcInfoNeeded">A pointer to an array size necessary to receive RM_PROCESS_INFO structures</param>
/// <param name="pnProcInfo">A pointer to the total number of RM_PROCESS_INFO structures in an array and number of structures filled.</param>
/// <param name="rgAffectedApps">An array of RM_PROCESS_INFO structures that list the applications and services using resources that have been registered with the session.</param>
/// <param name="lpdwRebootReasons">Pointer to location that receives a value of the RM_REBOOT_REASON enumeration that describes the reason a system restart is needed.</param>
/// <returns>Error code. 0 is successful.</returns>
[DllImport("RSTRTMGR.DLL")]
public static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons);
}
static void Main(string[] args)
{
Console.WriteLine("Starting...");
string[] file1 = new string[1];
MessageBox.Show("Debug C#");
file1[0] = @"C:ProcessMonitor.zip";
//DirectoryInfo dirInfo = new DirectoryInfo(folder);
GetProcessesUsingFiles(file1);
Console.WriteLine("End");``
}
}
}
简单的文件储物柜仅在非正式的含义上"锁定"文件,即,它可以保护文件免于访问,但是它不会通过在该文件上获得锁文件。取而代之的是,它使用较低级别的技术(文件系统过滤器驱动程序),该技术与防病毒软件保护文件免受任何未经授权的访问的方式大致相似。重新启动管理器API并非打算,也不是处理这种情况。
您的应用程序几乎肯定也不需要处理这种情况。这意味着简单的文件储物柜不是适合您特定需求的合适工具;扔掉。
为什么rmgetlist()会在一个文件锁定实用程序中失败,但可以与 另一个?
要回答这个问题,我们需要了解RmGetList
在内部的工作方式。在您的情况下,我们为其提供一个文件名,其输出是RM_PROCESS_INFO
结构的数组。为了构建此数组,Windows必须确定使用该文件的过程。但是Windows如何执行此操作?
函数ZwQueryInformationFile
(由ntdll.dll
导出)可以返回有关文件的大量信息。FILE_INFORMATION_CLASS
枚举中的选项之一是
FILEPROCESSIDSISISEFILEINFORMATION
a file_process_ids_using_file_information 结构。这个值是 保留用于系统使用。该值从Windows开始 远景。
和wdm.h
(这是Windows WDK的众所周知的文件),我们找到
typedef struct _FILE_PROCESS_IDS_USING_FILE_INFORMATION {
ULONG NumberOfProcessIdsInList;
ULONG_PTR ProcessIdList[1];
} FILE_PROCESS_IDS_USING_FILE_INFORMATION, *PFILE_PROCESS_IDS_USING_FILE_INFORMATION;
所以这个选项正是我们需要的!
算法是这样的:
- 使用
FILE_READ_ATTRIBUTES
访问打开文件(对于此信息类来说足够) - 致电
ZwQueryInformationFile(..,FileProcessIdsUsingFileInformation);
如果NumberOfProcessIdsInList
!= 0步行ProcessIdList
-
ProcessId
开放过程,用ProcessStartTime
查询(请参阅GetProcessTimes
)和其他要填充的属性RM_PROCESS_INFO
现在,我们知道它是如何工作的,让我们看一下您正在使用的两个实用程序。
第二个是一个非常简单的应用程序,它提供了源代码。它所做的就是用dwShareMode = 0
调用CreateFile
。那将获取文件上的独家锁,确保任何尝试使用dwDesiredAccess
打开文件的尝试其中包含FILE_READ_DATA
或FILE_WRITE_DATA
或DELETE
将使用ERROR_SHARING_VIOLATION
失败。但是,它确实不是,请阻止我们使用dwDesiredAccess = FILE_READ_ATTRIBUTES
打开文件,因此呼叫rmgetList()仍然可以正常工作。
但是,第一个工具是Xoslab的Easy File Locker,是使用Minifilter驱动程序(HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesxlkfs
)来限制对文件的访问。该驱动程序返回STATUS_ACCESS_DENIED
(将其转换为Win32 ERROR_ACCESS_DENIED
),以尝试打开文件。因此,当RmGetList
尝试在步骤(1)打开文件时,您会遇到错误ERROR_ACCESS_DENIED
,并且(由于API不知道该怎么办)此错误代码已返回给您。
这就是全部。该工具根本没有做您期望的事情。